Skip to content

Commit

Permalink
fix: always camelcase parsed options
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed Jan 15, 2020
1 parent 64e22f1 commit e9ea9b7
Show file tree
Hide file tree
Showing 10 changed files with 1,186 additions and 1,090 deletions.
83 changes: 47 additions & 36 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,28 @@ const getFileName = (input) => {
const m = /([^\\\/]+)$/.exec(input);
return m ? m[1] : '';
};
const camelcaseOptionName = (name) => {
// Camelcase the option name
// Don't camelcase anything after the dot `.`
return name
.split('.')
.map((v, i) => {
return i === 0 ? camelcase(v) : v;
})
.join('.');
};
class CACError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
}
else {
this.stack = new Error(message).stack;
}
}
}

class Option {
constructor(rawName, description, config) {
Expand All @@ -237,10 +259,10 @@ class Option {
this.negated = true;
name = name.replace(/^no-/, '');
}
return name;
return camelcaseOptionName(name);
})
.sort((a, b) => (a.length > b.length ? 1 : -1)); // Sort names
// Use the longese name (last one) as actual option name
// Use the longest name (last one) as actual option name
this.name = this.names[this.names.length - 1];
if (this.negated) {
this.config.default = true;
Expand All @@ -259,12 +281,9 @@ class Option {
}

const deno = typeof window !== 'undefined' && window.Deno;
const exit = (code) => {
return deno ? Deno.exit(code) : process.exit(code);
};
const processArgs = deno ? ['deno'].concat(Deno.args) : process.argv;
const platformInfo = deno
? `${Deno.platform.os}-${Deno.platform.arch} deno-${Deno.version.deno}`
? `${Deno.build.os}-${Deno.build.arch} deno-${Deno.version.deno}`
: `${process.platform}-${process.arch} node-${process.version}`;

class Command {
Expand Down Expand Up @@ -411,21 +430,18 @@ class Command {
: section.body;
})
.join('\n\n'));
exit(0);
}
outputVersion() {
const { name } = this.cli;
const { versionNumber } = this.cli.globalCommand;
if (versionNumber) {
console.log(`${name}/${versionNumber} ${platformInfo}`);
}
exit(0);
}
checkRequiredArgs() {
const minimalArgsCount = this.args.filter(arg => arg.required).length;
if (this.cli.args.length < minimalArgsCount) {
console.error(`error: missing required args for command \`${this.rawName}\``);
exit(1);
throw new CACError(`missing required args for command \`${this.rawName}\``);
}
}
/**
Expand All @@ -434,14 +450,13 @@ class Command {
* Exit and output error when true
*/
checkUnknownOptions() {
const { rawOptions, globalCommand } = this.cli;
const { options, globalCommand } = this.cli;
if (!this.config.allowUnknownOptions) {
for (const name of Object.keys(rawOptions)) {
for (const name of Object.keys(options)) {
if (name !== '--' &&
!this.hasOption(name) &&
!globalCommand.hasOption(name)) {
console.error(`error: Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
exit(1);
throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
}
}
}
Expand All @@ -450,16 +465,15 @@ class Command {
* Check if the required string-type options exist
*/
checkOptionValue() {
const { rawOptions, globalCommand } = this.cli;
const { options: parsedOptions, globalCommand } = this.cli;
const options = [...globalCommand.options, ...this.options];
for (const option of options) {
const value = rawOptions[option.name.split('.')[0]];
const value = parsedOptions[option.name.split('.')[0]];
// Check required option value
if (option.required) {
const hasNegated = options.some(o => o.negated && o.names.includes(option.name));
if (value === true || (value === false && !hasNegated)) {
console.error(`error: option \`${option.rawName}\` value is missing`);
exit(1);
throw new CACError(`option \`${option.rawName}\` value is missing`);
}
}
}
Expand Down Expand Up @@ -542,7 +556,6 @@ class CAC extends events.EventEmitter {
* When a sub-command is matched, output the help message for the command
* Otherwise output the global one.
*
* This will also call `process.exit(0)` to quit the process.
*/
outputHelp() {
if (this.matchedCommand) {
Expand All @@ -555,15 +568,13 @@ class CAC extends events.EventEmitter {
/**
* Output the version number.
*
* This will also call `process.exit(0)` to quit the process.
*/
outputVersion() {
this.globalCommand.outputVersion();
}
setParsedInfo({ args, options, rawOptions }, matchedCommand, matchedCommandName) {
setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
this.args = args;
this.options = options;
this.rawOptions = rawOptions;
if (matchedCommand) {
this.matchedCommand = matchedCommand;
}
Expand All @@ -585,11 +596,11 @@ class CAC extends events.EventEmitter {
let shouldParse = true;
// Search sub-commands
for (const command of this.commands) {
const mriResult = this.mri(argv.slice(2), command);
const commandName = mriResult.args[0];
const parsed = this.mri(argv.slice(2), command);
const commandName = parsed.args[0];
if (command.isMatched(commandName)) {
shouldParse = false;
const parsedInfo = Object.assign({}, mriResult, { args: mriResult.args.slice(1) });
const parsedInfo = Object.assign({}, parsed, { args: parsed.args.slice(1) });
this.setParsedInfo(parsedInfo, command, commandName);
this.emit(`command:${commandName}`, command);
}
Expand All @@ -599,15 +610,15 @@ class CAC extends events.EventEmitter {
for (const command of this.commands) {
if (command.name === '') {
shouldParse = false;
const mriResult = this.mri(argv.slice(2), command);
this.setParsedInfo(mriResult, command);
const parsed = this.mri(argv.slice(2), command);
this.setParsedInfo(parsed, command);
this.emit(`command:!`, command);
}
}
}
if (shouldParse) {
const mriResult = this.mri(argv.slice(2));
this.setParsedInfo(mriResult);
const parsed = this.mri(argv.slice(2));
this.setParsedInfo(parsed);
}
if (this.options.help && this.showHelpOnExit) {
this.outputHelp();
Expand Down Expand Up @@ -639,7 +650,10 @@ class CAC extends events.EventEmitter {
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
argv = argv.slice(0, doubleDashesIndex);
}
const parsed = lib(argv, mriOptions);
let parsed = lib(argv, mriOptions);
parsed = Object.keys(parsed).reduce((res, name) => {
return Object.assign({}, res, { [camelcaseOptionName(name)]: parsed[name] });
}, { _: [] });
const args = parsed._;
delete parsed._;
const options = {
Expand All @@ -666,18 +680,15 @@ class CAC extends events.EventEmitter {
}
}
}
// Camelcase option names and set dot nested option values
// Set dot nested option values
for (const key of Object.keys(parsed)) {
const keys = key.split('.').map((v, i) => {
return i === 0 ? camelcase(v) : v;
});
const keys = key.split('.');
setDotProp(options, keys, parsed[key]);
setByType(options, transforms);
}
return {
args,
options,
rawOptions: parsed
options
};
}
runMatchedCommand() {
Expand Down
Loading

0 comments on commit e9ea9b7

Please sign in to comment.