[ANN] First release of ppx_subliner, a ppx deriver and rewriter for Cmldliner sub-command

I am very pleased to announce the first release of ppx_subliner! The package is now available through OPAM.

I am always a big fan of ppx_deriving_cmdliner. It helps you construct Cmdliner.Term.t from record types and makes writing cli parsing intuitive and painless. But it lacks the ability to generate values for sub-command groups and the final Cmdliner evaluations. Therefore, ppx_subliner comes to life.

ppx_subliner can work with ppx_deriving_cmdliner to generate sub-command groups. You can do so by simply tagging the extension to a variant type.

type foo = { my_arg : string } [@@deriving cmdliner]

type params =
  | Foo of foo
  | Bar
[@@deriving_inline subliner]
include
  sig
    [@@@ocaml.warning "-32"]
    val params_cmdliner_group_cmds : (params -> 'a) -> 'a Cmdliner.Cmd.t list
  end[@@ocaml.doc "@inline"]
[@@@end]

It will generate a function which takes in a handle function and return the sub-command list. Here is a simple handle function.

let handle = function
  | Foo { my_arg } -> print_endline ("Foo " ^ my_arg)
  | Bar -> print_endline "Bar" 

From here, you either construct the final evaluation manually:

let cmd =
  let open Cmdliner in
  let doc = "Some docs" in
  let info = Cmd.info ~doc "foobar" in
  Cmd.group info (params_cmdliner_group_cmds handle)

let () = exit (Cmdliner.eval cmd)

or use the [%%subliner.cmds] rewriter, which reuses the setfield syntax:

(* {eval function}.{type name} <- {function expression> *)
[%%subliner.cmds eval.params <- handle]
[@@name "foobar"] [@@version "3.14"]
(** Some docs *)
$ foobar.exe foo --my-arg 123
Foo 123

Both the deriver and rewriter will respect the OCaml docstring. You can also use [@name], [@man], [@envs] etc to configure all aspects of the underlying Cmdliner.Cmd.info value.

You can also use different evaluation function and set optional arguments:

[%%subliner.cmds (eval_result ~catch:false).params <- 
  (function
    | Foo { my_arg } -> print_endline ("Foo " ^ my_arg) |> Result.ok
    | Bar -> print_endline "Bar" |> Result.ok)]

Please see more details in the documentation.

What’s next

I want to support inline record and enum as arg in the future, and maybe replicate some of ppx_deriving_cmdliner’s functionality, but better support for deriving_inline, lists (ie. -I liba -I libb instead of -I liba,libb), positional arguments and more compile time validation. We shall see.

Hope this is helpful. Happy hacking!

7 Likes