Something went wrong. Try again.
Odoc docs forked from jon.recoil.org/odd
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399open Cmdliner
let exit_doc_failure = 1
(* --prefix, shared by every command that touches a switch *)let switch_t = let doc = "Operate on the switch whose prefix is $(docv). Defaults to \ $(b,\\$OPAM_SWITCH_PREFIX), falling back to $(b,opam var prefix)." in let prefix = Arg.(value & opt (some string) None & info [ "prefix" ] ~docv:"DIR" ~doc) in let resolve = function | Some p -> Ok (Odd.Switch.v (Fpath.v p)) | None -> Odd.Switch.detect () in Term.(const resolve $ prefix)
(* --driver: the per-package documentation generator *)let driver_t = let doc = "The odoc driver run once per package (default: $(b,odd-driver), the \ driver bundled with odd). Resolved in $(b,PATH) unless it contains a \ directory separator, in which case it is taken relative to the current \ directory." in let arg = Arg.( value & opt string "odd-driver" & info [ "driver" ] ~docv:"BIN" ~doc ~env:(Cmd.Env.info "ODD_DRIVER")) in let resolve d = if String.contains d '/' then let p = Fpath.v d in let p = if Fpath.is_abs p then p else match Bos.OS.Dir.current () with | Ok cwd -> Fpath.(normalize (cwd // p)) | Error _ -> p in Bos.Cmd.v (Fpath.to_string p) else Bos.Cmd.v d in Term.(const resolve $ arg)
let print_outcome sw outcome = Printf.printf "odd: %s\n" (Odd.Sync.summary outcome); if outcome.Odd.Sync.failed <> [] then Printf.printf "odd: see %s\n" (Fpath.to_string (Odd.Switch.log_file sw))
(* sync — hook-facing: always exits 0. *)let sync_cmd = let run switch driver = (match switch with | Error (`Msg m) -> Printf.eprintf "odd sync: %s\n" m | Ok sw -> ( match Odd.Sync.sync ~driver sw with | Ok { built = []; failed = [] } -> () | Ok outcome -> print_outcome sw outcome | Error (`Msg m) -> Odd.Sync.log sw m; Printf.eprintf "odd sync: %s\n" m)); 0 in let doc = "Bring the switch documentation up to date (opam hook worker)" in let man = [ `S Manpage.s_description; `P "Rebuilds every package marked stale by the opam post-install hook, in \ dependency order, running the driver once per package, and \ regenerates the landing page (so packages deleted by the post-remove \ hook drop off it). Intended to be run from opam's \ $(b,post-session-commands) hook. Always exits 0; failed packages stay \ marked and are retried by the next session, with details in the log."; ] in Cmd.v (Cmd.info "sync" ~doc ~man) Term.(const run $ switch_t $ driver_t)
(* rebuild — user-facing: real exit codes. *)let rebuild_cmd = let all = Arg.( value & flag & info [ "all" ] ~doc:"Rebuild documentation for every installed package.") in let pkgs = Arg.( value & pos_all string [] & info [] ~docv:"PKG" ~doc:"Packages to rebuild.") in let run switch driver all pkgs = match switch with | Error (`Msg m) -> `Error (false, m) | Ok sw -> ( let installed = Odd.Switch.installed sw in let targets = if all then Ok installed else if pkgs = [] then Error "no packages given (use --all for every installed package)" else let missing = List.filter (fun p -> not (List.mem_assoc p installed)) pkgs in if missing <> [] then Error (Printf.sprintf "not installed in this switch: %s" (String.concat ", " missing)) else Ok (List.map (fun p -> (p, List.assoc p installed)) pkgs) in match targets with | Error m -> `Error (false, m) | Ok targets -> ( let mark_all = List.fold_left (fun acc (name, _version) -> Result.bind acc @@ fun () -> Odd.Pending.mark sw name) (Ok ()) targets in match Result.bind mark_all @@ fun () -> Odd.Sync.sync ~driver sw with | Error (`Msg m) -> `Error (false, m) | Ok outcome -> print_outcome sw outcome; if outcome.Odd.Sync.failed <> [] then `Ok exit_doc_failure else `Ok 0)) in let doc = "Mark packages stale and rebuild their documentation" in let man = [ `S Manpage.s_description; `P "The manual escape hatch: marks the given packages (or, with \ $(b,--all), every installed package) as stale and runs the same \ worker as $(b,odd sync). Unlike the hook-facing commands this \ exits non-zero when a documentation build fails."; ] in Cmd.v (Cmd.info "rebuild" ~doc ~man) Term.(ret (const run $ switch_t $ driver_t $ all $ pkgs))
(* order — debugging aid. *)let order_cmd = let pkgs = Arg.( value & pos_all string [] & info [] ~docv:"PKG" ~doc: "Packages to order. Defaults to every installed package. Names not \ installed in the switch are silently ignored, as in sync.") in let run switch pkgs = match switch with | Error (`Msg m) -> `Error (false, m) | Ok sw -> let pkgs = if pkgs = [] then List.map fst (Odd.Switch.installed sw) else pkgs in let warn m = Printf.eprintf "warning: %s\n" m in List.iter print_endline (Odd.Deps.order ~warn sw pkgs); `Ok 0 in let doc = "Print the dependency order sync would use" in let man = [ `S Manpage.s_description; `P "Prints the given packages in dependency order, one per line: every \ package appears after its (transitive) dependencies among the listed \ packages. This is the order $(b,odd sync) processes stale \ packages in."; ] in Cmd.v (Cmd.info "order" ~doc ~man) Term.(ret (const run $ switch_t $ pkgs))
(* search — user-facing: real exit codes. *)let search_cmd = let query = Arg.( required & pos 0 (some string) None & info [] ~docv:"QUERY" ~doc: "Search query. Space-separated words, optionally followed by a \ $(b,: type) annotation (e.g. $(b,map : list -> _)).") in let limit = Arg.( value & opt int 25 & info [ "limit"; "n" ] ~docv:"N" ~doc:"Maximum number of results to print.") in let packages = Arg.( value & opt_all string [] & info [ "package"; "p" ] ~docv:"PKG" ~doc: "Restrict the search to this package's documentation. Repeatable; \ defaults to every documented package in the switch.") in let run switch query limit packages = match switch with | Error (`Msg m) -> `Error (false, m) | Ok sw -> let warn m = Printf.eprintf "warning: %s\n" m in (match Odd.Search.search ~warn ~limit ~packages sw query with | [] -> Printf.printf "No results.\n"; `Ok 0 | results -> List.iter (fun r -> Format.printf "%a@." Odd.Search.pp r) results; `Ok 0) in let doc = "Search the switch's documentation" in let man = [ `S Manpage.s_description; `P "Searches the per-package sherlodoc databases under \ $(b,\\$OPAM_SWITCH_PREFIX/odoc) that the driver writes alongside the \ HTML, querying them together so a single query covers every \ documented package in the switch. A database that cannot be loaded \ (for instance one written by an incompatible sherlodoc version) is \ skipped with a warning on stderr."; ] in Cmd.v (Cmd.info "search" ~doc ~man) Term.(ret (const run $ switch_t $ query $ limit $ packages))
(* doc — user-facing: real exit codes. *)let doc_cmd = let reference = Arg.( required & pos 0 (some string) None & info [] ~docv:"REFERENCE" ~doc: "An odoc reference to an item, e.g. \ $(b,Odoc_model.Paths.Identifier) or $(b,Stdlib.List.map). The \ same syntax accepted inside $(b,{!...}) in doc comments, including \ kind tags such as $(b,val:) or $(b,type:) and package-qualified \ paths such as $(b,/stdlib/Stdlib.List.map) or $(b,/astring/index).") in let run switch reference = match switch with | Error (`Msg m) -> `Error (false, m) | Ok sw -> ( match Odd.Doc.doc sw reference with | Ok markdown -> print_string markdown; `Ok 0 | Error (`Msg m) -> (* A failure to resolve or find docs is a runtime outcome, not a command-line misuse, so report it on stderr and exit non-zero rather than with cmdliner's CLI-error code. *) Printf.eprintf "odd doc: %s\n" m; `Ok exit_doc_failure) in let doc = "Print the documentation for a reference, as Markdown" in let man = [ `S Manpage.s_description; `P "Resolves $(i,REFERENCE) against every package documented in the \ switch, using the same reference-resolution machinery as \ $(b,odoc link), then prints it as Markdown: a module, module type, \ class or page is rendered in full; a leaf item (value, type, \ exception, …) as its doc comment. Unlike a single-package tool, the \ whole switch is always searched, and package-qualified path \ references ($(b,/pkg/...)) resolve via per-package $(b,-L)/$(b,-P) \ roots reconstructed from the switch layout."; ] in Cmd.v (Cmd.info "doc" ~doc ~man) Term.(ret (const run $ switch_t $ reference))
(* complete — user-facing: emits candidates, always exits 0. *)let complete_cmd = let partial = Arg.( value & pos 0 string "" & info [] ~docv:"PARTIAL" ~doc: "The start of an odoc reference, e.g. $(b,List.m), \ $(b,module-List.type-), $(b,/o), or $(b,/odoc.model/). Defaults to \ the empty string (all top-level names).") in let run switch partial = match switch with | Error (`Msg m) -> `Error (false, m) | Ok sw -> List.iter print_endline (Odd.Complete.complete sw partial); `Ok 0 in let doc = "List the possible completions of a partial reference" in let man = [ `S Manpage.s_description; `P "Prints, one per line, the references $(i,PARTIAL) could be completed \ to: members of the module/type/class it names so far (with $(b,Stdlib) \ open, so bare names like $(b,List.m) work), package and library names \ for a leading $(b,/), or the units under a $(b,/pkg/) path. A kind tag \ in the final component (e.g. $(b,type-)) filters by kind. Intended as \ the engine behind shell completion; prints nothing when there is no \ match."; ] in Cmd.v (Cmd.info "complete" ~doc ~man) Term.(ret (const run $ switch_t $ partial))
(* setup *)let setup_cmd = let apply = Arg.( value & flag & info [ "apply" ] ~doc: "Apply the configuration via $(b,opam option --global) instead of \ printing it.") in let run apply = let exe = (* the absolute path of this binary, as the hooks must find it without relying on PATH *) let exe = Sys.executable_name in if Filename.is_relative exe then Filename.concat (Sys.getcwd ()) exe else exe in if not apply then ( Odd.Setup.print ~exe; `Ok 0) else match Odd.Setup.apply ~exe with | Ok () -> `Ok 0 | Error (`Msg m) -> `Error (false, m) in let doc = "Configure the opam hooks that drive odd" in let man = [ `S Manpage.s_description; `P "Adds to the global opam configuration: a $(b,post-install-commands) \ hook that marks a package's docs stale (a plain $(b,mkdir)+$(b,touch), \ so it works even when odd isn't installed), a \ $(b,post-remove-commands) hook that deletes a package's docs \ ($(b,rm -rf)), and a $(b,post-session-commands) hook that runs \ $(b,odd sync). Without $(b,--apply), prints the $(b,opam \ option) invocations instead of running them. Fields already carrying \ our command are skipped, so the command is idempotent."; ] in Cmd.v (Cmd.info "setup" ~doc ~man) Term.(ret (const run $ apply))
let main_cmd = let doc = "keep an opam switch's documentation up to date" in let man = [ `S Manpage.s_description; `P "$(mname) keeps the HTML documentation of an opam switch in sync with \ its installed packages. An opam post-install hook marks each changed \ package stale (a marker file it touches, needing no odd binary) \ and a post-remove hook deletes a package's docs; at the end of the \ session the worker rebuilds exactly the stale packages, in dependency \ order, using an odoc driver. Output goes to \ $(b,\\$OPAM_SWITCH_PREFIX/odoc), with a landing page at \ $(b,odoc/index.html)."; `P "Run $(b,odd setup) to configure the hooks."; ] in Cmd.group (Cmd.info "odd" ~version:"%%VERSION%%" ~doc ~man) [ sync_cmd; rebuild_cmd; order_cmd; search_cmd; doc_cmd; complete_cmd; setup_cmd; ]
let () = exit (Cmd.eval' main_cmd)