Getting current command and/or argument a little unclear
moll opened this issue · comments
Hey,
So far seems like a decent lib. At least the autogenerated help texts aren't an ugly mess of unnecessary whitespace they are in koolaid argparsers. :)
Anyways, I'm trying to create a simple CLI with commands.
By calling the various methods on the Argp
instance I've managed to set the necessary option requirements and commands. However, it's a little unclear, how does one get the command or argument name that was processed other than with a big messy if
statement or resorting to process.argv
iterating. I'm trying to keep things sequential rather than use events, too.
Let's say we're trying to emulate Git which can be called in the following way:
git --work-tree=/var/foo stash save --keep-index "Stash name"
How would you get a string stash
for command and string save
for subcommand/argument out of that with Argp
?
In my testing, when setting stash
as a command, the argv
returned object also lists stash
as an empty array besides the save
array. Shouldn't stash
contain all non-option args that come after it so you could at least shift
it and get save
?
I've skimmed the code in argp.js
and it seems to also make an assumption that the command is always in the first position, rather than coming right after global options. Did I read that code correctly?
Thanks!
I need some time to look this. Later I'll say you something.
I've skimmed the code in argp.js and it seems to also make an assumption that the command is always in the first position, rather than coming right after global options. Did I read that code correctly?
The command name must be the first token, so you cannot set global options. I currently don't have time to implement the global vars, but I'll try to add them in a future.
If stash
is a command, save
is an argument and --keep-index "Stash name"
is an option of this command, then:
var argv = require ("./lib").createParser ({ once: true })
.main ()
.body ()
...
.command ("stash")
.on ("end", stash)
.body ()
.argument ("save")
.option ({ long: "keep-index", metavar: "STR" })
.argv ();
function stash (argv){
console.log (argv);
//{ stash: [], 'keep-index': 'stash name', save: true }
}
stash
is an empty array because was defined with no trailing (.command ("stash", { trailing: { ... } })
).
save
is a boolean. All the arguments are booleans and are just like boolean options.
keep-index
is a string.
If you're using commands, I recommend to use the end
event. This allows you avoid that "big messy if statement". Events are good. Use them. They allow to do complex things in an easier manner.
You don't need to shift anything. If you want to check whether save
is set, inside the end
callback put this:
if (argv.save){
//"save" was set
}
Thanks for the reply. I finally understood on my own too that arguments are actually like options. Those are weird. I thought they were like subcommands. :) But that explains why they're booleans in the end object.
I think arguments are not a great fit for subcommands unfortunately. When you have more of them — like Git's stash
has list
, show
, drop
— you can't have them be set all at once (which boolean arguments would allow) nor would you get the argument name easily and would still have to have an if
-slide. How else would you know which one of those subcommands was called?
I don't know if Argp supports nesting commands, but the easier solution I went with was to collect arguments to an array. Argp is a little inconvenient to use because of this extra necessary boilerplate, but it's better than nothing. :-)
_.invoke(cmds, "sort")
_.invoke(cmds, "allowUndefinedArguments")
_.invoke(cmds, "on", "start", function(argv) { argv._ = [] })
_.invoke(cmds, "on", "argument", function(argv, arg, ignore) {
What is the stash
property in the returned argv
object supposed to contain? I asked about this earlier, too.
Yeah, events are great for decoupling, async code and handling different responsibilities. In this case I find them to be the wrong solution. If one can do things sequentially, one should. It's far easier to reason about as argument parsing is already synchronous.
Cheers
Well, I agree with you that the commands and arguments are a bit unclear. I had the same feeling while I was writing the library. I clearly need to refactor the behaviour of commands, but as I said before, I currently have no time. This will be the first thing I'll revisit.
But I still think that the evented system allows you to do very complex things with no pain (https://github.com/gagle/node-tftp/blob/master/bin/ntftp.js), and afaik this is the only lib that parses command line options and has an event system (and generates nice help messages).
What is the stash property in the returned argv object supposed to contain? I asked about this earlier, too.
Suppose you have this definition:
//app.js
.command ("foo", { trailing: { eq: 2 } })
If I do:
$ node app.js foo
I get an error:
app foo: Command 'foo' expects 2 argument/s
Try 'app foo --usage' for more information
If I do:
$ node app.js foo 1 2
I get:
{ foo: [ 1, 2 ] }
Each command has an array and each element of this array is a trailing argument.
How else would you know which one of those subcommands was called?
You cannot define "subcommands". This is another feature that can be added. For example, this would be nice to have:
.command ("stash")
.command ("clear")
.command ("create")
.command ("branch", { trailing: { min: 1, max: 2 } })
.command ("drop", { trailing: { max: 1 } })
.body ()
.option ({ short: "q", long: "quiet" })
For example, right now you cannot define this usage: git stash drop -q
, because stash
and drop
are commands. You can define that stash
is a command and drop
and argument of stash
but this is not what you want because the q|quiet
option would be an option of stash
.