larryhastings / appeal

Command-line parsing library for Python 3.
Other
122 stars 7 forks source link

Figure out how to communicate the concept of "argument groups" better in usage #9

Open larryhastings opened 1 year ago

larryhastings commented 1 year ago

Consider this program:

import appeal
app = appeal.Appeal()
def int_float(i: int, f:float):
    return (i, f)
@app.command()
def fgrep(value, i_f:int_float=None):
    ...
app.main()

The fgrep command now requires either one or three positional arguments:

But two positional arguments is illegal. If you specify two positional arguments, you'll get a usage error like this:

Error: fgrep requires 2 arguments in this argument group.

This is correct, but it's meaningless to the user... unless they already understand how Appeal thinks. How else would they know what an "argument group" is?

I wanna fix it. But I'm not sure how. So, this issue is a place to put blue-sky proposals. What would be the platonic ideal error message for Appeal to issue here? If we (I) can come up with something that communicates the concept clearly to the user, by golly I'll figure out how to make it work.

larryhastings commented 1 year ago

Here's what I'm thinking right now. I can't use the term "argument group" in usage errors, because nobody (apart from Appeal users) knows what that is. All I can do is talk about numbers of arguments.

Let's make up a command, just to have an example to talk about:

def int_float_strs(i: int, f: float, s: str=None, s2: str=None,):
    return (i, f, s, s2)

@app.command()
def walnut(value: int_float_strs, value2:int_float_strs=None):
   ...

Solely considering the permissible number of positional arguments, walnut could be called with 2, 3, 4, 6, 7, or 8 arguments. So: what error message should Appeal produce if you call it with 5 arguments? Again, currently, it's something inscrutable that mentions "2 arguments are required for the current argument group", which I consider bad UX.

What should the error message be? How about:

% myapp walnut 11 22.5 a b 33
"myapp walnut" doesn't support being run with 5 arguments.
It supports between 2 and 4 arguments, or between 6 and 8 arguments.
[usage goes here]
tusharsadhwani commented 7 months ago

How about we just mention the specific argument group that wasn't able to be satisfied?

In the first case it can be:

$ myapp fgrep someval 42
usage: asd.py fgrep value [i f]
                             ^
The command expected a value for `f`

Similarly for the walnut example:

$ myapp walnut 11 22.5 a b 33
usage: asd.py walnut i f [s [s2]] [i f [s [s2]]]
                                     ^
The command expected a value for `f`

P.S. the walnut example is broken in more than one way right now.

The error log: ```console $ python asd.py walnut 11 22.5 a b 33 Traceback (most recent call last): File "/private/tmp/asd.py", line 10, in app.main() File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 7410, in main processor.main(args, kwargs) File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 7573, in main sys.exit(self(sequence=args, mapping=kwargs)) File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 7563, in __call__ appeal.parse(self) File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 7373, in parse sub_appeal.analyze(processor) File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 7330, in analyze self._analyze_attribute("_global", processor) File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 7321, in _analyze_attribute program = charm_compile_command(self, processor, callable) File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 2890, in charm_compile_command return cc.assemble() File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 2228, in assemble self.program = self.root_a.assemble() File "/private/tmp/venv3/lib/python3.10/site-packages/appeal/__init__.py", line 1945, in assemble raise ConfigurationError(f"label description used twice: '{op.label}'") appeal.ConfigurationError: label description used twice: 'int_float_strs.s: exit after optional argument' ```