-
Notifications
You must be signed in to change notification settings - Fork 365
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
CLI task arguments #1
Comments
Iteration time:
Still reading over argparse to see just what it gives us -- would prefer to work within it instead of doing something totally custom (which would defeat one of the reasons to go with flags anyways.) |
Looks like argparse can't do interleaved "positional arguments" (i.e. task names) and options (flags), by default. May be able to hack something together with one of:
|
Custom action objects seem to only work if we knew beforehand how many "options" (non-flag values) were going to be passed in (as we'd need to call Though perhaps we could simply add all discovered tasks explicitly, as optional or something, to the parser. Slightly silly but might get the job done. Will triple check this approach and how Part of me wants to use PyParsing for that, but not sure it's worth the dependency. |
Right. Preprocessing isn't going to work because it's impossible to distinguish a non-equals-signed flag argument ( We could try the custom-action route linked above, relying on how the actions get called in order to reconstruct values -- this gives us all the "per argument/flag" parsing (
Theoretically we could go whole hog and just say "fuck it" to argparse and use PyParsing instead, but I'll only do that if bending argparse to my will fails or turns into a huge mess-o-code. And if I still think it's worth it to not use Fab-style tasks. |
|
@kennethreitz had one new spin on the "custom syntax to denote tasks apart" solution, which was to 'decorate' the task names in a custom manner, e.g. Benefits: aesthetically pleasing (much nicer than Drawbacks: falls into the "custom, non-traditional cli syntax" trap, possibly more confusing to use (what should happen if the colons are omitted? what does If we take "it's not going to look exactly like traditional argparse style" for granted, then we could also return to the idea of using some non-special-shell-meaning character to delimit task+arg chunks. E.g. |
Wrote up a shit ton of thoughts on this yesterday/today: http://docs.pyinvoke.org/en/latest/concepts/cli.html For now, I am leaning towards the |
I think this could be done by chunking up My initial thought is something like:
It wouldn't be pretty and requires at least two trips through |
FWIW, I'll try taking a stab at the parser and see what I can come up with. |
Thanks, I knew I'd forgotten at least one of the alternative approaches. Just added to the doc. I'm not a fan of forcing users to do this, I feel it's too much of a departure from the "orthodox" style people are used to, even though it would simplify parsing. (Rather, it departs from regular argparse style while looking like it, which is IMO more confusing than being obviously something custom like Fab 1.x style.) Re: the rest of your comment: if we went the "task names have no special decorations" route, we'd pretty much have to A) obtain knowledge of which flags need arguments and B) walk over I think it's quite possible, but we can't use I think the only use of |
👍 |
@tswicegood BTW I was hoping to do at least a proof of concept parser myself tonight / this weekend =( probably trying the colon-decorated approach first FWIW. |
@myusuf3 (or any other users) do you have any thoughts to share? Specifically, choosing between this style:
where you can't do positional arguments, but the task names have no special formatting; and this style:
where you have to do the funky Does the 2nd one look OK, dumb, other? Would you be sad to lose positional arguments if we did the first approach? etc. |
@bitprophet I like the second one better simply because you need to have positional args. It looks kind of weird when you first look at it; but I can see it easily becoming second nature after a bit of use. maybe embracing something like if majority still finds it funky looking. |
I've been doing some work in a CLI framework where I want to discover subcommands implemented in plugins, without loading the entire plugin set into the process space just to determine the names of the commands. I found optparse's ability to stop when it encountered a non-flag argument (a value that did not start with "-") helpful. Maybe you should be using that instead of argparse? https://github.com/dhellmann/cliff/blob/master/cliff/app.py#L36 |
@myusuf3 Well, the problem with characters like pipes ( Though that brings me to another observation I made today, when we remove flags/args from the equation, these 'decorated't tasks look kind of silly:
It would probably be possible to have context awareness so that the colons may be omitted, but that might complicate both parsing and usability. @dhellmann I thought argparse was a superset of optparse's functionality? (also, I just got bitten by github defaulting to master -- I think you meant to link to this version of the file?) But if it works the way I think you're saying, it might be possible to run optparse or argparse in a loop and get the sort of result we need here. I'll double check the differences between the two libs, I'm 99% sure argparse just wasn't able to do what I needed, but it's possible I missed something or optparse really does work differently. Thanks for the tip! |
As long as it only pays attention to trailing colons, it should be alright. I can't think of any scenario where someone would want them. If they really want to get around it, they can just inspect |
@bitprophet Oops, yes, I should have linked to the specific file since I had changes to push. :-) argparse is not a strict superset of optparse. It does not, for example, include the ability to do what you want here. The method parse_known_args() has slightly different semantics, and there is no disable_interspersed_args() as in optparse. With disable_interspersed_args() set, when you encounter: task1 --opt value --opt value task2 --opt value --opt value optparse will consume "task1 --opt value --opt value" and give you "task2 --opt value --opt value" in a list of arguments it did not consume. So it is very easy to run a loop, as you describe, and you don't need any special marker characters to tell the parser when to stop. That said, it would make it awkward to use positional arguments, because they would all need to appear before the options for a given task. task1 positional --opt value --opt value task2 positional positional --opt value --opt value |
I really do like the fact that it just results in a list of arguments that it didn't consume which you could iterate over till its completely exhausted. @dhellmann the positional arguments before the options is awkward, but compiling things with gcc has made me accustomed to that syntax. The problem is that you would still need to look for tasks names; which is burden on maintainers. |
I've written up a quick example of what I was talking about last night and posted it here: https://gist.github.com/2437861 With this style, each task would get a subset of args to parse. This does have the problem of arguments for one task that are named the same as a task that you intend to invoke. I'm ok with that as it can be worked around by using I'm -1 on the |
I like special syntax with colons more because options written as
looks like regular argparse/getopt arguments but have non-standard meaning (and imho this is worse than non-standard syntax + non-standard meaning). |
@kmike for those of us on BSD, we're already used to having to pay attention to how we place args anyhow. :-) |
I'm going to write tests so we can put parser solutions through their paces (or at least so edge/corner cases can be documented.) Would have to tweak to handle colon based vs regular style, but no big. @kmike We can't have 100% "normal" invocation, it doesn't mesh with the idea of multiple tasks. But having an otherwise normal syntax with an "order matters" limitation -- a la BSD as Travis points out, which isn't uncommon -- may be the next best thing. What's important to folks unhappy with Fabric 1.x's custom style, is that they can use flags and flag args: I agree that "Y looks like X but has subtle and frustrating differences" can be a killer, and that's why I don't want to cut corners in the flag parsing itself, e.g. to make @tswicegood Thanks for hacking on this. I acknowledge that We could apply the "deep knowledge" stuff I mentioned earlier: encounter potential task name; look at preceding flag, if any; ask currently-being-parsed task if its spec for that flag says it should take an argument; if so, treat the string as an argument and keep going; if not, it's a task name. That sort of thing. @dhellmann Re: optparse, my worry now is that even with a stop-start approach as described,
and invocation:
Optparse, upon encountering I think that's moot anyways because The obvious resolution (enforce globally unique kwarg names) feels unacceptable to me. |
@bitprophet What is the desired outcome. Do you want to be able to parse this:
and get:
I think to do that you need a
That kind of recognition means we're writing our own
That's not as pretty as the "task names and options can't be the same w/o an equal sign" code I had above, but it could be made to work. That said, I do like the idea of the task being the final arbiter of what it can and can't handle. Assuming |
@tswicegood yes, that's what I've been referring to as the "knowledge" approach, as per the first bullet point here (linked it on Twitter over the weekend, not sure if you saw it though, think I cc'd you). Yea, it means we have to reinvent parts of optparse/argparse, which is unfortunate but I don't see a great way around it, and it would tie in with what we'd have to do re: implicit-or-explicit type casting as well. As the other thoughts on that page highlight, the other main approach is to have an unambiguous "this is a task identifier" schema instead, which would let us break apart the invocation and then theoretically use actual-factual arg/optparse to handle the individual per-task chunks. That's @kennethreitz's idea with the trailing colon syntax. |
@bitprophet It would be nice if this could be developed as an enhancement to argparse that went back into the stdlib. |
@dhellmann +1, but I think we should play with it as a module inside invoke or maybe even a project by itself. The biggest problem (at least that I can think of right now) is how do you generically add something like the |
@dhellmann I'm not sure it would work -- this behavior strikes me as a fundamental change to the argparse/optparse model's core assumptions re CLI apps, and AFAIK would need to be implemented as a "make everything behave differently!" switch. But if I'm wrong and there's a way to make it fit, I'd certainly be +1. I don't have the energy to try developing/forcing a sea change in a stdlib module, especially since I need these features for 2.6+ (and sooner instead of later), but I am in favor of the idea at least. As per @tswicegood my plan is to have this be part of Invoke or maybe even another split-off lib that basically says "I'll do sorta argparse-like stuff for your CLI, but with this different focus." Reconciling that with argparse proper could run on its own schedule. |
@bitprophet It seems like you just need a way to say "this group of arguments may repeat and should be processed separately". Something similar to the mutually exclusive group, but for repetition instead. |
@dhellmann Oh yea, I guess that's true if we assumed somebody wanting this sort of multi task approach was going to use that "allow duplicate flags" feature in tandem with the "progressive consumption" approach discussed earlier. I still don't have the time to dive into their code now, but it's definitely something to think about for later. |
Have been bad about linking work to this ticket, but have some basic work done at this point, test-driven, and now using the Fluidity state machine library to help with the actual parse loop (a literal for loop was too brittle re: cleaning up trailing state.) Working: boolean flags, flags that take args, flag default values, multiple tasks/contexts per invoke. Not working: |
Really at the crux of it now. Stuff being dealt with:
|
Got that working, then realized it poses a problem: right now Arguments have N names, intended to be potential parser matches, via a dict interface in the parent Context object (e.g. So they need to be exactly what is seen on the CLI -- and so the test I just committed creates an Argument named Unfortunately, this same dict interface -- Clear answer seems to be for the Context to expose a slightly different API to the Parser than it does to the clients of the parser result. What exactly that should look like, I'm not sure. Alternately, we could simply add more aliases to Context's dict, so that it has both Brainstorming:
|
Sweet, got that working, went with Two more rough spots in the API and I think we can close this:
|
First bullet point done, plus related updates such that Argument Re: the latter, I think None is a decent default value for the time being -- can change later if real world use dictates it. |
Fleshed out more tests. Ran into one that should already work, but doesn't: This is in the integration level tests that use a Collection, its Pretty sure I have passing tests in Parser that handle multiple Contexts. On the other hand, I verified that this Collection's After this, an area that has not been accounted for yet in the parsing (uh oh!) -- non-space short flags, and combo short flags. The parse engine up til now has assumed space delimited tokens so this will be an interesting challenge. |
This is all set, what's left are some smaller skipped tests I'll fill in as we go. |
See also: Fabric #69
Problem
Need to allow parameterization of tasks as invoked via the CLI (tasks invoked via Python are simply passing regular args/kwargs or their list/dict counterparts.) Sub-problem: translating strings/flag existence into Python types such as lists, booleans etc.
Current implementation in Fabric is
fab taskname:positional_arg_value,keyword_arg=kwarg_value,...
. Some other tools use "regular" CLI flags, e.g.paver taskname -F value
."Custom"/"attached" args
Pluses:
Minuses:
"Regular" flag args
Pluses:
Minuses:
invoke task1 --arg1 --next task2 --arg2
(again, can argparse do this?)-c
/--collection
--foo
flag and a user declaresmytask(foo='bar')
, and then we unknowingly add--foo
to core in 1.2, that user's code will break.Potential solutions
make
uses shell environment variables, which is similar to how Thor treats flags -- every task is going to get the same values for any overlapping names.taskname[args, here]
instead oftaskname:args,here
. I find this actually worse than the colon we use, because some shells like zsh treat square-brackets specially and need extra escaping/quoting.The text was updated successfully, but these errors were encountered: