Command Line Subcommands in OCaml

Here’s the translation of the Go code to OCaml, formatted in Markdown suitable for Hugo:

Our program demonstrates how to implement command-line subcommands in OCaml. Each subcommand can have its own set of flags and arguments.

open Cmdliner

let foo_cmd =
  let enable =
    let doc = "Enable flag for foo command" in
    Arg.(value & flag & info ["enable"] ~doc)
  in
  let name =
    let doc = "Name for foo command" in
    Arg.(value & opt string "" & info ["name"] ~doc)
  in
  let foo enable name args =
    Printf.printf "subcommand 'foo'\n";
    Printf.printf "  enable: %b\n" enable;
    Printf.printf "  name: %s\n" name;
    Printf.printf "  tail: [%s]\n" (String.concat "; " args)
  in
  Term.(const foo $ enable $ name $ Arg.(value & pos_all string [] & info [] ~docv:"ARGS")),
  Term.info "foo" ~doc:"Foo subcommand"

let bar_cmd =
  let level =
    let doc = "Level for bar command" in
    Arg.(value & opt int 0 & info ["level"] ~doc)
  in
  let bar level args =
    Printf.printf "subcommand 'bar'\n";
    Printf.printf "  level: %d\n" level;
    Printf.printf "  tail: [%s]\n" (String.concat "; " args)
  in
  Term.(const bar $ level $ Arg.(value & pos_all string [] & info [] ~docv:"ARGS")),
  Term.info "bar" ~doc:"Bar subcommand"

let default_cmd =
  let doc = "Command-line subcommands example" in
  let sdocs = Manpage.s_common_options in
  let man = [
    `S Manpage.s_description;
    `P "This program demonstrates command-line subcommands in OCaml.";
  ] in
  Term.(ret (const (`Help (`Pager, None)))),
  Term.info "cli_subcommands" ~version:"1.0.0" ~doc ~sdocs ~man

let () =
  let cmds = [foo_cmd; bar_cmd] in
  match Term.eval_choice default_cmd cmds with
  | `Error _ -> exit 1
  | _ -> exit 0

This OCaml program uses the Cmdliner library to define and handle command-line subcommands. Here’s a breakdown of the code:

  1. We define two subcommands: foo and bar.

  2. For the foo subcommand, we define two flags: --enable (a boolean flag) and --name (a string option).

  3. For the bar subcommand, we define one flag: --level (an integer option).

  4. Each subcommand has its own handler function that prints out the values of its flags and any additional arguments.

  5. We use Cmdliner’s Term module to define the command-line interface for each subcommand.

  6. A default command is defined to display help information if no subcommand is provided.

  7. Finally, we use Term.eval_choice to parse the command-line arguments and execute the appropriate subcommand.

To run this program, you would compile it and then use it like this:

$ ocamlbuild -pkg cmdliner cli_subcommands.native
$ ./cli_subcommands.native foo --enable --name=joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: [a1; a2]

$ ./cli_subcommands.native bar --level 8 a1
subcommand 'bar'
  level: 8
  tail: [a1]

This OCaml implementation provides a more type-safe and declarative approach to handling command-line arguments compared to manually parsing them. The Cmdliner library handles much of the complexity of parsing and validating command-line inputs.