dbuenzli/cmdliner

`opt_all` options that mutually affect each other

bensmrs opened this issue · 12 comments

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

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

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.

I don't see how this helps.

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?

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).

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.

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).

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

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"; ].

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.

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

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.