-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Discussed in #1974
Originally posted by GwynethLlewelyn October 1, 2024
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 git approach, 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, and convert. 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, convert has an additional (optional) flag, -g, which allows the filetype to be specified (strictly speaking, this might not be needed). That works well, so
./mytool convert -g webp test.jpg test.webp
./mytool convert test.jpg test.webpwork, because webp is the default image format to convert to.
Similarly, the resize option has its own set of flags to set height and width, both of which are fine as well (compress doesn'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!
./mytool convert test.jpg test.png -g pngUh-oh. What happens now is that the -g flag is never 'seen' by convert. 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 to webp, 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 -d increases 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 -g at 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:
./mytool convert test.jog test.png -g png -dOk. 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 -d cannot be at the end. Let's try this:
$ ./mytool convert -d test.jpg test.png -g png
2024/10/01 21:09:47 flag provided but not defined: -dUh oh — say what?! What is this error?!
"Let me see," the user thinks. "Maybe -d is not the right flag, let's look at help..." And they type:
$ ./mytool convert -h test.jpg test.png -g png
2024/10/01 21:11:30 No help topic for 'test.jpg'. Did you mean "help"?
$ ./mytool convert help test.jpg test.png -g png
2024/10/01 21:11:35 No help topic for 'test.jpg'. Did you mean "help"?
$ ./mytool convert --help test.jpg test.png -g png
2024/10/01 21:11:42 No help topic for 'test.jpg'. Did you mean "help"?Arrrrrrgh! And, in despair:
$ ./mytool convert --help
NAME:
mytool convert - Convert between image types
USAGE:
mytool convert [command options]
DESCRIPTION:
You can use the API to convert your images to your desired image type. `mytool` currently supports converting between WebP, JPEG, and PNG. When you provide more than one image type in your convert request, the smallest version will be returned to you.
Image converting will count as one additional compression.
OPTIONS:
--image-type type, -g type [ --image-type type, -g type ] Valid image types are: `webp`, `png`, `jpg` (default: "webp")
--help, -h show help... 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, or gpg, or even go (to an extent... go also 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 -i for the input file and -o for the output file), such as in the following:
./mytool -d -k agh -i file1 convertAlthough 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 compress as default, this also means that omitting the command will, indeed, do the compress action.
But what if someone mistakenly adds a typo?
./mytool -d -k agh -i test.jpg convretI would expect one of the two to happen:
- It detects that there is no valid command, so defaults to
compresswhich is set as the default; or - It (correctly) identifies this as an invalid (sub)command and throws an error, shows the help, or perhaps even both.
Instead, none of the above happens.
What happens was tricky for me to debug. With an invalid command, what gets called instead is the Flags Action handler (!). And to be even more devious: the filename is set to test.jpg (since that comes from the flag level), but the convret typo 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 the convret file 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!