Can we use Commands *and* (global) Flags? What about a 'default' command? #1974
Replies: 3 comments 13 replies
-
|
Oookay. So this is the tool. usage: tool convert [-g gype] <filein> <fileout>And this works. tool convert -g webp test.jpg test.webp
tool convert test.jpg test.webpBut flag at the end doesn't work. tool convert test.jpg test.png -g pngWithout example in https://github.com/urfave/cli/tree/main/examples, no "flag at the end" in the docs https://cli.urfave.org/v3/examples/flags/ and no "flag in the end" issues https://github.com/urfave/cli/issues?q=is%3Aissue%20flag%20in%20the%20end%20in%3Atitle%20 and no code to run, I can not inspect how and if it supposed to work and in which v. |
Beta Was this translation helpful? Give feedback.
-
|
Correct me, if I'm wrong, but if you "like the Looking at git's man page I see: So the global flags clearly are before the command. Looking at |
Beta Was this translation helpful? Give feedback.
-
|
Gosh, I'm getting old, since, a year later, I was going to ask exactly the same thing, and even raise an issue... but then, like any well-behaved netizen, I searched around for a bit more, and re-found this discussion from last year. Well... a year has passed, I've moved on to urfave/cli v3, and the issue sort of persists, but with a few catches. I realise that I gave Then we have the likes of ImageMagick and Instead, I should have given as a better example Soooooo back to my original question... In the context of The flags defined there seem to propagate to commands and subcommands as well, that is, you might have some flags that are valid across the whole command/subcommands tree structure. Next, I thought (the manual implicitly says so) that we'd had the arguments and flags defined at the 'root' level. These I assumed (erroneously) that they would be available on all levels below 'root'. And, indeed, this seems to be the case with, say, my general-purpose flag to change the debug level: it was defined at the 'root' level, but it seems to work on the other levels as well (to be confirmed). The other thing are the arguments. Here, my main issue was having a command by default at the 'root' level. In that specific case, what happens is that the arguments get wiped out (i.e., they show up empty — they get 'consumed' by the default handler, and, once that command is called, it has no arguments left). This can be reproduced easily, and I would like to consider that it's not such an 'edge case'. One way of getting them back is, of course, to re-define them at each command (and subcommand). In my case, I just define a Possibly this is a very inefficient way of doing this, but, in general, on CLIs I don't worry too much on the resources (time, space...) consumed during the parsing phase, especially if the CLI is not interactive, but one-shot. Purists will claim that the parsing inefficiency will become a pain if you're calling your application on a tight loop, driven by a shell script that reads a directory with a million entries. In that case, I'd have to figure out if it's worth an optimisation or not (after all, the compiler might already be using pointers to the structure, which gets allocated only once — assuming, that is, that this structure remains immutable and does not get expanded later with more arguments...). Doing this with flags is a bit trickier (since different commands will probably have different sets of flags; however, some might be common to all commands), but, as said, I haven't encountered that issue with flags, only with arguments. As said, flags defined at the 'root' level, at least so far as I've tested, seem to behave as if they're 'global'. That said, I continue to have the issue with the command set as default: if the command is omitted, the correct default command is called, but the arguments are now wrong — in a subtle way. To be sure that it's just Consider this example: $ tool convert test.jpg test.png -g pngThat works now (under v3.6.1), so there is no problem with the 'flag at the end', even if it's a flag defined at the 'root' level. But assuming that "convert" is set as the default command, and that $ tool test.jpg test.png -g png
error: could not find `test.png` for openingSo. what happened here is that the pointer for 'next element on the command line' (or whoever this is actually been implemented) is not advancing correctly. On a first pass, the first token is retrieved from the command line: Under v3.6.0 and v3.6.1, this is quite consistent, i.e. $ tool -g png --lots-of-more-flags-here -a="some might be written with an equal sign" test.jpg test.png
error: could not find `test.png` for opening
Note that an amusing side effect of the default command is that anything that wasn't recognised as a valid command will be accepted, but in any of those cases, this 'bug' is not present (i.e., the token for the invalid command will be consumed as expected, leaving the arguments in the right position to be retrieved correctly). Finally, this is suspiciously similar to what happens in #2223 — but that issue is about empty flags, while this one is about empty commands. This is where the setup seems to be done for the Line 337 in 41334d4 The parsing logic seems to stem from here: Line 266 in 2240690 It's not quite obvious where the issue is. My best guess is that there is something that happens here, when the current command under consideration is called with the default command instead: Line 273 in 2240690 Possibly when we reach this point with an empty command on the line (which would be the case) and run Oh, and for the sake of completude, I'm trying to fix this fork of mine. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi there! 👋
I was actually a bit surprised that nobody asked this before...
I'm using the latest version of v2, even though I'm quite eager to "upgrade" to v3... as soon as at least some documentation is finished 😁
So, here is my apparently simple question, which, however, I'm not able to implement in a reasonable way.
I'm trying to do a tool that will perform some operations on images. Because I like the
gitapproach, I thought that it would be nice to have some commands (some of which have their own specific flags), as well as some global flags, which all commands can use.So consider the commands
compress,resize, andconvert. All can optionally take up to two arguments, i.e. the filename of an existing image, and the output filename. That's the part that works.Some commands need to have a few extra flags. For instance,
converthas an additional (optional) flag,-g, which allows the filetype to be specified (strictly speaking, this might not be needed). That works well, sowork, because
webpis the default image format to convert to.Similarly, the
resizeoption has its own set of flags to set height and width, both of which are fine as well (compressdoesn't have any flags, I think, but it may in the future).So far, so good. I can confirm this on the
Action:for each command, and check what it got, and it's as expected.Now, the problem starts when the flag is actually at the end!
Uh-oh. What happens now is that the
-gflag is never 'seen' byconvert. Instead, the command reports that it has four arguments, not two. And since only the two first are acknowledged by the code, well, this sort of works but... the image will be converted towebp, since that's the default anyway.This can still be sort-of-fixed by changing the help to say, "flags before filenames" or something to the effect.
But now I have another issue. What about the global flags?
For the moment, I have two crucial global flags defined:
-d(for the debugging level, multiple-dincreases that), and-k. The latter is, of course, an API key (which can also be retrieved via the environment). Suppose that we'd tried the previous command with the-gat the end, and we became confused, because we expected a PNG image, and we got a WebP instead. What's going on? Let's increase the debug level:Ok. So, this will give us zero errors. The image is still a WebP! Weird!
Maybe at some point the user figures out, well, probably the
-dcannot be at the end. Let's try this:Uh oh — say what?! What is this error?!
"Let me see," the user thinks. "Maybe
-dis not the right flag, let's look at help..." And they type:Arrrrrrgh! And, in despair:
... which gives a very useful description, sure, but a very wrong one.
All right, I can correct the texts, that's not a problem. But the above example show that this is not how commands and subcommands work on, say,
git, orgpg, or evengo(to an extent...goalso has its quirks).Because the problem comes when you've got tons of global flags to parse — they must all come before the command (which is awkward and not intuitive), except for the flags that are specific to that subcommand — these must come after the command, which, well, is just a recipe for trouble. Trouble when answering support requests, that is.
To make matters even slightly worse... where do I actually place the globals?
Consider the following. Besides having filenames as arguments (which is a 'normal' way of expressing things), sometimes it might be more useful to, say, use them as parameters passed by flags (I use
-ifor the input file and-ofor the output file), such as in the following:Although I dislike having the command at the end of everything, instead of at the start, like a 'normal' git-like CLI is supposed to work, the command above is fine, and works exactly as expected.
Since I set
compressas default, this also means that omitting the command will, indeed, do thecompressaction.But what if someone mistakenly adds a typo?
I would expect one of the two to happen:
compresswhich is set as the default; orInstead, none of the above happens.
What happens was tricky for me to debug. With an invalid command, what gets called instead is the Flags
Actionhandler (!). And to be even more devious: the filename is set totest.jpg(since that comes from the flag level), but theconvrettypo is assumed to be the first argument passed to the command line (which, if you think a bit about it, it makes sense from the perspective of the parser). Since the first argument, by definition, is the input filename, it overrides whatever comes after-i(thus, exactly the opposite of what one would expect), but instead tries to open theconvretfile instead, which of course doesn't exist and throws a strange error...Eventually, one might argue that this last error will make the user double-check on their command line and find the typo. But it would be nice to catch it much earlier on!
That said...
If I understand things correctly, mixing & matching Flags and Commands is Not A Good Idea™, right?
I believe I'll simply add flags instead of commands, and avoid the mess. Perhaps this is something that will get changed under v3?... it would certainly be nice!
Beta Was this translation helpful? Give feedback.
All reactions