New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--help isn't discoverable / cannot give --help after task names, only before #205

Closed
dlanger opened this Issue Jan 13, 2015 · 15 comments

Comments

Projects
None yet
7 participants
@dlanger

dlanger commented Jan 13, 2015

I have a number of tools built on invoke that take a number of arguments, and these two things still trip me up when I forget them:

  1. That the way to access the help on a task is inv --help taskname (as opposed to most *nix utiliies, where I could do inv taskname --help)
  2. That when I get the parameters wrong, invoke tells me 'taskname' did not receive all required positional arguments!, as opposed to showing me the help output to aid in figuring out what I forgot.

For the former, my ideal way it would work is that if a someone does inv taskname --help and help isn't an argument to the task, we show the help and exit (if it is, we treat it like any other argument). I'm thinking this would be in the Executor - is that a good place to start?

For the second, I have an idea for a PR, but it would require passing the collection to the Parser instance (to get access to the docstring). Would this be acceptable, or does that break the separation of concerns you had in mind?

@bitprophet

This comment has been minimized.

Member

bitprophet commented Jan 28, 2015

The "prefix --help" has definitely been a sore spot. As you've noticed it's due to the "per-task args" parsing design, but I could be persuaded to merge "if it doesn't clash w/ actual task arg, have suffixed --help behave like the core --help flag" functionality.

It could confuse the hell out of anybody who does define def mytask(c, help) (or rather - anybody using Invoke w/ that tasks collection and not expecting it) but I'm guessing that population is lower than the population expecting inv sometask --help to work.

Re: separation of concerns for number 2, we already create Parser with a set of ParserContext objs generated from the collection - do you need more info than is in those for this?

@matleh

This comment has been minimized.

matleh commented Mar 10, 2015

+1

@alexsavio

This comment has been minimized.

alexsavio commented Mar 14, 2016

Any update on this issue?

@nhoening

This comment has been minimized.

Contributor

nhoening commented Nov 4, 2016

I've also run into this little UX problem. However, my first expectation was different. Invoke-based CLIs are tools with subtasks to me - a well-known example being git, but there are others. So I was thinking of getting help like this:

git help diff

invoke help <task>

I believe this behaviour would model better what invoke-based CLIs are but I'm not sure how hard it is to bend the parser to achieve this.

@bitprophet

This comment has been minimized.

Member

bitprophet commented Nov 14, 2016

@nhoening That's definitely a common pattern but it's one I've resisted in the past - for tools like git the set of subcommands is largely "static" but for Invoke (or Make or etc) the entire point is for that set to be user-defined and highly dynamic.

In that case, the core program itself automatically injecting entries into the set of tasks/subcommands - entries that the user didn't themselves define - is "magic" and in my experience it's best to avoid magical behavior when reasonable alternatives exist.

All that said, this is the same argument for not injecting --help after task names, so my earlier counter-argument stands: the number of users who will be unpleasantly surprised by the magic, is probably much lower than the number expecting Invoke to work like other tools they've encountered.

And, in this particular case the total frustration level for the former group should be low, vs other "magic" features like Fabric 1's automatic slash/dollar escaping.

Especially since we can always throw exceptions if users accidentally 'shadow' the magic flag/task (i.e. def help() or def foo(help=xxx)) which eliminates one of the most frustrating corner cases that comes up when one has magic around.

@bitprophet bitprophet added this to the 1.0 milestone Nov 14, 2016

@bitprophet bitprophet changed the title from --help isn't discoverable to --help isn't discoverable / cannot give --help after task names, only before Apr 22, 2017

@bitprophet

This comment has been minimized.

Member

bitprophet commented May 8, 2017

As noted in the backlink from fabric/fabric#1594, this core functionality in the parser is needed for two similar but distinct things:

  • honoring flags considered part of the 'core' parsing context, appearing after task names (i.e. in a space currently considered part of 'per-task' parsing contexts).
    • Main example is --help but I could see it possibly extending to others, if desired, since the parser-level changes required would presumably allow us to honor arbitrary flags in this way.
    • MUCH LATER EDIT: there's also a distinction between per-task help and truly global core flags that might want to flexibly come anywhere in the command line, e.g. --no-dedupe or --list or something.
  • adding truly per-task flags whose values are interpreted & stored on a per-task basis, but which are not natively part of individual tasks' parser contexts (i.e. which aren't in their function signature or the corresponding state in raw Task objects.)
    • Just occurred to me while writing this out, this may not be exactly the same as --help, because it could be implemented as a manipulation of the ParserContext creation process (& interpretation of the post-parse results of same for generating data used by Executor) and so for purposes of the actual parse & immediate post-parse step, there would be no change required.
    • The salient difference comes down to whether a flag being "magically added" to all task contexts, is relevant to core args and the immediate post-parse step (like --help, which changes overall behavior from "execute things" to "stop and show help") or if it only affects the behavior of Executor and/or Context/task bodies (like --roles or things like a per-task scope of stuff like --echo perhaps).

