Skip to content

Commit

Permalink
Refactor parseOptions()
Browse files Browse the repository at this point in the history
Eliminates redundant calls and checks.
  • Loading branch information
aweebit committed Aug 6, 2023
1 parent e7c790a commit dd2c9c8
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 52 deletions.
116 changes: 65 additions & 51 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -1425,14 +1425,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
/**
* Parse options from `argv` removing known options,
* and return argv split into operands and unknown arguments,
* as well as a boolean indicating whether a help flag had been found before encountering a subcommand (or before encountering either a subcommand or a command-argument when the default command is being invoked implicitly).
* as well as a boolean indicating whether a help flag had been found before entering a subcommand.
*
* argv => operands, unknown, displayHelp
* --known kkk op => [op], [], false
* op --known kkk => [op], [], false
* sub --unknown uuu op => [sub], [--unknown uuu op], false
* sub -- --unknown uuu op => [sub --unknown uuu op], [], false
* --help => [], [], true
* sub --help => [], ['--help'], false
*
* @param {String[]} argv
* @return {{operands: String[], unknown: String[], displayHelp: boolean}}
Expand All @@ -1441,15 +1442,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
parseOptions(argv) {
const operands = []; // operands, not options or values
const unknown = []; // first unknown option and remaining unknown args
let displayHelp = false; // whether a help flag had been found before encountering a subcommand
let displayHelp = false; // whether a help flag had been found before entering a subcommand

let dest = operands;
const args = argv.slice();

function maybeOption(arg) {
return arg.length > 1 && arg[0] === '-';
}

// parse options
let activeVariadicOption = null;
let reprocessedBySubcommand = false;
Expand All @@ -1464,64 +1461,68 @@ Expecting one of '${allowedValues.join("', '")}'`);
break;
}

if (activeVariadicOption && !maybeOption(arg)) {
const isArgFlag = isFlag(arg);

if (activeVariadicOption && !isArgFlag) {
this.emit(`option:${activeVariadicOption.name()}`, arg);
continue;
}
activeVariadicOption = null;

if (maybeOption(arg)) {
if (isArgFlag) {
const isHelpOption = this._hasHelpOption && this._helpOption.is(arg);
// Options added via .option(), .addOption() and .version() have precedence over help option.
const option = this._findOption(arg) ?? (isHelpOption && this._helpOption);
if (option === this._helpOption) {
// Help option is always positional, consider unknown if reprocessed by subcommand.
displayHelp = !reprocessedBySubcommand;
if (displayHelp) continue;
} else if (!onlyConsumeHelpOption && option) {
// recognised option, call listener to assign value with possible custom processing
if (option.required) {
const value = args.shift();
if (value === undefined) this.optionMissingArgument(option);
this.emit(`option:${option.name()}`, value);
} else if (option.optional) {
let value = null;
// historical behaviour is optional value is following arg unless an option
if (args.length > 0 && !maybeOption(args[0])) {
value = args.shift();
} else if (!onlyConsumeHelpOption) {
if (option) {
// recognised option, call listener to assign value with possible custom processing
if (option.required) {
const value = args.shift();
if (value === undefined) this.optionMissingArgument(option);
this.emit(`option:${option.name()}`, value);
} else if (option.optional) {
let value = null;
// historical behaviour is optional value is following arg unless an option
if (args.length > 0 && !isFlag(args[0])) {
value = args.shift();
}
this.emit(`option:${option.name()}`, value);
} else { // boolean flag
this.emit(`option:${option.name()}`);
}
this.emit(`option:${option.name()}`, value);
} else { // boolean flag
this.emit(`option:${option.name()}`);
activeVariadicOption = option.variadic ? option : null;
continue;
}
activeVariadicOption = option.variadic ? option : null;
continue;
}
}

// Look for combo options following single dash, eat first one if known.
if (!onlyConsumeHelpOption && arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
const option = this._findOption(`-${arg[1]}`);
if (option) {
if (option.required || (option.optional && this._combineFlagAndOptionalValue)) {
// option with value following in same argument
this.emit(`option:${option.name()}`, arg.slice(2));
} else {
// boolean option, emit and put back remainder of arg for further processing
this.emit(`option:${option.name()}`);
args.unshift(`-${arg.slice(2)}`);
// Look for combo options following single dash, eat first one if known.
if (arg.length > 2 && arg[1] !== '-') {
const option = this._findOption(`-${arg[1]}`);
if (option) {
if (option.required || (option.optional && this._combineFlagAndOptionalValue)) {
// option with value following in same argument
this.emit(`option:${option.name()}`, arg.slice(2));
} else {
// boolean option, emit and put back remainder of arg for further processing
this.emit(`option:${option.name()}`);
args.unshift(`-${arg.slice(2)}`);
}
continue;
}
}
continue;
}
}

// Look for known long flag with value, like --foo=bar
if (!onlyConsumeHelpOption && /^--[^=]+=/.test(arg)) {
const index = arg.indexOf('=');
const option = this._findOption(arg.slice(0, index));
if (option && (option.required || option.optional)) {
this.emit(`option:${option.name()}`, arg.slice(index + 1));
continue;
// Look for known long flag with value, like --foo=bar
if (/^--[^=]+=/.test(arg)) {
const index = arg.indexOf('=');
const option = this._findOption(arg.slice(0, index));
if (option && (option.required || option.optional)) {
this.emit(`option:${option.name()}`, arg.slice(index + 1));
continue;
}
}
}
}

Expand All @@ -1538,13 +1539,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
const stopAtSubcommand = (
this._enablePositionalOptions || this._passThroughOptions
);
if (this._findCommand(arg)) {
if (!isArgFlag && this._findCommand(arg)) {
if (stopAtSubcommand) {
operands.push(arg);
unknown.push(...args);
break;
}
} else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
} else if (!isArgFlag &&
arg === this._helpCommandName &&
this._hasImplicitHelpCommand()
) {
if (stopAtSubcommand) {
operands.push(arg, ...args);
break;
Expand All @@ -1561,7 +1565,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
}

// An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
if (maybeOption(arg)) {
if (isArgFlag) {
dest = unknown;
}

Expand All @@ -1571,7 +1575,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
// - or it is off and so an error will be thrown anyway since no subcommand was found that could reprocess the option.
if (this._passThroughOptions) {
onlyConsumeHelpOption = true;
if (!this._hasHelpOption || !maybeOption(arg)) {
if (!this._hasHelpOption || !isArgFlag) {
dest.push(arg, ...args);
break;
}
Expand Down Expand Up @@ -2210,4 +2214,14 @@ function getCommandAndParents(startCommand) {
return result;
}

/**
* @param {string} arg
* @returns {boolean}
* @api private
*/

function isFlag(arg) {
return arg.length > 1 && arg[0] === '-';
}

exports.Command = Command;
3 changes: 2 additions & 1 deletion typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,14 +695,15 @@ export class Command {
/**
* Parse options from `argv` removing known options,
* and return argv split into operands and unknown arguments,
* as well as a boolean indicating whether a help flag had been found before encountering a subcommand (or before encountering either a subcommand or a command-argument when the default command is being invoked implicitly).
* as well as a boolean indicating whether a help flag had been found before entering a subcommand.
*
* argv => operands, unknown, displayHelp
* --known kkk op => [op], [], false
* op --known kkk => [op], [], false
* sub --unknown uuu op => [sub], [--unknown uuu op], false
* sub -- --unknown uuu op => [sub --unknown uuu op], [], false
* --help => [], [], true
* sub --help => [], ['--help'], false
*/
parseOptions(argv: string[]): ParseOptionsResult;

Expand Down

0 comments on commit dd2c9c8

Please sign in to comment.