Skip to content
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

Variadic arguments to commands #53

Closed
gampleman opened this issue Apr 28, 2012 · 13 comments · Fixed by #277
Closed

Variadic arguments to commands #53

gampleman opened this issue Apr 28, 2012 · 13 comments · Fixed by #277

Comments

@gampleman
Copy link

I would like to be able to specify:

program
  .command("new <NAME> [FILES...]")
  .action(function(name, files) {
    console.log(files);
  });

$ program new cool foo bar bad
>  ["foo", "bar", "baz"]
@tedeh
Copy link

tedeh commented May 2, 2012

You can do that, all arguments are available as an array in program.args.

@gampleman
Copy link
Author

Well I got pretty weird output doing that (actually looks like a bug):

program
  .command("foo [bar...]")
  .action(function() {
    console.log(program.args)
  });

$ program foo bar baz gloo
> [ 'bar',
  { commands: [],
    options: [],
    args: [ [Object] ],
    name: 'foo',
    parent: 
     { commands: [Object],
       options: [Object],
       args: [Circular],
       name: 'program',
       Command: [Function: Command],
       Option: [Function: Option],
       _version: '1.1.0',
       _events: [Object],
       rawArgs: [Object] } },
  'gloo' ]

Seems like baz is replaces by something that looks like a commander object.

millermedeiros added a commit to millermedeiros/commander.js that referenced this issue Nov 5, 2012
Will be easier to handle sub-commands that accepts multiple arguments, like:

    program
      .command("new <NAME> [FILES...]")
      .action(function(name, files) {
        console.log(files);
      })
      .parse(process.argv);

    $ program new cool foo bar bad
    >  ["foo", "bar", "baz"]

Added same functionality for Options as well:

    program
      .option('-f, --files <paths...>', 'file paths')
      .parse(process.argv);
    console.log( program.files );

    $ program -f foo.js bar.js baz.js
    >  ["foo.js", "bar.js", "baz.js"]
@jamlfy
Copy link

jamlfy commented Sep 16, 2013

+1

@scinos
Copy link

scinos commented Nov 23, 2013

+1. This is a must to be able to do things like