Could potentially do both the same way (and also possibly avoid part of the implementation unpleasantness that prevented this from being done originally) by temporarily granting these special flags to per-task parser contexts, then stripping the "core-only" ones like --help back out before returning. Lets us ride on the natural order of things without polluting the final result.

(A downside [though it may be a problem regardless of overall approach] is when the per-task parsing step would otherwise fail, e.g. a task requiring a positional argument, or a user giving --help alongside incorrect flags, since "just slap --help onto any invocation to get help" is a powerful feature to have. Trusting --help/etc to be treated as "just part of the regular process" makes it hard to guard against these sorts of things.)


It's been a while since I had my hands in the guts of the parser so I'll start by writing the overall tests for this, then trying various PoC implementations.

@bitprophet

This comment has been minimized.

Member

bitprophet commented May 8, 2017

Another random thing that just occurred to me: while accidental overlap between global --help and per-task, really-defined-by-user --help is unlikely, it seems much more likely that a user might have some task arg represented by the shortflag -h. So this feature may also require a tweak to the shortflag generation stuff such that it (perhaps just hardcoded-ly) acts like the h shortflag is always "taken" and will never be selected.

Kinda stinks since it will still surprise a user expecting def mytask(height=5): to be invokable as mytask -h 7 (it'll end up being eg. mytask -e 7). Possibly an argument for only having the 'long' version of the flag globally applied? (But I can see that still tripping people up...)

The Problem With Magic™

@ploxiln

This comment has been minimized.

ploxiln commented May 8, 2017

A compromise idea to help with discoverability: change the error message to something like:

'taskname' did not receive all required positional arguments! did you mean inv --help taskname?

@bitprophet

This comment has been minimized.

Member

bitprophet commented May 8, 2017

@ploxiln well ideally I'd like to solve this so that's not necessary and the parser is made smart enough to go "oh, inv takes-a-positional-arg --help? well I see the existence of --help so even tho I got no posarg I'll just ignore that". Pretty sure it's possible, just needs to be something I'm aware of and not ignoring by focusing solely on the base case :)

@bitprophet

This comment has been minimized.

Member

bitprophet commented May 22, 2017

Dumping some notes to self:

  • First approach was to update the parser such that it checks for initial-context flags during regular-context parsing, as a near-final step, in the heart of the parser which is a big ol' if/elsif/.../else construct.
    • As a side effect, this would have the property that ambiguous flags (ones existing in both core and per-task parse contexts) don't except but simply have the per-task version "win". Not ideal but OK for now?
  • Unfortunately this fell down because...we're not doing a single parse step with initial-vs-other contexts, but a two-step parsing, one with only initial, and the other with only the task contexts.
    • Seems clear that the current layout is legacy from before we added the ability to runtime-select where your tasks come from.
    • I think this means we can rework the Parser API somewhat (unless something else is still using it in a initial+tasks fashion?! EDIT: yes, the shell completion engine uses it that way still) so it's a single object whose state gets updated halfway, instead of two separate objects.
    • Alternately, keep the current high level design, but allow passing in a "core flags context that is not actually loaded up as the first context" option. Feels a bit grody tho.
  • Another option is to try the "just add some core context flags to the per-task contexts, then strip them back out at the end" approach.
    • This would work for --help specifically, but only because it sets optional=True allowing it to be legally parsed w/o an attached value. If we wanted this functionality for any value-required flags (say, --config=<path> and especially, --roles/--hosts) they would cause parser errors when used in this mode.
@bitprophet

This comment has been minimized.

Member

bitprophet commented May 22, 2017

Leaning towards the "just make it one parser that can be paused/updated/started again" idea, it feels conceptually cleaner and hopefully won't require a lot of hackery.

Currently, pseudocode:

result = Parser(initial, ignore_unknown=True).parse_argv(argv)
core_args = result[0]
contexts = get_task_contexts_from(core.collection)
context_args = Parser(contexts).parse_argv(result.unparsed)

Note how the 2nd parser has no info about the initial context, which is now bad.

I believe instead we can:

parser = Parser(initial, pause_on_unknown=True)
result = parser.parse_argv(argv)
core_args = result[0]
contexts = get_task_contexts_from(core.collection)
parser.contexts = contexts
context_args = parser.continue()
# Or perhaps just parser.continue_with(contexts)

The subtle difference being the continuation of a single Parser object which gets the contexts loaded up partway. It will retain its original initial context handle, and will also know where it left off in the tokenized argv.

@bitprophet

This comment has been minimized.

Member

bitprophet commented May 24, 2017

Got this working; it's not super pretty but it's not super awful either. It will technically work for other 'core' flags but I haven't tested them out yet.

@dmulter

This comment has been minimized.

dmulter commented Jul 8, 2017

Did the second part of the original issue ('taskname' did not receive all required positional arguments!) get lost? I think it would be very useful if the help was also displayed for any task that generates this error due to lack of parameters. As it is currently, every time I get the lack of args error, I then immediately type the command again with --help.

@bitprophet

This comment has been minimized.

Member

bitprophet commented Jul 10, 2017

I think that 2nd part is covered under #449.

@dmulter

This comment has been minimized.

dmulter commented Jul 10, 2017

sure does, thx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment