module Cmdliner:sig
..end
Cmdliner
provides a simple and compositional mechanism
to convert command line arguments to OCaml values and pass them to
your functions. The module automatically handles syntax errors,
help messages and UNIX man page generation. It supports programs
with single or multiple commands
(like darcs
or git
) and respect most of the
POSIX and
GNU conventions.
Consult the basics, details about the supported command line syntax and examples of use. Open the module to use it, it defines only three modules in your scope.
v1.0.3 — homepage
module Manpage:sig
..end
module Term:sig
..end
module Arg:sig
..end
With Cmdliner
your program evaluates a term. A term is a value
of type Cmdliner.Term.t
. The type parameter 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.($)
. For example for the function:
let revolt () = print_endline "Revolt!"
the term :
open Cmdliner
let revolt_t = Term.(const revolt $ const ())
is a term that evaluates to the result (and effect) of the revolt
function. Terms are evaluated with Cmdliner.Term.eval
:
let () = Term.exit @@ Term.eval (revolt_t, Term.info "revolt")
This defines a command line program named "revolt"
, without command
line arguments, that just prints "Revolt!"
on stdout
.
> ./revolt
Revolt!
The combinators in the Cmdliner.Arg
module allow to extract command line
argument data as terms. These terms can then be applied to lifted
OCaml functions to be evaluated by the program.
Terms corresponding to command line argument data that are part of a term evaluation implicitly define a command line syntax. We show this on an concrete example.
Consider the chorus
function that prints repeatedly a given message :
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:
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:
let count =
let doc = "Repeat the message $(docv) times." in
Arg.(value & opt int 10 & info ["c"; "count"] ~docv:"COUNT" ~doc)
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:
let msg =
let doc = "Overrides the default message to print." in
let env = Arg.env_var "CHORUS_MSG" ~doc in
let doc = "The message to print." in
Arg.(value & pos 0 string "Revolt!" & info [] ~env ~docv:"MSG" ~doc)
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.
The term for executing chorus
with these command line arguments is :
let chorus_t = Term.(const chorus $ count $ msg)
and we are now ready to define our program:
let info =
let doc = "print a customizable message repeatedly" in
let man = [
`S Manpage.s_bugs;
`P "Email bug reports to <hehey at example.org>." ]
in
Term.info "chorus" ~version:"%%VERSION%%" ~doc ~exits:Term.default_exits ~man
let () = Term.exit @@ Term.eval (chorus_t, info))
The info
value created with Cmdliner.Term.info
gives more information
about the term we execute and is used to generate the program's man
page. Since we provided a ~version
string, the program will
automatically respond to the --version
option by printing this
string.
A program using Cmdliner.Term.eval
always responds to the --help
option by
showing the man page about the program generated using the information
you provided with Cmdliner.Term.info
and Cmdliner.Arg.info
. Here is the output
generated by our example :
> ./chorus --help NAME chorus - print a customizable message repeatedly SYNOPSIS chorus [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. --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. 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 <hehey at example.org>.
If a pager is available, this output is written to a pager. This help
is also available in plain text or in the
groff man page format
by invoking the program with the option --help=plain
or
--help=groff
.
For examples of more complex command line definitions look and run the examples.
Cmdliner
also provides support for programs like darcs
or git
that have multiple commands each with their own syntax:
prog COMMAND [OPTION]... ARG...
A command is defined by coupling a term with term
information. The term information defines the command name and its
man page. Given a list of commands the function Cmdliner.Term.eval_choice
will execute the term corresponding to the COMMAND
argument or a
specific "main" term if there is no COMMAND
argument.
Manpage blocks and doc strings support the following markup language.
$(i,text)
and $(b,text)
, where text
is raw
text respectively rendered in italics and bold.$(var)
are substituted by marked up data. For example in a term's
man page $(tname)
is substituted by the term name in bold."\\$"
, "\\("
, "\\)"
,
"\\\\"
). 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.
Man page sections for a term are printed in the order specified by the
term manual as given to Cmdliner.Term.info
. Unless specified explicitely in
the term's manual the following sections are automaticaly created and
populated for you:
The various doc
documentation strings specified by the term's
subterms and additional metadata get inserted at the end of the
documentation section name docs
they respectively mention, in the
following order:
Cmdliner.Term.info
.Cmdliner.Arg.info
. Those are listed iff
both the docv
and doc
string is specified by Cmdliner.Arg.info
.Cmdliner.Arg.info
.Cmdliner.Term.exit_info
.Cmdliner.Arg.env_var
and Cmdliner.Term.env_info
.
If a docs
section name is mentioned and does not exist in the term's
manual, 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:
Cmdliner.Manpage.s_synopsis
if there is no such section.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 term's
manual.
Ideally all manual strings should be UTF-8 encoded. However at the
moment macOS (until at least 10.12) is stuck with groff 1.19.2
which
doesn't support `preconv(1)`. Regarding UTF-8 output, generating the
man page with -Tutf8
maps the hyphen-minus U+002D
to the minus
sign U+2212
which makes it difficult to search it in the pager, so
-Tascii
is used for now. Conclusion is that it is better to stick
to the ASCII set for now. Please contact the author if something seems
wrong in this reasoning or if you know a work around this.
--cmdliner
is reserved by the library.--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.Invalid_argument
.
For programs evaluating a single term the most general form of invocation is:
prog [OPTION]... [ARG]...
The program automatically reponds to the --help
option by printing
the help. If a version string is provided in the term
information, it also automatically responds to the --version
option
by printing this string.
Command line arguments are either optional or
positional. Both can be freely interleaved but since
Cmdliner
accepts many optional forms this may result in
ambiguities. The special token --
can be used to
resolve them.
Programs evaluating multiple terms also add this form of invocation:
prog COMMAND [OPTION]... [ARG]...
Commands automatically respond to the --help
option by printing
their help. The COMMAND
string must be the first string following
the program name and may be specified by a prefix as long as it is not
ambiguous.
An optional argument is specified on the command line by a name possibly followed by a value.
The name of an option can be short or long.
"-h"
, "-q"
, "-I"
."--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. Long names can be specified by any non
ambiguous prefix.
The value of an option can be specified in three different ways.
"-o a.out"
, "--output a.out"
."-oa.out"
."--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 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:
"-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"
.
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.
Non-required command line arguments can be backed up by an environment variable. If the argument is absent from the command line and that 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:
""
, "false"
, "no"
, "n"
or "0"
is false
."true"
, "yes"
, "y"
or "1"
is true
.
Note that environment variables are not supported for Cmdliner.Arg.vflag
and
Cmdliner.Arg.vflag_all
.
These examples are in the test
directory of the distribution.
rm
command
We define the command line interface of a rm
command with the synopsis:
rm [OPTION]... FILE...
The -f
, -i
and -I
flags define the prompt behaviour of rm
,
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's no
occurrence the last value of the default list [Always]
is taken,
i.e. the default is Always
).
(* 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
let files = Arg.(non_empty & pos_all file [] & info [] ~docv:"FILE")
let prompt =
let doc = "Prompt before every removal." in
let always = Always, Arg.info ["i"] ~doc in
let doc = "Ignore nonexistent files and never prompt." in
let never = Never, Arg.info ["f"; "force"] ~doc in
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
let once = 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 cmd =
let doc = "remove files or directories" in
let man = [
`S Manpage.s_description;
`P "$(tname) 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 `-', for example
`-foo', use one of these commands:";
`P "rm -- -foo"; `Noblank;
`P "rm ./-foo";
`P "$(tname) removes symbolic links, not the files referenced by the
links.";
`S Manpage.s_bugs; `P "Report bugs to <hehey at example.org>.";
`S Manpage.s_see_also; `P "$(b,rmdir)(1), $(b,unlink)(2)" ]
in
Term.(const rm $ prompt $ recursive $ files),
Term.info "rm" ~version:"v1.0.3" ~doc ~exits:Term.default_exits ~man
let () = Term.(exit @@ eval cmd)
cp
command
We define the command line interface of a cp
command with the synopsis:
cp [OPTION]... SOURCE... DEST
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 it the Cmdliner.Arg.string
type
and verify the constraint at the beginning of the cp
implementation. If unsatisfied we return an `Error
and by using
Cmdliner.Term.ret
on the lifted result cp_t
of cp
, Cmdliner
handles
the error reporting.
(* Implementation, we check the dest argument and print the args *)
let cp verbose recurse force srcs dest =
if List.length srcs > 1 &&
(not (Sys.file_exists dest) || not (Sys.is_directory dest))
then
`Error (false, dest ^ " is 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
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
Arg.(required & pos ~rev:true 0 (some string) None & info [] ~docv:"DEST"
~doc)
let cmd =
let doc = "copy files" in
let man_xrefs =
[ `Tool "mv"; `Tool "scp"; `Page (2, "umask"); `Page (7, "symlink") ]
in
let exits = Term.default_exits in
let man =
[ `S Manpage.s_bugs;
`P "Email them to <hehey at example.org>."; ]
in
Term.(ret (const cp $ verbose $ recurse $ force $ srcs $ dest)),
Term.info "cp" ~version:"v1.0.3" ~doc ~exits ~man ~man_xrefs
let () = Term.(exit @@ eval cmd)
tail
command
We define the command line interface of a tail
command with the
synopsis:
tail [OPTION]... [FILE]...
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
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.
(* 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
let lines =
let loc =
let parse 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 (`Msg "unable to parse integer")
in
let print ppf p = Format.fprintf ppf "%s" (loc_str p) in
Arg.conv ~docv:"N" (parse, print)
in
Arg.(value & opt loc (true, 10) & info ["n"; "lines"] ~docv:"N"
~doc:"Output the last $(docv) lines or use $(i,+)$(docv) to start
output after the $(i,N)-1th line.")
let follow =
let doc = "Output appended data as the file grows. $(docv) specifies how the
file should be tracked, by its `name' or by its `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 doc = "Never output headers giving file names." in
let quiet = Quiet, Arg.info ["q"; "quiet"; "silent"] ~doc in
let doc = "Always output headers giving file names." in
let verbose = 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 cmd =
let doc = "display the last part of a file" in
let man = [
`S Manpage.s_description;
`P "$(tname) 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 <hehey at example.org>.";
`S Manpage.s_see_also;
`P "$(b,cat)(1), $(b,head)(1)" ]
in
Term.(const tail $ lines $ follow $ verb $ pid $ files),
Term.info "tail" ~version:"%%VERSION%%" ~doc ~exits:Term.default_exits ~man
let () = Term.(exit @@ eval cmd)
darcs
command
We define the command line interface of a darcs
command with the
synopsis:
darcs [COMMAND] ...
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 a section called COMMON
OPTIONS
, since we also want to put --help
and --version
in this
section, the term information of commands makes a judicious use of the
sdocs
parameter of Cmdliner.Term.info
.
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.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_cmd
term.
(* 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
match conv 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
(* 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 `$(mname) $(i,COMMAND) --help' for help on a single command.";`Noblank;
`P "Use `$(mname) help patterns' for help on patch matching."; `Noblank;
`P "Use `$(mname) 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 $(mname) command." in
Arg.(value & opt (some string) None & info ["prehook"] ~docs ~doc)
in
Term.(const copts $ debug $ verb $ prehook)
(* Commands *)
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 exits = Term.default_exits 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
Term.(const initialize $ copts_t $ repodir),
Term.info "initialize" ~doc ~sdocs:Manpage.s_common_options ~exits ~man
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 exits = Term.default_exits 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
Term.(const record $ copts_t $ pname $ author $ all $ ask_deps $ files),
Term.info "record" ~doc ~sdocs:Manpage.s_common_options ~exits ~man
let help_cmd =
let topic =
let doc = "The topic to get help on. `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
Term.(ret
(const help $ copts_t $ Arg.man_format $ Term.choice_names $topic)),
Term.info "help" ~doc ~exits:Term.default_exits ~man
let default_cmd =
let doc = "a revision control system" in
let sdocs = Manpage.s_common_options in
let exits = Term.default_exits in
let man = help_secs in
Term.(ret (const (fun _ -> `Help (`Pager, None)) $ copts_t)),
Term.info "darcs" ~version:"v1.0.3" ~doc ~sdocs ~exits ~man
let cmds = [initialize_cmd; record_cmd; help_cmd]
let () = Term.(exit @@ eval_choice default_cmd cmds)