-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
reorder flags so they can come after arguments #1928
Conversation
I did something similar in another repo if you need this: #1583 (comment) |
2d6a54d
to
8b84935
Compare
Some Run tests are failed, please check @fiatjaf |
I don't understand what is failing. Looks like it's test coverage only? I can fix that, but can you tell me first if this change is wanted and would be merged if tests were ok? |
@fiatjaf No one has specifically asked for this feature, so it depends. Also we have persistent flag support, so flags can be defined so that they can be placed anywhere in the chain |
I've linked to two issues above with many people asking for it. I've seen you replied on these issues with that comment about persistent flags, but I don't think it's related. This is about specifying flags after arguments, not after subcommands. Like |
This trips up my team all the time — I’d definitely appreciate the feature! |
@fiatjaf This starts getting complicated. Can you provide more examples of the behaviour ? if you have say
that would be equivalent to
even if say '-s -t' are defined for subcmd1 and not root command. |
Subcommands should not be involved here. So |
I understand the appeal of this but you are interpreting what the user want to achieve. Its is entirely possible that the subcmd wants to handle all the args itself in the command action.
So you need a new boolean field in Command, something like ReorderArgs, which is off by default and can be enabled if a particular user of this library wishes it. Also you have to ensure that the reorder can be applied only to a leaf command and not to root/branch commands which have subcommands. |
checks notes one of the most critically requested features and reason for several projects migrating back to v1 and/or dropping |
https://github.com/urfave/cli/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc Alzo the v1 migration dataset is very interesting to see. |
@abitrolly you don't have to look far #1113 Also see what @fiatjaf referenced? There are several folks (including myself) who had to remigrate to v1. Additionally, several folks there and in other issues (use search) migrated to cobra because of this. I don't know who curates this list but this ordering issue should be on it imho Also, #1331 (comment) "its a long standing feature ask" |
I've opened a feature request here: #1950 Let's go there an upvote it! |
Can confirm, can't move to v2 in any of our project for ergonomic issues. "It's more POSIX compliant" is a very weak arguments, humans are not POSIX compliant. We have a lot of pattern that can be described as
because for CLI user it is easier to just copy front part ( Forcing flags to be at certain position just means unnecesary cursor movement |
I've done manual some testing @fiatjaf and it doesn't seem to be working for flags on sub-commands?
Where both I can't seem to trigger help output for |
That's true. It's broken indeed. I'll try to fix it soon. |
How many utilities you see provide 3-4 level subcmds ? This library has to support that use case so we cannot make choices assuming a certain depth. urfave/cli is more akin to a platform than a cli library. It allows lots of configurations so that people can achieve what they want from a cli library. Just because something is good for one user doesnt mean that others want it. Any changes have to be backward compatible(within major versions) and if it is an optional feature it needs to turned off by default. Interested users should be able to enable it as needed. I have thought about this in the past and felt there needs to be a "personality" setting for the library which configures a set of defaults automatically to provide a given behavior. As an example we would provide say 'ls' personality or 'git' personality and the cli would behave similar to what those utilities do in terms of flags/subcmds in terms of how options are parsed(reorder args) and help output. Additionally users would be able to plugin their own personality engine which would define behaviors expected of the platform. |
e6aa7ca
to
3a8b028
Compare
I made it work for subcommands -- now it seems to be working fine in all my manual tests -- and made the behavior optional. But some test in the suite and I'm having trouble understanding which. When I run |
command.go
Outdated
} | ||
|
||
// argIsFlag checks if an arg is one of our command flags | ||
func argIsFlag(commandFlags []Flag, arg string) (isFlag bool, isBooleanFlag bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func argIsFlag(commandFlags []Flag, arg string) (isFlag bool, isBooleanFlag bool) { | |
func *c *Command) argIsFlag(arg string) (isFlag bool, isBooleanFlag bool) { |
command.go
Outdated
return false, false | ||
} | ||
// this line turns `--flag` into `flag` | ||
if strings.HasPrefix(arg, "--") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if strings.HasPrefix(arg, "--") { | |
arg, _ = strings.CutPrefix(arg, "--") |
command.go
Outdated
arg = strings.Replace(arg, "-", "", 2) | ||
} | ||
// this line turns `-flag` into `flag` | ||
if strings.HasPrefix(arg, "-") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if strings.HasPrefix(arg, "-") { | |
arg, _ = strings.CutPrefix(arg, "-") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why, is this more efficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you dont need the if statement then. if string doesnt start with "-" it becomes a no-op
command.go
Outdated
// this line turns `flag=value` into `flag` | ||
arg = strings.Split(arg, "=")[0] | ||
// look through all the flags, to see if the `arg` is one of our flags | ||
for _, flag := range commandFlags { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to also think about short options handling here. What happens if the user gives multiple options like -fhsk where each of the f, h, s & k are bool flags
command.go
Outdated
@@ -109,6 +109,9 @@ type Command struct { | |||
// single-character bool arguments into one | |||
// i.e. foobar -o -v -> foobar -ov | |||
UseShortOptionHandling bool `json:"useShortOptionHandling"` | |||
// Boolean to enable reordering flags before passing them to the parser | |||
// such that `cli -f <val> <arg>` behaves the same as `cli <arg> -f <val>` | |||
AllowFlagsAfterArguments bool `json:"AllowFlagsAfterArguments"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AllowFlagsAfterArguments bool `json:"AllowFlagsAfterArguments"` | |
AllowFlagsAfterArguments bool `json:"allowFlagsAfterArguments"` |
command.go
Outdated
remainingArgs = append(remainingArgs, tail[i+1:]...) | ||
break | ||
// checks if this is an arg that should be re-ordered | ||
} else if isFlag, isBooleanFlag := argIsFlag(cmd.Flags, arg); isFlag { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this call out to the top and use the return values if all the if/else instead of calling argIsFlag again
reorderedArgs = append(reorderedArgs, arg) | ||
|
||
// if this arg does not contain a "=", then the next index may contain the value for this flag | ||
nextIndexMayContainValue = !strings.Contains(arg, "=") && !isBooleanFlag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if for some reason the value matches the name of a command/subcommand then the logic breaks down right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, if git
has a subcommand remote
but someone calls it with git -C remote
then the correct interpretation should be that remote
is the <path>
value given to -C
, it's a user error.
@fiatjaf Can you add some test cases for this first ? If you want to test locally you can do
that should give you test which failed immediately |
command.go
Outdated
for _, flag := range commandFlags { | ||
for _, key := range flag.Names() { | ||
if key == arg { | ||
_, isBooleanFlag = flag.(*BoolFlag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use the internal boolFlag interface to check if flag is a bool flag.
https://github.com/urfave/cli/blob/main/flag_impl.go#L17C6-L17C14
Working on tests now. |
@fiatjaf One more thing is to check the behavior when ShellCompletion is enabled. Pressing TAB and having the reorder, now sure how that might work. |
One thing I ran into on manual testing latest changes. One example to compare from another tool. You can do |
OK, I'm giving up, sorry. |
@fiatjaf if you'd like us to add this feature please keep the branch open. We can try picking it up later . As you can see it's not that trivial to implement . For a particular use case it's easy but to have it work for all users for different use cases is extremely difficult. That's something we've always struggled with |
We can add "Unsolvable challenges" chapter with examples and clarification to documentation. That will be useful for people from all CLI frameworks to tackle this problem without spending extra too much time on researching these tangled bits. |
This "unsolvable challenge" is implemented in every other CLI framework, it's the number 1 requested feature according to your own link above, and this PR implements it perfectly as far as I know (except for the autocomplete thing which I have no idea of how it works, never managed to get it working on my apps). Honestly, I don't understand why you're so dismissive of such a good feature that everybody wants. It would be a good idea to even sacrifice some of the other features just to get this, but I don't even think that is necessary. I just hope you take a look at it at some point in the future. |
@fiatjaf hey, I am not dismissive. Now that you've created the issue, the link https://github.com/urfave/cli/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc correctly lists it as the most upvoted feature (with my upvote included), but before that there was no data to back up you claim. ) I can confirm that in Python $ cat postarg.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('filename') # positional argument
parser.add_argument('-c', '--count') # option that takes a value
parser.add_argument('-v', '--verbose',
action='store_true') # on/off flag
args = parser.parse_args()
print(args.filename, args.count, args.verbose) $ python postarg.py -v filename -c 4
filename 4 True
$ python postarg.py -c4 -v filename
filename 4 True I personally $ go mod -h
go mod: unknown command
Run 'go help mod' for usage. It could be much better if |
👍 for this feature! I decided to try out urfave/cli, as it had some nice idiomatic patterns compared to cobra. The lack of this feature is a deciding factor that will keep me using cobra. I will always favor the ergonomics of my customers rather than my own ergonomics as a developer. |
@fiatjaf I have a potential fix at github.com:dearchap/cli/pos_args_flags_mix . Want to try it out ? |
Seems to work pretty well for me now! |
What type of PR is this?
What this PR does / why we need it:
This brings back (and improves) the
reorderFlags
function from v1 that allows flags to be specified after arguments and still work. It's a very much requested feature, see #1733 and #976.Which issue(s) this PR fixes:
Fixes #976
Testing
I've done some manual testing.