Commander.js: Collect: global option parsing and values

Created on 26 Mar 2020  Â·  4Comments  Â·  Source: tj/commander.js

Commander looks for the program options anywhere on the command line, including after operands (quite common) and after subcommands (less common). When a program option is defined the value is only available from the program options.

The scenarios people have asked about are:
1) only looking for program option before subcommand, so say subcommand could use same option name
2) treating the option as "global" so value available from the subcommand, or even appear in the subcommand help
3) stop parsing at the first operand and and subsequent "options" are included as ordinary arguments, and can be used for calling another program (without needing to use .allowUnknownOptions or --).

(This discussion uses program and subcommand for simplicity as though there were only two levels, but there could be more levels.)

Related issues:

  • #243 Accessing parent options from subcommand
  • #598 main options intertwine with command options
  • #797 sub-command not executed if using --version option
  • #1033 Conflict with global options and command specific options
  • #1078 Global options not displayed in sub-command help
  • #1127 How to stop parsing for options after first argument?
  • #1155 Add way to collect inherited/global options from parents
  • #1293 A clean way to get pass through arguments?
  • #1307 subcommands cannot have the same option as top level commands
  • #1426 Options shared by a root command and its subCommands

Standards

POSIX and GNU differ on how options are parsed relative to operands, but do not mention subcommands as such.

POSIX (Open Group)

POSIX has options strictly before operands.

https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html

Guideline 9:
All options should precede operands on the command line.

GNU

GNU relaxes the order.

https://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces

Note that the GNU version of getopt will normally permit options anywhere among the arguments unless the special argument ‘--’ is used. This is not what POSIX specifies; it is a GNU extension.

https://www.gnu.org/software/coreutils/manual/html_node/Common-options.html

Normally options and operands can appear in any order, and programs act as if all the options appear before any operands. For example, ‘sort -r passwd -t :’ acts like ‘sort -r -t : passwd’, since ‘:’ is an option-argument of -t. However, if the POSIXLY_CORRECT environment variable is set, options must appear before operands, unless otherwise specified for a particular command.

Examples from Other Implementations

Yargs has halt-at-non-option which defaults to false.

Should parsing stop at the first positional argument?

Cobra has Flags and PersistentFlags for whether flag [value] available to children,
and TraverseChildren for whether to look for local flags of parent in children arguments.

Most helpful comment

Waiting for a better solution, this is my workaround to have a set of common options for all my subcommands.

import commander from "commander";
import colors from "colors";

class myCommand extends commander.Command {
    command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
        let cmd = super.command(nameAndArgs, actionOptsOrExecDesc, execOpts);
        cmd
            .option("-C, --no-color", "remove color", false)
            .option("-v, --verbose", "output results", true)
            .option("--silent, --no-verbose", "disable output")
            .on('option:no-color', () => colors.disable())
            .on('command:' + cmd.name(), operands => colors.enable());

        return cmd;
    }
}

const program = new myCommand()
    .version(getCurrentVersion())
    .description("my commander test");

program
    .command("list")
    .alias("l")
    .description("list all available commands")
    .action(doList);

async function doList(options){
/* ... */
}

async function main() {
    if (!process.argv.slice(2).length) {
        program.outputHelp();
        process.exit();
    }
    return await program.parseAsync(process.argv);
}

main().catch(error => console.error(error.message));

All 4 comments

Waiting for a better solution, this is my workaround to have a set of common options for all my subcommands.

import commander from "commander";
import colors from "colors";

class myCommand extends commander.Command {
    command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
        let cmd = super.command(nameAndArgs, actionOptsOrExecDesc, execOpts);
        cmd
            .option("-C, --no-color", "remove color", false)
            .option("-v, --verbose", "output results", true)
            .option("--silent, --no-verbose", "disable output")
            .on('option:no-color', () => colors.disable())
            .on('command:' + cmd.name(), operands => colors.enable());

        return cmd;
    }
}

const program = new myCommand()
    .version(getCurrentVersion())
    .description("my commander test");

program
    .command("list")
    .alias("l")
    .description("list all available commands")
    .action(doList);

async function doList(options){
/* ... */
}

async function main() {
    if (!process.argv.slice(2).length) {
        program.outputHelp();
        process.exit();
    }
    return await program.parseAsync(process.argv);
}

main().catch(error => console.error(error.message));

  1. treating the option as "global" so value available from the subcommand...

A simple way to do this would be by adding an optional parameter to .opts(). This may mean an extra line of code to access the combined options, but easy and explicit.

  .option('--debug');

program
  .command('sub')
  .option('--mine')
  .action((options, command) => { // Commander 7 passes options+command
    console.log(command.opts({ includeGlobals: true })); // includes "debug" option
  });

Other possible names for the property:

Pull Request opened to add .enablePositionalOptions() and .passThroughOptions(): #1427

.enablePositionalOptions() and .passThroughOptions() included in Commander 7.0.0.
Not much interest in getting collected options so far, so closing this as covered most of the collected issues.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shadowspawn picture shadowspawn  Â·  3Comments

0505gonzalez picture 0505gonzalez  Â·  3Comments

jaredpetersen picture jaredpetersen  Â·  3Comments

DeoLeung picture DeoLeung  Â·  4Comments

mtrabelsi picture mtrabelsi  Â·  3Comments