dbuenzli / cmdliner

Declarative definition of command line interfaces for OCaml
http://erratique.ch/software/cmdliner
ISC License
296 stars 56 forks source link

`opt_all` options that mutually affect each other #188

Closed bensmrs closed 4 months ago

bensmrs commented 4 months ago

Hi!

I’m trying to add --include and --exclude options to my program, so that each option takes precedence over the previous ones, i.e. --include=/var --exclude=/var/log --include=/var/log/foo.

Doing it with opt_all gives two lists of parameters, one for --include and the other for --exclude, but I can’t find a clean way to store the result like [Include "/var"; Exclude "/var/log"; Include "/var/log/foo"]. Is there one?

My current non-clean solution is to use a mutable list with custom parsers for --include and --exclude that feed this list…

Thanks

dbuenzli commented 4 months ago

I don't think so. In general support for context dependent options is poor in cmdliner (on purpose).

bensmrs commented 4 months ago

Would exposing the id function from Cmdliner_info.Arg be that bad? At least it could help me reorder the two lists in a proper way.

dbuenzli commented 4 months ago

I don't see how this helps.

bensmrs commented 4 months ago

I thought it increased at parsing time but I’m now getting that it increases at option definition time… So for now, you’d recommend me to stick to my mutable list hack?

dbuenzli commented 4 months ago

Yes. Or change the way your cli works. Generally I rather define a set with all includes and then simply remove excludes from this set. This doesn't support your example but it has the advantage to be simpler to understand for users when you read the cli (not context dependent).

bensmrs commented 4 months ago

Unfortunately, the example I’ve shown is a typical use case for my CLI, I think tools like rsync do it too. But I completely understand that you want to stick to context-free stuff as much as possible. Maybe doing something like info ["include"; "exclude"] with a common description and having type 'a parser = ?name:string -> string -> [ `Ok of 'a | `Error of string ] in Cmdliner_arg (with name the option name) could do the trick without breaking already existing programs, but that may not be a path you’d want to pursue.

dbuenzli commented 4 months ago

Could you perhaps try something with with_used_args. I'm not longer exactly how it works. (Likely as painful but maybe a little bit less ugly).

bensmrs commented 4 months ago

Awesome, that’s exactly what I needed! It gives me terms with (initial_value, option_name). Thanks a lot!

dbuenzli commented 4 months ago

Be careful though IIRC this gives you the cli verbatim (it was added so that you could reply to the user with what she wrote verbatim) so you likely need to process that list I suspect that say --include=/var/ --exclude /var/log will give you ["--include=/var/"; "--exclude"; "/var/log"; ].

dbuenzli commented 4 months ago

type 'a parser = ?name:string -> string -> [ Ok of 'a |Error of string ]

Also that would break everyone, but an alternative conv constructor could be provided. I will think about that in the future.

bensmrs commented 4 months ago

Yes, the option is prefixed with dashes.

Also that would break everyone

Well… It sure breaks a few internal signatures, but the fact that name is optional here should make the change transparent to most people, as the “old” functions would still typecheck. But that’s just a guess.

Anyway, with_used_args is fine for me

dbuenzli commented 4 months ago

Yes, the option is prefixed with dashes.

More important, the option and the option argument may be in two different strings.

but the fact that name is optional here should make the change transparent to most people,

No. It breaks anyone who implemented the 'a parser signature: you require their function to have an optional ?name argument which they don't have.