This commit is contained in:
c-cube 2025-12-01 22:21:55 +00:00
parent 247371f990
commit e14e9845e6
10 changed files with 196 additions and 46 deletions

View file

@ -1,3 +1,36 @@
v2.1.0 2025-11-25 Zagreb
------------------------
- completion: add support for powershell (#214).
Thanks to Brian Ward for the work.
- bash completion: improve bash completion on files and directories (#238).
Thanks to Brian Ward for the report and fix.
- bash completion: improve completion of long options with `=` (#231).
Thanks to Brian Ward for the patch.
- zsh completion: fix completion of files and directories on glued
forms (#230).
Thanks to Brian Ward for the help.
- zsh completion: strip ANSI escapes from doc strings. The experience
is too unreliable (#220).
- cmdliner tool: add support for generating standalone completion
scripts via the `--standalone-completion` option. Can be used if you
distribute your software in a context where the cmdliner library
may not be installed (#243).
Thanks to Brian Ward for suggesting.
- Add alternative instructions to the cookbook for installing tool support
files with `dune` (#250).
Thanks to Brian Ward for the patch and research.
- `--help` output, improve graceful degradation on groff or pager
errors (#140).
Thanks to Sergey Fedorov for the report.
v2.0.0 2025-09-26 Zagreb v2.0.0 2025-09-26 Zagreb
------------------------ ------------------------

View file

@ -183,8 +183,8 @@ completion.
The completion process happens via a {{!completion_protocol}protocol} The completion process happens via a {{!completion_protocol}protocol}
which is interpreted by generic shell completion scripts that are which is interpreted by generic shell completion scripts that are
installed by the library. For now the [zsh] and [bash] shells are installed by the library. For now the [zsh], [bash], and PowerShell
supported. ([pwsh]) shells are supported.
Tool developers can easily {{!install_tool_completion}install} Tool developers can easily {{!install_tool_completion}install}
completion definitions that invoke these completion scripts. Tool completion definitions that invoke these completion scripts. Tool
@ -295,6 +295,45 @@ bash-completion [2.12] in favour of [_comp_load] but many distributions
are on [< 2.12] and in [2.12] [_completion_loader] simply calls are on [< 2.12] and in [2.12] [_completion_loader] simply calls
[_comp_load]. [_comp_load].
{3:user_pwsh For PowerShell ([pwsh])}
PowerShell Core supports tab completion but lacks the kind of
discovery mechanism that other shells have for these scripts.
Therefore, you need to explicitly source the completion script
for cmdliner based tools. This can be done in the
{{:https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles} [$profile]}
file by adding something like the following:
{@ps1[
# first, load the generic cmdliner completion function
. "$(opam var share)/powershell/cmdliner_generic_completion.ps1"
# then, register each tool you want completions for
# for example:
. "$(opam var share)/powershell/cmdliner_completion.ps1"
. "$(opam var share)/powershell/odoc_completion.ps1"
# ...
]}
Note that these instruction do not react dynamically to [opam]
switches changes so you may see odd completion behaviours when you do
so, see this {{:https://github.com/ocaml/opam/issues/6427}this opam
issue}.
With this setup, if you are using a cmdliner based tool named
[thetool] that did not {{!install_tool_completion}install} a completion
definition. You can always do it yourself by invoking:
{@ps1[
Invoke-Expression $(cmdliner tool-completion pwsh thetool)
]}
or if you find that easier to remember:
{@ps1[
Register-ArgumentCompleter -Native -CommandName thetool -ScriptBlock $Global:_cmdliner_generic
]}
{2:install_completion Install} {2:install_completion Install}
Completion scripts need to be installed in subdirectories of a Completion scripts need to be installed in subdirectories of a
@ -310,7 +349,8 @@ The final destination directory in [share] depends on the shell:
{ul {ul
{- For [zsh] it is [$SHAREDIR/zsh/site-functions]} {- For [zsh] it is [$SHAREDIR/zsh/site-functions]}
{- For [bash] it is [$SHAREDIR/bash-completion/completions]}} {- For [bash] it is [$SHAREDIR/bash-completion/completions]}
{- For [pwsh] it is [$SHAREDIR/powershell]}}
If that is unsatisfying you can output the completion scripts directly If that is unsatisfying you can output the completion scripts directly
where you want with the [cmdliner generic-completion] and where you want with the [cmdliner generic-completion] and

View file

@ -96,8 +96,8 @@ If you are porting your command line parsing to [Cmdliner] and that
you have conventions that clash with [Cmdliner]'s ones but you need to you have conventions that clash with [Cmdliner]'s ones but you need to
preserve backward compatibility, one way of proceeding is to preserve backward compatibility, one way of proceeding is to
pre-process {!Sys.argv} into a new array of the right shape before pre-process {!Sys.argv} into a new array of the right shape before
giving it to command {{!Cmdliner.Cmd.section-eval}evaluation giving it to command {{!Cmdliner.Cmd.section-eval}evaluation functions}
functions} via the [?argv] optional argument. via the [?argv] optional argument.
These are two common cases: These are two common cases:
@ -172,7 +172,7 @@ let cmd =
Cmd.group (Cmd.info "tool") ~default @@ Cmd.group (Cmd.info "tool") ~default @@
[Cmd_import.cmd; Cmd_serve.cmd; Cmd_user.cmd] [Cmd_import.cmd; Cmd_serve.cmd; Cmd_user.cmd]
let main () = Cmd.value' cmd let main () = Cmd.eval' cmd
let () = if !Sys.interactive then () else exit (main ()) let () = if !Sys.interactive then () else exit (main ())
]} ]}
@ -213,22 +213,70 @@ If [cmdliner] is only an optional dependency of your package use the
opam filter [{cmdliner:installed}] after the closing bracket of the command opam filter [{cmdliner:installed}] after the closing bracket of the command
invocation. invocation.
{3:top_tool_support_with_dune_install With [dune build @install]}
Users of [dune] 3.5 or later can use the [cmdliner install
tool-support] command described above, along with the
{{:https://dune.readthedocs.io/en/stable/reference/dune/rule.html#directory-targets}
[directory-targets]}
feature and
{{:https://dune.readthedocs.io/en/latest/reference/dune/install.html}
[install]} stanza, to hook into the existing [dune build @install] command.
For an executable named [mytool] defined in a [dune] file, you can add
the following to the same [dune] file to populate the completion
scripts and manpages when running [dune build @install] (which is what
is used by [opam install]):
{@dune[
(rule
(target (dir cmdliner-support))
(deps mytool.exe)
(action
(ignore-stdout
(run cmdliner install tool-support ./mytool.exe:mytool cmdliner-support))))
(install
(section share_root)
(dirs (cmdliner-support/share as .)))
]}
If these features of dune are not available to your project, you may
instead need to update the [opam] file, see {{!tip_tool_support_with_opam_dune}
these instructions}.
{3:tip_tool_support_with_opam_dune With [opam] and [dune]} {3:tip_tool_support_with_opam_dune With [opam] and [dune]}
First make sure your understand the First make sure your understand the
{{!tip_tool_support_with_opam}above basic instructions} for [opam]. {{!tip_tool_support_with_opam}above basic instructions} for [opam].
You then You then
{{:https://dune.readthedocs.io/en/stable/reference/packages.html#generating-opam-files}need to figure out} how to add the [cmdliner install] instruction to the [build:] {{:https://dune.readthedocs.io/en/stable/reference/packages.html#generating-opam-files}
field of the opam file after your [dune] build instructions. For a tool named need to add a [.opam.template]} file which inserts the [cmdliner
[tool] the result should eventually look this: install] instruction to the [build:] field of the opam file after your
[dune] build instructions. For a tool named [mytool] the
[mytool.opam.template] file should contain:
{@sh[ {@sh[
build: [ build: [
[ … ] # Your regular dune build instructions # These first two commands are what dune generates if you don't have
# a template
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
# This is the important command for cmdliner's files
["cmdliner" "install" "tool-support" ["cmdliner" "install" "tool-support"
"--update-opam-install=%{_:name}%.install" "--update-opam-install=%{_:name}%.install"
"_build/default/install/bin/tool" {os != "win32"} "_build/install/default/bin/mytool" {os-family != "windows"}
"_build/default/install/bin/tool.exe" {os = "win32"} "_build/install/default/bin/mytool.exe" {os-family = "windows"}
"_build/cmdliner-install"]] "_build/cmdliner-install"]]
]} ]}
@ -254,11 +302,11 @@ to support these conventions:
{[ {[
let infile = let infile =
let doc = "$(docv) is the file to read from. Use $(b,-) for $(b,stdin)" in let doc = "$(docv) is the file to read from. Use $(b,-) for $(b,stdin)" in
Arg.(value & opt string "-" & info ["i", "input-file"] ~doc ~docv:"FILE") Arg.(value & opt filepath "-" & info ["i", "input-file"] ~doc ~docv:"FILE")
let outfile = let outfile =
let doc = "$(docv) is the file to write to. Use $(b,-) for $(b,stdout)" in let doc = "$(docv) is the file to write to. Use $(b,-) for $(b,stdout)" in
Arg.(value & opt string "-" & info ["o", "output-file"] ~doc ~docv:"FILE") Arg.(value & opt filepath "-" & info ["o", "output-file"] ~doc ~docv:"FILE")
]} ]}
Here is {!Stdlib} based code to read to a string a file or standard Here is {!Stdlib} based code to read to a string a file or standard
@ -637,7 +685,7 @@ open Cmdliner
open Cmdliner.Term.Syntax open Cmdliner.Term.Syntax
let cmd = let cmd =
Cmd.make (Cmd.info "TODO" ~version:"v2.0.0") @@ Cmd.make (Cmd.info "TODO" ~version:"v2.1.0") @@
let+ unit = Term.const () in let+ unit = Term.const () in
tool unit tool unit
@ -660,7 +708,7 @@ open Cmdliner.Term.Syntax
let flag = Arg.(value & flag & info ["flag"] ~doc:"The flag") let flag = Arg.(value & flag & info ["flag"] ~doc:"The flag")
let infile = let infile =
let doc = "$(docv) is the input file. Use $(b,-) for $(b,stdin)." in let doc = "$(docv) is the input file. Use $(b,-) for $(b,stdin)." in
Arg.(value & pos 0 string "-" & info [] ~doc ~docv:"FILE") Arg.(value & pos 0 filepath "-" & info [] ~doc ~docv:"FILE")
let cmd = let cmd =
let doc = "The tool synopsis is TODO" in let doc = "The tool synopsis is TODO" in
@ -672,7 +720,7 @@ let cmd =
Cmd.Exit.info exit_todo ~doc:"When there is stuff todo" :: Cmd.Exit.info exit_todo ~doc:"When there is stuff todo" ::
Cmd.Exit.defaults Cmd.Exit.defaults
in in
Cmd.make (Cmd.info "TODO" ~version:"v2.0.0" ~doc ~man ~exits) @@ Cmd.make (Cmd.info "TODO" ~version:"v2.1.0" ~doc ~man ~exits) @@
let+ flag and+ infile in let+ flag and+ infile in
tool ~flag ~infile tool ~flag ~infile
@ -696,7 +744,7 @@ open Cmdliner.Term.Syntax
let flag = Arg.(value & flag & info ["flag"] ~doc:"The flag") let flag = Arg.(value & flag & info ["flag"] ~doc:"The flag")
let infile = let infile =
let doc = "$(docv) is the input file. Use $(b,-) for $(b,stdin)." in let doc = "$(docv) is the input file. Use $(b,-) for $(b,stdin)." in
Arg.(value & pos 0 file "-" & info [] ~doc ~docv:"FILE") Arg.(value & pos 0 filepath "-" & info [] ~doc ~docv:"FILE")
let hey_cmd = let hey_cmd =
let doc = "The hey command synopsis is TODO" in let doc = "The hey command synopsis is TODO" in
@ -712,7 +760,7 @@ let ho_cmd =
let cmd = let cmd =
let doc = "The tool synopsis is TODO" in let doc = "The tool synopsis is TODO" in
Cmd.group (Cmd.info "TODO" ~version:"v2.0.0" ~doc) @@ Cmd.group (Cmd.info "TODO" ~version:"v2.1.0" ~doc) @@
[hey_cmd; ho_cmd] [hey_cmd; ho_cmd]
let main () = Cmd.eval' cmd let main () = Cmd.eval' cmd

View file

@ -81,7 +81,7 @@ let rm_cmd =
`S Manpage.s_bugs; `P "Report bugs to <bugs@example.org>."; `S Manpage.s_bugs; `P "Report bugs to <bugs@example.org>.";
`S Manpage.s_see_also; `P "$(b,rmdir)(1), $(b,unlink)(2)" ] `S Manpage.s_see_also; `P "$(b,rmdir)(1), $(b,unlink)(2)" ]
in in
Cmd.make (Cmd.info "rm" ~version:"v2.0.0" ~doc ~man) @@ Cmd.make (Cmd.info "rm" ~version:"v2.1.0" ~doc ~man) @@
let+ prompt and+ recursive and+ files in let+ prompt and+ recursive and+ files in
rm ~prompt ~recurse:recursive files rm ~prompt ~recurse:recursive files
@ -154,7 +154,7 @@ let cp_cmd =
`S Manpage.s_bugs; `S Manpage.s_bugs;
`P "Email them to <bugs@example.org>."; ] `P "Email them to <bugs@example.org>."; ]
in in
Cmd.make (Cmd.info "cp" ~version:"v2.0.0" ~doc ~man ~man_xrefs) @@ Cmd.make (Cmd.info "cp" ~version:"v2.1.0" ~doc ~man ~man_xrefs) @@
Term.ret @@ Term.ret @@
let+ verbose and+ recurse and+ force and+ srcs and+ dest in let+ verbose and+ recurse and+ force and+ srcs and+ dest in
cp ~verbose ~recurse ~force srcs dest cp ~verbose ~recurse ~force srcs dest
@ -265,7 +265,7 @@ let tail_cmd =
`S Manpage.s_see_also; `S Manpage.s_see_also;
`P "$(b,cat)(1), $(b,head)(1)" ] `P "$(b,cat)(1), $(b,head)(1)" ]
in in
Cmd.make (Cmd.info "tail" ~version:"v2.0.0" ~doc ~man) @@ Cmd.make (Cmd.info "tail" ~version:"v2.1.0" ~doc ~man) @@
let+ lines and+ follow and+ verb and+ pid and+ files in let+ lines and+ follow and+ verb and+ pid and+ files in
tail ~lines ~follow ~verb ~pid files tail ~lines ~follow ~verb ~pid files
@ -444,7 +444,7 @@ let help_cmd =
let main_cmd = let main_cmd =
let doc = "a revision control system" in let doc = "a revision control system" in
let man = help_secs in let man = help_secs in
let info = Cmd.info "darcs" ~version:"v2.0.0" ~doc ~sdocs ~man in let info = Cmd.info "darcs" ~version:"v2.1.0" ~doc ~sdocs ~man in
let default = Term.(ret (const (fun _ -> `Help (`Pager, None)) $ copts_t)) in let default = Term.(ret (const (fun _ -> `Help (`Pager, None)) $ copts_t)) in
Cmd.group info ~default [initialize_cmd; record_cmd; help_cmd] Cmd.group info ~default [initialize_cmd; record_cmd; help_cmd]

View file

@ -1,4 +1,4 @@
{0 Cmdliner {%html: <span class="version">v2.0.0</span>%}} {0 Cmdliner {%html: <span class="version">v2.1.0</span>%}}
Cmdliner provides a simple and compositional mechanism Cmdliner provides a simple and compositional mechanism
to convert command line arguments to OCaml values and pass them to to convert command line arguments to OCaml values and pass them to

View file

@ -152,7 +152,7 @@ let chorus_cmd =
`S Manpage.s_bugs; `S Manpage.s_bugs;
`P "Email bug reports to <bugs@example.org>." ] `P "Email bug reports to <bugs@example.org>." ]
in in
Cmd.make (Cmd.info "chorus" ~version:"v2.0.0" ~doc ~man) @@ Cmd.make (Cmd.info "chorus" ~version:"v2.1.0" ~doc ~man) @@
let+ count and+ msg in let+ count and+ msg in
chorus ~count msg chorus ~count msg

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -53,7 +53,7 @@ let rm_cmd =
`S Manpage.s_bugs; `P &quot;Report bugs to &lt;bugs@example.org&gt;.&quot;; `S Manpage.s_bugs; `P &quot;Report bugs to &lt;bugs@example.org&gt;.&quot;;
`S Manpage.s_see_also; `P &quot;$(b,rmdir)(1), $(b,unlink)(2)&quot; ] `S Manpage.s_see_also; `P &quot;$(b,rmdir)(1), $(b,unlink)(2)&quot; ]
in in
Cmd.make (Cmd.info &quot;rm&quot; ~version:&quot;v2.0.0&quot; ~doc ~man) @@ Cmd.make (Cmd.info &quot;rm&quot; ~version:&quot;v2.1.0&quot; ~doc ~man) @@
let+ prompt and+ recursive and+ files in let+ prompt and+ recursive and+ files in
rm ~prompt ~recurse:recursive files rm ~prompt ~recurse:recursive files
@ -104,7 +104,7 @@ let cp_cmd =
`S Manpage.s_bugs; `S Manpage.s_bugs;
`P &quot;Email them to &lt;bugs@example.org&gt;.&quot;; ] `P &quot;Email them to &lt;bugs@example.org&gt;.&quot;; ]
in in
Cmd.make (Cmd.info &quot;cp&quot; ~version:&quot;v2.0.0&quot; ~doc ~man ~man_xrefs) @@ Cmd.make (Cmd.info &quot;cp&quot; ~version:&quot;v2.1.0&quot; ~doc ~man ~man_xrefs) @@
Term.ret @@ Term.ret @@
let+ verbose and+ recurse and+ force and+ srcs and+ dest in let+ verbose and+ recurse and+ force and+ srcs and+ dest in
cp ~verbose ~recurse ~force srcs dest cp ~verbose ~recurse ~force srcs dest
@ -188,7 +188,7 @@ let tail_cmd =
`S Manpage.s_see_also; `S Manpage.s_see_also;
`P &quot;$(b,cat)(1), $(b,head)(1)&quot; ] `P &quot;$(b,cat)(1), $(b,head)(1)&quot; ]
in in
Cmd.make (Cmd.info &quot;tail&quot; ~version:&quot;v2.0.0&quot; ~doc ~man) @@ Cmd.make (Cmd.info &quot;tail&quot; ~version:&quot;v2.1.0&quot; ~doc ~man) @@
let+ lines and+ follow and+ verb and+ pid and+ files in let+ lines and+ follow and+ verb and+ pid and+ files in
tail ~lines ~follow ~verb ~pid files tail ~lines ~follow ~verb ~pid files
@ -338,7 +338,7 @@ let help_cmd =
let main_cmd = let main_cmd =
let doc = &quot;a revision control system&quot; in let doc = &quot;a revision control system&quot; in
let man = help_secs in let man = help_secs in
let info = Cmd.info &quot;darcs&quot; ~version:&quot;v2.0.0&quot; ~doc ~sdocs ~man in let info = Cmd.info &quot;darcs&quot; ~version:&quot;v2.1.0&quot; ~doc ~sdocs ~man in
let default = Term.(ret (const (fun _ -&gt; `Help (`Pager, None)) $ copts_t)) in let default = Term.(ret (const (fun _ -&gt; `Help (`Pager, None)) $ copts_t)) in
Cmd.group info ~default [initialize_cmd; record_cmd; help_cmd] Cmd.group info ~default [initialize_cmd; record_cmd; help_cmd]

View file

@ -32,7 +32,7 @@ let count =
`S Manpage.s_bugs; `S Manpage.s_bugs;
`P &quot;Email bug reports to &lt;bugs@example.org&gt;.&quot; ] `P &quot;Email bug reports to &lt;bugs@example.org&gt;.&quot; ]
in in
Cmd.make (Cmd.info &quot;chorus&quot; ~version:&quot;v2.0.0&quot; ~doc ~man) @@ Cmd.make (Cmd.info &quot;chorus&quot; ~version:&quot;v2.1.0&quot; ~doc ~man) @@
let+ count and+ msg in let+ count and+ msg in
chorus ~count msg chorus ~count msg