Cmdliner for the case: tool [OPTION] [COMMAND] [OPTIONS]


I am using the library cmdliner to create a cli tool. It is great and working pretty fine for a set of commands with a set of options for each of them (all of them with default values so options). However there is one particular option (named --config) that would be great to be general so rather than

tool [COMMAND] --config=xxxx

something like

tool --config=xxxx [COMMAND] [OPTIONS]

In the list of examples of the documentation there is a case for darcs using subcomands, but not specifically a case with the second structure above. Is this combination possible?

It is highly possible that I did not understood the library properly, but the implementation of commands the idea is to create a function cmd that eventually has the form:

let cmd = 
  ... info ~default [ cmd1; cmd2; cmd3; ...]

but in the case of options it is

let cmd = 
  Cmd.v info Term.(...)

both will be used as

 let () = exit (Cmd.eval cmd)

But is it possible to build something combining both?


There are two things:

  1. Having an option (or many of them) common to many subcommands.
  2. Being able to specify that option before the subcommand.

For 1. since the declaration of options is declarative, just declare it somewhere and use it in the term of your commands. You will likely want to document this option under Man.s_common_options section.

If it’s common to all of them you can even define another function of yours that declares your commands and automatically applies the option as the final arg:

let config = … 
let make_cmd info term = Cmd.v info Term.(term $ config)

For 2 it is not possible, because in general, between command names, positional arguments and values of optional argument specified without an = (e.g. --config myconfig), everything becomes too ambiguous .

Thus commands always have to be specified first on the command line – also in passing it’s better to avoid defining command with subcommands that have a default subcommand unless the command has no positional arguments. Otherwise it forces your user to always use the -- token for specifying positional arguments to the command when there’s no subcommand to disambiguate between (sub)commands and positional arguments.

1 Like

Btw. slightly better style (unfortunately not followed by Cmdliner’s sample code) for this is:

let main () = Cmd.eval cmd 
let () = if !Sys.interactive then () else exit (main ())

This allows to load the cli tool in the toplevel which may occasionally be useful for inspecting stuff.


Thank you for the help!!

Regarding the motivation, the option --config=XXX is actually in all the commands of the tool, but one user requested to have something similar to the tool pubs where you can specify (optionally) a --config before the commands because he/she wants to create an alias (in the shell) to have different possible configurations associated to the value of config.

I feel that tool [option] [command] [options] is ugly even if it would be possible. From a practical point of view, I tried alternatives and it is possible to replicate the functionality of an alias in bash just with a function:

function tool {
   tool ${carg[0]} --config=tool ${carg[@]:1}

Anyway @dbuenzli thanks for developing cmdliner it is being very useful and I am using in several projects.