cmdln
Advanced tools
Comparing version 4.4.0 to 5.0.0
@@ -7,2 +7,69 @@ # node-cmdln Changelog | ||
## 5.0.0 | ||
- [Backward incompatible change, issue #12] Cmdln's dispatch to `do_*` | ||
subcommand handler functions is no longer wrapped in a try/catch block, which | ||
means that exceptions from programmer errors will no longer be swallowed. | ||
Before this change a programmer error could not be distinguished from a | ||
command calling back with a runtime error. | ||
Take this example: | ||
```javascript | ||
var util = require('util'); | ||
var cmdln = require('.'); | ||
function CLI() { | ||
cmdln.Cmdln.call(this, {name: 'boom'}); | ||
} | ||
util.inherits(CLI, cmdln.Cmdln); | ||
CLI.prototype.do_hi = function (subcmd, opts, args, cb) { | ||
someMissingHelperFunction(); // OOPS | ||
cb(); | ||
}; | ||
if (require.main === module) { | ||
cmdln.main(new CLI()); | ||
} | ||
``` | ||
Before cmdln v5: | ||
```shell | ||
$ node boom.js hi | ||
boom: error: someMissingHelperFunction is not defined | ||
$ echo $? | ||
1 | ||
``` | ||
And as of this change: | ||
``` | ||
$ node boom.js hi | ||
/Users/trentm/tm/node-cmdln/boom.js:10 | ||
someMissingHelperFunction(); | ||
^ | ||
ReferenceError: someMissingHelperFunction is not defined | ||
at CLI.do_hi (/Users/trentm/tm/node-cmdln/boom.js:10:5) | ||
at CLI.dispatch (/Users/trentm/tm/node-cmdln/lib/cmdln.js:1315:17) | ||
at mainInit (/Users/trentm/tm/node-cmdln/lib/cmdln.js:727:14) | ||
at CLI.init (/Users/trentm/tm/node-cmdln/lib/cmdln.js:965:5) | ||
at CLI.cmdlnMain [as main] (/Users/trentm/tm/node-cmdln/lib/cmdln.js:702:10) | ||
at Object.main (/Users/trentm/tm/node-cmdln/lib/cmdln.js:1493:9) | ||
at Object.<anonymous> (/Users/trentm/tm/node-cmdln/boom.js:15:11) | ||
at Module._compile (internal/modules/cjs/loader.js:778:30) | ||
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10) | ||
at Module.load (internal/modules/cjs/loader.js:653:32) | ||
$ echo $? | ||
1 | ||
``` | ||
- Switch testing to node-tap (requires node v6 to run test suite). | ||
- Drop "support" for node 0.8. | ||
- Switch checking/formatting to eslint/prettier. | ||
## 4.4.0 | ||
@@ -9,0 +76,0 @@ |
606
lib/cmdln.js
/* | ||
* Copyright (c) 2018, Trent Mick | ||
* Copyright (c) 2018, Joyent, Inc. | ||
* Copyright 2019 Trent Mick | ||
* Copyright 2019 Joyent, Inc. | ||
*/ | ||
@@ -11,3 +11,3 @@ | ||
} else { | ||
debug = function () {}; | ||
debug = function _internalDebug() {}; | ||
} | ||
@@ -17,8 +17,2 @@ | ||
format = util.format; | ||
var p = console.warn; | ||
var child_process = require('child_process'), | ||
spawn = child_process.spawn, | ||
exec = child_process.exec; | ||
var os = require('os'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
@@ -32,4 +26,2 @@ | ||
// ---- globals | ||
@@ -50,6 +42,5 @@ | ||
// ---- internal support stuff | ||
function indent(s, indentation) { | ||
function indentLines(s, indentation) { | ||
if (!indentation) { | ||
@@ -74,3 +65,3 @@ indentation = ' '; | ||
} | ||
Object.keys(obj).forEach(function (k) { | ||
Object.keys(obj).forEach(function onKey(k) { | ||
target[k] = obj[k]; | ||
@@ -100,6 +91,5 @@ }); | ||
// Replace {{variable}} in `s` with the template data in `d`. | ||
function renderTemplate(s, d) { | ||
return s.replace(/{{([a-zA-Z]+)}}/g, function (match, key) { | ||
return s.replace(/{{([a-zA-Z]+)}}/g, function onVar(match, key) { | ||
return d.hasOwnProperty(key) ? d[key] : match; | ||
@@ -109,3 +99,2 @@ }); | ||
// ---- Errors | ||
@@ -125,3 +114,5 @@ | ||
assert.optionalString(options.code, 'options.code'); | ||
if (!options.code) options.code = 'Cmdln'; | ||
if (!options.code) { | ||
options.code = 'Cmdln'; | ||
} | ||
assert.optionalObject(options.cause, 'options.cause'); | ||
@@ -131,9 +122,12 @@ var self = this; | ||
var args = []; | ||
if (options.cause) args.push(options.cause); | ||
if (options.cause) { | ||
args.push(options.cause); | ||
} | ||
args.push(options.message); | ||
WError.apply(this, args); | ||
var extra = Object.keys(options).filter( | ||
function (k) { return ['cause', 'message'].indexOf(k) === -1; }); | ||
extra.forEach(function (k) { | ||
var extra = Object.keys(options).filter(function onKey(k) { | ||
return ['cause', 'message'].indexOf(k) === -1; | ||
}); | ||
extra.forEach(function onExtra(k) { | ||
self[k] = options[k]; | ||
@@ -145,3 +139,2 @@ }); | ||
/* | ||
@@ -204,11 +197,9 @@ * An error returned when there is an error parsing a command or subcmd's | ||
} | ||
lines.push(line + ' ...'); // The "..." for the args. | ||
lines.push(line + ' ...'); // The "..." for the args. | ||
errHelp = lines.join('\n'); | ||
} | ||
return errHelp; | ||
}; | ||
/* | ||
@@ -252,3 +243,4 @@ * An error returned when there is a subcmd usage error (wrong args). | ||
var synopses = err._cmdlnInst.synopsesFromSubcmd( | ||
err._cmdlnSubcmd || err._cmdlnInst); | ||
err._cmdlnSubcmd || err._cmdlnInst | ||
); | ||
if (synopses.length === 1) { | ||
@@ -262,3 +254,2 @@ errHelp = 'usage: ' + synopses[0]; | ||
function UnknownCommandError(cause, command) { | ||
@@ -291,4 +282,5 @@ if (command === undefined) { | ||
*/ | ||
UnknownCommandError.prototype.cmdlnErrHelpFromErr = | ||
function ucErrHelpFromErr(err) { | ||
UnknownCommandError.prototype.cmdlnErrHelpFromErr = function ucErrHelpFromErr( | ||
err | ||
) { | ||
if (!err || !err._cmdlnInst) { | ||
@@ -300,3 +292,4 @@ return ''; | ||
var FuzzySet = require('fuzzyset.js'); | ||
} catch (requireErr) { | ||
} catch (_requireErr) { | ||
// Silently provide no suggestions if don't have this dep. | ||
return ''; | ||
@@ -309,7 +302,8 @@ } | ||
var ge50 = []; | ||
candidates && candidates.forEach(function (candidate) { | ||
if (candidate[0] >= 0.3) { | ||
ge50.push(candidate[1]); | ||
} | ||
}); | ||
candidates && | ||
candidates.forEach(function onCandidate(candidate) { | ||
if (candidate[0] >= 0.3) { | ||
ge50.push(candidate[1]); | ||
} | ||
}); | ||
if (ge50.length > 0) { | ||
@@ -322,3 +316,2 @@ errHelp = 'Did you mean this?\n ' + ge50.join('\n '); | ||
function NoCommandError() { | ||
@@ -334,3 +327,2 @@ CmdlnError.call(this, { | ||
/** | ||
@@ -374,3 +366,3 @@ * # errHelp | ||
* var cli = new MyCmdlnSubclass(); | ||
* cli.main(argv, function (err) { | ||
* cli.main(argv, function finish(err) { | ||
* if (err) { | ||
@@ -398,10 +390,9 @@ * console.error('%s: error: %s', cli.name, err.message); | ||
if (err && typeof (err.cmdlnErrHelpFromErr) === 'function') { | ||
if (err && typeof err.cmdlnErrHelpFromErr === 'function') { | ||
errHelp = err.cmdlnErrHelpFromErr(err); | ||
} | ||
return (errHelp || ''); | ||
}; | ||
return errHelp || ''; | ||
} | ||
/** | ||
@@ -416,3 +407,3 @@ * Return the full command name (e.g. 'triton instance list') at which the | ||
if (err._cmdlnHandler) { | ||
if (typeof (err._cmdlnHandler) === 'object') { | ||
if (typeof err._cmdlnHandler === 'object') { | ||
name = err._cmdlnHandler.name; | ||
@@ -426,3 +417,2 @@ } else if (err._cmdlnSubcmd) { | ||
// ---- Cmdln object | ||
@@ -477,8 +467,8 @@ | ||
config = config || {}; | ||
assert.optionalString(config.name, 'config.name') | ||
assert.optionalString(config.desc, 'config.desc') | ||
assert.optionalString(config.name, 'config.name'); | ||
assert.optionalString(config.desc, 'config.desc'); | ||
assert.optionalArrayOfObject(config.options, 'config.options'); | ||
assert.optionalArrayOfString(config.synopses, 'config.synopses'); | ||
assert.optionalObject(config.helpOpts, 'config.helpOpts') | ||
assert.optionalString(config.helpBody, 'config.helpBody') | ||
assert.optionalObject(config.helpOpts, 'config.helpOpts'); | ||
assert.optionalString(config.helpBody, 'config.helpBody'); | ||
assert.optionalObject(config.helpSubcmds, 'config.helpSubcmds'); | ||
@@ -493,18 +483,27 @@ | ||
this.helpSubcmds = config.helpSubcmds || null; | ||
if (!this.helpOpts.indent) | ||
if (!this.helpOpts.indent) { | ||
this.helpOpts.indent = space(4); | ||
else if (typeof (this.helpOpts.indent) === 'number') | ||
} else if (typeof this.helpOpts.indent === 'number') { | ||
this.helpOpts.indent = space(this.helpOpts.indent); | ||
} | ||
if (!this.helpOpts.groupIndent) { | ||
var gilen = Math.round(this.helpOpts.indent.length / 2); | ||
this.helpOpts.groupIndent = space(gilen); | ||
} else if (typeof (this.helpOpts.groupIndent) === 'number') { | ||
} else if (typeof this.helpOpts.groupIndent === 'number') { | ||
this.helpOpts.groupIndent = space(this.helpOpts.groupIndent); | ||
} | ||
if (!this.helpOpts.maxCol) this.helpOpts.maxCol = 80; | ||
if (!this.helpOpts.minHelpCol) this.helpOpts.minHelpCol = 20; | ||
if (!this.helpOpts.maxHelpCol) this.helpOpts.maxHelpCol = 40; | ||
if (!this.helpOpts.maxCol) { | ||
this.helpOpts.maxCol = 80; | ||
} | ||
if (!this.helpOpts.minHelpCol) { | ||
this.helpOpts.minHelpCol = 20; | ||
} | ||
if (!this.helpOpts.maxHelpCol) { | ||
this.helpOpts.maxHelpCol = 40; | ||
} | ||
this.optParser = new dashdash.Parser( | ||
{options: this.options, interspersed: false}); | ||
this.optParser = new dashdash.Parser({ | ||
options: this.options, | ||
interspersed: false | ||
}); | ||
@@ -526,10 +525,12 @@ // Find the tree of constructors (typically just this and the Cmdln | ||
this._nameFromAlias = {}; | ||
prototypes.forEach(function (proto) { | ||
prototypes.forEach(function onProto(proto) { | ||
Object.keys(proto) | ||
.filter(function (funcname) { return /^do_/.test(funcname); }) | ||
.forEach(function (funcname) { | ||
.filter(function onProp(funcname) { | ||
return /^do_/.test(funcname); | ||
}) | ||
.forEach(function onDoFunc(funcname) { | ||
var name = self.subcmdFromFuncname(funcname); | ||
var func = proto[funcname]; | ||
var allAliases; | ||
if (func.prototype.__proto__ === Cmdln.prototype) { | ||
if (func.prototype instanceof Cmdln) { | ||
/** | ||
@@ -552,8 +553,14 @@ * This is a `Cmdln` sub-class. Create the sub-Cmdln | ||
*/ | ||
['options', 'help'].forEach(function (prop) { | ||
['options', 'help'].forEach(function onProp(prop) { | ||
if (func.hasOwnProperty(prop)) { | ||
throw new Error(format( | ||
'cannot set "%s.prototype.do_%s.%s" property ' | ||
+ 'because "do_%s" is a sub-Cmdln handler', | ||
self.constructor.name, name, prop, name)); | ||
throw new Error( | ||
format( | ||
'cannot set "%s.prototype.do_%s.%s" property ' + | ||
'because "do_%s" is a sub-Cmdln handler', | ||
self.constructor.name, | ||
name, | ||
prop, | ||
name | ||
) | ||
); | ||
} | ||
@@ -563,5 +570,6 @@ }); | ||
// Copy class properies like 'alias', 'hidden', etc. | ||
for (prop in func) { | ||
if (prop === 'super_') | ||
for (var prop in func) { | ||
if (prop === 'super_') { | ||
continue; | ||
} | ||
handler[prop] = func[prop]; | ||
@@ -578,9 +586,16 @@ } | ||
allAliases = func.aliases || []; | ||
if (func.hiddenAliases) | ||
if (func.hiddenAliases) { | ||
allAliases = allAliases.concat(func.hiddenAliases); | ||
allAliases.forEach(function (alias) { | ||
} | ||
allAliases.forEach(function onAlias(alias) { | ||
if (self._nameFromAlias[alias]) { | ||
throw new Error(format('ambiguous alias "%s": ' + | ||
'refers to commands "%s" and "%s"', alias, | ||
name, self._nameFromAlias[alias])); | ||
throw new Error( | ||
format( | ||
'ambiguous alias "%s": ' + | ||
'refers to commands "%s" and "%s"', | ||
alias, | ||
name, | ||
self._nameFromAlias[alias] | ||
) | ||
); | ||
} | ||
@@ -599,3 +614,3 @@ self._nameFromAlias[alias] = name; | ||
var matchedNames = []; | ||
enumOrder.forEach(function (enumName) { | ||
enumOrder.forEach(function onSubcmdName(enumName) { | ||
if (self.helpSubcmds.indexOf(enumName) === -1) { | ||
@@ -610,4 +625,4 @@ unmatchedNames.push(enumName); | ||
self._subcmdOrder = []; | ||
self.helpSubcmds.forEach(function (sc) { | ||
if (typeof (sc) === 'object') { | ||
self.helpSubcmds.forEach(function onHelpSubcmd(sc) { | ||
if (typeof sc === 'object') { | ||
assert.string(sc.group, 'helpSubcmds.*.group'); | ||
@@ -620,4 +635,9 @@ assert.optionalBool(sc.unmatched, 'helpSubcmds.*.unmatched'); | ||
if (++unmatchCount > 1) { | ||
throw (new Error(format('"unmatched" directive used ' + | ||
'more than once in "helpSubcmds" option: %j', sc))); | ||
throw new Error( | ||
format( | ||
'"unmatched" directive used ' + | ||
'more than once in "helpSubcmds" option: %j', | ||
sc | ||
) | ||
); | ||
} | ||
@@ -641,4 +661,7 @@ | ||
if (matchedNames.indexOf(sc) === -1) { | ||
throw (new Error('command handler included in help order ' + | ||
'but not found: ' + sc)); | ||
throw new Error( | ||
'command handler included in help order ' + | ||
'but not found: ' + | ||
sc | ||
); | ||
} | ||
@@ -649,4 +672,8 @@ self._subcmdOrder.push(sc); | ||
if (unmatchedNames.length > 0) { | ||
throw (new Error('"helpSubcmds" error: unmatched command ' + | ||
'handlers found: ' + unmatchedNames.join(', ') + '.')); | ||
throw new Error( | ||
'"helpSubcmds" error: unmatched command ' + | ||
'handlers found: ' + | ||
unmatchedNames.join(', ') + | ||
'.' | ||
); | ||
} | ||
@@ -656,9 +683,4 @@ } else { | ||
} | ||
// p('_subcmdOrder:', this._subcmdOrder); | ||
// p('_handlerFromName: ', this._handlerFromName); | ||
// p('_nameFromAlias: ', this._nameFromAlias); | ||
} | ||
/** | ||
@@ -690,3 +712,3 @@ * Cmdln mainline. | ||
var decoErrAndCb = function (err) { | ||
var decoErrAndCb = function decoErrAndCb(err) { | ||
if (err && !err._cmdlnInst) { | ||
@@ -698,9 +720,9 @@ err._cmdlnInst = self; | ||
var finiAndCb = function (err, subcmd) { | ||
var finiAndCb = function finiAndCb(err, subcmd) { | ||
debug('-> <%s>.fini(%j, err=%s)', self.name, subcmd, err); | ||
self.fini(subcmd, err, function (finiErr) { | ||
self.fini(subcmd, err, function mainFini(finiErr) { | ||
debug('<- <%s>.fini: finiErr=%s', self.name, finiErr); | ||
decoErrAndCb(finiErr || err); | ||
}); | ||
} | ||
}; | ||
@@ -716,4 +738,4 @@ try { | ||
debug('-> <%s>.init(%j, %j)', self.name, this.opts, args); | ||
self.init(this.opts, args, function (initErr) { | ||
debug('<- <%s>.init: initErr=%s', self.name, initErr) | ||
self.init(this.opts, args, function mainInit(initErr) { | ||
debug('<- <%s>.init: initErr=%s', self.name, initErr); | ||
if (initErr) { | ||
@@ -726,3 +748,3 @@ finiAndCb(initErr); | ||
finiAndCb(); | ||
return | ||
return; | ||
} | ||
@@ -737,27 +759,24 @@ | ||
var subcmd = args.shift(); | ||
try { | ||
debug('-> <%s>.dispatch({subcmd: %j, argv: %j})', | ||
self.name, subcmd, subcmdArgv); | ||
self.dispatch({subcmd: subcmd, argv: subcmdArgv}, | ||
function (dispErr) { | ||
debug('<- <%s>.dispatch: dispErr=%s', | ||
self.name, dispErr); | ||
/* | ||
* Do in nextTick to assure that we do not callback twice | ||
* if (a) dispatch returns synchronously and (b) there is | ||
* an exception raised during `finiAndCb`. | ||
*/ | ||
process.nextTick(function () { | ||
finiAndCb(dispErr, subcmd); | ||
}); | ||
}); | ||
} catch (ex) { | ||
debug('<- <%s>.dispatch crash: ex=%s', self.name, ex); | ||
finiAndCb(ex, subcmd); | ||
} | ||
debug( | ||
'-> <%s>.dispatch({subcmd: %j, argv: %j})', | ||
self.name, | ||
subcmd, | ||
subcmdArgv | ||
); | ||
self.dispatch({subcmd: subcmd, argv: subcmdArgv}, function mainDispatch( | ||
dispErr | ||
) { | ||
debug('<- <%s>.dispatch: dispErr=%s', self.name, dispErr); | ||
/* | ||
* Do in nextTick to assure that we do not callback twice | ||
* if (a) dispatch returns synchronously and (b) there is | ||
* an exception raised during `finiAndCb`. | ||
*/ | ||
process.nextTick(function mainDispatchNextTick() { | ||
finiAndCb(dispErr, subcmd); | ||
}); | ||
}); | ||
}); | ||
}; | ||
/* BEGIN JSSTYLED */ | ||
@@ -825,32 +844,37 @@ /** | ||
var context = opts.context || ''; | ||
var includeHidden = (opts.includeHidden === undefined | ||
? false : opts.includeHidden); | ||
var includeHidden = | ||
opts.includeHidden === undefined ? false : opts.includeHidden; | ||
// Top-level. | ||
spec.push(dashdash.bashCompletionSpecFromOptions({ | ||
options: self.options, | ||
context: context, | ||
includeHidden: includeHidden | ||
})); | ||
spec.push( | ||
dashdash.bashCompletionSpecFromOptions({ | ||
options: self.options, | ||
context: context, | ||
includeHidden: includeHidden | ||
}) | ||
); | ||
var aliases = []; | ||
var allAliases = []; | ||
Object.keys(this._nameFromAlias).sort().forEach(function (alias) { | ||
if (alias === '?') { | ||
// '?' as a Bash completion is painful. Also, '?' as a default | ||
// alias for 'help' should die. | ||
return; | ||
} | ||
Object.keys(this._nameFromAlias) | ||
.sort() | ||
.forEach(function onAlias(alias) { | ||
if (alias === '?') { | ||
// '?' as a Bash completion is painful. Also, '?' as a default | ||
// alias for 'help' should die. | ||
return; | ||
} | ||
var name = self._nameFromAlias[alias]; | ||
var handler = self._handlerFromName[name]; | ||
var name = self._nameFromAlias[alias]; | ||
var handler = self._handlerFromName[name]; | ||
if (includeHidden || !handler.hidden) { | ||
aliases.push(alias); | ||
} | ||
allAliases.push(alias); | ||
}); | ||
if (includeHidden || !handler.hidden) { | ||
aliases.push(alias); | ||
} | ||
allAliases.push(alias); | ||
}); | ||
spec.push(format('local cmd%s_subcmds="%s"', context, aliases.join(' '))); | ||
spec.push(format('local cmd%s_allsubcmds="%s"', context, | ||
allAliases.join(' '))); | ||
spec.push( | ||
format('local cmd%s_allsubcmds="%s"', context, allAliases.join(' ')) | ||
); | ||
@@ -860,37 +884,48 @@ // Subcmds. | ||
// one has specified a hidden subcmd you get full completion under it. | ||
Object.keys(this._nameFromAlias).sort().forEach(function (alias) { | ||
if (alias === '?') { | ||
return; | ||
} | ||
spec.push(''); | ||
var context_ = context + '__' + alias.replace(/-/g, '_'); | ||
var name = self._nameFromAlias[alias]; | ||
var handler = self._handlerFromName[name]; | ||
Object.keys(this._nameFromAlias) | ||
.sort() | ||
.forEach(function onAlias(alias) { | ||
if (alias === '?') { | ||
return; | ||
} | ||
spec.push(''); | ||
var context_ = context + '__' + alias.replace(/-/g, '_'); | ||
var name = self._nameFromAlias[alias]; | ||
var handler = self._handlerFromName[name]; | ||
if (typeof (handler.bashCompletionSpec) === 'function') { | ||
// This is a `Cmdln` subclass, i.e. a sub-CLI. | ||
var subspec = handler.bashCompletionSpec({ | ||
context: context_, | ||
includeHidden: opts.includeHidden | ||
}); | ||
if (subspec) { | ||
spec.push(subspec); | ||
if (typeof handler.bashCompletionSpec === 'function') { | ||
// This is a `Cmdln` subclass, i.e. a sub-CLI. | ||
var subspec = handler.bashCompletionSpec({ | ||
context: context_, | ||
includeHidden: opts.includeHidden | ||
}); | ||
if (subspec) { | ||
spec.push(subspec); | ||
} | ||
} else { | ||
if (handler.completionArgtypes) { | ||
assert.arrayOfString( | ||
handler.completionArgtypes, | ||
'do_' + name + '.completionArgtypes' | ||
); | ||
spec.push( | ||
format( | ||
'local cmd%s_argtypes="%s"', | ||
context_, | ||
handler.completionArgtypes.join(' ') | ||
) | ||
); | ||
} | ||
spec.push( | ||
dashdash.bashCompletionSpecFromOptions({ | ||
options: handler.options || [], | ||
context: context_, | ||
includeHidden: includeHidden | ||
}) | ||
); | ||
} | ||
} else { | ||
if (handler.completionArgtypes) { | ||
assert.arrayOfString(handler.completionArgtypes, | ||
'do_' + name + '.completionArgtypes'); | ||
spec.push(format('local cmd%s_argtypes="%s"', | ||
context_, handler.completionArgtypes.join(' '))); | ||
} | ||
spec.push(dashdash.bashCompletionSpecFromOptions({ | ||
options: handler.options || [], | ||
context: context_, | ||
includeHidden: includeHidden | ||
})); | ||
} | ||
}); | ||
}); | ||
return spec.join('\n'); | ||
} | ||
}; | ||
@@ -930,7 +965,8 @@ /** | ||
var template = fs.readFileSync( | ||
dashdash.BASH_COMPLETION_TEMPLATE_PATH, 'utf8'); | ||
dashdash.BASH_COMPLETION_TEMPLATE_PATH, | ||
'utf8' | ||
); | ||
return renderTemplate(template, data); | ||
}; | ||
/** | ||
@@ -945,3 +981,3 @@ * Handler called for an empty line of input. By default this prints help | ||
Cmdln.prototype.emptyLine = function emptyLine(callback) { | ||
this.printHelp(function (helpErr) { | ||
this.printHelp(function onFinish(helpErr) { | ||
callback(helpErr || new NoCommandError()); | ||
@@ -951,3 +987,2 @@ }); | ||
/** | ||
@@ -968,3 +1003,3 @@ * Post-option processing initialization of this Cmdln instance. | ||
if (opts.help) { | ||
this.do_help(args[0], opts, [], function (helpErr) { | ||
this.do_help(args[0], opts, [], function onFinish(helpErr) { | ||
callback(helpErr || false); | ||
@@ -977,3 +1012,2 @@ }); | ||
/** | ||
@@ -988,7 +1022,6 @@ * Hook run after the subcommand handler is run. | ||
*/ | ||
Cmdln.prototype.fini = function fini(subcmd, err, callback) { | ||
Cmdln.prototype.fini = function fini(_subcmd, _err, callback) { | ||
callback(); | ||
}; | ||
/** | ||
@@ -1014,6 +1047,3 @@ * Print top-level tool help. | ||
lines = lines.concat([ | ||
this._renderHelp('{{usage}}', this), | ||
'' | ||
]); | ||
lines = lines.concat([this._renderHelp('{{usage}}', this), '']); | ||
if (this.optParser.help) { | ||
@@ -1024,11 +1054,12 @@ lines.push('Options:'); | ||
lines = lines.concat([ | ||
'Commands:' | ||
]); | ||
lines = lines.concat(['Commands:']); | ||
// Automatic command line from `this._handlerFromName`. | ||
// TODO: same helpCol as for the opts above, textwrap, etc. | ||
var cmdTemplate = format('%s%%-%ds %s', | ||
indent, helpOpts.minHelpCol - indent.length - 2); | ||
this._subcmdOrder.forEach(function (name, idx) { | ||
if (typeof (name) === 'object') { | ||
var cmdTemplate = format( | ||
'%s%%-%ds %s', | ||
indent, | ||
helpOpts.minHelpCol - indent.length - 2 | ||
); | ||
this._subcmdOrder.forEach(function onSubcmdName(name, idx) { | ||
if (typeof name === 'object') { | ||
if (idx > 0) { | ||
@@ -1063,4 +1094,5 @@ /* | ||
} | ||
var summary = handler.desc || | ||
(typeof (handler.help) === 'string' && handler.help) || | ||
var summary = | ||
handler.desc || | ||
(typeof handler.help === 'string' && handler.help) || | ||
''; | ||
@@ -1084,3 +1116,2 @@ summary = summary.split('\n', 1)[0]; // just leading line | ||
Cmdln.prototype.subcmdFromFuncname = function subcmdFromFuncname(funcname) { | ||
@@ -1090,3 +1121,2 @@ return funcname.slice(3).replace(/_/g, '-'); | ||
/** | ||
@@ -1100,3 +1130,3 @@ * Return the handler function for the given sub-command string (aka the | ||
if (!name) { | ||
return; | ||
return undefined; | ||
} | ||
@@ -1106,3 +1136,2 @@ return this._handlerFromName[name]; | ||
/** | ||
@@ -1129,3 +1158,3 @@ * Return the help content for the given sub-command string (aka the | ||
if (handler.help) { | ||
if (typeof (handler.help) === 'function') { | ||
if (typeof handler.help === 'function') { | ||
return handler.help; | ||
@@ -1137,7 +1166,9 @@ } else { | ||
// This is likely a `Cmdln` subclass. | ||
return function subCliHelp(subcmd, opts, args, cb) { | ||
handler.do_help('help', opts, args.slice(1), function (helpErr) { | ||
return function subCliHelp(_subcmd, opts, args, cb) { | ||
handler.do_help('help', opts, args.slice(1), function onHelp( | ||
helpErr | ||
) { | ||
cb(helpErr || false); | ||
}); | ||
} | ||
}; | ||
} else { | ||
@@ -1148,3 +1179,2 @@ return null; | ||
Cmdln.prototype._renderHelp = function _renderHelp(template, handler, alias) { | ||
@@ -1158,4 +1188,6 @@ assert.string(template, 'template'); | ||
if (synopses.length) { | ||
help = help.replace('{{usage}}', | ||
'Usage:\n' + indent(synopses.join('\n'))); | ||
help = help.replace( | ||
'{{usage}}', | ||
'Usage:\n' + indentLines(synopses.join('\n')) | ||
); | ||
} | ||
@@ -1165,7 +1197,9 @@ } | ||
var parser = new dashdash.Parser({options: handler.options}); | ||
var helpOpts = (handler.helpOpts | ||
var helpOpts = handler.helpOpts | ||
? objMerge(this.helpOpts, handler.helpOpts) | ||
: this.helpOpts); | ||
help = help.replace('{{options}}', | ||
'Options:\n' + parser.help(helpOpts)); | ||
: this.helpOpts; | ||
help = help.replace( | ||
'{{options}}', | ||
'Options:\n' + parser.help(helpOpts) | ||
); | ||
} | ||
@@ -1204,9 +1238,9 @@ help = help.trimRight(); | ||
Cmdln.prototype.synopsesFromSubcmd = function synopsesFromSubcmd(subcmd) { | ||
assert.ok(['function', 'string', 'object'].indexOf(typeof (subcmd)) !== -1); | ||
assert.ok(['function', 'string', 'object'].indexOf(typeof subcmd) !== -1); | ||
var name, handler; | ||
if (typeof (subcmd) === 'function') { | ||
if (typeof subcmd === 'function') { | ||
handler = subcmd; | ||
name = this.subcmdFromFuncname(handler.name); | ||
} else if (typeof (subcmd) === 'object') { | ||
} else if (typeof subcmd === 'object') { | ||
name = subcmd.name; | ||
@@ -1232,3 +1266,2 @@ handler = subcmd; | ||
/** | ||
@@ -1260,3 +1293,3 @@ * Dispatch to the appropriate "do_SUBCMD" function. | ||
var subcmd, argv, opts, args; | ||
if (typeof (callback) === 'function') { | ||
if (typeof callback === 'function') { | ||
// New call signature. | ||
@@ -1280,15 +1313,7 @@ assert.string(dispatchOpts.subcmd, 'dispatchOpts.subcmd'); | ||
assert.func(callback, 'callback'); | ||
assert.ok((argv && !opts && !args) || (!argv && opts && args), | ||
'must specify only one of "argv" *or* "opts/args"'); | ||
var self = this; | ||
assert.ok( | ||
(argv && !opts && !args) || (!argv && opts && args), | ||
'must specify only one of "argv" *or* "opts/args"' | ||
); | ||
var finish = function (err) { | ||
if (err) { | ||
if (!err._cmdlnInst) err._cmdlnInst = self; | ||
if (!err._cmdlnSubcmd && subcmd) err._cmdlnSubcmd = subcmd; | ||
if (!err._cmdlnHandler && handler) err._cmdlnHandler = handler; | ||
} | ||
callback(err); | ||
} | ||
if (argv) { | ||
@@ -1299,3 +1324,19 @@ opts = {}; | ||
var self = this; | ||
var handler = this.handlerFromSubcmd(subcmd); | ||
var finish = function finish(err) { | ||
if (err) { | ||
if (!err._cmdlnInst) { | ||
err._cmdlnInst = self; | ||
} | ||
if (!err._cmdlnSubcmd && subcmd) { | ||
err._cmdlnSubcmd = subcmd; | ||
} | ||
if (!err._cmdlnHandler && handler) { | ||
err._cmdlnHandler = handler; | ||
} | ||
} | ||
callback(err); | ||
}; | ||
if (!handler) { | ||
@@ -1306,8 +1347,8 @@ this.defaultHandler(subcmd, opts, args, finish); | ||
if (typeof (handler.main) === 'function') { | ||
if (typeof handler.main === 'function') { | ||
// This is likely a `Cmdln` subclass instance, i.e. a subcli. | ||
(function callCmdlnHandler(subcmd, opts, args, cb) { | ||
var argv = ['', ''].concat(args); | ||
handler.main(argv, cb); | ||
}).call(this, subcmd, opts, args, finish); | ||
(function callCmdlnHandler(_subcmd, _opts, subArgs, subCb) { | ||
var subArgv = ['', ''].concat(subArgs); | ||
handler.main(subArgv, subCb); | ||
}.call(this, subcmd, opts, args, finish)); | ||
} else { | ||
@@ -1321,6 +1362,10 @@ // This is a vanilla `do_SUBCMD` function on the Cmdln class. | ||
options: handler.options, | ||
interspersed: (handler.interspersedOptions !== undefined | ||
? handler.interspersedOptions : true), | ||
allowUnknown: (handler.allowUnknownOptions !== undefined | ||
? handler.allowUnknownOptions : false) | ||
interspersed: | ||
handler.interspersedOptions !== undefined | ||
? handler.interspersedOptions | ||
: true, | ||
allowUnknown: | ||
handler.allowUnknownOptions !== undefined | ||
? handler.allowUnknownOptions | ||
: false | ||
}); | ||
@@ -1340,3 +1385,7 @@ opts = parser.parse(argv, 3); | ||
Cmdln.prototype.defaultHandler = function defaultHandler( | ||
subcmd, opts, args, cb) { | ||
subcmd, | ||
opts, | ||
args, | ||
cb | ||
) { | ||
assert.string(subcmd, 'subcmd'); | ||
@@ -1362,10 +1411,6 @@ assert.optionalObject(opts, 'opts'); | ||
try { | ||
var help = this.helpFromSubcmd(alias); | ||
} catch (e) { | ||
callback(e); | ||
} | ||
var help = this.helpFromSubcmd(alias); | ||
if (!help) { | ||
callback(new CmdlnError({message: format('no help for "%s"', alias)})); | ||
} else if (typeof (help) === 'function') { | ||
} else if (typeof help === 'function') { | ||
help(subcmd, opts, args, callback); | ||
@@ -1380,4 +1425,2 @@ } else { | ||
// ---- convenience main function for a script | ||
@@ -1459,3 +1502,3 @@ | ||
*/ | ||
if (typeof (cli) === 'function') { | ||
if (typeof cli === 'function') { | ||
cli = new cli(); | ||
@@ -1498,6 +1541,11 @@ var argv = options; | ||
if (options.hasOwnProperty('finale')) { | ||
assert.ok(VALID_FINALES.indexOf(options.finale) !== -1, | ||
format('invalid options.finale "%s": valid values are "%s"', | ||
options.finale, VALID_FINALES.join('", "'))); | ||
finale = options.finale | ||
assert.ok( | ||
VALID_FINALES.indexOf(options.finale) !== -1, | ||
format( | ||
'invalid options.finale "%s": valid values are "%s"', | ||
options.finale, | ||
VALID_FINALES.join('", "') | ||
) | ||
); | ||
finale = options.finale; | ||
} else { | ||
@@ -1508,16 +1556,23 @@ finale = 'softexit'; | ||
assert.func(options.callback, 'options.callback'); | ||
assert.equal(finale, 'callback', | ||
'options.callback provided, but options.finale is not "callback"'); | ||
assert.equal( | ||
finale, | ||
'callback', | ||
'options.callback provided, but options.finale is not "callback"' | ||
); | ||
} | ||
cli.main(options.argv, function (err) { | ||
var exitStatus = (err ? err.exitStatus || 1 : 0); | ||
cli.main(options.argv, function mainFinish(err) { | ||
var exitStatus = err ? err.exitStatus || 1 : 0; | ||
// We show (i.e. console.error) an error by default, unless turned | ||
// off via `options.showErr` or `cli.showErr`. | ||
var showErr = (options.showErr !== undefined ? options.showErr | ||
: (cli.showErr !== undefined ? cli.showErr : true)); | ||
var showErr = | ||
options.showErr !== undefined | ||
? options.showErr | ||
: cli.showErr !== undefined | ||
? cli.showErr | ||
: true; | ||
if (err && showErr) { | ||
var code = (err.body ? err.body.code : err.code); | ||
var code = err.body ? err.body.code : err.code; | ||
if (code === 'NoCommand' && !options.showNoCommandErr) { | ||
@@ -1535,4 +1590,6 @@ /* jsl:pass */ | ||
// collected error messages. | ||
var showErrStack = (options.showErrStack === undefined | ||
? cli.showErrStack : options.showErrStack); | ||
var showErrStack = | ||
options.showErrStack === undefined | ||
? cli.showErrStack | ||
: options.showErrStack; | ||
@@ -1543,11 +1600,14 @@ var errDetails; | ||
if (err.ase_errors.length === 1) { | ||
errDetails = (showErrStack | ||
errDetails = showErrStack | ||
? VError.fullStack(err.ase_errors[0]) | ||
: err.ase_errors[0].message); | ||
: err.ase_errors[0].message; | ||
} else { | ||
errDetails = err.ase_errors.length + ' errors:\n ' | ||
+ err.ase_errors.map( | ||
function (e) { | ||
errDetails = | ||
err.ase_errors.length + | ||
' errors:\n ' + | ||
err.ase_errors | ||
.map(function onError(e) { | ||
return e.message; | ||
}).join('\n '); | ||
}) | ||
.join('\n '); | ||
if (showErrStack) { | ||
@@ -1558,4 +1618,5 @@ errDetails += '\n' + VError.fullStack(err); | ||
} else { | ||
errDetails = (showErrStack | ||
? VError.fullStack(err): err.message); | ||
errDetails = showErrStack | ||
? VError.fullStack(err) | ||
: err.message; | ||
} | ||
@@ -1566,14 +1627,19 @@ | ||
if (Object.keys(errInfo).length !== 0) { | ||
errDetails += '\nerror info:\n' | ||
+ indent(JSON.stringify(errInfo, null, 4)); | ||
errDetails += | ||
'\nerror info:\n' + | ||
indentLines(JSON.stringify(errInfo, null, 4)); | ||
} | ||
} | ||
console.error('%s: error%s: %s', | ||
console.error( | ||
'%s: error%s: %s', | ||
nameFromErr(err), | ||
(options.showCode && code ? format(' (%s)', code) : ''), | ||
errDetails); | ||
options.showCode && code ? format(' (%s)', code) : '', | ||
errDetails | ||
); | ||
var showErrHelp = (options.showErrHelp === undefined | ||
? true : options.showErrHelp); | ||
var showErrHelp = | ||
options.showErrHelp === undefined | ||
? true | ||
: options.showErrHelp; | ||
if (showErrHelp) { | ||
@@ -1624,4 +1690,2 @@ var errHelp = errHelpFromErr(err); | ||
// ---- exports | ||
@@ -1628,0 +1692,0 @@ |
{ | ||
"name": "cmdln", | ||
"version": "4.4.0", | ||
"version": "5.0.0", | ||
"description": "helper lib for creating CLI tools with subcommands; think `git`, `svn`, `zfs`", | ||
"author": "Trent Mick (http://trentm.com)", | ||
"main": "./lib/cmdln.js", | ||
"repository": { | ||
@@ -12,6 +11,16 @@ "type": "git", | ||
}, | ||
"engines": ["node >=0.8.0"], | ||
"keywords": ["cmdln", "cli", "tool", "bash", "completion"], | ||
"engines": [ | ||
"node >=0.10.0" | ||
], | ||
"keywords": [ | ||
"cmdln", | ||
"cli", | ||
"tool", | ||
"bash", | ||
"completion" | ||
], | ||
"license": "MIT", | ||
"files": [ | ||
"lib" | ||
], | ||
"dependencies": { | ||
@@ -21,8 +30,15 @@ "assert-plus": "^1.0.0", | ||
"verror": "^1.6.0", | ||
"dashdash": "^1.14.1", | ||
"dashdash": "^1.14.1" | ||
}, | ||
"optionalDependencies": { | ||
"fuzzyset.js": "^0.0.1" | ||
}, | ||
"devDependencies": { | ||
"nodeunit": "^0.9.0" | ||
"eslint": "^4.19.1", | ||
"eslint-config-prettier": "^2.9.0", | ||
"eslint-plugin-joyent": "~2.0.0", | ||
"eslint-plugin-prettier": "^2.6.0", | ||
"prettier": "1.18.2", | ||
"tap": "^12.7.0" | ||
} | ||
} |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
1510
0
93733
6
5
- Removedfuzzyset.js@^0.0.1