This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
484
unikernel/duniverse/cmdliner/doc/cli.mld
Normal file
484
unikernel/duniverse/cmdliner/doc/cli.mld
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
{0:cmdline Command line interface}
|
||||
|
||||
This manual describes how your tool ends up interacting
|
||||
with shells when you use Cmdliner.
|
||||
|
||||
{1:invocation Tool invocation}
|
||||
|
||||
For tools evaluating a command without subcommands the most general
|
||||
form of invocation is:
|
||||
|
||||
{v
|
||||
tool [OPTION]… [ARG]…
|
||||
v}
|
||||
|
||||
The tool automatically reponds to the [--help] option by printing
|
||||
{{!help}the help}. If a version string is provided in the
|
||||
{{!Cmdliner.Cmd.val-info}command information}, it also automatically
|
||||
responds to the [--version] option by printing this string on standard
|
||||
output.
|
||||
|
||||
Command line arguments are either {{!optargs}{e optional}} or
|
||||
{{!posargs}{e positional}}. Both can be freely interleaved but since
|
||||
[Cmdliner] accepts many optional forms this may result in
|
||||
ambiguities. The special {{!posargs} token [--]} can be used to
|
||||
resolve them: anything that follows it is treated as a positional
|
||||
argument.
|
||||
|
||||
Tools evaluating commands with subcommands have this form of invocation
|
||||
|
||||
{v
|
||||
tool [COMMAND]… [OPTION]… [ARG]…
|
||||
v}
|
||||
|
||||
Commands automatically respond to the [--help] option by printing
|
||||
{{!help}their help}. The sequence of [COMMAND] strings must be the first
|
||||
strings following the tool name – as soon as an optional argument is
|
||||
seen the search for a subcommand stops.
|
||||
|
||||
{1:args Arguments}
|
||||
|
||||
{2:optargs Optional arguments}
|
||||
|
||||
An optional argument is specified on the command line by a {e name}
|
||||
possibly followed by a {e value}.
|
||||
|
||||
The name of an option can be short or long.
|
||||
|
||||
{ul
|
||||
{- A {e short} name is a dash followed by a single alphanumeric
|
||||
character: [-h], [-q], [-I].}
|
||||
{- A {e long} name is two dashes followed by alphanumeric
|
||||
characters and dashes: [--help], [--silent], [--ignore-case].}}
|
||||
|
||||
More than one name may refer to the same optional argument. For
|
||||
example in a given program the names [-q], [--quiet] and [--silent]
|
||||
may all stand for the same boolean argument indicating the program to
|
||||
be quiet.
|
||||
|
||||
The value of an option can be specified in three different ways.
|
||||
|
||||
{ul
|
||||
{- As the next token on the command line: [-o a.out], [--output a.out].}
|
||||
{- Glued to a short name: [-oa.out].}
|
||||
{- Glued to a long name after an equal character: [--output=a.out].}}
|
||||
|
||||
Glued forms are especially useful if the value itself starts with a
|
||||
dash as is the case for negative numbers, [--min=-10].
|
||||
|
||||
An optional argument without a value is either a {e flag} (see
|
||||
{!Cmdliner.Arg.flag}, {!Cmdliner.Arg.vflag}) or an optional argument with
|
||||
an optional value (see the [~vopt] argument of {!Cmdliner.Arg.opt}).
|
||||
|
||||
Short flags can be grouped together to share a single dash and the
|
||||
group can end with a short option. For example assuming [-v] and
|
||||
[-x] are flags and [-f] is a short option:
|
||||
|
||||
{ul
|
||||
{- [-vx] will be parsed as [-v -x].}
|
||||
{- [-vxfopt] will be parsed as [-v -x -fopt].}
|
||||
{- [-vxf opt] will be parsed as [-v -x -fopt].}
|
||||
{- [-fvx] will be parsed as [-f=vx].}}
|
||||
|
||||
{2:posargs Positional arguments}
|
||||
|
||||
Positional arguments are tokens on the command line that are not
|
||||
option names and are not the value of an optional argument. They are
|
||||
numbered from left to right starting with zero.
|
||||
|
||||
Since positional arguments may be mistaken as the optional value of an
|
||||
optional argument or they may need to look like option names, anything
|
||||
that follows the special token ["--"] on the command line is
|
||||
considered to be a positional argument:
|
||||
|
||||
{v
|
||||
tool --option -- but --now we -are --all positional --argu=ments
|
||||
v}
|
||||
|
||||
{2:constraints Constraints on option names}
|
||||
|
||||
Using the cmdliner library puts the following constraints on your
|
||||
command line interface:
|
||||
|
||||
{ul
|
||||
{- The option names [--cmdliner] and [--__complete] are reserved by the
|
||||
library.}
|
||||
{- The option name [--help], (and [--version] if you specify a version
|
||||
string) is reserved by the library. Using it as a term or option
|
||||
name may result in undefined behaviour.}
|
||||
{- Defining the same option or command name via two different
|
||||
arguments or terms is illegal and raises [Invalid_argument].}}
|
||||
|
||||
{1:envlookup Environment variables}
|
||||
|
||||
Non-required command line arguments can be backed up by an environment
|
||||
variable. If the argument is absent from the command line and
|
||||
the environment variable is defined, its value is parsed using the
|
||||
argument converter and defines the value of the argument.
|
||||
|
||||
For {!Cmdliner.Arg.flag} and {!Cmdliner.Arg.flag_all} that do not have an
|
||||
argument converter a boolean is parsed from the lowercased variable value
|
||||
as follows:
|
||||
|
||||
{ul
|
||||
{- [""], ["false"], ["no"], ["n"] or ["0"] is [false].}
|
||||
{- ["true"], ["yes"], ["y"] or ["1"] is [true].}
|
||||
{- Any other string is an error.}}
|
||||
|
||||
Note that environment variables are not supported for
|
||||
{!Cmdliner.Arg.vflag} and {!Cmdliner.Arg.vflag_all}.
|
||||
|
||||
{1:help Help and man pages}
|
||||
|
||||
Help and man pages are are generated when you call your tool or a subcommand
|
||||
with [--help]. By default, if the [TERM] environment variable
|
||||
is not [dumb] or unset, the tool tries to {{!paging}page} the manual
|
||||
so that you can directly search it. Otherwise it outputs the manual
|
||||
as plain text.
|
||||
|
||||
Alternative help formats can be specified with the optional argument
|
||||
of [--help], see your own [tool --help] for more information.
|
||||
|
||||
{@sh[
|
||||
tool --help
|
||||
tool cmd --help
|
||||
tool --help=groff > tool.1
|
||||
]}
|
||||
|
||||
{2:paging Paging}
|
||||
|
||||
The pager is selected by looking up, in order:
|
||||
|
||||
{ol
|
||||
{- The [MANPAGER] variable.}
|
||||
{- The [PAGER] variable.}
|
||||
{- The tool [less].}
|
||||
{- The tool [more].}}
|
||||
|
||||
Regardless of the pager, it is invoked with [LESS=FRX] set in the
|
||||
environment unless, the [LESS] environment variable is set in your
|
||||
environment.
|
||||
|
||||
{2:install_tool_manpages Install}
|
||||
|
||||
The manpages of a tool and its subcommands can be installed to a root
|
||||
[man] directory [$MANDIR] by invoking:
|
||||
|
||||
{@shell[
|
||||
cmdliner install tool-manpages thetool $MANDIR
|
||||
]}
|
||||
|
||||
This looks up [thetool] in the [PATH]. Use an explicit file path like
|
||||
[./thetool] to directly specify an executable.
|
||||
|
||||
If you are also {{!install_tool_completion}installing completions}
|
||||
rather use the [install tool-support] command, see this
|
||||
{{!page-cookbook.tip_tool_support}cookbook tip} which also has
|
||||
instructions on how to install if you are using [opam].
|
||||
|
||||
{1:cli_completion Command line completion}
|
||||
|
||||
Cmdliner programs automatically get support for shell command line
|
||||
completion.
|
||||
|
||||
The completion process happens via a {{!completion_protocol}protocol}
|
||||
which is interpreted by generic shell completion scripts that are
|
||||
installed by the library. For now the [zsh] and [bash] shells are
|
||||
supported.
|
||||
|
||||
Tool developers can easily {{!install_tool_completion}install}
|
||||
completion definitions that invoke these completion scripts. Tool
|
||||
end-users need to {{!user_configuration}make sure} these definitions are
|
||||
looked up by their shell.
|
||||
|
||||
{2:user_configuration End-user configuration}
|
||||
|
||||
If you are the user of a cmdliner based tool, the following
|
||||
shell-dependent steps need to be performed in order to benefit from
|
||||
command line completion.
|
||||
|
||||
{3:user_zsh For [zsh]}
|
||||
|
||||
The [FPATH] environment variable must be setup to include the
|
||||
directory where the generic cmdliner completion function is
|
||||
{{!install_completion} installed} {b before} properly initializing the
|
||||
completion system.
|
||||
|
||||
For example, {{:https://github.com/ocaml/opam/issues/6427}for now}, if
|
||||
you are using [opam]. You should add something like this to your
|
||||
[.zshrc]:
|
||||
|
||||
{@sh[
|
||||
FPATH="$(opam var share)/zsh/site-functions:${FPATH}"
|
||||
autoload -Uz compinit
|
||||
compinit -u
|
||||
]}
|
||||
|
||||
Also make sure this {b happens before} [opam]'s [zsh] init script
|
||||
inclusion, see {{:https://github.com/ocaml/opam/issues/6428}this
|
||||
issue}. 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}.
|
||||
|
||||
After this, to test everything is right, check that the [_cmdliner_generic]
|
||||
function can be looked by invoking it (this will result in an error).
|
||||
|
||||
{@sh[
|
||||
> autoload _cmdliner_generic
|
||||
> _cmdliner_generic
|
||||
_cmdliner_generic:1: words: assignment to invalid subscript range
|
||||
]}
|
||||
|
||||
If the function cannnot be found make sure the [cmdliner] library is
|
||||
installed, that the generic scripts were
|
||||
{{!install_generic_completion}installed} and that the
|
||||
[_cmdliner_generic] file can be found in one of the directories
|
||||
mentioned in the [FPATH] variable.
|
||||
|
||||
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:
|
||||
|
||||
{@sh[
|
||||
autoload _cmdliner_generic
|
||||
compdef _cmdliner_generic thetool
|
||||
]}
|
||||
|
||||
{3:user_bash For [bash]}
|
||||
|
||||
These instructions assume that you have
|
||||
{{:https://repology.org/project/bash-completion/versions}[bash-completion]}
|
||||
installed and setup in some way in your [.bashrc].
|
||||
|
||||
The [XDG_DATA_DIRS] environment variable must be setup to include the
|
||||
[share] directory where the generic cmdliner completion function is
|
||||
{{!install_completion}installed}.
|
||||
|
||||
For example, {{:https://github.com/ocaml/opam/issues/6427}for now}, if
|
||||
you are using [opam]. You should add something like this to your
|
||||
[.bashrc]:
|
||||
{@sh[
|
||||
XDG_DATA_DIRS="$(opam var share):${XDG_DATA_DIRS}"
|
||||
]}
|
||||
|
||||
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}.
|
||||
|
||||
After this, to test everything is right, check that the [_cmdliner_generic]
|
||||
function can be looked up:
|
||||
|
||||
{@sh[
|
||||
> _completion_loader _cmdliner_generic
|
||||
> declare -F _cmdliner_generic &>/dev/null && echo "Found" || echo "Not found"
|
||||
Found!
|
||||
]}
|
||||
|
||||
If the function cannot be found make sure the [cmdliner] library is
|
||||
installed, that the generic scripts were
|
||||
{{!install_generic_completion}installed} and that the
|
||||
[_cmdliner_generic] file can be looked up by [_completion_loader].
|
||||
|
||||
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:
|
||||
|
||||
{@sh[
|
||||
_completion_loader _cmdliner_generic
|
||||
complete -F _cmdliner_generic thetool
|
||||
]}
|
||||
|
||||
{b Note.} {{:https://github.com/scop/bash-completion/commit/9efc596735c4509001178f0cf28e02f66d1f7703}It seems} [_completion_loader] was deprecated in
|
||||
bash-completion [2.12] in favour of [_comp_load] but many distributions
|
||||
are on [< 2.12] and in [2.12] [_completion_loader] simply calls
|
||||
[_comp_load].
|
||||
|
||||
{2:install_completion Install}
|
||||
|
||||
Completion scripts need to be installed in subdirectories of a
|
||||
{{:https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s11.html}[share]}
|
||||
directory which we denote by the [$SHAREDIR] variable below. In a
|
||||
package installation script this variable is typically defined by:
|
||||
|
||||
{@sh[
|
||||
SHAREDIR="$DESTDIR/$PREFIX/share"
|
||||
]}
|
||||
|
||||
The final destination directory in [share] depends on the shell:
|
||||
|
||||
{ul
|
||||
{- For [zsh] it is [$SHAREDIR/zsh/site-functions]}
|
||||
{- For [bash] it is [$SHAREDIR/bash-completion/completions]}}
|
||||
|
||||
If that is unsatisfying you can output the completion scripts directly
|
||||
where you want with the [cmdliner generic-completion] and
|
||||
[cmdliner tool-completion] commands.
|
||||
|
||||
{3:install_generic_completion Generic completion scripts}
|
||||
|
||||
The generic completion scripts must be installed by the
|
||||
[cmdliner] library. They should not be part of your tool install. If
|
||||
they are not installed you can inspect and install them with the
|
||||
following invocations, invoke with [--help] for more information.
|
||||
|
||||
{@sh[
|
||||
cmdliner generic-completion zsh # Output generic zsh script on stdout
|
||||
cmdliner install generic-completion $SHAREDIR # All shells
|
||||
cmdliner install generic-completion --shell zsh $SHAREDIR # Only zsh
|
||||
]}
|
||||
|
||||
Directories are created as needed. Use option [--dry-run] to see which
|
||||
paths would be written by an [install] invocation.
|
||||
|
||||
{3:install_tool_completion Tool completion scripts}
|
||||
|
||||
If your tool named [thetool] uses Cmdliner you should install completion
|
||||
definitions for them. They rely on the {{!install_generic_completion}generic
|
||||
scripts} to be installed. These tool specific scripts can be inspected
|
||||
and installed via these invocations:
|
||||
|
||||
{@sh[
|
||||
cmdliner tool-completion zsh thetool # Output tool zsh script on stdout.
|
||||
cmdliner install tool-completion thetool $SHAREDIR # All shells
|
||||
cmdliner install tool-completion --shell zsh thetool $SHAREDIR # Only zsh
|
||||
]}
|
||||
|
||||
Directories are created as needed. Use option [--dry-run] to see which
|
||||
paths would be written by an [install] invocation.
|
||||
|
||||
If you are also {{!install_tool_manpages}installing manpages} rather
|
||||
use the [install tool-support] command, see this
|
||||
{{!page-cookbook.tip_tool_support}cookbook tip} which also has
|
||||
instructions on how to install if you are using [opam].
|
||||
|
||||
{2:completion_protocol Completion protocol}
|
||||
|
||||
There is no standard that allows tools and shells to interact to
|
||||
perform shell command line completion. Completion is supposed to
|
||||
happen through idiosyncratic, ad-hoc, obscure and brain damaging
|
||||
shell-specific completion scripts.
|
||||
|
||||
To alleviate this, Cmdliner defines one generic script per shell and
|
||||
interacts with it using the protocol described below. The protocol can
|
||||
be used to implement generic completion scripts for other shells. The
|
||||
protocol is versioned but can change even between minor versions of
|
||||
Cmdliner. Generic scripts for popular shells can be inspected via
|
||||
the [cmdliner generic-completion] command.
|
||||
|
||||
The protocol betwen the shell completion {e script} and a
|
||||
cmdliner based {e tool} is as follows:
|
||||
|
||||
{ol
|
||||
{- When completion is requested the script invokes the tool with a
|
||||
modified command line:
|
||||
{ul
|
||||
{- The first argument to the tool ([Sys.argv.(1)]) must be the
|
||||
option [--__complete].}
|
||||
{- The (possibly empty) argument [ARG] on which the completion is
|
||||
requested must be replaced by {e exactly} [--__complete=ARG]. Note
|
||||
that this can happen after the [--] token, this is the reason
|
||||
why we have an explicit [--__complete] argument in [Sys.argv.(1)]:
|
||||
it indicates the command line parser must operate in a special mode.}}}
|
||||
{- The tool responds by writing on standard output a list of
|
||||
completion directives which match the [completions] rule of the grammar
|
||||
given below.}
|
||||
{- The script interprets the completion directives according
|
||||
to the given semantics below so that the shell can display the
|
||||
completions. The script is free to ignore directives
|
||||
or data that it is unable to present.}}
|
||||
|
||||
The following ABNF grammar is described using the notations of
|
||||
{{:https://www.rfc-editor.org/rfc/rfc5234}RFC 5234} and
|
||||
{{:https://www.rfc-editor.org/rfc/rfc7405}RFC 7405}. A few constraints
|
||||
are not expressed by the grammar:
|
||||
|
||||
{ul
|
||||
{- Except in the [completion] rule, the byte stream may contain ANSI escape
|
||||
sequences introduced by the byte [0x1B].}
|
||||
{- After stripping the ANSI escape sequences, the resulting byte stream must
|
||||
be valid UTF-8 text.}}
|
||||
|
||||
{@abnf[
|
||||
completions = version nl directives
|
||||
version = "1"
|
||||
directives = *(directive nl)
|
||||
directive = message / group / %s"files" / %s"dirs" / %"restart"
|
||||
message = %s"message" nl text nl %s"message-end"
|
||||
group = %s"group" nl group_name nl *item
|
||||
group_name = *pchar
|
||||
item = %s"item" nl completion nl item_doc nl %s"item-end"
|
||||
completion = *pchar
|
||||
item_doc = text
|
||||
text = *(pchar / nl)
|
||||
nl = %0A
|
||||
pchar = %20-%7E / %8A-%FF
|
||||
]}
|
||||
|
||||
The semantics of directives is as follows:
|
||||
|
||||
{ul
|
||||
{- A [message] directive defines a message to be reported to the user.
|
||||
It is multi-line ANSI styled text which cannot have a line that is
|
||||
exactly made of the text [message-end] as it is used to signal the
|
||||
end of the message. Messages should be reported in the order they
|
||||
are received.}
|
||||
{- A [group] directive defines an informational [group_name] followed
|
||||
by a possibly empty list of completion items that are part of the
|
||||
group. An item provides a [completion] value, this is a string that
|
||||
defines what the requested [ARG] value can be replaced with. It is
|
||||
followed by an [item_doc], multi-line ANSI styled text which cannot
|
||||
have a line that is exactly made of the text [item-end] as it is
|
||||
used to signal the end of the item.}
|
||||
{- A [file] directive indicates that the script should add existing
|
||||
files staring with [ARG] to completion values.}
|
||||
{- A [dir] directive indicates that the script should add existing
|
||||
directories starting with [ARG] to completion values.}
|
||||
{- A [restart] directive indicates that the script should restart
|
||||
shell completion as if the command line was starting after the leftmost
|
||||
[--] disambiguation token. The directive never gets emited if
|
||||
there is no [--] on the command line.}}
|
||||
|
||||
You can easily inspect the completions of any cmdliner based tool by
|
||||
invoking it like the protocol suggests. For example for the [cmdliner]
|
||||
tool itself:
|
||||
|
||||
{@shell[
|
||||
cmdliner --__complete --__complete=
|
||||
]}
|
||||
|
||||
{1:error_message_styling Error message ANSI styling}
|
||||
|
||||
Since Cmdliner 2.0 error messages printed on [stderr] use styled text
|
||||
with ANSI escapes unless one of the following conditions is met:
|
||||
|
||||
{ul
|
||||
{- The [NO_COLOR] environment variable is set and different
|
||||
from the empty string. Yes, even if you have [NO_COLOR=false], that's
|
||||
what the particularly dumb {:https://no-color.org} standard says.}
|
||||
{- The [TERM] environment variable is [dumb].}
|
||||
{- The [TERM] environment variable is unset and {!Sys.backend_type} is
|
||||
not [Other "js_of_ocaml"]. Yes, browser consoles support
|
||||
ANSI escapes. Yes, you can run Cmdliner in your browser.}}
|
||||
|
||||
{1:legacy_prefix_specification Legacy prefix specification}
|
||||
|
||||
Before Cmdliner 2.0, command names, long option names and
|
||||
{!Cmdliner.Arg.enum} values could be specified by a prefix as long as
|
||||
the prefix was not ambiguous.
|
||||
|
||||
This turned out to be a mistake. It makes the user experience of the
|
||||
tool unstable as it evolves: former user established shortcuts or
|
||||
invocations in scripts may be broken by new command, option and
|
||||
enumerant additions.
|
||||
|
||||
Therefore this behaviour was unconditionally removed in Cmdliner
|
||||
2.0. If you happen to have scripts that rely on it, you can invoke
|
||||
them with [CMDLINER_LEGACY_PREFIXES=true] set in the environment to
|
||||
recover the old behaviour. {b However the scripts should be fixed: this
|
||||
escape hatch will be removed in the future.}
|
||||
|
||||
The [CMDLINER_LEGACY_PREFIX=true] escape hatch should not be used for
|
||||
interactive tool interaction. In particular the behaviour of Cmdliner
|
||||
completion support under this setting is undefined.
|
||||
720
unikernel/duniverse/cmdliner/doc/cookbook.mld
Normal file
720
unikernel/duniverse/cmdliner/doc/cookbook.mld
Normal file
|
|
@ -0,0 +1,720 @@
|
|||
{0 [Cmdliner] cookbook}
|
||||
|
||||
A few recipes and starting {{!blueprints}blueprints} to describe your
|
||||
command lines with {!Cmdliner}.
|
||||
|
||||
{b Note.} Some of the code snippets here assume they are done after:
|
||||
{[
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
]}
|
||||
|
||||
{1:tips Tips and pitfalls}
|
||||
|
||||
Command line interfaces are a rather crude and inexpressive user
|
||||
interaction medium. It is tempting to try to be nice to users in
|
||||
various ways but this often backfires in confusing context sensitive
|
||||
behaviours. Here are a few tips and Cmdliner features you {b should
|
||||
rather not use}.
|
||||
|
||||
{2:tip_avoid_default_command Avoid default commands in groups}
|
||||
|
||||
Command {{!Cmdliner.Cmd.group}groups} can have a default command, that
|
||||
is be of the form [tool [CMD]]. Except perhaps at the top level of
|
||||
your tool, it's better to avoid them. They increase command line
|
||||
parsing ambiguities.
|
||||
|
||||
In particular if the default command has positional arguments, users
|
||||
are forced to use the {{!cli.posargs}disambiguation token [--]} to
|
||||
specify them so that they can be distinguished from command
|
||||
names. For example:
|
||||
|
||||
{@sh[
|
||||
tool -- file …
|
||||
]}
|
||||
|
||||
One thing that is acceptable is to have a default command that simply
|
||||
{{!cmds_show_docs}shows documentation} for the group of subcommands as
|
||||
this not interfere with tool operation.
|
||||
|
||||
{2:tip_avoid_default_option_values Avoid default option values}
|
||||
|
||||
Optional arguments {{!Cmdliner.Arg.opt}with values} can have a default
|
||||
value, that is be of the form [--opt[=VALUE]]. In general it is better
|
||||
to avoid them as they lead to context sensitive command lines
|
||||
specifications and surprises when users refine invocations. For examples
|
||||
suppose you have the synopsis
|
||||
|
||||
{@sh[
|
||||
tool --opt[=VALUE] [FILE]
|
||||
]}
|
||||
|
||||
Trying to refine the following invocation to add a [FILE] parameter is
|
||||
error prone and painful:
|
||||
|
||||
{@sh[
|
||||
tool --opt
|
||||
]}
|
||||
|
||||
There is more than one way but the easiest way is to specify:
|
||||
{@sh[
|
||||
tool --opt -- FILE
|
||||
]}
|
||||
which is not obvious unless you have [tool]'s cli hard wired in your
|
||||
brain. This would have been a careless refinement if [--opt] did not
|
||||
have a default option value.
|
||||
|
||||
{2:tip_avoid_required_opt Avoid required optional arguments}
|
||||
|
||||
Cmdliner allows to define required optional arguments. Avoid doing
|
||||
this, it's a contradiction in the terms. In command line interfaces
|
||||
optional arguments are defined to be… optional, not doing so is
|
||||
surprising for your users. Use required positional arguments if
|
||||
arguments are required by your command invocation.
|
||||
|
||||
Required optional arguments can be useful though if your tool is not
|
||||
meant to be invoked manually but rather through scripts and has many
|
||||
required arguments. In this case they become a form of labelled
|
||||
arguments which can make invocations easier to understand.
|
||||
|
||||
{2:tip_avoid_manpages Avoid making manpages your main documentation}
|
||||
|
||||
Unless your tool is very simple, avoid making manpages the main
|
||||
documentation medium of your tool. The medium is rather limited and
|
||||
even though you can convert them to HTML, its cross references
|
||||
capabilities are rather limited which makes discussing your tool
|
||||
online more difficult.
|
||||
|
||||
Keep information in manpages to the minimum needed to operate your
|
||||
tool without having to leave the terminal too much and defer reference
|
||||
manuals, conceptual information and tutorials to a more evolved medium
|
||||
like HTML.
|
||||
|
||||
{2:tip_migrating Migrating from other conventions}
|
||||
|
||||
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
|
||||
preserve backward compatibility, one way of proceeding is to
|
||||
pre-process {!Sys.argv} into a new array of the right shape before
|
||||
giving it to command {{!Cmdliner.Cmd.section-eval}evaluation
|
||||
functions} via the [?argv] optional argument.
|
||||
|
||||
These are two common cases:
|
||||
|
||||
{ul
|
||||
{- Long option names with a single dash like [-warn-error]. In this
|
||||
case simply prefix an additional [-] to these arguments when they
|
||||
occur in {!Sys.argv} before the [--] argument; after it, all arguments are
|
||||
positional and to be treated literally.}
|
||||
{- Long option names with a single letter like [--X]. In this
|
||||
case simply chop the first [-] to make it a short option when they
|
||||
occur in {!Sys.argv} before the [--] argument; after it all arguments are
|
||||
positional and to be treated literally.}}
|
||||
|
||||
{2:tip_src_structure Source code structure}
|
||||
|
||||
In general Cmdliner wants you to see your tools as regular OCaml functions
|
||||
that you make available to the shell. This means adopting the following
|
||||
source structure:
|
||||
|
||||
{[
|
||||
(* Implementation of your command. Except for exit codes does not deal with
|
||||
command line interface related matters and is independent from
|
||||
Cmdliner. *)
|
||||
|
||||
let exit_ok = 0
|
||||
let tool … = …; exit_ok
|
||||
|
||||
(* Command line interface. Adds metadata to your [tool] function arguments
|
||||
so that they can be parsed from the command line and documented. *)
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let cmd = … (* Has a term that invokes [tool] *)
|
||||
let main () = Cmd.eval' cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
In particular it is good for your readers' understanding that your
|
||||
program has a single point where it {!Stdlib.exit}s. This structure is
|
||||
also useful for playing with your program in the OCaml toplevel
|
||||
(REPL), you can invoke its [main] function without having the risk of it
|
||||
[exit]ing the toplevel.
|
||||
|
||||
If your tool named [tool] is growing into multiple commands which
|
||||
have a lot of definitions it is advised to:
|
||||
{ul
|
||||
{- Gather command line definition commonalities such as argument
|
||||
converters or common options in a module called [Tool_cli].}
|
||||
{- Define each command named [name] in a separate module [Cmd_name] which
|
||||
exports its command as a [val cmd : int Cmd.t] value.}
|
||||
{- Gather the commands with {!Cmdliner.Cmd.group} in a source file
|
||||
called [tool_main.ml].}}
|
||||
|
||||
For an hypothetic tool named [tool] with commands [import], [serve]
|
||||
and [user], this leads to the following set of files:
|
||||
|
||||
{[
|
||||
cmd_import.ml cmd_serve.ml cmd_user.ml tool_cli.ml tool_main.ml
|
||||
cmd_import.mli cmd_serve.mli cmd_user.mli tool_cli.mli
|
||||
]}
|
||||
|
||||
The [.mli] files simply export commands:
|
||||
{[
|
||||
val cmd : int Cmdliner.Cmd.t
|
||||
]}
|
||||
|
||||
And the [tool_main.ml] gathers them with a {!Cmdliner.Cmd.group}:
|
||||
{[
|
||||
let cmd =
|
||||
let default = Term.(ret (const (`Help (`Auto, None)))) (* show help *) in
|
||||
Cmd.group (Cmd.info "tool") ~default @@
|
||||
[Cmd_import.cmd; Cmd_serve.cmd; Cmd_user.cmd]
|
||||
|
||||
let main () = Cmd.value' cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{2:tip_tool_support Installing completions and manpages}
|
||||
|
||||
The [cmdliner] tool can be used to install completion scripts and
|
||||
manpages for you tool and its subcommands by using the dedicated
|
||||
{{!page-cli.install_tool_completion}[install tool-completion]} and
|
||||
{{!page-cli.install_tool_manpages}[install tool-manpages]} subcommands.
|
||||
|
||||
To install both directly (and possibly other support files in the future)
|
||||
it is more concise to use the [install
|
||||
tool-support] command. Invoke with [--help] for more information.
|
||||
|
||||
{3:tip_tool_support_with_opam With [opam]}
|
||||
|
||||
If you are installing your package with [opam] for a tool named [tool]
|
||||
located in the build at the path [$BUILD/tool], you can add the following
|
||||
instruction after your build instructions in the [build:] field of
|
||||
your [opam] file (also works if your build system is not using a
|
||||
[.install] file).
|
||||
|
||||
{@sh[
|
||||
build: [
|
||||
[ … ] # Your regular build instructions
|
||||
["cmdliner" "install" "tool-support"
|
||||
"--update-opam-install=%{_:name}%.install"
|
||||
"$BUILD/tool" "_build/cmdliner-install"]]
|
||||
]}
|
||||
|
||||
You need to specify the path to the built executable, as it cannot be
|
||||
looked up in the [PATH] yet. Also more than one tool can be specified
|
||||
in a single invocation and there is a syntax for specifying the actual
|
||||
tool name if it is renamed on install; see [--help] for more
|
||||
details.
|
||||
|
||||
If [cmdliner] is only an optional dependency of your package use the
|
||||
opam filter [{cmdliner:installed}] after the closing bracket of the command
|
||||
invocation.
|
||||
|
||||
{3:tip_tool_support_with_opam_dune With [opam] and [dune]}
|
||||
|
||||
First make sure your understand the
|
||||
{{!tip_tool_support_with_opam}above basic instructions} for [opam].
|
||||
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:]
|
||||
field of the opam file after your [dune] build instructions. For a tool named
|
||||
[tool] the result should eventually look this:
|
||||
|
||||
{@sh[
|
||||
build: [
|
||||
[ … ] # Your regular dune build instructions
|
||||
["cmdliner" "install" "tool-support"
|
||||
"--update-opam-install=%{_:name}%.install"
|
||||
"_build/default/install/bin/tool" {os != "win32"}
|
||||
"_build/default/install/bin/tool.exe" {os = "win32"}
|
||||
"_build/cmdliner-install"]]
|
||||
]}
|
||||
|
||||
{1:conventions Conventions}
|
||||
|
||||
By simply using Cmdliner you are already abiding to a great deal
|
||||
of command line interface conventions. Here are a few other ones that
|
||||
are not necessarily enforced by the library but that are good to
|
||||
adopt for your users.
|
||||
|
||||
{2:conv_use_dash Use ["-"] to specify [stdio] in file path arguments}
|
||||
|
||||
Whenever a command line argument specifies a file path to read or
|
||||
write you should let the user specify [-] to denote standard in or
|
||||
standard out, if possible. If you worry about a file sporting this
|
||||
name, note that the user can always specify it using [./-] for
|
||||
the argument.
|
||||
|
||||
Very often tools default to [stdin] or [stdout] when a file
|
||||
input or output is unspecified, here is typical argument definitions
|
||||
to support these conventions:
|
||||
|
||||
{[
|
||||
let infile =
|
||||
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")
|
||||
|
||||
let outfile =
|
||||
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")
|
||||
]}
|
||||
|
||||
Here is {!Stdlib} based code to read to a string a file or standard
|
||||
input if [-] is specified:
|
||||
|
||||
{[
|
||||
let read_file file =
|
||||
let read file ic = try Ok (In_channel.input_all ic) with
|
||||
| Sys_error e -> Error (Printf.sprintf "%s: %s" file e)
|
||||
in
|
||||
let binary_stdin () = In_channel.set_binary_mode In_channel.stdin true in
|
||||
try match file with
|
||||
| "-" -> binary_stdin (); read file In_channel.stdin
|
||||
| file -> In_channel.with_open_bin file (read file)
|
||||
with Sys_error e -> Error e
|
||||
]}
|
||||
|
||||
Here is {!Stdlib} based code to write a string to a file or standard output
|
||||
if [-] is specified:
|
||||
|
||||
{[
|
||||
let write_file file s =
|
||||
let write file s oc = try Ok (Out_channel.output_string oc s) with
|
||||
| Sys_error e -> Error (Printf.sprintf "%s: %s" file e)
|
||||
in
|
||||
let binary_stdout () = Out_channel.(set_binary_mode stdout true) in
|
||||
try match file with
|
||||
| "-" -> binary_stdout (); write file s Out_channel.stdout
|
||||
| file -> Out_channel.with_open_bin file (write file s)
|
||||
with Sys_error e -> Error e
|
||||
]}
|
||||
|
||||
{2:conv_env_defaults Environment variables as default modifiers}
|
||||
|
||||
Cmdliner has support to back values defined by arguments with
|
||||
environment variables. The value specified via an environment variable
|
||||
should never take over an argument specified explicitely on the
|
||||
command line. The environment variable should be seen as providing
|
||||
the default value when the argument is absent.
|
||||
|
||||
This is exactly what Cmdliner's support for environment variables does,
|
||||
see {!env_args}
|
||||
|
||||
{1:args Arguments}
|
||||
|
||||
{2:args_positional How do I define a positional argument?}
|
||||
|
||||
Positional arguments are extracted from the command line using
|
||||
{{!Cmdliner.Arg.posargs}these combinators} which use zero-based
|
||||
indexing. The following example extracts the first argument and if
|
||||
the argument is absent from the command line it evaluates
|
||||
to ["Revolt!"].
|
||||
{[
|
||||
let msg =
|
||||
let doc = "$(docv) is the message to utter." and docv = "MSG" in
|
||||
Arg.(value & pos 0 string "Revolt!" & info [] ~doc ~docv)
|
||||
]}
|
||||
|
||||
{2:args_optional How do I define an optional argument?}
|
||||
|
||||
Optional arguments are extracted from the command line using
|
||||
{{!Cmdliner.Arg.optargs}these combinators}. The actual option
|
||||
name is defined in the {!Cmdliner.Arg.val-info} structure without
|
||||
dashes. One character strings define short options, others long
|
||||
options (see the {{!page-cli.optargs}parsed syntax}).
|
||||
|
||||
The following defines the [-l] and [--loud] options. This is a simple
|
||||
command line argument without a value also known as a command line {e
|
||||
flag}. The term [loud] evaluates to [false] when the argument is
|
||||
absent on the command line and [true] otherwise.
|
||||
|
||||
{[
|
||||
let loud =
|
||||
let doc = "Say the message loudly." in
|
||||
Arg.(value & flag & info ["l"; "loud"] ~doc)
|
||||
]}
|
||||
|
||||
The following defines the [-m] and [--message] options. The term [msg] evalutes
|
||||
to ["Revolt!"] when the option is absent on the command line.
|
||||
{[
|
||||
let msg =
|
||||
let doc = "$(docv) is the message to utter." and docv = "MSG" in
|
||||
Arg.(value & opt string "Revolt!" & info ["m"; "message"] ~doc ~docv)
|
||||
]}
|
||||
|
||||
{2:args_required How do I define a required argument?}
|
||||
|
||||
Some of the constraints on the presence of arguments occur when the
|
||||
specification of arguments is {{!Cmdliner.Arg.argterms}converted} to
|
||||
terms. The following says that the first positional argument is required:
|
||||
|
||||
{[
|
||||
let msg =
|
||||
let msg = "$(docv) is the message to utter." and docv = "MSG" in
|
||||
Arg.(required & pos 0 (some string) None & info [] ~absent ~doc ~docv)
|
||||
]}
|
||||
|
||||
The value [msg] ends up being a term of type [string]. If the argument
|
||||
is not provided, Cmdliner will automatically bail out during evaluation
|
||||
with an error message.
|
||||
|
||||
Note that while it is possible to define required positional argument
|
||||
it is {{!tip_avoid_required_opt}discouraged}.
|
||||
|
||||
{2:args_detect_absent How can I know if an argument was absent?}
|
||||
|
||||
Most {{!Cmdliner.Arg.posargs}positional} and
|
||||
{{!Cmdliner.Arg.optargs}optional} arguments have a default value. You
|
||||
can use a [None] for the default argument and the {!Cmdliner.Arg.some} or
|
||||
{!Cmdliner.Arg.some'} combinators on your argument converter which simply
|
||||
wrap its result in a [Some].
|
||||
|
||||
{[
|
||||
let msg =
|
||||
let msg = "$(docv) is the message to utter." in
|
||||
let absent = "Random quote." in
|
||||
Arg.(value & pos 0 (some string) None & info [] ~absent ~doc ~docv:"MSG")
|
||||
]}
|
||||
|
||||
There is more than one way to document the value when it is
|
||||
absent. See {!args_absent_doc}
|
||||
|
||||
{2:args_absent_doc How do I document absent argument behaviours?}
|
||||
|
||||
There are three ways to document the behaviour when an argument is
|
||||
unspecified on the command line.
|
||||
|
||||
{ul
|
||||
{- If you specify a default value in the argument combinator, this value
|
||||
gets printed in bold using the {{!Cmdliner.Arg.conv_printer}printer}
|
||||
of the converter.}
|
||||
{- If you are using the {!Cmdliner.Arg.some'} and {!Cmdliner.Arg.some}
|
||||
there is an optional [none] argument that allows you to specify
|
||||
the default value. If you can exhibit this value at definition
|
||||
point use {!Cmdliner.Arg.some'}, the underlying converter's
|
||||
{{!Cmdliner.Arg.conv_printer}printer} will be used. If not
|
||||
you can specify it as a string rendered in bold via {!Cmdliner.Arg.some}.}
|
||||
{- If you want to describe a more complex, but short, behaviour use
|
||||
the [~absent] parameter of {!Cmdliner.Arg.val-info}. Using this
|
||||
parameter overrides the two previous ways. See
|
||||
{{!args_detect_absent}this} example. }}
|
||||
|
||||
{2:args_completion How can I customize positional and option value completion?}
|
||||
|
||||
Positional argument values and option values are completed according
|
||||
to the {{!Cmdliner.Arg.argconv}argument converter} you use for defining
|
||||
the optional or positional argument.
|
||||
|
||||
A couple of predefined argument converter like {!Cmdliner.Arg.path},
|
||||
{!Cmdliner.Arg.filepath} and {!Cmdliner.Arg.dirpath} or
|
||||
{!Cmdliner.Arg.enum} automatically handle this for you.
|
||||
|
||||
If you would like to perform custom or more elaborate context
|
||||
sensitive completions you can define your own argument converter with
|
||||
a completion defined with {!Cmdliner.Arg.Completion.make}.
|
||||
|
||||
Here is an example where the first positional argument is completed
|
||||
with the filenames found in a directory specified via the [--dir]
|
||||
option (which defaults to the current working directory if unspecified).
|
||||
{[
|
||||
let dir = Arg.(value & opt dirpath "." & info ["d"; "dir"])
|
||||
let dir_filenames_conv =
|
||||
let complete dir ~token = match dir with
|
||||
| None -> Error "Could not determine directory to lookup"
|
||||
| Some dir ->
|
||||
match Array.to_list (Sys.readdir dir) with
|
||||
| exception Sys_error e -> Error (String.concat ": " [dir; e])
|
||||
| fnames ->
|
||||
let fnames = List.filter (String.starts_with ~prefix:token) fnames in
|
||||
Ok (List.map Arg.Completion.string fnames)
|
||||
in
|
||||
let completion = Arg.Completion.make ~context:dir complete in
|
||||
Arg.Conv.of_conv ~completion Arg.string
|
||||
|
||||
let pos0 = Arg.(required & pos 0 (some dir_filenames_conv) None & info [])
|
||||
]}
|
||||
|
||||
Note that when you use [pos0] in a command line definition you also
|
||||
need to make sure [dir] is part of the term otherwise the context will
|
||||
always be [None]:
|
||||
|
||||
{[
|
||||
let+ pos0 and+ dir and+ … in …
|
||||
]}
|
||||
|
||||
{1:envs Environment variables}
|
||||
|
||||
{2:env_args How can environment variables define defaults?}
|
||||
|
||||
As mentioned in {!conv_env_defaults}, any non-required argument can be
|
||||
defined by an environment variable when absent. This works by
|
||||
specifying the [env] argument in the argument's {!Cmdliner.Arg.val-info}
|
||||
information. For example:
|
||||
|
||||
{[
|
||||
let msg =
|
||||
let doc = "$(docv) is the message to utter." and docv = "MSG" in
|
||||
let env = Cmd.Env.info "MESSAGE" in
|
||||
Arg.(value & pos 0 string "Revolt!" & info [] ~env ~doc ~docv)
|
||||
]}
|
||||
|
||||
When the first positional argument is absent it takes the default
|
||||
value ["Revolt!"], unless the [MESSAGE] variable is defined in
|
||||
the environment in which case it takes its value.
|
||||
|
||||
Cmdliner handles the environment variable lookup for you. By using the
|
||||
[msg] term in your command definition all this gets automatically
|
||||
documented in the tool help.
|
||||
|
||||
{2:env_cmd How do I document environment variables influencing a command?}
|
||||
|
||||
Environment variable that are used to change {{!env_args}argument
|
||||
defaults} automatically get documented in a command's man page when
|
||||
you use the argument's term in the command's term.
|
||||
|
||||
However if your command implementation looks up other variables and you
|
||||
wish to document them in the command's man page, use the [envs]
|
||||
argument of {!Cmdliner.Cmd.val-info} or the [docs_env] argument
|
||||
of {!Cmdliner.Arg.val-info}.
|
||||
|
||||
This documents in the {!Cmdliner.Manpage.s_environment} manual section
|
||||
of [tool] that [EDITOR] is looked up to find the tool to invoke to
|
||||
edit the files:
|
||||
|
||||
{[
|
||||
let editor_env = "EDITOR"
|
||||
let tool … = … Sys.getenv_opt editor_env
|
||||
let cmd =
|
||||
let env = Cmd.Env.info editor_env ~doc:"The editor used to edit files." in
|
||||
Cmd.make (Cmd.info "tool" ~envs:[env]) @@
|
||||
…
|
||||
]}
|
||||
|
||||
{1:cmds Commands}
|
||||
|
||||
{2:cmds_exit_code_docs How do I document command exit codes?}
|
||||
|
||||
Exit codes are documentd by {!Cmdliner.Cmd.Exit.type-info} values and
|
||||
must be given to the command's {!Cmdliner.Cmd.type-info} value via the
|
||||
[exits] optional arguments. For example:
|
||||
|
||||
{[
|
||||
let conf_not_found = 1
|
||||
let tool … =
|
||||
let tool_cmd =
|
||||
let exits =
|
||||
Cmd.Exit.info conf_not_found "if no configuration could be found." ::
|
||||
Cmd.Exit.defaults
|
||||
in
|
||||
Cmd.make (Cmd.info "mycmd" ~exits) @@
|
||||
…
|
||||
]}
|
||||
|
||||
{2:cmds_show_docs How do I show help in a command group's default?}
|
||||
|
||||
While it is usually {{!tip_avoid_default_command}not advised} to have a default
|
||||
command in a group, just showing docs is acceptable. A term can request
|
||||
Cmdliner's generated help by using {!Cmdliner.Term.val-ret}:
|
||||
{[
|
||||
let group_cmd =
|
||||
let default = Term.(ret (const (`Help (`Auto, None)))) (* show help *) in
|
||||
Cmd.group (Cmd.info "group") ~default @@
|
||||
[first_cmd; second_cmd]
|
||||
]}
|
||||
|
||||
{2:cmds_which_eval Which [Cmd] evaluation function should I use?}
|
||||
|
||||
There are (too) many {{!Cmdliner.Cmd.section-eval}command evaluation}
|
||||
functions. They have grown organically in a rather ad-hoc manner. Some
|
||||
of these are there for backwards compatibility reasons and advanced
|
||||
usage for complex tools.
|
||||
|
||||
Here are the main ones to use and why you may want to use them which
|
||||
essentially depends on how you want to handle errors and exit codes
|
||||
in your tool function.
|
||||
|
||||
{ul
|
||||
{- {!Cmdliner.Cmd.val-eval}. This forces your tool function to return [()].
|
||||
The evaluation function always returns an exit code of [0] unless a
|
||||
command line parsing error occurs.}
|
||||
{- {!Cmdliner.Cmd.eval'}. {b Recommended}. This forces your tool function to
|
||||
return an exit code [exit] which is returned by the evaluation function
|
||||
unless a command line parsing error occurs. This is the recommended
|
||||
function to use as it forces you to think about how to report errors and
|
||||
design useful exit codes for users.}
|
||||
{- {!Cmdliner.Cmd.eval_result} is akin to {!Cmdliner.Cmd.val-eval} except
|
||||
it forces your function to return either [Ok ()] or [Error msg].
|
||||
The evaluation function returns with exit code [0] unless [Error msg] is
|
||||
computed in which case [msg] is printed on the error stream prefixed by the
|
||||
executable name and the evaluation function returns with
|
||||
exit code {!Cmdliner.Cmd.Exit.some_error}.}
|
||||
{- {!Cmdliner.Cmd.eval_result'} is akin to {!Cmdliner.Cmd.eval_result}, except
|
||||
the [Ok] case carries an exit code which is returned by the evaluation
|
||||
function.}}
|
||||
|
||||
{2:cmds_howto_complete How can my tool support command line completion?}
|
||||
|
||||
The command line interface manual has all
|
||||
{{!page-cli.cli_completion}the details} and
|
||||
{{!page-cli.install_tool_completion} specific instructions} for
|
||||
complementing your tool install. See also {!tip_tool_support}.
|
||||
|
||||
{2:cmds_listing How can I list all the commands of my tool?}
|
||||
|
||||
In a shell the invocation [cmdliner tool-commands $TOOL] lists every
|
||||
command of the tool $TOOL.
|
||||
|
||||
{2:cmds_errmsg_styling How can I suppress error message styling?}
|
||||
|
||||
Since Cmdliner 2.0, error message printed on [stderr] use styled text
|
||||
with ANSI escapes. Styled text is disabled if one of the conditions
|
||||
mentioned {{!page-cli.error_message_styling}here} is met.
|
||||
|
||||
If you want to be more aggressive in suppressing them you can use the
|
||||
[err] formatter argument of {{!Cmdliner.Cmd.section-eval}command
|
||||
evaluation} functions with a suitable formatter on which a function
|
||||
like
|
||||
{{:https://erratique.ch/software/more/doc/More/Fmt/index.html#val-strip_styles}
|
||||
this one} has been applied that automatically strips the styling.
|
||||
|
||||
{1:manpage Manpages}
|
||||
|
||||
{2:manpage_hide How do I prevent an item from being automatically listed?}
|
||||
|
||||
In general it's not a good idea to hide stuff from your users but in
|
||||
case an item needs to be hidden you can use the special
|
||||
{!Cmdliner.Manpage.s_none} section name. This ensures the item does
|
||||
not get listed in any section.
|
||||
|
||||
{[
|
||||
let secret = Arg.(value & flag & info ["super-secret"] ~docs:Manpage.s_none)
|
||||
]}
|
||||
|
||||
{2:manpage_synopsis How can I write a better command synopsis section?}
|
||||
|
||||
Define the {!Cmdliner.Manpage.s_synopsis} section in the manpage of
|
||||
your command. It takes over the one generated by Cmdliner. For example:
|
||||
|
||||
{[
|
||||
let man = [
|
||||
`S Manpage.s_synopsis;
|
||||
`P "$(cmd) $(b,--) $(i,TOOL) [$(i,ARG)]…"; `Noblank;
|
||||
`P "$(cmd) $(i,COMMAND) …";
|
||||
`S Manpage.s_description;
|
||||
`P "Without a command $(cmd) invokes $(i,TOOL)"; ]
|
||||
]}
|
||||
|
||||
{2:manpage_install How can I install all the manpages of my tool?}
|
||||
|
||||
The command line interface manual
|
||||
{{!page-cli.install_tool_manpages}the details} on how to install the manpages
|
||||
of your tool and its subcommands. See also {!tip_tool_support}.
|
||||
|
||||
{1:blueprints Blueprints}
|
||||
|
||||
These blueprints when copied to a [src.ml] file can be compiled and run with:
|
||||
|
||||
{@sh[
|
||||
ocamlfind ocamlopt -package cmdliner -linkgpkg src.ml
|
||||
./a.out --help
|
||||
]}
|
||||
|
||||
More concrete examples can be found on the {{!page-examples}examples page}
|
||||
and the {{!page-tutorial}tutorial} may help too.
|
||||
|
||||
These examples follow a conventional {!tip_src_structure}.
|
||||
|
||||
{2:blueprint_min Minimal}
|
||||
|
||||
A minimal example.
|
||||
|
||||
{@ocaml name=blueprint_min.ml[
|
||||
let tool () = Cmdliner.Cmd.Exit.ok
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let cmd =
|
||||
Cmd.make (Cmd.info "TODO" ~version:"v2.0.0+dune") @@
|
||||
let+ unit = Term.const () in
|
||||
tool unit
|
||||
|
||||
let main () = Cmd.eval' cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{2:blueprint_tool A simple tool}
|
||||
|
||||
This is a tool that has a flag, an optional positional argument for
|
||||
specifying an input file. It also responds to the [--version] option.
|
||||
|
||||
{@ocaml name=blueprint_tool.ml[
|
||||
let exit_todo = 1
|
||||
let tool ~flag ~infile = exit_todo
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let flag = Arg.(value & flag & info ["flag"] ~doc:"The flag")
|
||||
let infile =
|
||||
let doc = "$(docv) is the input file. Use $(b,-) for $(b,stdin)." in
|
||||
Arg.(value & pos 0 string "-" & info [] ~doc ~docv:"FILE")
|
||||
|
||||
let cmd =
|
||||
let doc = "The tool synopsis is TODO" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "$(cmd) does TODO" ]
|
||||
in
|
||||
let exits =
|
||||
Cmd.Exit.info exit_todo ~doc:"When there is stuff todo" ::
|
||||
Cmd.Exit.defaults
|
||||
in
|
||||
Cmd.make (Cmd.info "TODO" ~version:"v2.0.0+dune" ~doc ~man ~exits) @@
|
||||
let+ flag and+ infile in
|
||||
tool ~flag ~infile
|
||||
|
||||
let main () = Cmd.eval' cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{2:blueprint_cmds A tool with subcommands}
|
||||
|
||||
This is a tool with two subcommands [hey] and [ho]. If your tools
|
||||
grows many subcommands you may want to follow these
|
||||
{{!tip_src_structure}source code conventions}.
|
||||
|
||||
{@ocaml name=blueprint_cmds.ml[
|
||||
let hey () = Cmdliner.Cmd.Exit.ok
|
||||
let ho () = Cmdliner.Cmd.Exit.ok
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let flag = Arg.(value & flag & info ["flag"] ~doc:"The flag")
|
||||
let infile =
|
||||
let doc = "$(docv) is the input file. Use $(b,-) for $(b,stdin)." in
|
||||
Arg.(value & pos 0 file "-" & info [] ~doc ~docv:"FILE")
|
||||
|
||||
let hey_cmd =
|
||||
let doc = "The hey command synopsis is TODO" in
|
||||
Cmd.make (Cmd.info "hey" ~doc) @@
|
||||
let+ unit = Term.const () in
|
||||
ho ()
|
||||
|
||||
let ho_cmd =
|
||||
let doc = "The ho command synopsis is TODO" in
|
||||
Cmd.make (Cmd.info "ho" ~doc) @@
|
||||
let+ unit = Term.const () in
|
||||
ho unit
|
||||
|
||||
let cmd =
|
||||
let doc = "The tool synopsis is TODO" in
|
||||
Cmd.group (Cmd.info "TODO" ~version:"v2.0.0+dune" ~doc) @@
|
||||
[hey_cmd; ho_cmd]
|
||||
|
||||
let main () = Cmd.eval' cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
453
unikernel/duniverse/cmdliner/doc/examples.mld
Normal file
453
unikernel/duniverse/cmdliner/doc/examples.mld
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
{0 Examples}
|
||||
|
||||
The examples are self-contained, cut and paste them in a file to play
|
||||
with them. See also the suggested {{!page-cookbook.tip_src_structure}source
|
||||
code structure} and program {{!page-cookbook.blueprints}blueprints}.
|
||||
|
||||
{1:example_rm A [rm] command}
|
||||
|
||||
We define the command line interface of an [rm] command with the
|
||||
synopsis:
|
||||
|
||||
{v
|
||||
rm [OPTION]… FILE…
|
||||
v}
|
||||
|
||||
The [-f], [-i] and [-I] flags define the prompt behaviour of [rm]. It
|
||||
is represented in our program by the [prompt] type. If more than one
|
||||
of these flags is present on the command line the last one takes
|
||||
precedence.
|
||||
|
||||
To implement this behaviour we map the presence of these flags to
|
||||
values of the [prompt] type by using {!Cmdliner.Arg.vflag_all}.
|
||||
|
||||
This argument will contain all occurrences of the flag on the command
|
||||
line and we just take the {!Cmdliner.Arg.last} one to define our term
|
||||
value. If there is no occurrence the last value of the default list
|
||||
[[Always]] is taken. This means the default prompt behaviour is [Always].
|
||||
|
||||
{@ocaml name=example_rm.ml[
|
||||
(* Implementation of the command, we just print the args. *)
|
||||
|
||||
type prompt = Always | Once | Never
|
||||
let prompt_str = function
|
||||
| Always -> "always" | Once -> "once" | Never -> "never"
|
||||
|
||||
let rm ~prompt ~recurse files =
|
||||
Printf.printf "prompt = %s\nrecurse = %B\nfiles = %s\n"
|
||||
(prompt_str prompt) recurse (String.concat ", " files)
|
||||
|
||||
(* Command line interface *)
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let files = Arg.(non_empty & pos_all file [] & info [] ~docv:"FILE")
|
||||
let prompt =
|
||||
let always =
|
||||
let doc = "Prompt before every removal." in
|
||||
Always, Arg.info ["i"] ~doc
|
||||
in
|
||||
let never =
|
||||
let doc = "Ignore nonexistent files and never prompt." in
|
||||
Never, Arg.info ["f"; "force"] ~doc
|
||||
in
|
||||
let once =
|
||||
let doc = "Prompt once before removing more than three files, or when
|
||||
removing recursively. Less intrusive than $(b,-i), while
|
||||
still giving protection against most mistakes."
|
||||
in
|
||||
Once, Arg.info ["I"] ~doc
|
||||
in
|
||||
Arg.(last & vflag_all [Always] [always; never; once])
|
||||
|
||||
let recursive =
|
||||
let doc = "Remove directories and their contents recursively." in
|
||||
Arg.(value & flag & info ["r"; "R"; "recursive"] ~doc)
|
||||
|
||||
let rm_cmd =
|
||||
let doc = "Remove files or directories" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "$(cmd) removes each specified $(i,FILE). By default it does not
|
||||
remove directories, to also remove them and their contents, use the
|
||||
option $(b,--recursive) ($(b,-r) or $(b,-R)).";
|
||||
`P "To remove a file whose name starts with a $(b,-), for example
|
||||
$(b,-foo), use one of these commands:";
|
||||
`Pre "$(cmd) $(b,-- -foo)"; `Noblank;
|
||||
`Pre "$(cmd) $(b,./-foo)";
|
||||
`P "$(cmd.name) removes symbolic links, not the files referenced by the
|
||||
links.";
|
||||
`S Manpage.s_bugs; `P "Report bugs to <bugs@example.org>.";
|
||||
`S Manpage.s_see_also; `P "$(b,rmdir)(1), $(b,unlink)(2)" ]
|
||||
in
|
||||
Cmd.make (Cmd.info "rm" ~version:"v2.0.0+dune" ~doc ~man) @@
|
||||
let+ prompt and+ recursive and+ files in
|
||||
rm ~prompt ~recurse:recursive files
|
||||
|
||||
let main () = Cmd.eval rm_cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{1:example_cp A [cp] command}
|
||||
|
||||
We define the command line interface of a [cp] command with the synopsis:
|
||||
|
||||
{v
|
||||
cp [OPTION]… SOURCE… DEST
|
||||
v}
|
||||
|
||||
The [DEST] argument must be a directory if there is more than one
|
||||
[SOURCE]. This constraint is too complex to be expressed by the
|
||||
combinators of {!Cmdliner.Arg}.
|
||||
|
||||
Hence we just give [DEST] the {!Cmdliner.Arg.string} type and verify
|
||||
the constraint at the beginning of the implementation of [cp]. If the
|
||||
constraint is unsatisfied we return an [`Error] result. By using
|
||||
{!Cmdliner.Term.val-ret} on the command's term for [cp], [Cmdliner]
|
||||
handles the error reporting.
|
||||
|
||||
{@ocaml name=example_cp.ml[
|
||||
(* Implementation, we check the dest argument and print the args *)
|
||||
|
||||
let cp ~verbose ~recurse ~force srcs dest =
|
||||
let many = List.length srcs > 1 in
|
||||
if many && (not (Sys.file_exists dest) || not (Sys.is_directory dest))
|
||||
then `Error (false, dest ^ ": not a directory") else
|
||||
`Ok (Printf.printf
|
||||
"verbose = %B\nrecurse = %B\nforce = %B\nsrcs = %s\ndest = %s\n"
|
||||
verbose recurse force (String.concat ", " srcs) dest)
|
||||
|
||||
(* Command line interface *)
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let verbose =
|
||||
let doc = "Print file names as they are copied." in
|
||||
Arg.(value & flag & info ["v"; "verbose"] ~doc)
|
||||
|
||||
let recurse =
|
||||
let doc = "Copy directories recursively." in
|
||||
Arg.(value & flag & info ["r"; "R"; "recursive"] ~doc)
|
||||
|
||||
let force =
|
||||
let doc = "If a destination file cannot be opened, remove it and try again."in
|
||||
Arg.(value & flag & info ["f"; "force"] ~doc)
|
||||
|
||||
let srcs =
|
||||
let doc = "Source file(s) to copy." in
|
||||
Arg.(non_empty & pos_left ~rev:true 0 file [] & info [] ~docv:"SOURCE" ~doc)
|
||||
|
||||
let dest =
|
||||
let doc = "Destination of the copy. Must be a directory if there is more \
|
||||
than one $(i,SOURCE)." in
|
||||
let docv = "DEST" in
|
||||
Arg.(required & pos ~rev:true 0 (some string) None & info [] ~docv ~doc)
|
||||
|
||||
let cp_cmd =
|
||||
let doc = "Copy files" in
|
||||
let man_xrefs =
|
||||
[`Tool "mv"; `Tool "scp"; `Page ("umask", 2); `Page ("symlink", 7)]
|
||||
in
|
||||
let man = [
|
||||
`S Manpage.s_bugs;
|
||||
`P "Email them to <bugs@example.org>."; ]
|
||||
in
|
||||
Cmd.make (Cmd.info "cp" ~version:"v2.0.0+dune" ~doc ~man ~man_xrefs) @@
|
||||
Term.ret @@
|
||||
let+ verbose and+ recurse and+ force and+ srcs and+ dest in
|
||||
cp ~verbose ~recurse ~force srcs dest
|
||||
|
||||
let main () = Cmd.eval cp_cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{1:example_tail A [tail] command}
|
||||
|
||||
We define the command line interface of a [tail] command with the
|
||||
synopsis:
|
||||
|
||||
{v
|
||||
tail [OPTION]… [FILE]…
|
||||
v}
|
||||
|
||||
The [--lines] option whose value specifies the number of last lines to
|
||||
print has a special syntax where a [+] prefix indicates to start
|
||||
printing from that line number. In the program this is represented by
|
||||
the [loc] type. We define a custom [loc_arg]
|
||||
{{!Cmdliner.Arg.type-conv}argument converter} for this option.
|
||||
|
||||
The [--follow] option has an optional enumerated value. The argument
|
||||
converter [follow], created with {!Cmdliner.Arg.enum} parses the
|
||||
option value into the enumeration. By using {!Cmdliner.Arg.some} and
|
||||
the [~vopt] argument of {!Cmdliner.Arg.opt}, the term corresponding to
|
||||
the option [--follow] evaluates to [None] if [--follow] is absent from
|
||||
the command line, to [Some Descriptor] if present but without a value
|
||||
and to [Some v] if present with a value [v] specified.
|
||||
|
||||
{@ocaml name=example_tail.ml[
|
||||
(* Implementation of the command, we just print the args. *)
|
||||
|
||||
type loc = bool * int
|
||||
type verb = Verbose | Quiet
|
||||
type follow = Name | Descriptor
|
||||
|
||||
let str = Printf.sprintf
|
||||
let opt_str sv = function None -> "None" | Some v -> str "Some(%s)" (sv v)
|
||||
let loc_str (rev, k) = if rev then str "%d" k else str "+%d" k
|
||||
let follow_str = function Name -> "name" | Descriptor -> "descriptor"
|
||||
let verb_str = function Verbose -> "verbose" | Quiet -> "quiet"
|
||||
|
||||
let tail ~lines ~follow ~verb ~pid files =
|
||||
Printf.printf
|
||||
"lines = %s\nfollow = %s\nverb = %s\npid = %s\nfiles = %s\n"
|
||||
(loc_str lines) (opt_str follow_str follow) (verb_str verb)
|
||||
(opt_str string_of_int pid) (String.concat ", " files)
|
||||
|
||||
(* Command line interface *)
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let loc_arg =
|
||||
let parser s =
|
||||
try
|
||||
if s <> "" && s.[0] <> '+'
|
||||
then Ok (true, int_of_string s)
|
||||
else Ok (false, int_of_string (String.sub s 1 (String.length s - 1)))
|
||||
with Failure _ -> Error "unable to parse integer"
|
||||
in
|
||||
let pp ppf p = Format.fprintf ppf "%s" (loc_str p) in
|
||||
Arg.Conv.make ~docv:"N" ~parser ~pp ()
|
||||
|
||||
let lines =
|
||||
let doc = "Output the last $(docv) lines or use $(i,+)$(docv) to start \
|
||||
output after the $(i,N)-1th line."
|
||||
in
|
||||
Arg.(value & opt loc_arg (true, 10) & info ["n"; "lines"] ~docv:"N" ~doc)
|
||||
|
||||
let follow =
|
||||
let doc = "Output appended data as the file grows. $(docv) specifies how \
|
||||
the file should be tracked, by its $(b,name) or by its \
|
||||
$(b,descriptor)."
|
||||
in
|
||||
let follow = Arg.enum ["name", Name; "descriptor", Descriptor] in
|
||||
Arg.(value & opt (some follow) ~vopt:(Some Descriptor) None &
|
||||
info ["f"; "follow"] ~docv:"ID" ~doc)
|
||||
|
||||
let verb =
|
||||
let quiet =
|
||||
let doc = "Never output headers giving file names." in
|
||||
Quiet, Arg.info ["q"; "quiet"; "silent"] ~doc
|
||||
in
|
||||
let verbose =
|
||||
let doc = "Always output headers giving file names." in
|
||||
Verbose, Arg.info ["v"; "verbose"] ~doc
|
||||
in
|
||||
Arg.(last & vflag_all [Quiet] [quiet; verbose])
|
||||
|
||||
let pid =
|
||||
let doc = "With -f, terminate after process $(docv) dies." in
|
||||
Arg.(value & opt (some int) None & info ["pid"] ~docv:"PID" ~doc)
|
||||
|
||||
let files = Arg.(value & (pos_all non_dir_file []) & info [] ~docv:"FILE")
|
||||
|
||||
let tail_cmd =
|
||||
let doc = "Display the last part of a file" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "$(cmd) prints the last lines of each $(i,FILE) to standard output.
|
||||
If no file is specified reads standard input. The number of printed
|
||||
lines can be specified with the $(b,-n) option.";
|
||||
`S Manpage.s_bugs;
|
||||
`P "Report them to <bugs@example.org>.";
|
||||
`S Manpage.s_see_also;
|
||||
`P "$(b,cat)(1), $(b,head)(1)" ]
|
||||
in
|
||||
Cmd.make (Cmd.info "tail" ~version:"v2.0.0+dune" ~doc ~man) @@
|
||||
let+ lines and+ follow and+ verb and+ pid and+ files in
|
||||
tail ~lines ~follow ~verb ~pid files
|
||||
|
||||
let main () = Cmd.eval tail_cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{1:example_darcs A [darcs] command}
|
||||
|
||||
We define the command line interface of a [darcs] command with the
|
||||
synopsis:
|
||||
|
||||
{v
|
||||
darcs [COMMAND] …
|
||||
v}
|
||||
|
||||
The [--debug], [-q], [-v] and [--prehook] options are available in
|
||||
each command. To avoid having to pass them individually to each
|
||||
command we gather them in a record of type [copts]. By lifting the
|
||||
record constructor [copts] into the term [copts_t] we now have a term
|
||||
that we can pass to the commands to stand for an argument of type
|
||||
[copts]. These options are documented in the section
|
||||
{!Cmdliner.Manpage.s_common_options}.
|
||||
|
||||
The [help] command shows help about commands or other topics. The help
|
||||
shown for commands is generated by [Cmdliner] by making an appropriate
|
||||
use of {!Cmdliner.Term.val-ret} on the lifted [help] function.
|
||||
|
||||
If the program is invoked without a command we just want to show the
|
||||
help of the program as printed by [Cmdliner] with [--help]. This is
|
||||
done by the [default] term.
|
||||
|
||||
{@ocaml name=example_darcs.ml[
|
||||
(* Implementations, just print the args. *)
|
||||
|
||||
type verb = Normal | Quiet | Verbose
|
||||
type copts = { debug : bool; verb : verb; prehook : string option }
|
||||
|
||||
let str = Printf.sprintf
|
||||
let opt_str sv = function None -> "None" | Some v -> str "Some(%s)" (sv v)
|
||||
let opt_str_str = opt_str (fun s -> s)
|
||||
let verb_str = function
|
||||
| Normal -> "normal" | Quiet -> "quiet" | Verbose -> "verbose"
|
||||
|
||||
let pr_copts oc copts = Printf.fprintf oc
|
||||
"debug = %B\nverbosity = %s\nprehook = %s\n"
|
||||
copts.debug (verb_str copts.verb) (opt_str_str copts.prehook)
|
||||
|
||||
let initialize copts repodir = Printf.printf
|
||||
"%arepodir = %s\n" pr_copts copts repodir
|
||||
|
||||
let record copts name email all ask_deps files = Printf.printf
|
||||
"%aname = %s\nemail = %s\nall = %B\nask-deps = %B\nfiles = %s\n"
|
||||
pr_copts copts (opt_str_str name) (opt_str_str email) all ask_deps
|
||||
(String.concat ", " files)
|
||||
|
||||
let help copts man_format cmds topic = match topic with
|
||||
| None -> `Help (`Pager, None) (* help about the program. *)
|
||||
| Some topic ->
|
||||
let topics = "topics" :: "patterns" :: "environment" :: cmds in
|
||||
let conv = Cmdliner.Arg.enum (List.rev_map (fun s -> (s, s)) topics) in
|
||||
let parse = Cmdliner.Arg.Conv.parser conv in
|
||||
match parse topic with
|
||||
| Error e -> `Error (false, e)
|
||||
| Ok t when t = "topics" -> List.iter print_endline topics; `Ok ()
|
||||
| Ok t when List.mem t cmds -> `Help (man_format, Some t)
|
||||
| Ok t ->
|
||||
let page = (topic, 7, "", "", ""), [`S topic; `P "Say something";] in
|
||||
`Ok (Cmdliner.Manpage.print man_format Format.std_formatter page)
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
(* Help sections common to all commands *)
|
||||
|
||||
let help_secs = [
|
||||
`S Manpage.s_common_options;
|
||||
`P "These options are common to all commands.";
|
||||
`S "MORE HELP";
|
||||
`P "Use $(tool) $(i,COMMAND) --help for help on a single command.";`Noblank;
|
||||
`P "Use $(tool) $(b,help patterns) for help on patch matching."; `Noblank;
|
||||
`P "Use $(tool) $(b,help environment) for help on environment variables.";
|
||||
`S Manpage.s_bugs; `P "Check bug reports at http://bugs.example.org.";]
|
||||
|
||||
(* Options common to all commands *)
|
||||
|
||||
let copts debug verb prehook = { debug; verb; prehook }
|
||||
let copts_t =
|
||||
let docs = Manpage.s_common_options in
|
||||
let debug =
|
||||
let doc = "Give only debug output." in
|
||||
Arg.(value & flag & info ["debug"] ~docs ~doc)
|
||||
in
|
||||
let verb =
|
||||
let doc = "Suppress informational output." in
|
||||
let quiet = Quiet, Arg.info ["q"; "quiet"] ~docs ~doc in
|
||||
let doc = "Give verbose output." in
|
||||
let verbose = Verbose, Arg.info ["v"; "verbose"] ~docs ~doc in
|
||||
Arg.(last & vflag_all [Normal] [quiet; verbose])
|
||||
in
|
||||
let prehook =
|
||||
let doc = "Specify command to run before this $(tool) command." in
|
||||
Arg.(value & opt (some string) None & info ["prehook"] ~docs ~doc)
|
||||
in
|
||||
Term.(const copts $ debug $ verb $ prehook)
|
||||
|
||||
(* Commands *)
|
||||
|
||||
let sdocs = Manpage.s_common_options
|
||||
|
||||
let initialize_cmd =
|
||||
let repodir =
|
||||
let doc = "Run the program in repository directory $(docv)." in
|
||||
Arg.(value & opt file Filename.current_dir_name & info ["repodir"]
|
||||
~docv:"DIR" ~doc)
|
||||
in
|
||||
let doc = "make the current directory a repository" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "Turns the current directory into a Darcs repository. Any
|
||||
existing files and subdirectories become …";
|
||||
`Blocks help_secs; ]
|
||||
in
|
||||
Cmd.make (Cmd.info "initialize" ~doc ~sdocs ~man) @@
|
||||
let+ copts_t and+ repodir in
|
||||
initialize copts_t repodir
|
||||
|
||||
let record_cmd =
|
||||
let pname =
|
||||
let doc = "Name of the patch." in
|
||||
Arg.(value & opt (some string) None & info ["m"; "patch-name"] ~docv:"NAME"
|
||||
~doc)
|
||||
in
|
||||
let author =
|
||||
let doc = "Specifies the author's identity." in
|
||||
Arg.(value & opt (some string) None & info ["A"; "author"] ~docv:"EMAIL"
|
||||
~doc)
|
||||
in
|
||||
let all =
|
||||
let doc = "Answer yes to all patches." in
|
||||
Arg.(value & flag & info ["a"; "all"] ~doc)
|
||||
in
|
||||
let ask_deps =
|
||||
let doc = "Ask for extra dependencies." in
|
||||
Arg.(value & flag & info ["ask-deps"] ~doc)
|
||||
in
|
||||
let files = Arg.(value & (pos_all file) [] & info [] ~docv:"FILE or DIR") in
|
||||
let doc = "create a patch from unrecorded changes" in
|
||||
let man =
|
||||
[`S Manpage.s_description;
|
||||
`P "Creates a patch from changes in the working tree. If you specify
|
||||
a set of files…";
|
||||
`Blocks help_secs; ]
|
||||
in
|
||||
Cmd.make (Cmd.info "record" ~doc ~sdocs ~man) @@
|
||||
let+ copts_t and+ pname and+ author and+ all and+ ask_deps and+ files in
|
||||
record copts_t pname author all ask_deps files
|
||||
|
||||
let help_cmd =
|
||||
let topic =
|
||||
let doc = "The topic to get help on. $(b,topics) lists the topics." in
|
||||
Arg.(value & pos 0 (some string) None & info [] ~docv:"TOPIC" ~doc)
|
||||
in
|
||||
let doc = "display help about darcs and darcs commands" in
|
||||
let man =
|
||||
[`S Manpage.s_description;
|
||||
`P "Prints help about darcs commands and other subjects…";
|
||||
`Blocks help_secs; ]
|
||||
in
|
||||
Cmd.make (Cmd.info "help" ~doc ~man) @@
|
||||
Term.ret @@
|
||||
let+ copts_t and+ man_format = Arg.man_format
|
||||
and+ choice_names = Term.choice_names and+ topic in
|
||||
help copts_t man_format choice_names topic
|
||||
|
||||
let main_cmd =
|
||||
let doc = "a revision control system" in
|
||||
let man = help_secs in
|
||||
let info = Cmd.info "darcs" ~version:"v2.0.0+dune" ~doc ~sdocs ~man in
|
||||
let default = Term.(ret (const (fun _ -> `Help (`Pager, None)) $ copts_t)) in
|
||||
Cmd.group info ~default [initialize_cmd; record_cmd; help_cmd]
|
||||
|
||||
let main () = Cmd.eval main_cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
46
unikernel/duniverse/cmdliner/doc/index.mld
Normal file
46
unikernel/duniverse/cmdliner/doc/index.mld
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{0 Cmdliner {%html: <span class="version">v2.0.0+dune</span>%}}
|
||||
|
||||
Cmdliner provides a simple and compositional mechanism
|
||||
to convert command line arguments to OCaml values and pass them to
|
||||
your functions.
|
||||
|
||||
The library automatically handles command line completion, syntax
|
||||
errors, help messages and UNIX man page generation. It supports
|
||||
programs with single or multiple commands (like [git]) and respect
|
||||
most of the
|
||||
{{:http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html}
|
||||
POSIX} and
|
||||
{{:http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html}
|
||||
GNU} conventions.
|
||||
|
||||
{1:manuals Manuals}
|
||||
|
||||
The following manuals are available.
|
||||
|
||||
{ul
|
||||
{- The {{!page-tutorial}tutorial} makes you write your first command line
|
||||
interface with Cmdliner.}
|
||||
{- The {{!page-cookbook}cookbook} has a few off-the-shelf recipes,
|
||||
tips about {{!page-cookbook.tip_src_structure}source code structure},
|
||||
and {{!page-cookbook.blueprints}blueprints} to define your command lines
|
||||
with Cmdliner.}
|
||||
{- The {{!page-cli}command line interface manual} describes how command
|
||||
lines and environment variables are parsed by Cmdliner and how command line
|
||||
completion is performed. This can be communicated to the users of your
|
||||
tools.}
|
||||
{- The {{!page-tool_man}tool man page} manual describes how
|
||||
Cmdliner generates man pages for your tools and their commands and how
|
||||
you can format them.}
|
||||
{- The {{!page-examples}examples page} has examples of a some
|
||||
classic UNIX tools with their command line interface implemented by
|
||||
Cmdliner.}}
|
||||
|
||||
{1:library Library [cmdliner]}
|
||||
|
||||
{!modules: Cmdliner}
|
||||
{!modules:
|
||||
Cmdliner.Arg
|
||||
Cmdliner.Cmd
|
||||
Cmdliner.Manpage
|
||||
Cmdliner.Term
|
||||
}
|
||||
73
unikernel/duniverse/cmdliner/doc/tool_man.mld
Normal file
73
unikernel/duniverse/cmdliner/doc/tool_man.mld
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{0:tool_man Tool man pages}
|
||||
|
||||
See also the {{!page-cli.help}section} about man pages in the command
|
||||
line interface manual.
|
||||
|
||||
{1:manual Man page generation}
|
||||
|
||||
Man page sections for a command are printed in the order specified by
|
||||
the [man] value given to {!Cmdliner.Cmd.val-info}. Unless
|
||||
specified explicitly in the [man] value the following sections
|
||||
are automatically created and populated for you:
|
||||
|
||||
{ul
|
||||
{- {{!Cmdliner.Manpage.s_name}[NAME]} section.}
|
||||
{- {{!Cmdliner.Manpage.s_synopsis}[SYNOPSIS]} section.}}
|
||||
|
||||
The various [doc] documentation strings specified by the command's
|
||||
term arguments get inserted at the end of the documentation section
|
||||
they respectively mention in their [docs] argument:
|
||||
|
||||
{ol
|
||||
{- For commands, see {!Cmdliner.Cmd.val-info}.}
|
||||
{- For positional arguments, see {!Cmdliner.Arg.type-info}. Those are listed iff
|
||||
both the [docv] and [doc] string is specified by {!Cmdliner.Arg.val-info}.}
|
||||
{- For optional arguments, see {!Cmdliner.Arg.val-info}.}
|
||||
{- For exit statuses, see {!Cmdliner.Cmd.Exit.val-info}.}
|
||||
{- For environment variables, see {!Cmdliner.Cmd.Env.val-info}.}}
|
||||
|
||||
If a [docs] section name is mentioned and does not exist in the command's
|
||||
[man] value, an empty section is created for it, after which the [doc] strings
|
||||
are inserted, possibly prefixed by boilerplate text (e.g. for
|
||||
{!Cmdliner.Manpage.s_environment} and {!Cmdliner.Manpage.s_exit_status}).
|
||||
|
||||
If the created section is:
|
||||
{ul
|
||||
{- {{!Cmdliner.Manpage.standard_sections}standard}, it
|
||||
is inserted at the right place in the order specified
|
||||
{{!Cmdliner.Manpage.standard_sections}here}, but after a
|
||||
possible non-standard
|
||||
section explicitly specified by the command's [man] value since the latter
|
||||
get the order number of the last previously specified standard section
|
||||
or the order of {!Cmdliner.Manpage.s_synopsis} if there is no such section.}
|
||||
{- non-standard, it is inserted before the {!Cmdliner.Manpage.s_commands}
|
||||
section or the first subsequent existing standard section if it
|
||||
doesn't exist. Taking advantage of this behaviour is discouraged,
|
||||
you should declare manually your non standard section in the command's
|
||||
manual page.}}
|
||||
|
||||
Finally note that the header of empty sections are dropped from the
|
||||
output. This allows you to share section placements among many
|
||||
commands and render them only if something actually gets inserted in
|
||||
it.
|
||||
|
||||
{1:doclang Documentation markup language}
|
||||
|
||||
Manpage {{!Cmdliner.Manpage.block}blocks} and the doc strings of the
|
||||
various [info] values support the following markup language.
|
||||
|
||||
{ul
|
||||
{- Markup directives [$(i,text)] and [$(b,text)], where [text] is raw
|
||||
text respectively rendered in italics and bold.}
|
||||
{- Outside markup directives, context dependent variables of the form
|
||||
[$(var)] are substituted by marked up data. For example in a command
|
||||
man page [$(cmd)] is substituted by the command's invocation in
|
||||
bold.}
|
||||
{- Characters '$', '(', ')' and '\' can respectively be escaped by \$, \(, \)
|
||||
and \\ . In OCaml strings this will be ["\\$"], ["\\("], ["\\)"],
|
||||
["\\\\"]. Escaping '$' and '\' is mandatory everywhere. Escaping ')' is
|
||||
mandatory only in markup directives. Escaping '(' is only here for
|
||||
your symmetric pleasure. Any other sequence of characters starting
|
||||
with a '\' is an illegal character sequence.}
|
||||
{- Referring to unknown markup directives or variables will generate
|
||||
errors on standard error during documentation generation.}}
|
||||
228
unikernel/duniverse/cmdliner/doc/tutorial.mld
Normal file
228
unikernel/duniverse/cmdliner/doc/tutorial.mld
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
{0:tutorial Tutorial}
|
||||
|
||||
See also the {{!page-cookbook}cookbook},
|
||||
{{!page-cookbook.blueprints}blueprints} and
|
||||
{{!page-examples}examples}.
|
||||
|
||||
{1:terms Commands and terms}
|
||||
|
||||
With [Cmdliner] your tool's [main] function evaluates a command.
|
||||
|
||||
A command is a value of type {!Cmdliner.Cmd.t} which gathers a command
|
||||
name and a term of type {!Cmdliner.Term.t}. A term represents both a
|
||||
command line syntax fragment and an expression to be evaluated that
|
||||
implements your tool. The type parameter of the term (and the command)
|
||||
indicates the type of the result of the evaluation.
|
||||
|
||||
One way to create terms is by lifting regular OCaml values with
|
||||
{!Cmdliner.Term.const}. Terms can be applied to terms evaluating to
|
||||
functional values with {!Cmdliner.Term.app}.
|
||||
|
||||
For example, in a [revolt.ml] file, for the function:
|
||||
|
||||
{@ocaml name=example_revolt1.ml[
|
||||
let revolt () = print_endline "Revolt!"
|
||||
]}
|
||||
|
||||
the term :
|
||||
|
||||
{@ocaml name=example_revolt1.ml[
|
||||
open Cmdliner
|
||||
|
||||
let revolt_term = Term.app (Term.const revolt) (Term.const ())
|
||||
]}
|
||||
|
||||
is a term that evaluates to the result (and effect) of the [revolt]
|
||||
function. This term can be associated to a command:
|
||||
|
||||
{@ocaml name=example_revolt1.ml[
|
||||
let cmd_revolt = Cmd.make (Cmd.info "revolt") revolt_term
|
||||
]}
|
||||
|
||||
and evaluated with {!Cmdliner.Cmd.val-eval}:
|
||||
{@ocaml name=example_revolt1.ml[
|
||||
let main () = Cmd.eval cmd_revolt
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
This defines a command line tool named ["revolt"] (this name will be
|
||||
used in error reporting and documentation generation), without command
|
||||
line arguments, that just prints ["Revolt!"] on [stdout].
|
||||
|
||||
{@sh[
|
||||
> ocamlfind ocamlopt -linkpkg -package cmdliner -o revolt revolt.ml
|
||||
> ./revolt
|
||||
Revolt!
|
||||
]}
|
||||
|
||||
{1:term_syntax Term syntax}
|
||||
|
||||
There is a special syntax that uses OCaml's
|
||||
{{:https://ocaml.org/manual/5.3/bindingops.html}binding operators} for
|
||||
writing terms which is less error prone when the number of arguments
|
||||
you want to give to your function grows. In particular it allows you to
|
||||
easily lift functions which have labels.
|
||||
|
||||
So in fact the program we have just shown above is usually rather
|
||||
written this way:
|
||||
|
||||
{@ocaml name=example_revolt2.ml[
|
||||
let revolt () = print_endline "Revolt!"
|
||||
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let cmd_revolt =
|
||||
Cmd.make (Cmd.info "revolt") @@
|
||||
let+ () = Term.const () in
|
||||
revolt ()
|
||||
|
||||
let main () = Cmd.eval cmd_revolt
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
{1:args_as_terms Command line arguments as terms}
|
||||
|
||||
The combinators in the {!Cmdliner.Arg} module allow to extract command
|
||||
line arguments as terms. These terms can then be applied to lifted
|
||||
OCaml functions to be evaluated. A term that uses terms that correspond
|
||||
to command line argument implicitely defines a command line syntax
|
||||
fragment. We show this on an concrete example.
|
||||
|
||||
In a [chorus.ml] file, consider the [chorus] function that prints
|
||||
repeatedly a given message :
|
||||
|
||||
{@ocaml name=example_chorus.ml[
|
||||
let chorus ~count msg = for i = 1 to count do print_endline msg done
|
||||
]}
|
||||
|
||||
we want to make it available from the command line with the synopsis:
|
||||
|
||||
{@sh[
|
||||
chorus [-c COUNT | --count=COUNT] [MSG]
|
||||
]}
|
||||
|
||||
where [COUNT] defaults to [10] and [MSG] defaults to ["Revolt!"]. We
|
||||
first define a term corresponding to the [--count] option:
|
||||
|
||||
{@ocaml name=example_chorus.ml[
|
||||
open Cmdliner
|
||||
open Cmdliner.Term.Syntax
|
||||
|
||||
let count =
|
||||
let doc = "Repeat the message $(docv) times." in
|
||||
Arg.(value & opt int 10 & info ["c"; "count"] ~doc ~docv:"COUNT")
|
||||
]}
|
||||
|
||||
This says that [count] is a term that evaluates to the value of an
|
||||
optional argument of type [int] that defaults to [10] if unspecified
|
||||
and whose option name is either [-c] or [--count]. The arguments [doc]
|
||||
and [docv] are used to generate the option's man page information.
|
||||
|
||||
The term for the positional argument [MSG] is:
|
||||
|
||||
{@ocaml name=example_chorus.ml[
|
||||
let msg =
|
||||
let env =
|
||||
let doc = "Overrides the default message to print." in
|
||||
Cmd.Env.info "CHORUS_MSG" ~doc
|
||||
in
|
||||
let doc = "The message to print." in
|
||||
Arg.(value & pos 0 string "Revolt!" & info [] ~env ~doc ~docv:"MSG")
|
||||
]}
|
||||
|
||||
which says that [msg] is a term whose value is the positional argument
|
||||
at index [0] of type [string] and defaults to ["Revolt!"] or the
|
||||
value of the environment variable [CHORUS_MSG] if the argument is
|
||||
unspecified on the command line. Here again [doc] and [docv] are used
|
||||
for the man page information.
|
||||
|
||||
We can now define a term and command for invoking the [chorus] function
|
||||
using the {{!term_syntax}term syntax} and the obscure but handy
|
||||
{{:https://ocaml.org/manual/5.2/bindingops.html#ss%3Aletops-punning}
|
||||
let-punning} OCaml notation. This also shows that the
|
||||
value {!Cmdliner.Cmd.val-info} can be given more
|
||||
information about the term we execute which is notably used to
|
||||
to generate the tool's man page.
|
||||
|
||||
{@ocaml name=example_chorus.ml[
|
||||
let chorus_cmd =
|
||||
let doc = "Print a customizable message repeatedly" in
|
||||
let man = [
|
||||
`S Manpage.s_bugs;
|
||||
`P "Email bug reports to <bugs@example.org>." ]
|
||||
in
|
||||
Cmd.make (Cmd.info "chorus" ~version:"v2.0.0+dune" ~doc ~man) @@
|
||||
let+ count and+ msg in
|
||||
chorus ~count msg
|
||||
|
||||
let main () = Cmd.eval chorus_cmd
|
||||
let () = if !Sys.interactive then () else exit (main ())
|
||||
]}
|
||||
|
||||
Since we provided a [~version] string, the tool will automatically
|
||||
respond to the [--version] option by printing this string.
|
||||
|
||||
Besides a tool using {!Cmdliner.Cmd.val-eval} always responds to the
|
||||
[--help] option by showing the tool's man page
|
||||
{{!page-tool_man.manual}generated} using the information you provided
|
||||
with {!Cmdliner.Cmd.val-info} and {!Cmdliner.Arg.val-info}. Here is
|
||||
the manual generated by our example:
|
||||
|
||||
{v
|
||||
> ocamlfind ocamlopt -linkpkg -package cmdliner -o chorus chorus.ml
|
||||
> ./chorus --help
|
||||
NAME
|
||||
chorus - Print a customizable message repeatedly
|
||||
|
||||
SYNOPSIS
|
||||
chorus [--count=COUNT] [OPTION]… [MSG]
|
||||
|
||||
ARGUMENTS
|
||||
MSG (absent=Revolt! or CHORUS_MSG env)
|
||||
The message to print.
|
||||
|
||||
OPTIONS
|
||||
-c COUNT, --count=COUNT (absent=10)
|
||||
Repeat the message COUNT times.
|
||||
|
||||
COMMON OPTIONS
|
||||
--help[=FMT] (default=auto)
|
||||
Show this help in format FMT. The value FMT must be one of auto,
|
||||
pager, groff or plain. With auto, the format is pager or plain
|
||||
whenever the TERM env var is dumb or undefined.
|
||||
|
||||
--version
|
||||
Show version information.
|
||||
|
||||
EXIT STATUS
|
||||
chorus exits with the following status:
|
||||
|
||||
0 on success.
|
||||
|
||||
123 on indiscriminate errors reported on standard error.
|
||||
|
||||
124 on command line parsing errors.
|
||||
|
||||
125 on unexpected internal errors (bugs).
|
||||
|
||||
ENVIRONMENT
|
||||
These environment variables affect the execution of chorus:
|
||||
|
||||
CHORUS_MSG
|
||||
Overrides the default message to print.
|
||||
|
||||
BUGS
|
||||
Email bug reports to <bugs@example.org>.
|
||||
v}
|
||||
|
||||
If a pager is available, this output is written to a pager. This help
|
||||
is also available in plain text or in the
|
||||
{{:http://www.gnu.org/software/groff/groff.html}groff} man page format
|
||||
by invoking the program with the option [--help=plain] or
|
||||
[--help=groff].
|
||||
|
||||
And with this you should master the basics of Cmdliner, for examples
|
||||
of more complex command line definitions consult the
|
||||
{{!page-examples}examples}. For more tips, off-the-shelf recipes and
|
||||
conventions have look at the {{!page-cookbook}cookbook}.
|
||||
Loading…
Add table
Add a link
Reference in a new issue