docopt.rs
docopt.rs copied to clipboard
More readable struct fields
I don't know Rust, but for the C implementation the strategy was similar—to generate a struct. However, the idea was to either generate struct under one "namespace":
-g => args.g # unless --group is a synonym
--group => args.group
FILE => args.file
<file> => args.file
build => args.build
Or have separate nested structs for options, arguments and commands:
-g => args.options.g # unless --group is a synonym
--group => args.options.group
FILE => args.positional.file
<file> => args.positional.file
build => args.commands.build
I would prefer any of the two better than the current scheme.
The problem with the first approach is you can end up with collisions, although I suppose an error could be generated in that case.
@alexcrichton What do you think? I don't have any terribly strong opinions. The current approach was just something That Worked. I'm not attached to it.
I'd avoid the first for the same reasons, and foo.options.bar does seem a little better than foo.flag_bar (foo.flags.bar?). I also don't have too too strong a preference!
I think that a single namespace is still a viable option, because when was the last time anyone had a --file option and a <file> argument?!
Right, wouldn't collisions represent symbol reuse within a single pattern? That seems inadvisable to begin with. Since the first, singly-namespaced approach better matches what other docopt implementations provide, I'd vote for it over struct nesting.
Since the first, singly-namespaced approach better matches what other docopt implementations provide
The reference docopt implementation is namespaced.
If it's truly inadvisable to have a flag and, say, a positional argument with the same name, then perhaps Docopt itself should rule it out. But as of now, it allows it, so I don't really see a reason to arbitrarily disallow it.
If it's truly inadvisable to have a flag and, say, a positional argument with the same name, then perhaps Docopt itself should rule it out. But as of now, it allows it, so I don't really see a reason to arbitrarily disallow it.
I would totally disallow it. It just happens that some major languages have fist-class support for json-like dictionaries, so the problem never came up there.
For languages where it makes more sense to generate a custom data type (like struct in Rust and C), I think it's totally fine to raise error in case of a collision (like --file vs <file>).
It's just a matter if we decide on a single namespace, or on multiple nested namespaces (options, arguments, commands).
Correct me if I'm wrong, but the ref impl generates a dictionary where the only nested data structure is the <args> list. Options and positional arguments are inserted into the dictionary as top-level key value pairs.
Collisions could still occur across separate patterns in the event that e.g. a positional argument's name is the same as a command. I'm of the opinion that avoiding such cases of conflated terms would also remove a potential source of confusion for users.
@evnm What you say is true, but that doesn't mean the dictionary isn't namespaced. For example, all flags start with - or --. Likewise, currently, struct fields that are flags start with flag_.
Arguments are always keys of the form <arg> and ARG. A command, is, by definition, everything else. Therefore there is no overlap.
In the reference Docopt, you can have a flag, command and positional argument all have the same name:
Usage: program -a <a> a
And it works fine:
[andrew@Liger docopt] python examples/quick_example.py -a hi a
{'-a': True,
'<a>': 'hi',
'a': True}
In other words: namespaced.
(My understanding is that @halst does not like this? Even in the reference implementation? I don't see what native JSON dictionaries have to do with whether you have proper namespaces or not. I had assumed this namespacing was an explicit design decision, so I copied it.)
@evnm in the reference implementation, each --option, <argument> and command is inserted as key in a json-like dictionary. The values of those keys could be different:
- for
--optionsit's boolaen - for
--optionswith an argument, it's a string - for
--optionswith an argument which could be repeated (thanks to...operator), it's a list of values - for options which take no argument, but could be repeated (
-v...), the value is a number (of repetitions) - for
commandsit's same as for options with no argument - for
<arguments>it is either a single string, or a list of strings if argument could be repeated.
Since in the reference implementation keys are arbitrary strings, the cannot collide.
@BurntSushi the ref. imp. is using full names (with characters like -<>), not to separate them from each other, but only for readability.
Gotcha, thank you both. Excuse my ignorance. I'm not very familiar with docopt and came across this project when poking around for Rust CLI libs.
This makes more sense now. Insofar as my less-informed opinion matters, I think the args.{command,options,arguments} approach is a good compromise between adhering to the reference implementation and leveraging Rust structs.
@evnm That is the way I'm leaning. Note that structs are a convenience for this library. You can still access values using a hashmap with keys like the reference Docopt implementation: https://github.com/docopt/docopt.rs/blob/master/examples/hashmap.rs
If we really think that having a usage like program -a <a> a is a bad idea, then it should be properly codified in the Docopt spec.
Such collision avoidance might be worth posing to the docopt folks via a separate issue on docopt/docopt, but is outside of the scope of #7. As long as the hashmap-based API has parity with the Python lib, then I think docopt.rs should have fairly free reign over the sugary struct-based API.
I think docopt.rs should have fairly free reign over the sugary struct-based API.
:+1: