(**************************************************************************) (* *) (* Copyright (c) 2014 - 2018. *) (* Dynamic Ledger Solutions, Inc. *) (* *) (* All rights reserved. No warranty, explicit or implicit, provided. *) (* *) (**************************************************************************) type service_repr = { path : string list ; meth : Resto.meth ; description : string ; input : Json_schema.schema option ; output : Json_schema.schema option ; example : string option ; error : Json_schema.schema option } type service_tree = | Root of service_tree list | Node of service_repr * service_tree list | SymbNode of string list * service_tree list let make_descr = function | None | Some "" -> "No description" | Some s -> s (** Inspect a JSON schema: if it doesn't contain any field (e.g. { }), return None *) let normalize_json_schema = function | None -> None | Some schema -> let open Json_schema in let elt = root schema in match elt.kind with | Object specs when specs = object_specs || specs = { object_specs with additional_properties = None } -> None | _ -> Some schema let repr_of_service path RPC_description.{ description ; error ; meth ; input ; output ; _ } : service_repr = { path ; meth ; description = make_descr description ; input = normalize_json_schema input ; output = normalize_json_schema (Some output) ; example = None ; error = Some error } open Format let pp_print_service fmt { path ; meth } = fprintf fmt "%s %s" (Resto.string_of_meth meth) (String.concat "/" path) let rec pp_print_service_tree fmt = function | Root l -> fprintf fmt "@[/"; List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" pp_print_service_tree tree ) l; fprintf fmt "@]" | Node (repr, l) -> fprintf fmt "@[%a" pp_print_service repr; List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" pp_print_service_tree tree ) l; fprintf fmt "@]" | SymbNode (sl, l) -> fprintf fmt "@[%s" (String.concat "/" sl); List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" pp_print_service_tree tree ) l; fprintf fmt "@]" let make_tree cctxt path = (* TODO : add automatic example generation *) let open RPC_description in describe cctxt ~recurse:true path >>=? fun dir -> let rec loop path : _ directory -> service_tree list = function | Dynamic descr -> [ Node ({ path ; meth=`POST ; description=make_descr descr ; input = None ; output = None ; example = None ; error = None }, []) ] | Empty -> [] | Static { services ; subdirs = None } -> let l = RPC_service.MethMap.bindings services in let l = List.map snd l in List.map (fun service -> Node (repr_of_service path service, [])) l | Static { services ; subdirs = Some (Suffixes subdirs) } -> let subdirs = Resto.StringMap.bindings subdirs in let l = List.map (fun (name, subdir) -> loop (path @ [ name ]) subdir ) subdirs |> List.concat in let services = RPC_service.MethMap.bindings services in let services = List.map snd services in begin match services with | [] -> [ SymbNode (path, l) ] | service::[] -> [ Node (repr_of_service path service, l) ] | _ -> assert false (* ? *) end | Static { services ; subdirs = Some (Arg (arg, solo)) } -> let name = Printf.sprintf "<%s>" arg.RPC_arg.name in let services = RPC_service.MethMap.bindings services in let services = List.map snd services in let l = loop (path @ [ name ]) solo in begin match services with | [] -> [ SymbNode (path, l) ] | service::[] -> [ Node (repr_of_service path service, l) ] | _ -> assert false (* ? *) end in return (Root (loop path dir)) let rec pp_print_hierarchy fmt = let open Format in function | Root l -> List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" pp_print_hierarchy tree ) l; fprintf fmt "@]" | SymbNode (path, l) | Node ( { path } , l) -> if List.length path = 0 then begin List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" pp_print_hierarchy tree ) l; fprintf fmt "@]" end else begin let name = "/" ^ List.hd (List.rev path) in let offset = max 2 (String.length name / 2) in if List.length l = 0 then pp_open_vbox fmt 0 else pp_open_vbox fmt offset; fprintf fmt "%s" name; List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" pp_print_hierarchy tree) l; pp_close_box fmt () end (**************** RST PRINTING ****************) let pp_print_rst_title ~char ppf title = let sub = String.map (fun _ -> char) title in Format.fprintf ppf "@[%s@ %s@ @ @]" title sub let pp_print_rst_h1 = pp_print_rst_title ~char:'#' let pp_print_rst_h2 = pp_print_rst_title ~char:'*' let pp_print_rst_h3 = pp_print_rst_title ~char:'=' let pp_print_rst_h4 = pp_print_rst_title ~char:'`' let pp_print_rst_raw_html fmt str = (* let ic = open_in file in *) fprintf fmt "@[.. raw:: html@ @ %s@ @ @]" str let label_table = Hashtbl.create 17 let make_counter () = let i = ref 1 in fun () -> incr i; !i let count = make_counter () let rst_label_of_path path = let label = Printf.sprintf "ref%d" (count ()) in Hashtbl.add label_table path label; "<" ^ label ^ "_>" let ref_of_path path = Hashtbl.find label_table path let rec pp_print_rst_hierarchy fmt ~title node = fprintf fmt "%a@ " pp_print_rst_h2 title; let rst_name = String.lowercase_ascii title |> String.map (function ' ' -> '-' | x -> x) in let rst_name = let rec loop str = let open Stringext in if find_from str ~pattern:"--" <> None then loop (replace_all_assoc str [("--","-")]) else str in loop rst_name in fprintf fmt "%a@." pp_print_rst_raw_html (sprintf "" rst_name rst_name rst_name); let rec loop fmt tree = match tree with | Root l -> (* fprintf fmt "@[/"; *) fprintf fmt "@["; List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" loop tree ) l; fprintf fmt "@]" | Node ( { path }, l) -> let name = "/" ^ String.concat "/" path in fprintf fmt "@["; fprintf fmt "`%s %s`_" name (rst_label_of_path path); fprintf fmt "@ "; List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" loop tree ) l; fprintf fmt "@]" | SymbNode (path, l) -> if List.length path > 0 then begin let name = "\\/" ^ String.concat "/" path in fprintf fmt "@[%s" name; (* fprintf fmt "%s" name; *) fprintf fmt "@ "; List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" loop tree ) l; fprintf fmt "@]" end else List.iter (fun tree -> fprintf fmt "@ "; fprintf fmt "%a" loop tree ) l in loop fmt node let pp_print_html_tab_button fmt ?(default=false) ~shortlabel ~content path = let target_ref = ref_of_path path in fprintf fmt "@ " (if default then " defaultOpen" else "") (target_ref ^ shortlabel) target_ref content let pp_print_html_tab_content fmt ~tag ~shortlabel ~pp_content ~content path = let target_ref = ref_of_path path in fprintf fmt "@[
@ " (target_ref ^ shortlabel) target_ref; fprintf fmt "<%s>@ %a@ " tag pp_content content tag; fprintf fmt "
@]" let pp_print_html_tabs fmt { path ; description ; input ; output ; _ (* example ; error *) } = fprintf fmt "@[.. raw:: html@ @ "; fprintf fmt "@[
@ "; fprintf fmt "%a" (pp_print_html_tab_button ~default:true ~shortlabel:"descr" ~content:"Description") path; (match input with | Some _ -> fprintf fmt "%a" (pp_print_html_tab_button ~default:false ~shortlabel:"input" ~content:"Input format") path; | None -> ()); (match output with | Some _ -> fprintf fmt "%a" (pp_print_html_tab_button ~default:false ~shortlabel:"output" ~content:"Output format") path; | None -> ()); (* (match example with * | Some _ -> * fprintf fmt "%a" (pp_print_html_tab_button ~default:false ~shortlabel:"example" ~content:"Example") path; * | None -> ()); *) fprintf fmt "
@]@ "; fprintf fmt "%a@ " (pp_print_html_tab_content ~tag:"p" ~shortlabel:"descr" ~pp_content:pp_print_string ~content:description) path; (match input with | Some x -> fprintf fmt "%a@ " (pp_print_html_tab_content ~tag:"pre" ~shortlabel:"input" ~pp_content:Json_schema.pp ~content:x) path; | None -> ()); (match output with | Some x -> fprintf fmt "%a@ " (pp_print_html_tab_content ~tag:"pre" ~shortlabel:"output" ~pp_content:Json_schema.pp ~content:x) path; | None -> ()); (* (match example with * | Some x -> fprintf fmt "%a@ " (pp_print_html_tab_content ~tag:"pre" ~shortlabel:"example" * ~pp_content:pp_print_string ~content:x) path; * | None -> ()); *) fprintf fmt "@]" let pp_print_rst_full_service fmt ({ path ; meth } as repr) = fprintf fmt ".. _%s :@\n@\n**%s %s**@\n@\n" (ref_of_path path) (Resto.string_of_meth meth) ("/" ^ String.concat "/" path); fprintf fmt "%a" pp_print_html_tabs repr let rec pp_print_rst_service_tree fmt node = let open Format in match node with | Root l -> (pp_print_list ~pp_sep:pp_print_cut pp_print_rst_service_tree) fmt l | SymbNode (_, l) -> (pp_print_list ~pp_sep:pp_print_cut pp_print_rst_service_tree) fmt l | Node ( repr , l) -> (* Generates services details and ref for links *) fprintf fmt "%a@\n@\n" pp_print_rst_full_service repr; fprintf fmt "%a" (pp_print_list ~pp_sep:pp_print_newline pp_print_rst_service_tree) l let style = "" let script = "" let ppf = Format.std_formatter let err_ppf = Format.err_formatter let run ?(rpc_port=18731) () = (* Client context *) let rpc_config = { RPC_client.default_config with port=rpc_port } in let open Client_config in let {block; _} = default_cli_args in let (cctxt : #Tezos_client_base.Client_context.full) = new Client_context_unix.unix_full ~block ~base_dir: "/" ~rpc_config in let print_header () = (* Style : hack *) fprintf ppf "%a@." pp_print_rst_raw_html style; (* Script : hack *) fprintf ppf "%a@." pp_print_rst_raw_html script; (* Page title *) fprintf ppf "%a" pp_print_rst_h1 "RPC API"; (* include/copy usage.rst from input *) let rec loop () = let s = read_line () in fprintf ppf "%s@\n" s; loop () in begin try loop () with End_of_file -> () end in (make_tree cctxt [] >>= function | Ok service_tree -> (* Print header!! *) fprintf ppf "@\n"; print_header (); fprintf ppf "@\n"; (* Shell RPCs tree *) fprintf ppf "%a@." (pp_print_rst_hierarchy ~title:"Client RPCs - Index") service_tree; fprintf ppf "%a" pp_print_rst_h2 "Client RPCs - Full description"; fprintf ppf "%a@." pp_print_rst_service_tree service_tree; Lwt.return 0 | Error _ -> Format.eprintf "[RPC Doc Generation] Client : Couldn't reach node\n"; Lwt.return 1) >>= function | 0 -> (* Alpha Protocol RPCs *) let path_proto_alpha = String.split '/' "/blocks/head/proto" in begin make_tree cctxt path_proto_alpha >>= function | Ok service_tree -> (* Proto alpha RPCs tree *) fprintf ppf "%a@." (pp_print_rst_hierarchy ~title:"Protocol Alpha RPCs - Index") service_tree; fprintf ppf "%a" pp_print_rst_h2 "Protocol Alpha RPCs - Full description"; fprintf ppf "%a@." pp_print_rst_service_tree service_tree; Lwt.return 0 | Error _ -> Format.fprintf err_ppf "[RPC Doc Generation] Proto alpha : Couldn't reach node\n"; Lwt.return 1 end | _ -> Lwt.return 1 let () = Pervasives.exit (Lwt_main.run begin try if Array.length Sys.argv > 1 then let rpc_port = int_of_string Sys.argv.(1) in run ~rpc_port () else run () with _ -> run () end)