A declarative command-line parser for OCaml.
Programs written in OCaml should conform to existing UX conventions so as to
match the expectations of users coming from other tools. For command-line
programs which wish to parse their arguments in a declarative style, existing
solutions all seem to deviate from the conventions established by common Unix
tools. The two popular libraries for declarative command-line argument parsing
in OCaml arecmdliner
and base
's Command
module. Both of these libraries
present unconventional behaviour in that non-ambiguous prefixes of arguments are
treated as the full argument names. Additionally, cmdliner
lacks support for
generating shell autocompletion scripts, and base
only supports arguments
beginning with a single -
.
This library aims to be an alternative to cmdliner
and Base.Command
with
support for generating autocompletion scripts and which behaves as
conventionally as possible.
Here's a complete example program with built-in support for generating its own completion script.
open Climate
module Color = struct
type t =
| Red
| Green
| Blue
(* Tell climate how to handle colours *)
let conv =
let open Arg_parser in
enum ~default_value_name:"COLOR" [ "red", Red; "green", Green; "blue", Blue ]
;;
end
(* Ansi escape sequence to reset the termminal style *)
let ansi_reset = "\x1b[0m"
(* Returns the escape sequence to set the terminal style *)
let ansi_style ~bold ~underline ~color =
let effects =
List.append (if bold then [ ";1" ] else []) (if underline then [ ";4" ] else [])
in
let color_code =
match (color : Color.t option) with
| None -> 0
| Some Red -> 31
| Some Green -> 32
| Some Blue -> 34
in
Printf.sprintf "\x1b[%d%sm" color_code (String.concat "" effects)
;;
(* Print the words in the given style *)
let main ~bold ~underline ~color words =
print_string (ansi_style ~bold ~underline ~color);
print_string (String.concat " " words);
print_string ansi_reset;
print_newline ()
;;
let () =
let command =
Command.singleton ~desc:"Echo with style!"
@@
let open Arg_parser in
(* Describe and parse the command line arguments:*)
let+ bold = flag [ "bold" ] ~desc:"Make the text bold"
and+ underline = flag [ "underline" ] ~desc:"Underline the text"
and+ color = named_opt [ "color" ] Color.conv ~desc:"Set the text color"
and+ words = pos_all string
and+ completion =
flag [ "completion" ] ~desc:"Print this program's completion script and exit"
in
if completion
then `Completion
else `Main (fun () -> main ~bold ~underline ~color words)
in
(* Run the parser yielding either a main function to call or an indication
that we should print the completion script. *)
match Command.run command with
| `Completion -> print_endline (Command.completion_script_bash command)
| `Main main -> main ()
;
This program lives in examples/echo_ansi.ml
. Run it with dune exec examples/echo_ansi.exe -- <ARGS>
. E.g.
$ dune exec examples/echo_ansi.exe -- --help
Usage: _build/default/examples/echo_ansi.exe [OPTIONS] [STRING]...
Echo with style!
Options:
--bold Make the text bold
--underline Underline the text
--color <COLOR> Set the text color
--completion Print this program's completion script and exit
--help, -h Print help
The easiest way to setup the completion script is to first put the executable in a directory in your PATH. E.g.
$ mkdir -p ~/bin
$ export PATH=$HOME/bin:$PATH
$ dune build
$ cp _build/default/examples/echo_ansi.exe ~/bin/echo_ansi
You can ask echo_ansi
to print out its own completion script with:
$ echo_ansi --completion
#!/usr/bin/env bash
# Completion script for echo_ansi. Generated by climate.
__climate_complete_1184462387__complete() {
__climate_complete_1184462387__comp_words_traverse_init
if [ "$COMP_CWORD" == "0" ]; then
...
Put the completion script in a file and then source
that file in your shell to enable completions for echo_ansi
:
$ echo_ansi --completion > /tmp/echo_ansi_completions.sh
$ source /tmp/echo_ansi_completions.sh
$ echo_ansi --color <TAB>
blue green red
Currently only bash is supported, though it also works in zsh if bash compatibility is enabled:
# put this in your ~/.zshrc
autoload -Uz compinit bashcompinit
compinit
bashcompinit
Term will refer to each space-delimited string on the command line after the
program name. The command ls -l --color=always /etc/
has 3 terms. The program
name is ls
(not a term), and the terms are -l
, --color=always
, and
/etc/
.
Argument will refer to each distinct piece of information passed to the
program on the command line. The command make -td --jobs 4 all
has 4
arguments. The -td
term is made up of two arguments combined into a single
term: -t
and -d
(more on this later). --jobs 4
is a single argument
comprising two terms, where 4
is a parameter to the argument --jobs
. The
final term all
is also an argument.
Arguments may be positional or named. Positional arguments are
identified by their position in the argument list rather than by name. Named
arguments may have two forms: short and long. Short named arguments
begin with a single -
followed by a single non -
character, such as -l
.
Long named arguments begin with --
followed by one or more non -
characters,
such as --jobs
. A collection of short named arguments may be combined together
with a single leading -
followed by each short argument name. For example in
ls -la
, the -la
is an alternative way of writing -l -a
.
A named argument may take a parameter. A parameter is a single value which
follows the argument on the command line. Using make
's --jobs
argument as an
example, here are the different ways of passing a parameter to a named argument
on the command line:
make --jobs=4 # long name with equals sign
make --jobs 4 # long name space delimited
make -j 4 # short name space delimited
make -j4 # short name without space
If multiple short arguments are combined into a single term then only one of
those arguments may take a parameter. If the parameterized argument appears as
the final argument in the sequence then the following term will be treated as
its parameter, such as in make -dj 4
, which is equivalent to make -d -j 4
.
If the parameterized argument appears in a non-final position within the
sequence then the remainder of the sequence is treated as its parameter, such as
in make -dj4
which is also equivalent to make -d -j 4
.