Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

clap

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

clap - npm Package Compare versions

Comparing version 2.0.1 to 3.0.0-beta.1

lib/params.js

34

CHANGELOG.md

@@ -0,1 +1,35 @@

## 3.0.0-beta.1
- Restored wrongly removed `Command#extend()`
- Changed `Command`'s constructor and `Command#command(method)` to take `usage` only (i.e. `command('name [param]')` instead `command('name', '[param]')`)
- Added `Command#clone()` method
- Added `Command#getCommand(name)` and `Command#getCommands()` methods
- Added `Command#getOption(name)` and `Command#getOptions()` methods
- Added `Command#messageRef()` and `Option#messageRef()` methods
- Added `Command#createOptionValues(values)` method
- Added `Command#help()` method similar to `Command#version()`, use `Command#help(false)` to disable default help action option
- Fixed `Command#showHelp()`, it's now logs help message in console instead of returning it
- Renamed `Command#showHelp()` into `Command#outputHelp()`
- Changed `Command` to store params info (as `Command#params`) even if no params
- Removed `Command#infoOption()` method, use `action` in option's config instead, i.e. `option(usage, description, { action: ... })`
- Removed `Command#infoOptionAction` and `infoOptionAction` option for `Command` constructor as well
- Removed `Command#shortcut()` method, use `shortcut` in option's config instead, i.e. `option(usage, description, { shortcut: ... })`
- Changed `Command#command()` to raise an exception when subcommand name already in use
- Removed `Command#setOptions()` method
- Removed `Command#setOption()` method
- Removed `Command#hasOptions()` method
- Removed `Command#hasOption()` method
- Removed `Command#hasCommands()` method
- Removed `Command#normalize()` method (use `createOptionValues()` instead)
- Changed `Option` to store params info as `Option#params`, it always an object even if no params
- Added `Option#names()` method
- Removed name validation for subcommands
- Allowed a number for options's short name
- Changed argv parse handlers to [`init()` -> `applyConfig()` -> `prepareContext()`]+ -> `action()`
- Changed exports
- Added `getCommandHelp()` function
- Added `Params` class
- Removed `Argument` class
- Removed `color` option
## 2.0.1 (December 16, 2019)

@@ -2,0 +36,0 @@

478

lib/command.js

@@ -1,364 +0,228 @@

