Core.Command (and the closely-related Async.Command) is an OCaml library for creating command line programs with nice interfaces (including help text and argument parsing). This article is an overview of Command.Param, the newer interface for defining your command's arguments (replacing Command.Spec).

This article is not exhaustive. For more information, refer to the official documentation:

Basic Param.t functions

Command.Param implements an applicative functor, which is just a monad with a both function instead of a bind function. The most important functions are:

  • return takes any value and turns it into a Command.Param.t
  • both takes two params and turns them into one param
  • map takes a param and a function that takes the param's value and returns a new value, returning a new Command.Param.t containing the function's return value
  • anon creates a param whose value comes an anonymous (or positional) argument.

    A basic string anonymous argument would be defined like:

    ocaml Command.(anon ("name_for_docs" %: string))

    See Command.Anons for details.

  • flag creates a param whose value comes from a - or -- named flag argument.

    A basic boolean flag would be defined like:

    ocaml Command.(flag ~doc:"NAME_FOR_DOCS help text goes here" "--example" no_arg)

    See Command.Flag for details.

Commands with no arguments

Command.basic expects a (unit -> unit) Param.t, so in the simplest case, you can use return to wrap a unit -> unit function:

open Core

let () =
  Command.basic
    ~summary:"example command with no arguments"
    (Command.Param.return (fun () ->
      print_endline "running example"))
  |> Command.run

Commands with one argument

A simple command taking one argument would use either flag or anon and then map that param to a function (presumably using the value of the param in the function).

For example:

let () =
  Command.basic
    ~summary:"example with one argument"
    Command.Param.(
      anon ("positional_arg" %: int)
      |> map ~f:(fun positional_arg ->
        fun () ->
          printf "positional_arg is %d\n" positional_arg))
  |> Command.run

Core's documentation recommends using ppx_let, which would look like this:

let () =
  Command.basic
    ~summary:"example with one argument"
    Command.Param.(
      let%map.Command positional_arg = anon ("positional_arg" %: int) in
      fun () ->
        printf "positional_arg is %d\n" positional_arg)
  |> Command.run

The recommended syntax goes one step further and uses [%map_open] (also from ppx_let), which automatically puts the various helper functions in Command into scope on the right hand side of your argument definitions (note how we don't need the local-open of Command.Param.(...) anymore):

let () =
  Command.basic
    ~summary:"example with one argument"
    [%map_open.Command
      let positional_arg = anon ("positional_arg" %: int) in
      fun () ->
        printf "positional_arg is %d\n" positional_arg]
  |> Command.run

Commands with multiple arguments

To extend the above to multiple arguments, you just need to use ppx_let's and syntax (which internally uses both) to merge two params into one, then map from that param to a unit -> unit function like usual.

let () =
  Command.basic
    ~summary:"two argument example"
    [%map_open.Command
      let optional = flag ~doc:"OPTIONAL an optional flag" "-o" no_arg
      and positional = anon ("positional_arg" %: string) in
      fun () ->
        printf
          "optional arg is %s and positional arg is %s\n"
          (Bool.to_string optional)
          positional]
  |> Command.run

Without ppx_let

This code quickly becomes unreadable without ppx_let, but here's the same example without it in case that's helpful for understanding:

let () =
  Command.basic
    ~summary:"two argument example"
    Command.Param.(
      both
        (flag ~doc:"OPTIONAL an optional flag" "-o" no_arg)
        (anon ("positional_arg" %: string))
      |> map ~f:(fun (optional, positional) () ->
        printf
          "optional arg is %s and positional arg is %s\n"
          (Bool.to_string optional)
          positional))
  |> Command.run

Commands with more arguments

To extend this to any number of arguments, just keeping adding and's:

let () =
  Command.basic
    ~summary:"two argument example"
    [%map_open.Command
      let first = anon ("first" %: string)
      and second = anon ("second" %: string)
      and third = anon ("third" %: string)
      and fourth = anon ("fourth" %: string) in
      fun () ->
        printf "args are %s %s %s %s\n" first second third fourth]
  |> Command.run