File: cookbook.mld

package info (click to toggle)
cmdliner 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 704 kB
  • sloc: ml: 7,287; sh: 146; makefile: 108
file content (768 lines) | stat: -rw-r--r-- 27,345 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
{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.eval' 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:top_tool_support_with_dune_install With [dune build @install]}

Users of [dune] 3.5 or later can use the [cmdliner install
tool-support] command described above, along with the
{{:https://dune.readthedocs.io/en/stable/reference/dune/rule.html#directory-targets}
[directory-targets]}
feature and
{{:https://dune.readthedocs.io/en/latest/reference/dune/install.html}
[install]} stanza, to hook into the existing [dune build @install] command.

For an executable named [mytool] defined in a [dune] file, you can add
the following to the same [dune] file to populate the completion
scripts and manpages when running [dune build @install] (which is what
is used by [opam install]):

{@dune[
(rule
 (target (dir cmdliner-support))
 (deps mytool.exe)
 (action
  (ignore-stdout
   (run cmdliner install tool-support ./mytool.exe:mytool cmdliner-support))))

(install
 (section share_root)
 (dirs (cmdliner-support/share as .)))
]}

If these features of dune are not available to your project, you may
instead need to update the [opam] file, see {{!tip_tool_support_with_opam_dune}
these instructions}.

{3:tip_tool_support_with_opam_dune With [opam] and [dune]}

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 add a [.opam.template]} file which inserts the [cmdliner
install] instruction to the [build:] field of the opam file after your
[dune] build instructions. For a tool named [mytool] the
[mytool.opam.template] file should contain:

{@sh[
build: [
  # These first two commands are what dune generates if you don't have
  # a template
  ["dune" "subst"] {dev}
  [
    "dune"
    "build"
    "-p"
    name
    "-j"
    jobs
    "@install"
    "@runtest" {with-test}
    "@doc" {with-doc}
   ]
   # This is the important command for cmdliner's files
   ["cmdliner" "install" "tool-support"
               "--update-opam-install=%{_:name}%.install"
               "_build/install/default/bin/mytool" {os-family != "windows"}
               "_build/install/default/bin/mytool.exe" {os-family = "windows"}
               "_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 filepath "-" & 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 filepath "-" & 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.1.0") @@
  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 filepath "-" & 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.1.0" ~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 filepath "-" & 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.1.0" ~doc) @@
  [hey_cmd; ho_cmd]

let main () = Cmd.eval' cmd
let () = if !Sys.interactive then () else exit (main ())
]}