Node.js commander with optional+variadic arguments
Asked Answered
K

2

8

Please help me get node's commander module to parse arguments the way I want.

I'm looking to upload a list of files to a named database. There is a default database name, so the user shouldn't need to include a database parameter.

I'd like this command to work as following:

>>> ./upload.js --db ReallyCoolDB /files/uploadMe1.txt /files/uploadMe2.txt
(uploads "uploadMe1.txt" and "uploadMe2.txt" to database "ReallyCoolDB")

>>> ./upload.js /files/uploadMe1.txt /files/uploadMe2.txt
(uploads "uploadMe1.txt" and "uploadMe2.txt" to the default database)

>>> ./upload.js --db ReallyCoolDB
(returns an error; no files provided)

How can I implement this with commander? I've tried a number of things already, currently I'm stuck with this code which doesn't work:

// upload.js:

#!/usr/bin/env node

var program = require('commander');
program
  .version('0.1.0')
  .description('Upload files to a database')
  .command('<path1> [morePaths...]')
  .option('-d, --db [dbName]', 'Optional name of db', null)
  .action(function(path1, morePaths) {
    
    // At this point I simply want:
    // 1) a String "dbName" var
    // 2) an Array "paths" containing all the paths the user provided
    var dbName = program.db || getDefaultDBName();
    var paths = [ path1 ].concat(morePaths || []);
    console.log(dbName, paths);
    
    // ... do the upload ...
    
  })
  .parse(process.argv);

When I try to run ./upload.js, I get no output!

How can I use commander to accept a single optional parameter, and a non-empty list of strings??

EDIT: Thanks to Rob Raisch's answer I've solved my problem! The solution is to use usage instead of action, do all work after the program commands (instead of within an action function), work with program.db and program.args, and manually ensure that program.args is non-empty:

var program = require('commander');

program
    .version('0.1.0')
    .description('Upload files to a database')
    .usage('[options] <path1> [morePaths ...]') // This improves "--help" output
    .option('-d, --db [dbName]', 'Optional name of db', null)
    .parse(process.argv);

var dbName = program.db || getDefaultDBName();
var paths = program.args;

if (!paths.length) {
    console.log('Need to provide at least one path.');
    process.exit(1);
}

// Do the upload!
Khania answered 30/10, 2017 at 19:16 Comment(0)
C
2

The README.md file for the commander command line processing module answers your use case in its second paragraph:

"Options with commander are defined with the .option() method, also serving as documentation for the options. The example below parses args and options from process.argv, leaving remaining args as the program.args array which were not consumed by options."

Chiropodist answered 30/10, 2017 at 19:50 Comment(1)
So what is the correct way to accept an optional parameter + a non-empty list with commander? Should I use commander to parse the optional parameter, and then manually parse program.args?Khania
C
2
  1. Your first example code declares a subcommand with slightly the wrong syntax (it should have a command name too). But you don't want a subcommand anyway, so changing one line will make your program work as written with the arguments added to the program.
// .command('sub-command-name <path1> [morePaths...]')
.arguments('<path1> [morePaths...]')
  1. If you want one or more parameters in a single parameter (a non-empty array) then declare it as a single variadic argument with a required command-argument like:
.arguments('<paths...>')

or use the new syntax for an argument at a time (so more like adding subcommands and options):

.argument('<paths...>', 'description')
  1. It looks like the --db option should have an option-argument if it is used, so I suggest <dbName> rather than [dnName].

  2. You could specify the default db with the option so it can be seen in the help too.

Putting it all together:

#!/usr/bin/env node

var program = require('commander');
program
  .version('0.1.0')
  .description('Upload files to a database')
  .argument('<paths...>', 'one or more files to upload')
  .option('-d, --db <dbName>', 'name of db', getDefaultDBName())
  .action(function(paths, options) {
    
    // At this point I simply want:
    // 1) a String "dbName" var
    // 2) an Array "paths" containing all the paths the user provided
    console.log(options.db, paths);
    
    // ... do the upload ...
    
  })
  .parse(process.argv);
Conform answered 8/8, 2022 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.