Skip to content

Cookbook recipes to parse command-line arguments #3038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions data/cookbook/parse-command-line-arguments/00-stdlib.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
packages: []
---
(* `Sys.argv` is the command-line arguments as an array of strings
([standard library documentation](https://ocaml.org/manual/5.3/api/Sys.html#VALargv)).
Here we print each argument with its corresponding index in the array. *)
let simplest_parser () =
Sys.argv
|> Array.to_list
|> List.iteri (Printf.printf "argument %d: %s\n")

(* `Arg` is a module for parsing command-line arguments, and it is part of
OCaml's standard library
([documentation](https://ocaml.org/manual/5.3/api/Arg.html)).
In this function we define the structure of the command-line arguments we
expect, the argument types types and their documentation. This is basically
the same function defined in the module's documentation. *)
let arg_module_parser () =
let usage_msg =
"mycmd [--verbose] <file1> [<file2>] ... -o <output>"
and verbose = ref false
and input_files = ref []
and output_file = ref "" in
(* This function is called once for each anonymous argument. *)
let anonymous_args_f filename =
input_files := filename :: !input_files
(* The spec list defines argument keywords, "setter" functions to handle the
values, and their corresponding documentation. *)
and spec_list =
[("--verbose", Arg.Set verbose, "Output debug information");
("-o", Arg.Set_string output_file, "Set output file name")]
in
Arg.parse spec_list anonymous_args_f usage_msg;
Printf.printf "verbose: %b\n" !verbose;
Printf.printf "input files: %s\n"
(!input_files |> String.concat ", ");
Printf.printf "output file: %s\n" !output_file

(* Given a command-line like `mycmd --verbose file1 -o /tmp/out`, we should
expect the following output:

```
=== Simplest parser ===
argument 0: mycmd
argument 1: --verbose
argument 2: file1
argument 3: -o
argument 4: /tmp/out

=== Arg.parse ===
verbose: true
input files: file1
output file: /tmp/out
``` *)
let () =
print_endline "=== Simplest parser ===";
simplest_parser ();

Printf.printf "\n%!";

print_endline "=== Arg.parse ===";
arg_module_parser ()
78 changes: 78 additions & 0 deletions data/cookbook/parse-command-line-arguments/01-cmdliner.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
packages:
- name: cmdliner
tested_version: "1.3.0"
used_libraries:
- cmdliner
---
(* In this example, we define a parser for a command-line like this:

```
mycmd --verbose input_file -o output_file
```

`--verbose` and `-o` are optional, and the command should accept multiple
input files.

We can find more examples on
[this page](https://erratique.ch/software/cmdliner/doc/examples.html).
*)
let cmdliner_parser () =
(* `Cmdliner` is a library that allows the declarative definition of
command-line interfaces
([documentation page](https://erratique.ch/software/cmdliner/doc/index.html)).
*)
let open Cmdliner in
(* First we declare the expected arguments of our command-line, and how
`Cmdliner` should parse them. *)
let verbose =
let doc = "Output debug information" in
(* `&` is a right associative composition operator
([documentation](https://erratique.ch/software/cmdliner/doc/Cmdliner/Arg/index.html#val-(&))).
*)
Arg.(value & flag & info ["v" ; "verbose"] ~doc)
and input_files =
let doc = "Input file(s)" in
Arg.(non_empty & pos_all file []
& info [] ~docv:"INPUT" ~doc)
and output_file =
let doc = "Output file"
and docv = "OUTPUT" in
Arg.(value & opt (some string) None
& info ["o"] ~docv ~doc)
(* `mycmd` is the function that the program will apply to the parsed
arguments. *)
and mycmd verbose input_files output_file =
Printf.printf "verbose: %b\n" verbose;
Printf.printf "input files: %s\n"
(input_files |> String.concat ", ");
Printf.printf "output file: %s\n"
(Option.value ~default:"" output_file)
in
(* Declaration of the complete command, including its man-like
documentation. *)
let cmd =
let doc = "An example command"
and man = [
`S Manpage.s_description;
`P "A command that can take multiple files
and outputs a file (optional)."
]
in
Cmd.v (Cmd.info "mycmd" ~doc ~man)
Term.(const mycmd $ verbose $ input_files $ output_file)
in
Cmd.eval cmd


(* Given a command-line like
`mycmd --verbose -o somefile ./dune-project ./cmd_cookbook.opam`, we should
expect the following output:

```
verbose: true
input files: ./dune-project, ./cmd_cookbook.opam
output file: somefile
``` *)
let () =
exit @@ cmdliner_parser ()
Loading