Unnamed parameter with commander.js (positional arguments)
Asked Answered
N

3

22

I am currently taking a look at commander.js as I want to implement a CLI using Node.js.

Using named parameters is easy, as the example of a "pizza" program shows:

program
  .version('0.0.1')
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
  .parse(process.argv);

Now, e.g., I can call the program using:

$ app -p -b

But what about an unnamed parameter? What if I want to call it using

$ app italian -p -b

? I think this is not so very uncommon, hence providing files for the cp command does not require you to use named parameters as well. It's just

$ cp source target

and not:

$ cp -s source -t target

How do I achieve this using commander.js?

And, how do I tell commander.js that unnamed parameters are required? E.g., if you take a look at the cp command, source and target are required.

Nubile answered 28/1, 2013 at 5:37 Comment(0)
K
15

With the present version of commander, it's possible to use positional arguments. See the docs on argument syntax for details, but using your cp example it would be something like:

program
.version('0.0.1')
.arguments('<source> <target>')
.action(function(source, target) {
    // do something with source and target
})
.parse(process.argv);

This program will complain if both arguments are not present, and give an appropriate warning message.

Khadijahkhai answered 14/2, 2016 at 23:28 Comment(0)
S
8

You get all the unnamed parameters through program.args. Add the following line to your example

console.log(' args: %j', program.args);

When you run your app with -p -b -c gouda arg1 arg2 you get

you ordered a pizza with:
- peppers
- bbq
- gouda cheese
args: ["arg1","arg2"]

Then you could write something like

copy args[0] to args[1] // just to give an idea
Stupefacient answered 28/1, 2013 at 8:2 Comment(5)
Basically, this is a fine option, but - how do I specify that the arguments are mandatory?Nubile
you could do something like if (!program.args.length) console.log('Please use the following syntax: cp source destination')Stupefacient
Yes, of course, but my question is whether commander.js has built-in support for this, including coercion and all its other nice features.Nubile
Unfortunately not, so I'm still looking for an answer, too :-/Nubile
@Golo Roden you can make the arguments mandatory by specifying them with angled brackets -e.g. <arg1>, and optional by using squared brackets -e.g. [arg2].Symposium
R
1

program.argument('<myarg>') + program.namedArgs as of 9.4.1 for positional arguments

Tested as of commander 9.4.1 the best option for positional arguments seems to be the .argument function.

This method achieves the basic features you'd expect from a decent library:

  • gives an error if mandatory arguments are not passed
  • allows you to set defaults and do custom parsing/checks e.g. integer conversion
  • give an error if there are too many positional with program.allowExcessArguments(false) (shame not the default)

We use program.namedArgs because program.args ignores myParseInt or defaults.

TODO built-in way to

  • access positional arguments by their name (e.g. program.processedArgs.arg1) rather than by index (program.processedArgs[0])

Sample usage:

positional.js

function myParseInt(value, dummyPrevious) {
  const parsedValue = parseInt(value, 10);
  if (isNaN(parsedValue)) {
    throw new commander.InvalidArgumentError('Not a number.');
  }
  return parsedValue;
}

const commander = require('commander');
const program = commander.program
program.option('-m --myopt <myopt>', 'doc for myopt', 'myopt-default');
program.argument('<arg1>', 'doc for arg1');
program.argument('<arg2>', 'doc for arg2');
program.argument('[arg3]', 'doc for arg3', myParseInt, 1);
program.allowExcessArguments(false);
program.parse(process.argv);
const [arg1, arg2, arg3] = program.processedArgs
const opts = program.opts()

// Use the arguments.
console.error(`arg1: ${arg1} (${typeof arg1})`);
console.error(`arg2: ${arg2} (${typeof arg2})`);
console.error(`arg3: ${arg3} (${typeof arg3})`);
console.error(`myopt: ${opts.myopt} (${typeof opts.myopt})`);

Sample calls:

$ ./positional.js 
error: missing required argument 'arg1'
$ ./positional.js a
error: missing required argument 'arg2'
$ ./positional.js a b
arg1: a (string)
arg2: b (string)
arg3: 1 (number)
myopt: myopt-default (string)
$ ./positional.js a b c
error: command-argument value 'c' is invalid for argument 'arg3'. Not a number.
$ ./positional.js a b 2
arg1: a (string)
arg2: b (string)
arg3: 2 (number)
myopt: myopt-default (string)
$ ./positional.js a b 2 c
error: too many arguments. Expected 3 arguments but got 4.
$ ./positional.js -m c a b 2
arg1: a (string)
arg2: b (string)
arg3: 2 (number)
myopt: c (string)
$ ./positional.js a b 2 -m c
arg1: a (string)
arg2: b (string)
arg3: 2 (number)
myopt: c (string)
$ ./positional.js -h
Usage: positional [options] <arg1> <arg2> [arg3]

Arguments:
  arg1                doc for arg1
  arg2                doc for arg2
  arg3                doc for arg3 (default: 1)

Options:
  -m --myopt <myopt>  doc for myopt (default: "myopt-default")
  -h, --help          display help for command
Ruyter answered 6/12, 2022 at 9:25 Comment(3)
program.args has the plain text arguments. See program.processedArgs for the arguments after custom processing.Wakeless
@Wakeless nice, updated. Any way to make commander check if there are too many positional arguments besides manual program.args check?Ruyter
Yes: program.allowExcessArguments(false)Wakeless

© 2022 - 2024 — McMap. All rights reserved.