Help with cmdliner

cmdliner

#1

I’m trying to wrap my head around cmdliner and can’t seem to figure out how to make it work for my problem.

I want to support syntax of this kind

my_program FILE command -options

This means that FILE is required, but passed to command which can be several different commands (all of them work on the file).

Currently I have this for parsing the file:

let file_t =
  Arg.(required & pos 0 (some non_dir_file) None & info [] ~docv:"FILE")

But I can’t figure out how to combine it with another command and pass the result of file_t to the command.

Probably my problem can even be reworded further into - I can’t figure out how to pass the results of cmdliner to my functions… And how to call my functions at all. I even suspect that I’m not supposed to do that, but instead use Term.exit and Term.eval, but I don’t get why, so some crash course into the philosophy of cmdliner would be great. I can’t seem to be able to figure out why I need all of this complexity at all.


#2

Did you try follow the tutorial ? If you did could you tell me what you find unclear or how it could be improved ?


#3

Yes, I did try it. What is missing is probably “why is it designed like this”, because currently I’m trying to match the interfaces while I don’t understand what problem am I trying to solve. These abstractions are “too much” for a newbie.

Also, the currently given examples are too complex - even the one with the chorus. It combines too many moving parts without explaining the motivation.

Of course, I understand some of the problems you are addressing with these abstractions, but probably quite a small part.

The ideal tutorial would be one starting from Sys.argv, then explaining “why do we need this more complex thing” and “why even this is not enough”. I’d be very happy to write it once I grok it.


#4

Also, probably all these “bells and whistles” are too much for a tutorial. When I start implementing a program, initially I don’t care about man pages and exit codes. I just want to be able to control the behavior of my program from the command line, just a simple way to switch between commands, so that I don’t need to modify my main function’s code while developing. And I can’t figure out how to strip the man page generation etc. from your examples. I don’t know if it’s even possible. I just want it really simple in the beginning.

Of course, I can probably just not use cmdliner, but I plan on writing quite a complex interface later, just don’t want to have to do it right now.


#5

As the module preamble indicates:

Cmdliner provides a simple and compositional mechanism to
convert command line arguments to OCaml values and pass
them to your functions.

So the problem you are trying to solve is how to parse and pass command line arguments to your OCaml functions which are/should be unaware from the cli machinery altogether. Cmdliner terms provides you this mecanism along with the metadata that allows to generate the cli documentation.

People have different ways of learning, I think the chorus example gives a reasonable hands-on working example which provides a basis for tweaking and thus gradually understanding the bits of the API.

I’m not sure providing too much rationale behind the design would help here, these things become more evident when the API is absorbed by using it (e.g. compositionality).

Cmdliner precisely doesn’t require you to think about these things, it gives them to you for free and makes your program a resonably good cli citizen without you having to bother. The chorus example (you can skip the man definition) is a good example of this.


#6

It’s your software and you might be right, but currently I think it would help me specifically in solving my actual problem.

From what I see, cmdliner does not allow me to “take” what it has parsed and “give it” to my functions/commands. Instead, it seems like I have to “call my functions through cmdliner”. I’m frustrated by this and I don’t understand why it is. And I can’t figure out how to take the results of one parsed option (the file in my case) and give it to another command.

Or maybe there is something very basic that I’m missing.


#7

So your implementation function that you seek to invoke on the cli may be something like

let my_program file cmd = match cmd with 
| "thecmd" -> ...
| ...

A simple start would be

let file_t = ...
let cmd_t = Arg.(required & pos 1 (some string) None & info [] ~docv:"CMD")
let my_program_t = Term.(const my_program $ file_t $ cmd_t)
let () = Term.exit @@ Term.eval (my_program_t, Term.info "my_program")

#8

Wow, this example (because of the context which I understand for my use case) helped me understand the chorus example too, which previously I didn’t manage to.