$ program --files myFiles/*

@n1cholasv
Copy link

I just encountered this. 2nd arg is overwritten with the program object.

e.g. a simple program
program
.command('load [file]')
.description('load data file')
.action(function(file){
...
}
Then running this:
myscript load file* # 3 files file1, file2. file3

Generates:
program.args[0] is file1
program.args[1] is the program object
program.args[2] is file3

file2 is not in program.args, but still found in rawArgs.

@joshacheson
Copy link

This behaviour is caused by line 247 in index.js (the overwriting of your 2nd argument).

The reason is because you're specifying 1 mandatory argument, and unfortunately the code at line 247 of index.js basically states "If we have n expected arguments (as opposed to no expected arguments), let's put our options (a reference to self, really) at the n index of the arguments array".

The way to solve this would be to either A) fork the library and remove that code for your own specific purpose or B) augment the existing arguments parser with a new syntax to know when variadic arguments are expected (maybe recognizing a pattern like "[*...]" where asterisk is any text. Then, if variadic arguments are known to exist the code at 247 (and in that area) could be changed to act accordingly.

The code that exists now might not seem to make sense, but it does in a case where you are expecting 3 arguments and the user only enters 1. With the way the code is now, where the options is specifically set to args[self._args.length], options should arrive at the expected argument index in your action callback.

By this I mean If you have a command like "dosomething " (so we have 3 expected arguments) and your action callback therefor looks something like

function(arg1, arg2, arg3, options){
  //this is your action callback
}

with the existing code your options value will always come in the expected position, regardless of whether or not the user enters the intended amount of arguments.

It strikes me that a robust solution to this might be difficult... and a less robust solution to this might involve making concessions in quality.

Anyways, I'm gonna think about this when I have more spare time and maybe do a pull request. I've already got a forked copy of this repo with a change I intend to do a pull request for when I have the time.

edit: I'm going to make a note here (for myself or anyone else who might want to implement this feature) that as well as text parsing to recognize variadic arguments, the best thing to do is probably to store the n + 1 ... m (where n is the number of required non-variadic arguments and m is the total length of the arguments array) values in an array. this would mean a command with description

"dosomething [FILES...]"

would call the action callback like so

(function(command, someotherarg, files, options){
  console.log(command) //whatever was passed in the first position
  console.log(someotherarg) // whatever was passed in the second position
  console.log(files) //an array containing every remaining passed argument
  console.log(options) //the this/self object (which contains options)
});

@joshacheson
Copy link

I've augmented my own fork of commander.js to offer a means of having variadic arguments.

Basically if the last argument in a command declaration is something like this

program.command("somecommand <arg1> <arg2> <arg3> [FILES...]"

(Where 'FILES' can be replaced by any text) the last argument will be interpreted to be variadic. What this means for someone parsing variadic arguments is that their .action() callback will receive an array of passed parameters at the same parameter index of the declared variadic argument. In plain english this means that if the variadic argument was the 4th declared argument in your command declaration (as it is in the exmaple above) it will be available at the 4th position in your action callback REGARDLESS of the actual number of arguments passed to your command (for instance if a user omits optional arguments that might make things unpredictable). So the action callback for the above command would be

.action(function(arg1, arg2, arg3, files, options){
  console.log(files instanceof Array) //true in all cases, regardless of number of passed arguments
});

Unfortunately my own fork of commander.js contains augmentations other than variadic arguments, so I can't just submit a pull request for this change on its own simply. I intend to isolate this feature in a new fork and put together a proper pull request (with tests of course). That'll have to wait a bit though as I'm swamped, I just managed to find the time to develop this feature while riding the bus :)

I'm only posting this comment here in case someone comes across it and thinsk there is an issue with the feature i'm proposing, in which case I'm very much open to comments/criticisms.

@whitlockjc
Copy link
Contributor

I am having an issue with this as well. I was able to work around it using this:

#!/usr/bin/env node

'use strict';

var program = require('commander');

program
  .version('1.0');

program
//   .command('info <requiredArg> [optionalArgs...]')
  .command('info')
  .usage('<requiredArg> [optionalArgs...]')
  .description('Display validation results for the Swagger document(s)')
  .action(function () {
    console.log(program.args);
  });

program.parse(process.argv);

I think it's time this gets fixed, time to fork and patch I guess. In the meantime, use the approach above if you're running into this issue.

@whitlockjc
Copy link
Contributor

There's really two problems here:

  • Supporting variadic arguments officially
  • The assumption here that you can just blindly erase an argument

Is .args suppose to be only the known arguments? If so, I can see the second bullet point being moot but I do think if you stick to that, you should probably remove all array entries after the last expected argument so you don't end up with mixed arguments as detailed above.

@whitlockjc
Copy link
Contributor

If any of you have a minute, it would be great to get some eyes on the pull request above. This is a very painful bug and this commit fixes the issue.

@scinos
Copy link

scinos commented Oct 20, 2014

How is the pull request #277 different than #103, or the solution proposed by @joshacheson here: https://github.com/joshacheson/commander.js/commit/6534da626fa254b7b87d88a5460cd7eacee0bf58 ?

IMHO, we need any of the solutions (I don't care which one) integrated with command.js.

@SomeKittens
Copy link
Collaborator

@scinos For one, @whitlockjc's PR is up to date.

Thanks for the PR, we'll take a look at it.

@whitlockjc
Copy link
Contributor

I didn't know about #103, this was the first thing I found and since it was still open, I came here. To answer your question, while our approaches are different, there are a few things I don't see the linked commit/PR handling similarly to me:

Variadic Argument Location

It seems the linked commit/PR will blindly allow something like this: .command('mycommand <variadicArg...> [optionalArg]'). The problem here is that since variadicArg is not the last argument, it will work the same as it does now. My code treats this as an error and will alert you to the problem.

Argument Naming

(Minor nit since these values are not technically exposed.)

This is probably a minor nit but when it comes to the argument names, the ... is not part of the argument name and I handle that while the naming in the linked commit/PR doesn't. So in my code self._args[1].name === 'variadicArg' while in the linked commit/PR, it would be self._args[1].name === 'variadicArg...'.

Both approaches will likely work and neither are likely perfect. If I can help further, let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants