Unnamed parameter with commander.js (positional arguments) - node.js

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.

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.

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

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

Related

Groovy named positional argument with default value

I have this code:
def myFunc(arg1, arg2="default arg2", arg3="default arg3") {
println("arg1: ${arg1}")
println("arg2: ${arg2}")
println("arg3: ${arg3}")
}
myFunc(1, arg3="foo")
My desired output:
arg1: 1
arg2: default arg2
arg3: foo
Actual output:
arg1: 1
arg2: foo
arg3: default arg3
I want to override the last argument without changing the middle arg2 which has a default value I need.
I cannot use classes.
What are my options?
This is not possible in groovy, The right most optional parameter is dropped first and so on and so forth.
Better explanation:
https://mrhaki.blogspot.com/2009/09/groovy-goodness-parameters-with-default.html
The implementor would need to pass in the default 2nd param when invoking this method.
As already answered, this does not work - groovy has no named arguments.
One thing people often confuse for named arguments, is the passing of
maps. E.g. you can have the defaults be merged with the arguments and
come close to what you are after:
def myFunc(Map kwargs = [:]) {
def args = [arg1: "a1", arg2: "a2", arg3: "a3"].tap{putAll(kwargs)}
println([args.arg1, args.arg2, args.arg3])
}
myFunc(arg3: "new a3")
// => [a1, a2, new a3]
You can also check for the incoming keys beforehand, that they are in
the default to notify the consumer at least at runtime, that they are
sending unknown keys.

Can __rrshift__ take more than one argument?

I am trying to overload the __rrshift__ method on one class. The basic implementation follows:
class A:
def __rrshift__(self, arg0):
print(f'called A.__rrshift__ with arg0={arg0}')
a = A()
42 >> a
Here I got the expected result:
called A.__rrshift__ with arg0=42
But if I call this method with more than one argument, only the last one is used:
'hello', 'world' >> a
Which returns:
called A.__rrshift__ with arg0=world
If I try and add arguments to the __rrshift__ method, the behavior is not modified as I expected:
class B:
def __rrshift__(self, arg0, arg1=None):
print(f'called B.__rrshift__ with arg0={arg0}, arg1={arg1}')
b = B()
42 >> b
'hello', 'world' >> b
# called B.__rrshift__ with arg0=42, arg1=None
# called B.__rrshift__ with arg0=world, arg1=None
Is it possible to consider more than one argument for the __rrshift__ method?
I'm afraid that's not possible.
__rrshift__, like __add__, __sub__ et al. are binary operators. They accept exactly two arguments: self and whatever_other_argument.
Of course, you can cheat the system by calling these methods explicitly, and then they'll be able to accept as many arguments as you want, but if you use the operators like >>, +, - et al., then the syntax of the language will force them to accept two arguments exactly.
You can probably modify that by hacking the heck of Python's grammar with the ast module, but that won't be Python anymore.
Here's how a, b >> c is seen by the Python parser, according to the grammar:
>>> ast.dump(ast.parse('a, b >> c'))
# I prettified this myself. The actual output of `dump` is horrendous looking.
Module(
body=[
Expr(
# Build a tuple...
value=Tuple(elts=[
Name(id='a', ctx=Load()), # ...containing `a`...
# ...and the result of a BINARY OPERATOR (RShift)...
BinOp(
left=Name(id='b', ctx=Load()), # ...which is applied to `b`...
op=RShift(),
right=Name(id='c', ctx=Load()) # ...and `c`
)
],
ctx=Load()
)
)
]
)
The production in the grammar that produces [sic] the tuple seems to be the following:
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
As you can see, it then goes on to parser the test production, which is then unpacked all the way to the expr production, which then arrives at the following production:
shift_expr: arith_expr (('<<'|'>>') arith_expr)*
So, the first test in testlist_star_expr resolves to atom: NAME, and the second one - to shift_expr. This later ends up constructing the tuple.
I am not sure what you are after but if you just need to supply some args (kwargs eventually), this might show you how to achieve that:
class A:
def __rrshift__(self, *args):
if len(args) > 1:
print(f'called A.__rrshift__ with arg={", ".join(args)}')
else:
print(f'called A.__rrshift__ with arg={args[0]}')
a = A()
a.__rrshift__('hello', 'world')
a.__rrshift__(42)
#called A.__rrshift__ with arg=hello, world
#called A.__rrshift__ with arg=42

How to define parameter with arity 0 or 1

I'm using jcommander 1.72 to handle commands in a REPL (not directly from the command line). I have a parameter that can optionally take a value; if a value is provided, the value is set. If no value is provided, then the current value is printed. E.g.:
> myCmd --foo 5
set foo = 5
> myCmd --foo
current value of foo = 5
I tried this:
#Parameter(names = {"-f", "--foo"}, variableArity=true)
List<String> foo = new ArrayList<>()
But I get this error:
com.beust.jcommander.ParameterException: Expected a value after parameter --foo
at com.beust.jcommander.JCommander.processFixedArity(JCommander.java:886)
at com.beust.jcommander.JCommander.processVariableArity(JCommander.java:859)
at com.beust.jcommander.JCommander.parseValues(JCommander.java:705)
at com.beust.jcommander.JCommander.parse(JCommander.java:340)
at com.beust.jcommander.JCommander.parseValues(JCommander.java:787)
at com.beust.jcommander.JCommander.parse(JCommander.java:340)
at com.beust.jcommander.JCommander.parse(JCommander.java:319)
...
How can I accomplish this in jcommander?

