Skip to content
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

Cookbook: recipes for parse-command-line-arguments #2573

Open
wants to merge 5 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
74 changes: 74 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,74 @@
---
packages: []
discussion: |
For a longer introduction, see the [Command-line arguments](https://ocaml.org/docs/cli-arguments) tutorial in the docs.
The standard library documents the [Arg module](https://ocaml.org/manual/latest/api/Arg.html).
---
(* At the lowest level, we can interact with `Sys.argv` and access arguments by index.

With this approach all our error checking will have to be entirely manual.
If we pass a non-numeric first argument, then `int_of_string` raises an exception.
A leading dash on our second argument will just be treated as part of the
name - there is no option handling here.
*)
let () =
if Array.length Sys.argv <> 3 then (
print_endline "Usage: command <num-repeats> <name>";
exit 1)
else
let command_name = Sys.argv.(0) in
let num_repeats = Sys.argv.(1) |> int_of_string in
let greeting_name = Sys.argv.(2) in
for i = 1 to num_repeats do
Printf.printf "%d: Command '%s' says 'hello %s'\n" i command_name
greeting_name
done

(* The `Arg` module from the standard library gives us a higher-level
interface than `Sys.argv`. It is a good choice for basic command-line
applications.

We can handle options with values and repeating positional arguments, too.
It also automatically provides a `--help` option to our application.

The `Arg` module is quite imperative and updates references to a value.
Typically we initialise each option with a default value ("en", 1 etc).

To handle multiple positional (anonymous) arguments, we need a function like
`record_anon_arg` to construct a list or otherwise accumulate each item.

With `speclist`, we define the arguments we will parse.
Each argument needs the option-characters themselves, the action that will
be run when matched and a short explanatory piece of text.

Finally, `Arg.parse` will either print an error message or succed and update
the global refs.
*)
let greet language name =
match language with
| "en" -> Printf.printf "Hello %s\n" name
| "fr" -> Printf.printf "Bonjour %s\n" name
| "sp" -> Printf.printf "Hola %s\n" name
| _ -> Printf.printf "Hi %s\n" name

let usage_msg = "arg_module [-l en|fr|sp] [-r <NUM REPEATS>] name1 [name2] ..."
let language = ref "en"
let num_repeats = ref 1
let names = ref []

let record_anon_arg arg = names := arg :: !names

let speclist =
[
("-l", Arg.Set_string language, "Language to use (en|fr|sp, default en)");
( "-r",
Arg.Set_int num_repeats,
"Number of times to repeat greeting (default 1)" );
]

let () =
Arg.parse speclist record_anon_arg usage_msg;
for i = 1 to !num_repeats do
Printf.printf "Greeting %d of %d\n" i !num_repeats;
List.iter (greet !language) !names
done
49 changes: 49 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,49 @@
---
packages:
- name: cmdliner
tested_version: 1.3.0
used_libraries:
- cmdliner
discussion: |
The main [Cmdliner docs](https://erratique.ch/software/cmdliner/doc/index.html)
offer a quick tutorial, guides to how the parsing and man-page construction
works as well as example parsers and links to API docs.
---
(* The Cmdliner package offers a sophisticated compositional way of handling
command-line parsing. It handles options, positional arguments, and
subcommands. It will automatically generate help and a manpage.

The core of our application in this recipe is the `greeter` function.
Cmdliner will call this for us.

For `num_repeats`, we want an optional integer (with a default of 1)

For the `names`, we want all the positional arguments in a list.

The top-level `Term` of our parser-definition combines the function to call
along with the arguments to parse.

The `cmd` represents the body of our application.

Try to parse the command line and call "greeter" if successful. If not,
it prints formatted help-text and returns a non-zero exit code.
*)
open Cmdliner

let greeter num_repeats names =
for i = 1 to num_repeats do
Printf.printf "Greeting %d of %d\n" i num_repeats;
List.iter (fun name -> Printf.printf "Hello %s\n" name) names
done

let num_repeats =
let doc = "Repeat the greeting $(docv) times." in
Arg.(value & opt int 1 & info [ "r"; "repeat" ] ~docv:"REPEAT" ~doc)

let names = Arg.(non_empty & pos_all string [] & info [] ~docv:"NAME")

let recipe_t = Term.(const greeter $ num_repeats $ names)

let cmd = Cmd.v (Cmd.info "cmdliner_module") recipe_t

let () = exit (Cmd.eval cmd)