gagle / node-argp

Command-line option parser.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.