const processArgv = require('./parse-argv');
const parseParams = require('./parse-params');
const showCommandHelp = require('./help');
const getCommandHelp = require('./help');
const parseArgv = require('./parse-argv');
const Params = require('./params');
const Option = require('./option');
function noop() {
// nothing todo
}
const noop = () => {}; // nothing todo
const self = value => value;
const defaultHelpAction = (instance, _, { commandPath }) => instance.outputHelp(commandPath);
const defaultVersionAction = instance => console.log(instance.meta.version);
const lastCommandHost = new WeakMap();
const lastAddedOption = new WeakMap();
function addOptionToCommand(command, option) {
var commandOption;
const handlers = ['init', 'applyConfig', 'finishContext', 'action'].reduce((res, name) => {
res.initial[name] = name === 'action' ? self : noop;
res.setters[name] = function(fn) {
this.handlers[name] = fn.bind(null);
// short
if (option.short) {
commandOption = command.short[option.short];
return this;
};
return res;
}, { initial: {}, setters: {} });
if (commandOption) {
throw new Error('Short option name -' + option.short + ' already in use by ' + commandOption.usage + ' ' + commandOption.description);
}
module.exports = class Command {
constructor(usage = '') {
const [name, params] = usage.trim().split(/(\s+.*)$/);
command.short[option.short] = option;
}
this.name = name;
this.params = new Params(params, `"${name}" command definition`);
this.options = new Map();
this.commands = new Map();
this.meta = {
description: '',
version: '',
help: null
};
// long
commandOption = command.long[option.long];
this.handlers = { ...handlers.initial };
Object.assign(this, handlers.setters);
if (commandOption) {
throw new Error('Long option --' + option.long + ' already in use by ' + commandOption.usage + ' ' + commandOption.description);
this.help();
}
command.long[option.long] = option;
// definition chaining
extend(fn, ...args) {
fn(this, ...args);
return this;
}
description(description) {
this.meta.description = description;
// camel
commandOption = command.options[option.camelName];
if (commandOption) {
throw new Error('Name option ' + option.camelName + ' already in use by ' + commandOption.usage + ' ' + commandOption.description);
return this;
}
version(version, usage, description, action) {
this.meta.version = version;
this.option(
usage || '-v, --version',
description || 'Output version',
{ action: action || defaultVersionAction }
);
command.options[option.camelName] = option;
// set default value
if (typeof option.defValue !== 'undefined') {
command.setOption(option.camelName, option.defValue, true);
return this;
}
return option;
}
function setFunctionFactory(name) {
return function(fn) {
var property = name + '_';
if (this[property] !== noop) {
throw new Error('Method `' + name + '` could be invoked only once');
help(usage, description, action) {
if (this.meta.help) {
this.meta.help.names().forEach(name => this.options.delete(name));
this.meta.help = null;
}
if (typeof fn !== 'function') {
throw new Error('Value for `' + name + '` method should be a function');
if (usage !== false) {
this.option(
usage || '-h, --help',
description || 'Output usage information',
{ action: action || defaultHelpAction }
);
this.meta.help = lastAddedOption.get(this);
}
this[property] = fn;
return this;
};
}
}
option(usage, description, ...optionOpts) {
const option = new Option(usage, description, ...optionOpts);
const nameType = ['Long option', 'Short option', 'Option'];
const names = option.names();
/**
* @class
*/
var Command = function(name, params, config) {
config = config || {};
names.forEach((name, idx) => {
if (this.options.has(name)) {
throw new Error(
`${nameType[names.length === 2 ? idx * 2 : idx]} name "${name}" already in use by ${this.getOption(name).messageRef()}`
);
}
});
this.name = name;
this.params = false;
try {
if (params) {
this.params = parseParams(params);
for (const name of names) {
this.options.set(name, option);
}
} catch (e) {
throw new Error('Bad paramenter description in command definition: ' + this.name + ' ' + params);
}
this.commands = {};
this.options = {};
this.short = {};
this.long = {};
this.values = {};
this.defaults_ = {};
lastAddedOption.set(this, option);
if ('defaultHelp' in config === false || config.defaultHelp) {
this.infoOption('-h, --help', 'Output usage information', chunks =>
showCommandHelp(this, chunks.slice(0, chunks.length - 1).map(chunk => chunk.command.name))
);
return this;
}
command(usageOrCommand) {
const subcommand = typeof usageOrCommand === 'string'
? new Command(usageOrCommand)
: usageOrCommand;
const name = subcommand.name;
if (typeof config.infoOptionAction === 'function') {
this.infoOptionAction = config.infoOptionAction;
}
};
Command.prototype = {
params: null,
commands: null,
options: null,
short: null,
long: null,
values: null,
defaults_: null,
description_: '',
version_: '',
initContext_: noop,
init_: noop,
delegate_: noop,
action_: noop,
args_: noop,
end_: null,
infoOptionAction: function(info) {
console.log(info);
process.exit(0);
},
option: function(usage, description, opt1, opt2) {
addOptionToCommand(this, new Option(usage, description, opt1, opt2));
return this;
},
infoOption: function(usage, description, getInfo) {
this.option(
usage,
description,
commands => this.infoOptionAction(getInfo(commands)),
Option.info
);
},
shortcut: function(usage, description, fn, opt1, opt2) {
if (typeof fn !== 'function') {
throw new Error('fn should be a function');
// search for existing one
if (this.commands.has(name)) {
throw new Error(
`Subcommand name "${name}" already in use by ${this.getCommand(name).messageRef()}`
);
}
var command = this;
var option = addOptionToCommand(this, new Option(usage, description, opt1, opt2));
var normalize = option.normalize;
// attach subcommand
this.commands.set(name, subcommand);
lastCommandHost.set(subcommand, this);
option.normalize = function(value) {
var values;
return subcommand;
}
end() {
const host = lastCommandHost.get(this) || null;
lastCommandHost.delete(this);
return host;
}
value = normalize.call(command, value);
values = fn(value);
for (var name in values) {
if (hasOwnProperty.call(values, name)) {
if (hasOwnProperty.call(command.options, name)) {
command.setOption(name, values[name]);
} else {
command.values[name] = values[name];
}
}
// parse & run
parse(argv, suggest) {
let chunk = {
context: {
commandPath: [],
options: null,
args: null,
literalArgs: null
},
next: {
command: this,
argv: argv || process.argv.slice(2)
}
command.values[option.name] = value;
return value;
};
return this;
},
hasOption: function(name) {
return hasOwnProperty.call(this.options, name);
},
hasOptions: function() {
return Object.keys(this.options).length > 0;
},
setOption: function(name, value, isDefault) {
if (!this.hasOption(name)) {
throw new Error('Option `' + name + '` is not defined');
}
do {
chunk = parseArgv(
chunk.next.command,
chunk.next.argv,
chunk.context,
suggest
);
} while (chunk.next);
var option = this.options[name];
var oldValue = this.values[name];
var newValue = option.normalize.call(this, value, oldValue);
return chunk;
}
run(argv) {
const chunk = this.parse(argv);
this.values[name] = option.maxArgsCount ? newValue : value;
if (isDefault && !hasOwnProperty.call(this.defaults_, name)) {
this.defaults_[name] = this.values[name];
if (typeof chunk.action === 'function') {
return chunk.action.call(null, chunk.context);
}
},
setOptions: function(values) {
for (var name in values) {
if (hasOwnProperty.call(values, name) && this.hasOption(name)) {
this.setOption(name, values[name]);
}
}
},
reset: function() {
this.values = {};
}
Object.assign(this.values, this.defaults_);
},
clone(deep) {
const clone = Object.create(Object.getPrototypeOf(this));
command: function(nameOrCommand, params, config) {
var name;
var command;
if (nameOrCommand instanceof Command) {
command = nameOrCommand;
name = command.name;
} else {
name = nameOrCommand;
command = new Command(name, params, config);
for (const [key, value] of Object.entries(this)) {
clone[key] = value && typeof value === 'object'
? (value instanceof Map
? new Map(value)
: Object.assign(Object.create(Object.getPrototypeOf(value)), value))
: value;
}
if (!/^[a-zA-Z][a-zA-Z0-9\-\_]*$/.test(name)) {
throw new Error('Bad subcommand name: ' + name);
}
// search for existing one
var subcommand = this.commands[name];
if (!subcommand) {
// create new one if not exists
subcommand = command;
subcommand.end_ = this;
this.commands[name] = subcommand;
}
return subcommand;
},
end: function() {
return this.end_;
},
hasCommands: function() {
return Object.keys(this.commands).length > 0;
},
version: function(version, usage, description) {
if (this.version_) {
throw new Error('Version for command could be set only once');
}
this.version_ = version;
this.infoOption(
usage || '-v, --version',
description || 'Output version',
function() {
return version;
if (deep) {
for (const [name, subcommand] of clone.commands.entries()) {
clone.commands.set(name, subcommand.clone(deep));
}
);
return this;
},
description: function(description) {
if (this.description_) {
throw new Error('Description for command could be set only once');
}
this.description_ = description;
return clone;
}
createOptionValues(values) {
const storage = Object.create(null);
return this;
},
init: setFunctionFactory('init'),
initContext: setFunctionFactory('initContext'),
args: setFunctionFactory('args'),
delegate: setFunctionFactory('delegate'),
action: setFunctionFactory('action'),
parse: function(args, suggest) {
if (!args) {
args = process.argv.slice(2);
}
return processArgv(this, args, suggest);
},
run: function(args, context) {
var commands = this.parse(args);
for (var i = 0; i < commands.length; i++) {
var item = commands[i];
if (item.infoOptions.length) {
item.infoOptions[0].info(commands.slice(0, i + 1));
return;
for (const { name, normalize, default: value } of this.getOptions()) {
if (typeof value !== 'undefined') {
storage[name] = normalize(value);
}
}
var prevCommand;
var context = Object.assign({}, context || this.initContext_());
for (var i = 0; i < commands.length; i++) {
var item = commands[i];
var command = item.command;
return Object.assign(new Proxy(storage, {
set: (obj, key, value, reciever) => {
const option = this.getOption(key);
// reset command values
command.reset();
command.context = context;
command.root = this;
if (prevCommand) {
prevCommand.delegate_(command);
}
// apply beforeInit options
item.options.forEach(function(entry) {
if (entry.option.beforeInit) {
command.setOption(entry.option.camelName, entry.value);
if (!option) {
return true; // throw new Error(`Unknown option: "${key}"`);
}
});
command.init_(item.args.slice()); // use slice to avoid args mutation in handler
const oldValue = obj[option.name];
const newValue = option.normalize(value, oldValue);
const retValue = Reflect.set(obj, option.name, newValue);
if (item.args.length) {
command.args_(item.args.slice()); // use slice to avoid args mutation in handler
}
// apply regular options
item.options.forEach(function(entry) {
if (!entry.option.beforeInit) {
command.setOption(entry.option.camelName, entry.value);
if (option.shortcut) {
Object.assign(reciever, option.shortcut.call(null, newValue, oldValue));
}
});
prevCommand = command;
}
// return last command action result
if (command) {
return command.action_(item.args, item.literalArgs);
}
},
normalize: function(values) {
var result = {};
if (!values) {
values = {};
}
for (var name in this.values) {
if (hasOwnProperty.call(this.values, name)) {
result[name] = hasOwnProperty.call(values, name) && hasOwnProperty.call(this.options, name)
? this.options[name].normalize.call(this, values[name])
: this.values[name];
return retValue;
}
}
}), values);
}
for (var name in values) {
if (hasOwnProperty.call(values, name) && !hasOwnProperty.call(result, name)) {
result[name] = values[name];
}
}
return result;
},
showHelp: function(commandPath) {
return showCommandHelp(this, commandPath);
// misc
messageRef() {
return `${this.usage}${this.params.args.map(arg => ` ${arg.name}`)}`;
}
getOption(name) {
return this.options.get(name) || null;
}
getOptions() {
return [...new Set(this.options.values())];
}
getCommand(name) {
return this.commands.get(name) || null;
}
getCommands() {
return [...this.commands.values()];
}
outputHelp(commandPath) {
console.log(getCommandHelp(this, Array.isArray(commandPath) ? commandPath.slice(0, -1) : null));
}
};
module.exports = Command;
const MAX_LINE_WIDTH = process.stdout.columns || 200;
const MIN_OFFSET = 25;
const MIN_OFFSET = 20;
const reAstral = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
const ansiRegex = /\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[m|K]/g;
const byName = (a, b) => a.name > b.name || -(a.name < b.name);
let chalk;

@@ -12,3 +13,3 @@

chalk = new ChalkInstance({
level: Number(module.exports.color && process.stdout.isTTY)
level: Number(process.stdout.isTTY)
});

@@ -57,29 +58,32 @@ }

function args(command) {
return command.params.args
.map(({ name, required }) => required ? '<' + name + '>' : '[' + name + ']')
.join(' ');
function args(params, fn = s => s) {
if (params.args.length === 0) {
return '';
}
return ' ' + fn(
params.args
.map(({ name, required }) => required ? '<' + name + '>' : '[' + name + ']')
.join(' ')
);
}
function valuesSortedByKey(dict) {
return Object.keys(dict)
.sort()
.map(key => dict[key]);
function formatLines(lines) {
const maxNameLength = Math.max(MIN_OFFSET, ...lines.map(line => stringLength(line.name)));
return lines.map(line => (
' ' + pad(maxNameLength, line.name) +
' ' + breakByLines(line.description, maxNameLength + 8)
));
}
function commandsHelp(command) {
if (!command.hasCommands()) {
if (command.commands.size === 0) {
return '';
}
const lines = valuesSortedByKey(command.commands).map(subcommand => ({
name: chalk.green(subcommand.name) + chalk.gray(
(subcommand.params ? ' ' + args(subcommand) : '')
),
description: subcommand.description_ || ''
const lines = command.getCommands().sort(byName).map(({ name, meta, params }) => ({
description: meta.description,
name: chalk.green(name) + args(params, chalk.gray)
}));
const maxNameLength = lines.reduce(
(max, line) => Math.max(max, stringLength(line.name)),
MIN_OFFSET - 2
);

@@ -90,6 +94,3 @@ return [

'',
...lines.map(line => (
' ' + pad(maxNameLength, line.name) +
' ' + breakByLines(line.description, maxNameLength + 8)
)),
...formatLines(lines),
''

@@ -100,21 +101,16 @@ ].join('\n');

function optionsHelp(command) {
if (!command.hasOptions()) {
if (command.options.size === 0) {
return '';
}
const hasShortOptions = Object.keys(command.short).length > 0;
const lines = valuesSortedByKey(command.long).map(option => ({
name: option.usage
.replace(/^(?:-., |)/, (m) =>
m || (hasShortOptions ? ' ' : '')
)
.replace(/(^|\s)(-[^\s,]+)/ig, (m, p, flag) =>
p + chalk.yellow(flag)
),
description: option.description
const options = command.getOptions().sort(byName);
const shortPlaceholder = options.some(option => option.short) ? ' ' : '';
const lines = options.map(({ short, long, params, description }) => ({
description,
name: [
short ? chalk.yellow(short) + ', ' : shortPlaceholder,
chalk.yellow(long),
args(params)
].join('')
}));
const maxNameLength = lines.reduce(
(max, line) => Math.max(max, stringLength(line.name)),
MIN_OFFSET - 2
);

@@ -126,6 +122,3 @@ // Prepend the help information

'',
...lines.map(line => (
' ' + pad(maxNameLength, line.name) +
' ' + breakByLines(line.description, maxNameLength + 8)
)),
...formatLines(lines),
''

@@ -141,3 +134,3 @@ ].join('\n');

*/
module.exports = function showCommandHelp(command, commandPath) {
module.exports = function getCommandHelp(command, commandPath) {
initChalk();

@@ -150,8 +143,8 @@

return [
(command.description_ ? command.description_ + '\n\n' : '') +
(command.meta.description ? command.meta.description + '\n\n' : '') +
'Usage:\n\n' +
' ' + chalk.cyan(commandPath) +
(command.params ? ' ' + chalk.magenta(args(command)) : '') +
(command.hasOptions() ? ' [' + chalk.yellow('options') + ']' : '') +
(command.hasCommands() ? ' [' + chalk.green('command') + ']' : ''),
args(command.params, chalk.magenta) +
(command.options.size !== 0 ? ' [' + chalk.yellow('options') + ']' : '') +
(command.commands.size !== 0 ? ' [' + chalk.green('command') + ']' : ''),
commandsHelp(command) +

@@ -161,3 +154,1 @@ optionsHelp(command)

};
module.exports.color = true;

@@ -1,22 +0,21 @@

var Option = require('./option');
var Argument = require('./argument');
var Command = require('./command');
var Error = require('./parse-argv').CliError;
var showCommandHelp = require('./help');
const path = require('path');
const Params = require('./params');
const Option = require('./option');
const Command = require('./command');
const Error = require('./parse-argv-error');
const getCommandHelp = require('./help');
function nameFromProcessArgv() {
return path.basename(process.argv[1], path.extname(process.argv[1]));
}
module.exports = {
Error,
Argument,
Params,
Command,
Option,
set color(value) {
return showCommandHelp.color = Boolean(value);
},
get color() {
return showCommandHelp.color;
},
getCommandHelp,
command: function(name, params, config) {
name = name || require('path').basename(process.argv[1]) || 'command';
name = name || nameFromProcessArgv() || 'command';

@@ -23,0 +22,0 @@ return new Command(name, params, config);

@@ -1,119 +0,71 @@

var parseParams = require('./parse-params');
const Params = require('./params');
const camelcase = name => name.replace(/-(.)/g, (m, ch) => ch.toUpperCase());
const ensureFunction = (fn, fallback) => typeof fn === 'function' ? fn : fallback;
const self = value => value;
function camelize(name) {
return name.replace(/-(.)/g, function(m, ch) {
return ch.toUpperCase();
});
}
module.exports = class Option {
static normalizeOptions(opt1, opt2) {
const raw = typeof opt1 === 'function'
? { normalize: opt1, default: opt2 }
: opt1 && typeof opt1 === 'object'
? opt1
: { default: opt1 };
/**
* @class
* @param {string} usage
* @param {string} description
*/
var Option = function(usage, description, opt1, opt2) {
var self = this;
var params;
var left = usage.trim()
// short usage
// -x
.replace(/^-([a-zA-Z])(?:\s*,\s*|\s+)/, function(m, name) {
self.short = name;
return {
default: raw.default,
normalize: ensureFunction(raw.normalize, self),
shortcut: ensureFunction(raw.shortcut),
action: ensureFunction(raw.action),
config: Boolean(raw.config)
};
}
return '';
})
// long usage
// --flag
// --no-flag - invert value if flag is boolean
.replace(/^--([a-zA-Z][a-zA-Z0-9\-\_]+)\s*/, function(m, name) {
self.long = name;
self.name = name.replace(/(^|-)no-/, '$1');
self.defValue = self.name !== self.long;
static parseUsage(usage) {
const [m, short, long = ''] = usage.trim()
.match(/^(?:(-[a-z\d])(?:\s*,\s*|\s+))?(--[a-z][a-z\d\-\_]*)\s*/i) || [];
return '';
});
if (!m) {
throw new Error(`Usage has no long name: ${usage}`);
}
if (!this.long) {
throw new Error('Usage has no long name: ' + usage);
}
let params = new Params(usage.slice(m.length), `option usage: ${usage}`);
try {
params = parseParams(left);
} catch (e) {
throw new Error('Bad paramenter in option usage: ' + usage, e);
return { short, long, params };
}
if (params) {
left = '';
this.name = this.long;
this.defValue = undefined;
constructor(usage, description, ...rawOptions) {
const { short, long, params } = Option.parseUsage(usage);
const options = Option.normalizeOptions(...rawOptions);
Object.assign(this, params);
}
const isBool = params.maxCount === 0 && !options.action;
let name = camelcase(long.replace(isBool ? /^--(no-)?/ : /^--/, '')); // --no-flag - invert value if flag is boolean
if (left) {
throw new Error('Bad usage for option: ' + usage);
}
if (options.action) {
options.default = undefined;
} else if (isBool) {
options.normalize = Boolean;
options.default = long.startsWith('--no-');
}
if (!this.name) {
this.name = this.long;
}
// names
this.short = short;
this.long = long;
this.name = name;
this.description = description || '';
this.usage = usage.trim();
this.camelName = camelize(this.name);
// meta
this.usage = usage.trim();
this.description = description || '';
if (typeof opt1 !== 'undefined') {
if (opt2 === Option.info) {
this.info = opt1;
this.defValue = undefined;
} else if (typeof opt2 !== 'undefined') {
if (typeof opt1 === 'function') {
this.normalize = opt1;
}
// attributes
this.params = params;
Object.assign(this, options);
}
this.defValue = opt2;
} else {
if (opt1 && opt1.constructor === Object) {
for (var key in opt1) {
if (key === 'normalize' ||
key === 'defValue' ||
key === 'beforeInit') {
this[key] = opt1[key];
}
}
messageRef() {
return `${this.usage} ${this.description}`;
}
// old name for `beforeInit` setting is `hot`
if (opt1.hot) {
this.beforeInit = true;
}
} else {
if (typeof opt1 === 'function') {
this.normalize = opt1;
} else {
this.defValue = opt1;
}
}
}
names() {
return [this.long, this.short, this.name].filter(Boolean);
}
};
Option.info = {};
Option.prototype = {
name: '',
description: '',
short: '',
long: '',
beforeInit: false,
required: false,
minArgsCount: 0,
maxArgsCount: 0,
args: null,
info: undefined,
defValue: undefined,
normalize: value => value
};
module.exports = Option;

@@ -0,92 +1,80 @@

const CliError = require('./parse-argv-error');
function findVariants(command, entry) {
return [
...Object.keys(command.long).map(name => '--' + name),
...Object.keys(command.commands)
...command.getOptions().map(option => option.long),
...command.commands.keys()
].filter(item => item.startsWith(entry)).sort();
}
function createResultChunk(command) {
return {
command,
args: [],
literalArgs: [],
options: [],
infoOptions: []
};
}
function consumeOptionParams(option, rawOptions, argv, index, suggestPoint) {
const tokens = [];
let value;
/**
* @class
*/
if (option.params.maxCount) {
for (let j = 0; j < option.params.maxCount; j++) {
const token = argv[index + j];
var CliError = function(message) {
this.message = message;
};
CliError.prototype = Object.create(Error.prototype);
CliError.prototype.name = 'CliError';
CliError.prototype.clap = true;
// TODO: suggestions for option params
if (index + j === suggestPoint) {
return suggestPoint;
}
module.exports = function processArgv(command, args, suggest) {
function processOption(option) {
var params = [];
if (!token || token[0] === '-') {
break;
}
if (typeof option.info === 'function') {
resultChunk.infoOptions.push(option);
return;
tokens.push(token);
}
if (option.maxArgsCount) {
for (var j = 0; j < option.maxArgsCount; j++) {
var suggestPoint = suggest && i + 1 + j >= args.length - 1;
var nextToken = args[i + 1];
if (tokens.length < option.params.minCount) {
throw new CliError(
`Option ${argv[index - 1]} should be used with at least ${option.params.minCount} argument(s)\n` +
`Usage: ${option.usage}`
);
}
// TODO: suggestions for options
if (suggestPoint) {
// search for suggest
noSuggestions = true;
i = args.length;
return;
}
value = option.params.maxCount === 1 ? tokens[0] : tokens;
} else {
value = !option.default;
}
if (!nextToken || nextToken[0] === '-') {
break;
}
rawOptions.push({
option,
value
});
params.push(args[++i]);
}
return index + tokens.length - 1;
}
if (params.length < option.minArgsCount) {
throw new CliError('Option ' + token + ' should be used with at least ' + option.minArgsCount + ' argument(s)\nUsage: ' + option.usage);
}
module.exports = function parseArgv(command, argv, context, suggestMode) {
const suggestPoint = suggestMode ? argv.length - 1 : -1;
const rawOptions = [];
const result = {
context,
action: null,
next: null
};
if (option.maxArgsCount === 1) {
params = params[0];
}
} else {
params = !option.defValue;
}
command = command.clone();
context.commandPath.push(command.name);
context.options = Object.freeze(command.createOptionValues());
context.args = [];
context.literalArgs = null;
// command.values[option.camelName] = newValue;
resultChunk.options.push({
option: option,
value: params
});
}
command.handlers.init(command, context);
var suggestStartsWith = '';
var noSuggestions = false;
var resultChunk = createResultChunk(command);
var result = [resultChunk];
for (var i = 0; i < argv.length; i++) {
const token = argv[i];
for (var i = 0; i < args.length; i++) {
var suggestPoint = suggest && i === args.length - 1;
var token = args[i];
if (suggestPoint && (token === '--' || token === '-' || token[0] !== '-')) {
suggestStartsWith = token;
break; // returns long option & command list outside the loop
if (i === suggestPoint) {
return findVariants(command, token); // returns long option & command list
}
if (token === '--') {
resultChunk.literalArgs.push(...args.slice(i + 1));
if (suggestPoint > i) {
return [];
}
context.literalArgs = argv.slice(i + 1);
break;

@@ -96,57 +84,58 @@ }

if (token[0] === '-') {
if (token[1] === '-') {
if (token[1] === '-' || token.length === 2) {
// long option
var option = command.long[token.substr(2)];
const option = command.getOption(token);
if (!option) {
// option doesn't exist
if (suggestPoint) {
return findVariants(command, token);
}
if (option === null) {
throw new CliError(`Unknown option: ${token}`);
}
throw new CliError('Unknown option: ' + token);
// process option params
i = consumeOptionParams(option, rawOptions, argv, i + 1, suggestPoint);
if (i === suggestPoint) {
return [];
}
// process option
processOption(option, command);
} else {
// short options sequence
if (!/^-[a-zA-Z]+$/.test(token)) {
throw new CliError('Wrong short option sequence: ' + token);
if (!/^-[a-zA-Z0-9]+$/.test(token)) {
throw new CliError(`Bad short option sequence: ${token}`);
}
for (var j = 1; j < token.length; j++) {
var option = command.short[token[j]];
for (let j = 1; j < token.length; j++) {
const option = command.getOption(`-${token[j]}`);
if (!option) {
throw new CliError('Unknown short option: -' + token[j]);
if (option === null) {
throw new CliError(`Unknown option "${token[j]}" in short option sequence: ${token}`);
}
if (option.maxArgsCount > 0 && token.length > 2) {
throw new CliError('Non-boolean option -' + token[j] + ' can\'t be used in short option sequence: ' + token);
if (option.params.maxCount > 0) {
throw new CliError(
`Non-boolean option "-${token[j]}" can\'t be used in short option sequence: ${token}`
);
}
processOption(option, command);
rawOptions.push({
option,
value: !option.default
});
}
}
} else {
if (command.commands[token] &&
(!command.params || resultChunk.args.length >= command.params.minArgsCount)) {
// switch control to another command
command = command.commands[token];
const subcommand = command.getCommand(token);
resultChunk = createResultChunk(command);
result.push(resultChunk);
if (subcommand !== null &&
context.args.length >= command.params.minCount) {
// set next command and rest argv
result.next = {
command: subcommand,
argv: argv.slice(i + 1)
};
break;
} else {
if (resultChunk.options.length === 0 &&
(command.params && resultChunk.args.length < command.params.maxArgsCount)) {
resultChunk.args.push(token);
continue;
if (rawOptions.length !== 0 ||
context.args.length >= command.params.maxCount) {
throw new CliError(`Unknown command: ${token}`);
}
if (suggestPoint) {
return findVariants(command, token);
}
throw new CliError('Unknown command: ' + token);
context.args.push(token);
}

@@ -156,14 +145,47 @@ }

if (suggest) {
if (resultChunk.literalArgs.length || noSuggestions) {
return [];
// final checks
if (suggestMode && !result.next) {
return findVariants(command, '');
} else if (context.args.length < command.params.minCount) {
throw new CliError(`Missed required argument(s) for command "${command.name}"`);
}
// create new option values storage
context.options = command.createOptionValues();
// process action option
const actionOption = rawOptions.find(({ option }) => option.action);
if (actionOption) {
const { option, value } = actionOption;
result.action = () => option.action(command, value, context);
result.next = null;
return result;
}
// apply config options
for (const { option, value } of rawOptions) {
if (option.config) {
context.options[option.name] = value;
}
}
return findVariants(command, suggestStartsWith);
} else if (command.params && resultChunk.args.length < command.params.minArgsCount) {
throw new CliError('Missed required argument(s) for command `' + command.name + '`');
// run apply config handler
command.handlers.applyConfig(context);
// apply regular options
for (const { option, value } of rawOptions) {
if (!option.config) {
context.options[option.name] = value;
}
}
// run context finish handler
command.handlers.finishContext(context);
// set action if no rest argv
if (!result.next) {
result.action = command.handlers.action;
}
return result;
};
module.exports.CliError = CliError;

@@ -8,3 +8,3 @@ {

"license": "MIT",
"version": "2.0.1",
"version": "3.0.0-beta.1",
"keywords": [

@@ -31,3 +31,4 @@ "cli",

"mocha": "^6.2.2",
"nyc": "^14.1.0"
"nyc": "^14.1.0",
"test-console": "^1.1.0"
},

@@ -34,0 +35,0 @@ "scripts": {

@@ -7,4 +7,10 @@ [![NPM version](https://img.shields.io/npm/v/clap.svg)](https://www.npmjs.com/package/clap)

Argument parser for command-line interfaces. It primary target to large tool sets that provides a lot of subcommands. Support for argument coercion and completion makes task run much easer, even if you doesn't use CLI.
A library for node.js to build command-line interfaces (CLI). With its help, making a simple CLI application is a trivial task. It equally excels in complex tools with a lot of subcommands and specific features. This library supports argument coercion and completion suggestion — typing the commands is much easier.
Inspired by [commander.js](https://github.com/tj/commander.js)
Features:
- TBD
## Usage

@@ -19,3 +25,3 @@

const myCommand = cli.command('my-command', '[optional-arg]')
const myCommand = cli.command('my-command [optional-arg]')
.description('Optional description')

@@ -26,13 +32,13 @@ .version('1.2.3')

.option('--bar [bar]', 'Option with optional argument')
.option('--baz [value]', 'Option with optional argument and normalize function', function(value) {
// calls on init and for any value set
return Number(value);
}, 123) // 123 is default
.action(function(args, literalArgs) {
.option('--baz [value]', 'Option with optional argument and normalize function',
value => Number(value),
123 // 123 is default
)
.action(function({ options, args, literalArgs }) {
// options is an object with collected values
// args goes before options
// literal args goes after --
// this.values is an object with collected values
// literal args goes after "--"
});
myCommand.run(); // runs with process.argv.slice(2)
myCommand.run(); // the same as "myCommnad.run(process.argv.slice(2))"
myCommand.run(['--foo', '123', '-b'])

@@ -52,4 +58,80 @@

## API
### Command
```
.command()
// definition
.description(value)
.version(value, usage, description, action)
.help(usage, description, action)
.option(usage, description, ...options)
.command(usageOrCommand)
.extend(fn, ...options)
.end()
// argv processing pipeline handler setters
.init(command, context)
.applyConfig(context)
.prerareContenxt(context)
.action(context)
// main methods
.parse(argv, suggest)
.run(argv)
// misc
.clone(deep)
.createOptionValues()
.getCommand(name)
.getCommands()
.getOption(name)
.getOptions()
.outputHelp()
```
### .option(usage, description, ...options)
There are two usage:
```
.option(usage, description, normalize, value)
.option(usage, description, options)
```
Where `options`:
```
{
default: any, // default value
normalize: (value, oldValue) => { ... }, // any value for option is passing through this function and its result stores as option value
shortcut: (value, oldValue) => { ... }, // for shortcut options, the handler is executed after the value is set, and its result (an object) is used as a source of values for other options
action: () => { ... }, // for an action option, which breaks regular args processing and preform and action (e.g. show help or version)
config: boolean // mark option is about config and should be applied before `applyConfig()`
}
```
### Argv processing
- `init(command, context)` // before arguments parsing
- invoke action option and exit if any
- apply **config** options
- `applyConfig(context)`
- apply all the rest options
- `prepareContext(context)` // after arguments parsing
- switch to next command -> command is prescending
- `init(command, context)`
- invoke action option and exit if any
- apply **config** options
- `applyConfig(context)`
- apply all the rest options
- `prepareContext(context)` // after arguments parsing
- switch to next command
- ...
- `action(context)` -> command is target
- `action(context)` -> command is target
## License
MIT
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc