diff --git a/data/cookbook/parse-command-line-arguments/00-stdlib.ml b/data/cookbook/parse-command-line-arguments/00-stdlib.ml new file mode 100644 index 0000000000..05a6abc8ab --- /dev/null +++ b/data/cookbook/parse-command-line-arguments/00-stdlib.ml @@ -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 "; + 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 ] 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 diff --git a/data/cookbook/parse-command-line-arguments/01-cmdliner.ml b/data/cookbook/parse-command-line-arguments/01-cmdliner.ml new file mode 100644 index 0000000000..c95efc4eec --- /dev/null +++ b/data/cookbook/parse-command-line-arguments/01-cmdliner.ml @@ -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)