Could someone explain what "process.argv" means in node.js please?

I'm currently learning node.js, and I was just curious what that meant, I am learning and could you tell me why this code does what it does:
var result = 0;
for (var i = 2; i < process.argv.length; i++){
result += Number(process.argv[i]);
}
console.log(result);
I know it adds the numbers that you add to the command line, but why does "i" start with 2? I understand the for loop, so you don't have to go into detail about that.
Thank you so much in advance.
Do a quick console.log(process.argv) and you'll immediately spot the problem.
It starts on 2 because process.argv contains the whole command-line invocation:
process.argv = ['node', 'yourscript.js', ...]
Elements 0 and 1 are not "arguments" from the script's point of view, but they are for the shell that invoked the script.
It starts with 2 because the code will be run with
node myprogram.js firstarg secondarg
So
process.argv[0] == "node"
process.argv[1] == "myprogram.js"
process.argv[2] == "firstarg"
Online docs
Your program prints the sum of the numerical values of the "command line arguments" provided to the node script.
For example:
$ /usr/local/bin/node ./sum-process-argv.js 1 2 3
6
From the Node.js API documentation for process.argv:
An array containing the command line arguments. The first element will be 'node', the second element will be the name of the JavaScript file. The next elements will be any additional command line arguments.
In the above examples those values are:
process.argv[0] == '/usr/local/bin/node'
process.argv[1] == '/Users/maerics/src/js/sum-process-argv.js'
process.argv[2] == '1'
process.argv[3] == '2'
process.argv[4] == '3'
See also the Number(...) function/contructor for JavaScript.
In the node.js, the command line arguments are always stored in an array. In that array, the first element is the node command we refer to because we begin the command line with word “node”. The second element is the javascript (JS) file we refer to that often comes after the node command.
As we know, in the first element in JS array begins from zero, the second element is 1, and it goes 2, 3, 4 and on. Let’s name this array process.argv and add command line arguments x and y. Then, this is how we are going to call these elements:
var process.argv = ['node', 'file.js', ‘x’, ‘y’];
var process.argv [0] = node;
var process.argv [1]= file.js;
var process.argv[2] = x;
var process.argv[3] = y;
In short, element 0 and 1 are native to node.js, and we don't use them when we write any command line argument. That's why we ignore 0 and 1 and always begin from 2.
If we want to loop through the command line arguments, again we have to start from 2. Here is what we use for looping.
for (i = 2; i < process.argv.length; i++){
console.log(process.argv[i]);
}
My answer is not about on how process.argv works -'cause there is a lot of answers here-, instead, it is on how you can get the values using array destructuring syntax.
For example, if you run your script with:
node app.js arthur 35
you can get those values in a more readable way like this:
const [node, script, name, age] = process.argv;
console.log(node); // node
console.log(script); // app.js
console.log(name); // arthur
console.log(age); // 35
You can omit the first and second places of your process.argv, and stay only with name and age:
const [, , name, age] = process.argv;
If you want all the arguments in an array, you can do it using the rest syntax, that collects multiple elements and condenses them into a single element like this:
const [node, script, ...params] = process.argv;
console.log(node); // node
console.log(script); // app.js
console.log(params); // [ 'arthur', '35' ]
and of course, you can omit the first and second places of your process.argv, and stay only with your params:
const [, , ...params] = process.argv;
When you execute it like:
node code.js <argument> <argument>....
It take into account all command line invocation. For process.argv[] array will have ["node","code.js","<argument>",...]
Because of that your arguments that in array start with index 2
process.agrv[i]- basically loops through the command line arguments passed in the terminal while executing the file.
for example- If you run the file as
$ node prog.js 1 2 3 , then process.argv[0]=1 and so on..

Doing a readLine inside an if

I'm writing a small command-line utility in Haskell which should accept a command with an optional command-line argument - but if the argument is not present, the user should be prompted to enter it*. For example:
$ my_prog add item_name
Adding... done
$ my_prog add
Enter item name: item_name
Adding... done
My initial attempt looked something like this:
add args = do
let id = if length args > 0
then head args
else input where
input <- readLine
-- Do stuff with id
putStrLn id
Which fails to parse at the <-.
*I have since decided that this is a silly idea, but I thought I'd ask the question anyway.
You are attempting to use the do-notation inside the if, this will not work (and besides, won't typecheck since the whole if is outside the IO monad).
add args = do
id <- if length args > 0
then return $ head args
else readLine
putStrLn id

Resources