The unpac monorepo manager self-hosting as a monorepo using unpac

Add `cmdliner install tool-{manpage,support}` (close #228)

+344 -104
+3 -2
B0.ml
··· 95 95 |> ~~ B0_opam.build {|[[ make "all" "PREFIX=%{prefix}%" ]]|} 96 96 |> ~~ B0_opam.install 97 97 {|[[make "install" "BINDIR=%{_:bin}%" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%" 98 - "SHAREDIR=%{share}%"] 99 - [make "install-doc" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%"]]|} 98 + "SHAREDIR=%{share}%" "MANDIR=%{man}%"] 99 + [make "install-doc" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%" 100 + "SHAREDIR=%{share}%" "MANDIR=%{man}%"]]|} 100 101 |> B0_meta.tag B0_opam.tag 101 102 in 102 103 let locked = false (* So that it looks up b0.std *) in
+6 -6
CHANGES.md
··· 205 205 functions and `Term.info` information values in favor of the new 206 206 `Cmdliner.Cmd` module. 207 207 208 - The `Cmd` module generalizes the existing sub command support to allow 209 - arbitrarily nested sub commands each with its own man page and command 208 + The `Cmd` module generalizes the existing subcommand support to allow 209 + arbitrarily nested subcommands each with its own man page and command 210 210 line syntax represented by a `Term.t` value. 211 211 212 212 The mapping between the old interface and the new one should be rather ··· 221 221 * The `?exits` argument which defaults to `Cmd.Exit.defaults` 222 222 rather than the empty list. 223 223 * The `?man_xrefs` which defaults to the list ``[`Main]`` rather 224 - than the empty list (this means that by default sub commands 224 + than the empty list (this means that by default subcommands 225 225 at any level automatically cross-reference the main command). 226 226 * The `?sdocs` argument which defaults to `Manpage.s_common_options` 227 227 rather than `Manpage.s_options`. ··· 241 241 or `Term.term_result`. 242 242 243 243 Finally be aware that if you replace, in an existing tool, an encoding 244 - of sub commands as positional arguments you will effectively break the 244 + of subcommands as positional arguments you will effectively break the 245 245 command line compatibility of your tool since options can no longer be 246 - specified before the sub commands, i.e. your tool synopsis moves from: 246 + specified before the subcommands, i.e. your tool synopsis moves from: 247 247 248 248 ``` 249 249 tool cmd [OPTION]… SUBCMD [ARG]… ··· 472 472 the sense that only more command lines are parsed. Thanks to Hugo 473 473 Heuzard for the patch. 474 474 - End user error message improvements using heuristics and edit 475 - distance search in the optional argument and sub command name 475 + distance search in the optional argument and subcommand name 476 476 spaces. Thanks to Hugo Heuzard for the patch. 477 477 - Adds `Arg.doc_{quote,alts,alts_enum}`, documentation string 478 478 helpers.
+12 -4
Makefile
··· 17 17 LIBDIR=$(DESTDIR)$(PREFIX)/lib/ocaml/cmdliner 18 18 SHAREDIR=$(DESTDIR)$(PREFIX)/share 19 19 DOCDIR=$(SHAREDIR)/doc/cmdliner 20 + MANDIR=$(SHAREDIR)/man 20 21 BASHCOMPDIR=$(SHAREDIR)/bash-completion/completions 21 22 ZSHCOMPDIR=$(SHAREDIR)/zsh/site-functions 22 23 NATIVE=$(shell ocamlopt -version > /dev/null 2>&1 && echo true) ··· 31 32 32 33 ifeq ($(NATIVE),true) 33 34 BUILD-EXE=build-native-exe 34 - BUILD-TARGETS=build-byte build-native build-native-exe build-completions 35 + BUILD-TARGETS=build-byte build-native build-native-exe build-completions \ 36 + build-man 35 37 INSTALL-TARGETS=install-common install-srcs install-byte install-native \ 36 38 install-exe install-completions 37 39 ifeq ($(NATDYNLINK),true) ··· 40 42 endif 41 43 else 42 44 BUILD-EXE=build-byte-exe 43 - BUILD-TARGETS=build-byte build-byte-exe build-completions 45 + BUILD-TARGETS=build-byte build-byte-exe build-completions \ 46 + build-man 44 47 INSTALL-TARGETS=install-common install-srcs install-byte install-exe \ 45 48 install-completions 46 49 endif ··· 73 76 $(TOOL) generic-completion zsh > $(TOOLBDIR)/zsh-completion.sh 74 77 $(TOOL) tool-completion zsh cmdliner > $(TOOLBDIR)/zsh-cmdliner.sh 75 78 79 + build-man: $(BUILD-EXE) 80 + $(TOOL) install tool-manpages $(TOOLBDIR)/cmdliner $(TOOLBDIR)/man 81 + 76 82 prepare-prefix: 77 83 $(INSTALL) -d "$(BINDIR)" "$(LIBDIR)" 78 84 ··· 95 101 $(INSTALL) -m 644 $(BASE).cmxs "$(LIBDIR)" 96 102 97 103 install-exe: 98 - $(INSTALL) -m 755 "$(B)/src/tool/cmdliner" "$(BINDIR)/cmdliner" 104 + $(INSTALL) -m 755 "$(TOOLBDIR)/cmdliner" "$(BINDIR)/cmdliner" 99 105 100 106 install-doc: 107 + $(INSTALL) -d "$(MANDIR)/man1" 108 + $(INSTALL) -m 644 $(wildcard $(TOOLBDIR)/man/man1/*.1) "$(MANDIR)/man1" 101 109 $(INSTALL) -d "$(DOCDIR)/odoc-pages" 102 110 $(INSTALL) -m 644 CHANGES.md LICENSE.md README.md "$(DOCDIR)" 103 111 $(INSTALL) -m 644 doc/index.mld doc/cli.mld doc/examples.mld \ ··· 116 124 .PHONY: all install install-doc clean build-byte build-native \ 117 125 build-native-dynlink build-byte-exe build-native-exe build-completions \ 118 126 prepare-prefix install-common install-byte install-native install-dynlink \ 119 - install-exe install-completions 127 + install-exe install-completions build-man
+9 -1
cmdliner.opam
··· 37 37 "LIBDIR=%{_:lib}%" 38 38 "DOCDIR=%{_:doc}%" 39 39 "SHAREDIR=%{share}%" 40 + "MANDIR=%{man}%" 40 41 ] 41 - [make "install-doc" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%"] 42 + [ 43 + make 44 + "install-doc" 45 + "LIBDIR=%{_:lib}%" 46 + "DOCDIR=%{_:doc}%" 47 + "SHAREDIR=%{share}%" 48 + "MANDIR=%{man}%" 49 + ] 42 50 ] 43 51 dev-repo: "git+https://erratique.ch/repos/cmdliner.git" 44 52 x-maintenance-intent: ["(latest)"]
+47 -18
doc/cli.mld
··· 5 5 6 6 {1:invocation Tool invocation} 7 7 8 - For tools evaluating a command without sub commands the most general 8 + For tools evaluating a command without subcommands the most general 9 9 form of invocation is: 10 10 11 11 {v ··· 25 25 resolve them: anything that follows it is treated as a positional 26 26 argument. 27 27 28 - Tools evaluating commands with sub commands have this form of invocation 28 + Tools evaluating commands with subcommands have this form of invocation 29 29 30 30 {v 31 31 tool [COMMAND]… [OPTION]… [ARG]… ··· 34 34 Commands automatically respond to the [--help] option by printing 35 35 {{!help}their help}. The sequence of [COMMAND] strings must be the first 36 36 strings following the tool name – as soon as an optional argument is 37 - seen the search for a sub command stops. 37 + seen the search for a subcommand stops. 38 38 39 39 {1:args Arguments} 40 40 ··· 130 130 131 131 {1:help Help and man pages} 132 132 133 - Help and man pages are are generated when you call your tool or a sub 134 - command with [--help]. By default, if the [TERM] environment variable 133 + Help and man pages are are generated when you call your tool or a subcommand 134 + with [--help]. By default, if the [TERM] environment variable 135 135 is not [dumb] or unset, the tool tries to {{!paging}page} the manual 136 136 so that you can directly search it. Otherwise it outputs the manual 137 137 as plain text. ··· 159 159 environment unless, the [LESS] environment variable is set in your 160 160 environment. 161 161 162 + {2:install_tool_manpages Install} 163 + 164 + The manpages of a tool and its subcommands can be installed to a root 165 + [man] directory [$MANDIR] by invoking: 166 + 167 + {@shell[ 168 + cmdliner install tool-manpages thetool $MANDIR 169 + ]} 170 + 171 + This looks up [thetool] in the [PATH]. Use an explicit file path like 172 + [./thetool] to directly specify an executable. 173 + 174 + If your are installing your package for [thetool] with [opam] 175 + and have [cmdliner] as a direct dependency and your tool is located 176 + in the build at the path [$BUILD/thetool] you can add the following 177 + [install:] instruction (also works if your build system is using 178 + a [.install] file). 179 + {@sh[ 180 + install: ["cmdliner" "install" "tool-manpages" "$BUILD/thetool" "%{man}%"] 181 + ]} 182 + Note that you need to specify the path to the built executable, trying 183 + to lookup the tool in the [PATH] does not work as the install does not 184 + see it yet. If [cmdliner] is only an optional dependency use the 185 + [{cmdliner:installed}] opam filter on the command invocation. 186 + 187 + If you are also {{!install_tool_completion}installing completions} rather 188 + use the [install tool-support] command, see this 189 + {{!page-cookbook.tip_tool_support}cookbook tip}. 190 + 162 191 {1:cli_completion Command line completion} 163 192 164 193 Cmdliner programs automatically get support for shell command line ··· 169 198 installed by the library. For now the [zsh] and [bash] shells are 170 199 supported. 171 200 172 - Tool developers can easily {{!install_completion_tool}install} 201 + Tool developers can easily {{!install_tool_completion}install} 173 202 completion definitions that invoke these completion scripts. Tool 174 203 end-users need to {{!user_configuration}make sure} these definitions are 175 204 looked up by their shell. ··· 212 241 213 242 If the function cannnot be found make sure the [cmdliner] library is 214 243 installed, that the generic scripts were 215 - {{!install_completion_generic}installed} and that the 244 + {{!install_generic_completion}installed} and that the 216 245 [_cmdliner_generic] file can be found in one of the directories 217 246 mentioned in the [FPATH] variable. 218 247 219 248 With this setup, if you are using a cmdliner based tool named 220 - [thetool] that did not {{!install_completion_tool}install} a completion 249 + [thetool] that did not {{!install_tool_completion}install} a completion 221 250 definition. You can always do it yourself by invoking: 222 251 223 252 {@sh[ ··· 253 282 254 283 If the function cannot be found make sure the [cmdliner] library is 255 284 installed, that the generic scripts were 256 - {{!install_completion_generic}installed} and that the 285 + {{!install_generic_completion}installed} and that the 257 286 [_cmdliner_generic] file can be looked up by [_completion_loader]. 258 287 259 288 With this setup, if you are using a cmdliner based tool named 260 - [thetool] that did not {{!install_completion_tool}install} a completion 289 + [thetool] that did not {{!install_tool_completion}install} a completion 261 290 definition. You can always do it yourself by invoking: 262 291 263 292 {@sh[ ··· 291 320 where you want with the [cmdliner generic-completion] and 292 321 [cmdliner tool-completion] commands. 293 322 294 - {3:install_completion_generic Generic completion scripts} 323 + {3:install_generic_completion Generic completion scripts} 295 324 296 325 The generic completion scripts must be installed by the 297 326 [cmdliner] library. They should not be part of your tool install. If ··· 307 336 Directories are created as needed. Use option [--dry-run] to see which 308 337 paths would be written by an [install] invocation. 309 338 310 - {3:install_completion_tool Tool completion scripts} 339 + {3:install_tool_completion Tool completion scripts} 311 340 312 341 If your tool named [thetool] uses Cmdliner you should install completion 313 - definitions for them. They rely on the {{!install_completion_generic}generic 342 + definitions for them. They rely on the {{!install_generic_completion}generic 314 343 scripts} to be installed. These tool specific scripts can be inspected 315 344 and installed via these invocations: 316 345 ··· 333 362 install: [ "cmdliner" "install" "tool-completion" "thetool" "%{share}%"] 334 363 ]} 335 364 336 - If [cmdliner] is only an optional dependency use: 365 + If [cmdliner] is only an optional dependency use the [{cmdliner:installed}] 366 + filter on the command invocation. 337 367 338 - {@sh[ 339 - install: [ "cmdliner" "install" "tool-completion" "thetool" "%{share}%" 340 - {cmdliner:installed} ] 341 - ]} 368 + If you are also {{!install_tool_manpages}installing manpages} rather 369 + use the [install tool-support] command, see this 370 + {{!page-cookbook.tip_tool_support}cookbook tip}. 342 371 343 372 {2:completion_protocol Completion protocol} 344 373
+38 -22
doc/cookbook.mld
··· 76 76 77 77 Unless your tool is very simple, avoid making manpages the main 78 78 documentation medium of your tool. The medium is rather limited and 79 - even though you can convert them to HTML, their cross references 79 + even though you can convert them to HTML, its cross references 80 80 capabilities are rather limited which makes discussing your tool 81 81 online more difficult. 82 82 ··· 170 170 let main () = Cmd.value' tool 171 171 let () = if !Sys.interactive then () else exit (main ()) 172 172 ]} 173 + 174 + {2:tip_tool_support Installing completions and manpages} 175 + 176 + The [cmdliner] tool can be used to install completion scripts and 177 + manpages for you tool and its subcommands by using the dedicated 178 + {{!page-cli.install_tool_completion}[install tool-completion]} and 179 + {{!page-cli.install_tool_manpages}[install tool-manpages]} subcommands. 180 + 181 + To install both directly (and possibly other support files in the future) 182 + it is more concise to use the [install 183 + tool-support] command. Invoke with [--help] for more information. 184 + 185 + If you are installing your package for [thetool] with [opam] and have 186 + [cmdliner] as a direct dependency and the tool is located in the 187 + build at the path [$BUILD/thetool], you can add the following 188 + [install:] instruction (also works if your build system is using a 189 + [.install] file). 190 + 191 + {@sh[ 192 + install: ["cmdliner" "install" "tool-support" 193 + "--sharedir=%{share}%" "--mandir=%{man}%" 194 + "$BUILD/thetool" "%{prefix}%"] 195 + ]} 196 + 197 + Note that you need to specify the path to the built executable, trying 198 + to lookup the tool in the [PATH] does not work as the install does not 199 + see it yet. Also more than one tool can be specified and there is a 200 + syntax for specifying the actual tool name if it is renamed when 201 + installed; see [--help] for more details. If [cmdliner] is only an 202 + optional dependency use the opam filter [{cmdliner:installed}] on the 203 + command invocation. 173 204 174 205 {1:conventions Conventions} 175 206 ··· 453 484 454 485 The command line interface manual has all 455 486 {{!page-cli.cli_completion}the details} and 456 - {{!page-cli.install_completion_tool} specific instructions} for 457 - complementing your tool install. 487 + {{!page-cli.install_tool_completion} specific instructions} for 488 + complementing your tool install, see also {!tip_tool_support}. 458 489 459 490 {2:cmds_listing How can I list all the commands of my tool?} 460 491 ··· 485 516 {{:https://erratique.ch/software/b0/doc/B0_std/Fmt/index.html#val-strip_styles} 486 517 this one} has been applied that automatically strips the styling. 487 518 488 - 489 519 {1:manpage Manpages} 490 520 491 521 {3:manpage_hide How do I prevent an item from being automatically listed?} ··· 513 543 `P "Without a command $(cmd) invokes $(i,TOOL)"; ] 514 544 ]} 515 545 516 - {2:manpage_gen How can I generate all the command manpages of my tool?} 546 + {2:manpage_install How can I install all the manpages of my tool?} 517 547 518 - The [tool-commands] command of the [cmdliner] tool allows to list 519 - all the commands of a cmdliner based tool. The following shell snippet 520 - generates all the manpages in the current working directory (note it 521 - assumes that the manual section of each command is [1]): 522 - {@shell[ 523 - TOOL=mytool 524 - $TOOL --help=groff > $TOOL.1 525 - while IFS= read -r SUB; do 526 - $TOOL $SUB --help=groff > $TOOL-$(echo $SUB | tr ' ' '-').1 527 - done < <(cmdliner tool-commands $TOOL) 528 - ]} 529 - 530 - See also {!manpage_install}. 531 - 532 - {2:manpage_install How can I install all the command manpages of my tool?} 533 - 534 - TODO 548 + The command line interface manual 549 + {{!page-cli.install_tool_manpages}the details} on how to install the manpages 550 + of your tool and its subcommands. See also {!tip_tool_support}. 535 551 536 552 {1:blueprints Blueprints} 537 553
+6 -6
src/cmdliner.mli
··· 270 270 {- [`Help (format, name)], the evaluation fails and [Cmdliner] prints 271 271 a manpage in format [format]. If [name] is [None] this is the 272 272 the main command's manpage. If [name] is [Some c] this is 273 - the man page of the sub command [c] of the main command.}} *) 273 + the man page of the subcommand [c] of the main command.}} *) 274 274 end 275 275 276 276 (** Commands. ··· 279 279 values. A command value binds a term and its documentation to a 280 280 command name. 281 281 282 - A command can group a list of sub commands (and recursively). In this 282 + A command can group a list of subcommands (and recursively). In this 283 283 case your tool defines a tree of commands, each with its own command 284 284 line syntax. The root of that tree is called the {e main command}; 285 285 it represents your tool and its name. *) ··· 456 456 457 457 val group : ?default:'a Term.t -> info -> 'a t list -> 'a t 458 458 (** [group i ?default cmds] is a command with information [i] that 459 - groups sub commands [cmds]. [default] is the command line syntax 460 - to parse if no sub command is specified on the command line. If 461 - [default] is [None] (default), the tool errors when no sub 462 - command is specified. *) 459 + groups subcommands [cmds]. [default] is the command line syntax 460 + to parse if no subcommand is specified on the command line. If 461 + [default] is [None] (default), the tool errors when no subcommand 462 + is specified. *) 463 463 464 464 val name : 'a t -> string 465 465 (** [name c] is the name of [c]. *)
+223 -45
src/tool/cmdliner_main.ml
··· 56 56 with 57 57 | Sys_error e -> Error e 58 58 59 - (* Tool command list *) 59 + (* Cmdliner based tool introspection. 60 + 61 + Note this is a bit hackish but does the job. At some point we could 62 + investigate cleaner protocols with the `--cmdliner` reserved option. *) 63 + 64 + let split_toolname toolexec = 65 + let tool, name = Scanf.sscanf toolexec "%s@:%s" (fun n e -> n, e) in 66 + let name = 67 + if name <> "" then name else 68 + let name = Filename.basename tool in 69 + match Filename.chop_suffix_opt ~suffix:".exe" tool with 70 + | None -> name | Some name -> name 71 + in 72 + tool, name 60 73 61 - let tool_commands tool = 74 + let get_tool_commands tool = 75 + (* We get that by using the completion protocol, see doc/cli.mld *) 62 76 try 63 77 let subcommands cmd = 64 78 let rec find_subs = function ··· 66 80 let rec subs acc = function 67 81 | "group" :: _ | [] -> acc 68 82 | "item" :: sub :: lines -> 69 - let sub = 70 - if cmd = "" then sub else String.concat " " [cmd; sub] 71 - in 83 + let sub = if cmd = "" then sub else String.concat " " [cmd; sub]in 72 84 subs (sub :: acc) lines 73 85 | _ :: lines -> subs acc lines 74 86 in ··· 86 98 loop (if cmd <> "" then cmd :: acc else acc) (List.rev_append subs cmds) 87 99 | [] -> List.sort String.compare acc 88 100 in 89 - List.iter print_endline (loop [] [""]); 90 - Cmdliner.Cmd.Exit.ok 91 - with 92 - | Failure e -> 93 - prerr_endline e; 94 - prerr_endline "Are you sure this a cmdliner based tool?"; 95 - Cmdliner.Cmd.Exit.some_error 101 + Ok (loop [] [""]) 102 + with Failure e -> Error e 103 + 104 + let get_tool_command_man tool ~name cmd = 105 + let exec = if cmd = "" then tool else String.concat " " [tool; cmd] in 106 + let man_basename = 107 + let exec = if cmd = "" then name else String.concat " " [name; cmd] in 108 + (String.map (function ' ' -> '-' | c -> c) exec) 109 + in 110 + let add_section man = 111 + let rec extract_section = function 112 + | line :: lines -> 113 + begin match Scanf.sscanf line ".TH %s %d" (fun _ n -> n) with 114 + | n -> Ok (n, man_basename, man) 115 + | exception Scanf.Scan_failure _ -> extract_section lines 116 + end 117 + | [] -> 118 + Error (strf "%s command: Could not extract section from manual" exec) 119 + in 120 + extract_section (String.split_on_char '\n' man) 121 + in 122 + let get_groff = exec ^ " --help=groff" in 123 + match exec_stdout get_groff with 124 + | Error _ as e -> e 125 + | Ok man -> add_section man 126 + 127 + let get_tool_manpages tool ~name = match get_tool_commands tool with 128 + | Error _ as e -> e 129 + | Ok cmds -> 130 + try 131 + let man cmd = match get_tool_command_man tool ~name cmd with 132 + | Error e -> failwith e 133 + | Ok man -> man 134 + in 135 + Ok (List.sort compare (List.map man ("" :: cmds))) 136 + with 137 + | Failure e -> Error e 96 138 97 139 (* File path actions *) 98 140 ··· 111 153 | Error e -> failwith e 112 154 end 113 155 114 - (* Shells *) 156 + (* Shells completion *) 115 157 116 158 module type SHELL = sig 117 159 val name : string ··· 163 205 print_string (Shell.tool_completion ~toolname); 164 206 Cmdliner.Cmd.Exit.ok 165 207 208 + (* Install commands *) 209 + 166 210 let install_generic_completion ~dry_run shells sharedir = 167 211 with_binary_stdout @@ fun () -> 168 212 let install ~dry_run sharedir (module Shell : SHELL) = ··· 174 218 List.iter (install ~dry_run sharedir) shells; 175 219 Cmdliner.Cmd.Exit.ok 176 220 177 - let install_tool_completion ~dry_run shells ~toolnames sharedir = 221 + let install_tool_completion ~dry_run ~shells ~toolnames ~sharedir = 178 222 with_binary_stdout @@ fun () -> 179 223 let install ~dry_run ~toolnames sharedir (module Shell : SHELL) = 180 224 let dest = Filename.concat sharedir Shell.sharedir in ··· 188 232 List.iter (install ~dry_run ~toolnames sharedir) shells; 189 233 Cmdliner.Cmd.Exit.ok 190 234 235 + let install_tool_manpages ~dry_run ~tools ~mandir = 236 + (* Note this correctly handles manpages sections but at the moment 237 + all manpages for tool and commands are in section 1. *) 238 + let rec get_mans tool = 239 + let tool, name = split_toolname tool in 240 + match get_tool_manpages tool ~name with 241 + | Ok mans -> mans 242 + | Error e -> failwith e 243 + in 244 + try 245 + let mans = List.sort compare (List.concat (List.map get_mans tools)) in 246 + let rec install ~dry_run ~last_sec = function 247 + | (sec, basename, man) :: mans -> 248 + let mandir = Filename.concat mandir (strf "man%d" sec) in 249 + let manfile = Filename.concat mandir (strf "%s.%d" basename sec) in 250 + if last_sec <> sec then mkdir ~dry_run mandir; 251 + write_file ~dry_run manfile man; 252 + install ~dry_run ~last_sec:sec mans 253 + | [] -> () 254 + in 255 + install ~dry_run ~last_sec:(-1) mans; 256 + Cmdliner.Cmd.Exit.ok 257 + with Failure e -> prerr_endline e; Cmdliner.Cmd.Exit.some_error 258 + 259 + let install_tool_support ~dry_run tools shells ~prefix ~sharedir ~mandir = 260 + let sharedir = match sharedir with 261 + | None -> Filename.concat prefix "share" | Some sharedir -> sharedir 262 + in 263 + let mandir = match mandir with 264 + | None -> Filename.concat sharedir "man" | Some mandir -> mandir 265 + in 266 + let rc = install_tool_manpages ~dry_run ~tools ~mandir in 267 + if rc <> Cmdliner.Cmd.Exit.ok then rc else 268 + let toolnames = List.map snd (List.map split_toolname tools) in 269 + install_tool_completion ~dry_run ~shells ~toolnames ~sharedir 270 + 271 + (* Tool command listing command *) 272 + 273 + let tool_commands tool = match get_tool_commands tool with 274 + | Ok subs -> List.iter print_endline subs; Cmdliner.Cmd.Exit.ok 275 + | Error e -> prerr_endline e; Cmdliner.Cmd.Exit.some_error 276 + 191 277 (* Command line interface *) 192 278 193 279 open Cmdliner ··· 197 283 let doc = "Do not install, output paths that would be written." in 198 284 Arg.(value & flag & info ["dry-run"] ~doc) 199 285 286 + let prefix = 287 + let doc = "$(docv) is the install prefix. For example $(b,/usr/local)." in 288 + Arg.(required & pos ~rev:true 0 (some dirpath) None & 289 + info [] ~doc ~docv:"PREFIX") 290 + 291 + let sharedir_doc = "$(docv) is the $(b,share) directory to install to." 292 + let sharedir_docv = "SHAREDIR" 200 293 let sharedir_posn ~rev n = 201 - let doc = "$(docv) is the $(b,share) directory to install to." in 202 294 Arg.(required & pos ~rev n (some dirpath) None & 203 - info [] ~doc ~docv:"SHAREDIR") 295 + info [] ~doc:sharedir_doc ~docv:sharedir_docv) 204 296 205 297 let sharedir_pos0 = sharedir_posn ~rev:false 0 206 298 let sharedir_poslast = sharedir_posn ~rev:true 0 299 + let sharedir_opt = 300 + let absent = "$(i,PREFIX)$(b,/share)" in 301 + Arg.(value & opt (some dirpath) None & 302 + info ["sharedir"] ~doc:sharedir_doc ~docv:sharedir_docv ~absent) 303 + 304 + let mandir_doc = "$(docv) is the root $(b,man) directory to install to." 305 + let mandir_docv = "MANDIR" 306 + let mandir_poslast = 307 + Arg.(required & pos ~rev:true 0 (some dirpath) None & 308 + info [] ~doc:mandir_doc ~docv:mandir_docv) 309 + 310 + let mandir_opt = 311 + let absent = "$(i,SHAREDIR)$(b,/man)" in 312 + Arg.(value & opt (some dirpath) None & 313 + info ["mandir"] ~doc:mandir_doc ~docv:mandir_docv ~absent) 207 314 208 315 let shell_assoc = List.map (fun ((module S : SHELL) as s) -> S.name, s) shells 209 316 let shells_doc = Arg.doc_alts_enum shell_assoc ··· 221 328 let shell_pos1 = shell_posn 1 222 329 223 330 let toolname_posn n = 224 - let doc = "$(docv) is the name of the tool to complete" in 225 - Arg.(required & pos n (some string) None & info [] ~doc ~docv:"TOOLNAME") 331 + let doc = "$(docv) is the name of the tool to complete." in 332 + Arg.(required & pos n (some filepath) None & info [] ~doc ~docv:"TOOLNAME") 226 333 227 334 let toolname_pos0 = toolname_posn 0 228 335 let toolname_pos1 = toolname_posn 1 ··· 231 338 Arg.(non_empty & pos_left ~rev:true 0 string [] & 232 339 info [] ~doc ~docv:"TOOLNAME") 233 340 341 + let tools_posleft = 342 + let doc = 343 + "$(i,TOOLEXEC) is the tool executable. Searched in the $(b,PATH) unless \ 344 + an explicit file path is specified. $(i,NAME) is the tool name, if \ 345 + unspecified derived from $(i,TOOLEXEC) by taking the basename and \ 346 + stripping any $(b,.exe) extension. Repeatable." 347 + in 348 + let docv = "TOOLEXEC[:NAME]" in 349 + Arg.(non_empty & pos_left ~rev:true 0 filepath [] & info [] ~doc ~docv) 350 + 234 351 let generic_completion_cmd = 235 352 let doc = "Output generic completion scripts" in 236 353 let man = 237 354 [ `S Manpage.s_description; 238 - `P "$(cmd) outputs the generic completion script of a given shell. \ 239 - For example:"; 355 + `P "$(cmd) outputs the generic cmdliner completion script for a given \ 356 + shell. Examples:"; 240 357 `Pre "$(cmd) $(b,zsh)"; `Noblank; 241 - `Pre "$(b,eval) \\$($(cmd) $(b,zsh))"; ] 358 + `Pre "$(b,eval) $(b,\\$\\()$(cmd) $(b,zsh\\))"; 359 + `P "The script needs to be loaded in a shell for tool specific \ 360 + scripts output by the command $(b,tool-completion) to work. See \ 361 + command $(b,install generic-completion) to install them."; 362 + ] 242 363 in 243 364 Cmd.make (Cmd.info "generic-completion" ~doc ~man) @@ 244 365 let+ shell = shell_pos0 in 245 366 generic_completion shell 246 367 247 368 let tool_commands_cmd = 248 - let doc = "Output all commands of a cmdliner tool" in 369 + let doc = "Output all subcommands of a cmdliner tool" in 249 370 let man = 250 371 [ `S Manpage.s_description; 251 - `P "$(cmd) $(i,TOOL) outputs all the commands of the cmdliner based \ 252 - tool, one per line."; 372 + `P "$(cmd) outputs all the subcommands of a given cmdliner based \ 373 + tool, one per line. Examples:"; 374 + `Pre "$(cmd) $(b,./mytool)"; `Noblank; 375 + `Pre "$(cmd) $(b,cmdliner)"; 253 376 ] 254 377 in 255 378 Cmd.make (Cmd.info "tool-commands" ~doc ~man) @@ 256 379 let+ tool = 257 - Arg.(required & pos 0 (some filepath) None & info [] ~docv:"TOOL") 380 + let doc = 381 + "$(docv) is the tool executable. Searched in the $(b,PATH) unless \ 382 + an explicit file path is specified." 383 + in 384 + Arg.(required & pos 0 (some string) None & info [] ~doc ~docv:"TOOLEXEC") 258 385 in 259 386 tool_commands tool 260 387 261 - 262 388 let tool_completion_cmd = 263 389 let doc = "Output tool completion scripts" in 264 390 let man = 265 391 [ `S Manpage.s_description; 266 - `P "$(cmd) outputs the tool completion script of a given shell. \ 267 - For example:"; 268 - `Pre "$(cmd) $(b,zsh mytool)"; ] 392 + `P "$(cmd) outputs the tool specific completion script of a given shell. \ 393 + Example:"; 394 + `Pre "$(cmd) $(b,zsh mytool)"; 395 + `P "Note that tool specific completion script need the corresponding \ 396 + generic completion script output by $(b,generic-completion) to be \ 397 + loaded in the shell. To install these scripts see command \ 398 + $(b,install tool-completion)."; 399 + ] 269 400 in 270 401 Cmd.make (Cmd.info "tool-completion" ~doc ~man) @@ 271 402 let+ shell = shell_pos0 and+ toolname = toolname_pos1 in ··· 277 408 `S Manpage.s_description; 278 409 `P "$(cmd) installs the generic completion script of given shells in \ 279 410 a $(b,share) directory according to specific shell conventions. \ 280 - Directories are not created. \ 411 + Directories are created if needed. \ 281 412 Use option $(b,--dry-run) to see which paths would be written. \ 282 - For example:"; 413 + Examples:"; 283 414 `Pre "$(cmd) $(b,/usr/local/share) # All supported shells"; `Noblank; 284 - `Pre "$(cmd) $(b,--shell zsh /usr/local/share)"; ] 415 + `Pre "$(cmd) $(b,--shell zsh /usr/local/share)"; 416 + `P "To inspect the actual scripts use the command \ 417 + $(b,generic-completion)."; 418 + ] 285 419 in 286 420 Cmd.make (Cmd.info "generic-completion" ~doc ~man) @@ 287 421 (* No let punning in < 4.13 *) ··· 293 427 let doc = "Install tool completion scripts" in 294 428 let man = [ 295 429 `S Manpage.s_description; 296 - `P "$(cmd) installs the tool completion script of given shells in \ 430 + `P "$(cmd) installs tool completion script of given tools and shells in \ 297 431 a $(b,share) directory according to specific shell conventions. \ 298 - Directories are not created. \ 432 + Directories are created if needed. \ 299 433 Use option $(b,--dry-run) to see which paths would be written. \ 300 - For example:"; 434 + Example:"; 301 435 `Pre "$(cmd) $(b,mytool) $(b,/usr/local/share) # All supported shells"; 302 436 `Noblank; 303 - `Pre "$(cmd) $(b,--shell zsh mytool /usr/local/share)"; ] 437 + `Pre "$(cmd) $(b,--shell zsh mytool /usr/local/share)"; 438 + `P "Note that the command $(b,install tool-support) also installs \ 439 + completions like this command does. To inspect the actual scripts \ 440 + use the command $(b,tool-completion)."; 441 + ] 304 442 in 305 443 Cmd.make (Cmd.info "tool-completion" ~doc ~man) @@ 306 444 (* No let punning in < 4.13 *) 307 445 let+ dry_run = dry_run and+ shells = shells_opt 308 - and+ toolnames = toolnames_posleft and+ sharedir_poslast = sharedir_poslast in 309 - install_tool_completion ~dry_run shells ~toolnames sharedir_poslast 446 + and+ toolnames = toolnames_posleft and+ sharedir = sharedir_poslast in 447 + install_tool_completion ~dry_run ~shells ~toolnames ~sharedir 448 + 449 + let install_tool_manpages_cmd = 450 + let doc = "Install tool and subcommand manpages" in 451 + let man = [ 452 + `S Manpage.s_description; 453 + `P "$(cmd) installs the manpages of the tool and its commands \ 454 + according in directories of a $(b,man) directory. Directories are \ 455 + created if needed. \ 456 + Use option $(b,--dry-run) to see which paths would be written. \ 457 + Example:"; 458 + `Pre "$(cmd) $(b,./mytool) $(b,/usr/local/share/man)"; 459 + `P "Note that the command $(b,install tool-support) also installs manpages \ 460 + like this command does." 461 + ] 462 + in 463 + Cmd.make (Cmd.info "tool-manpages" ~doc ~man) @@ 464 + let+ dry_run = dry_run 465 + and+ tools = tools_posleft and+ mandir = mandir_poslast in 466 + install_tool_manpages ~dry_run ~tools ~mandir 467 + 468 + let install_tool_support_cmd = 469 + let doc = "Install both tool completion and manpages" in 470 + let man = [ 471 + `S Manpage.s_description; 472 + `P "$(cmd) combines commands $(b,install tool-completion) and \ 473 + $(b,install tool-manpages) to install all tool support files \ 474 + in a given $(i,PREFIX) which is assumed to follow the Filesystem \ 475 + Hierarchy Standard. 476 + Use options $(b,--sharedir) and/or $(b,--mandir) if that is 477 + not the case (e.g. in $(b,opam) as of writing). 478 + Use option $(b,--dry-run) to see which paths would be written. \ 479 + Example:"; 480 + `Pre "$(cmd) $(b,./mytool) $(b,/usr/local)"; ] 481 + in 482 + Cmd.make (Cmd.info "tool-support" ~doc ~man) @@ 483 + let+ dry_run = dry_run and+ shells = shells_opt 484 + and+ tools = tools_posleft and+ sharedir = sharedir_opt 485 + and+ mandir = mandir_opt and+ prefix = prefix in 486 + install_tool_support ~dry_run tools shells ~prefix ~sharedir ~mandir 310 487 311 488 let install_cmd = 312 - let doc = "Install cmdliner support files" in 489 + let doc = "Install support files for cmdliner tools" in 313 490 let man = 314 491 [ `S Manpage.s_description; 315 492 `P "$(cmd) subcommands install cmdliner support files. \ ··· 317 494 subcommands with $(b,--help) for more details."; ] 318 495 in 319 496 Cmd.group (Cmd.info "install" ~doc ~man) @@ 320 - [install_generic_completion_cmd; install_tool_completion_cmd] 497 + [install_generic_completion_cmd; install_tool_completion_cmd; 498 + install_tool_manpages_cmd; install_tool_support_cmd] 321 499 322 500 let main_cmd = 323 - let doc = "Helper tool for cmdliner based programs" in 501 + let doc = "Helper tool for cmdliner based tools" in 324 502 let default = Term.(ret (const (`Help (`Pager, None)))) in 325 503 let man = 326 504 [ `S Manpage.s_description; 327 - `P "$(tool) is a helper tool for Cmdliner based programs. It \ 328 - helps with installing command line completion scripts. \ 329 - See the library documentation or invoke \ 330 - subcommands with $(b,--help) for more details."; ] 505 + `P "$(tool) is a helper for tools using the cmdliner command line \ 506 + interface library. It helps with installing command line \ 507 + completion scripts and manpages. See the library documentation or \ 508 + invoke subcommands with $(b,--help) for more details."; ] 331 509 in 332 510 Cmd.group (Cmd.info "cmdliner" ~version:"%%VERSION%%" ~doc ~man) ~default @@ 333 511 [generic_completion_cmd; tool_commands_cmd; tool_completion_cmd; install_cmd]