+43
-0
| # node-cmdln Changelog | ||
| ## 3.1.0 | ||
| - [issue #5] Add `helpSubcmds` constructor option to allow control over the | ||
| output of the "Commands:" section of top-level help. For example, this code: | ||
| helpSubcmds: [ | ||
| 'help', | ||
| { group: '' }, | ||
| 'in-empty-group', | ||
| { group: 'Most Excellent Commands' }, | ||
| 'awesome', | ||
| { group: 'Other Commands', unmatched: true } | ||
| ] | ||
| yields help output something like: | ||
| ... | ||
| Commands: | ||
| help (?) Help on a specific sub-command. | ||
| in-empty-group Do in-empty-group things. | ||
| Most Excellent Commands: | ||
| awesome Do awesome things. | ||
| Other Commands: | ||
| something-else Do something-else things. | ||
| By Josh Clulow. | ||
| - [issue #4] Add `Cmdln.prototype.helpFromSubcmd(subcmd)` to get the help string | ||
| for the given subcommand. This can be useful for tools that want to emit usage | ||
| information as part of a usage error. E.g.: | ||
| MyCLI.prototype.do_frob = do_frob(subcmd, opts, args, cb) { | ||
| if (!opts.frobber) { | ||
| return callback(new Error('you forgot the frobber\n' + | ||
| this.helpFromSubcmd(subcmd))); | ||
| } | ||
| // ... | ||
| }; | ||
| ## 3.0.2 | ||
@@ -4,0 +47,0 @@ |
+165
-24
| /* | ||
| * Copyright (c) 2014, Trent Mick. All rights reserved. | ||
| * Copyright (c) 2014, Joyent, Inc. All rights reserved. | ||
| * Copyright 2015 Joyent, Inc. | ||
| */ | ||
| var DEBUG = false; | ||
@@ -166,2 +167,16 @@ if (DEBUG) { | ||
| * help output. | ||
| * - @param helpSubcmds {Array} Control the output of the "Commands:" | ||
| * section of top-level help output. By default all commands are | ||
| * listed in the order defined in code. `helpSubcmds` allows one to | ||
| * specify the order and, optionally, groupings with group headers. E.g.: | ||
| * helpSubcmds: [ | ||
| * 'help', | ||
| * { group: '' }, // an empty group, results in a blank line | ||
| * 'foo', | ||
| * 'bar', | ||
| * { group: 'Bling' }, // a group header | ||
| * 'bloom', | ||
| * // Use `unmatched: true` to include remaining subcmds. | ||
| * { group: 'Other Commands', unmatched: true } | ||
| * ] | ||
| * - @param options {Array} Custom options (in the format used by | ||
@@ -188,2 +203,3 @@ * [dashdash](https://github.com/trentm/node-dashdash)). If not | ||
| assert.optionalString(config.helpBody, 'config.helpBody') | ||
| assert.optionalObject(config.helpSubcmds, 'config.helpSubcmds'); | ||
@@ -195,2 +211,3 @@ this.name = config.name || this.constructor.name.toLowerCase(); | ||
| this.helpBody = config.helpBody; | ||
| this.helpSubcmds = config.helpSubcmds || null; | ||
| if (!this.helpOpts.indent) | ||
@@ -200,2 +217,8 @@ this.helpOpts.indent = space(4); | ||
| 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') { | ||
| this.helpOpts.groupIndent = space(this.helpOpts.groupIndent); | ||
| } | ||
| if (!this.helpOpts.maxCol) this.helpOpts.maxCol = 80; | ||
@@ -220,3 +243,3 @@ if (!this.helpOpts.minHelpCol) this.helpOpts.minHelpCol = 20; | ||
| // Load subcmds (do_* methods) and aliases (`do_*.aliases`). | ||
| this._subcmdOrder = []; | ||
| var enumOrder = []; | ||
| this._handlerFromName = {}; | ||
@@ -281,3 +304,3 @@ this._nameFromAlias = {}; | ||
| } | ||
| self._subcmdOrder.push(name); | ||
| enumOrder.push(name); | ||
| self._nameFromAlias[name] = name; | ||
@@ -289,2 +312,63 @@ (func.aliases || []).forEach(function (alias) { | ||
| }); | ||
| if (self.helpSubcmds !== null) { | ||
| /* | ||
| * Reconcile the provided subcommand order (and group headings) with | ||
| * the discovered options. | ||
| */ | ||
| var unmatchedNames = []; | ||
| var matchedNames = []; | ||
| enumOrder.forEach(function (enumName) { | ||
| if (self.helpSubcmds.indexOf(enumName) === -1) { | ||
| unmatchedNames.push(enumName); | ||
| } else { | ||
| matchedNames.push(enumName); | ||
| } | ||
| }); | ||
| var unmatchCount = 0; | ||
| self._subcmdOrder = []; | ||
| self.helpSubcmds.forEach(function (sc) { | ||
| if (typeof (sc) === 'object') { | ||
| assert.string(sc.group, 'helpSubcmds.*.group'); | ||
| assert.optionalBool(sc.unmatched, 'helpSubcmds.*.unmatched'); | ||
| self._subcmdOrder.push(sc); | ||
| if (sc.unmatched) { | ||
| if (++unmatchCount > 1) { | ||
| throw (new Error(format('"unmatched" directive used ' + | ||
| 'more than once in "helpSubcmds" option: %j', sc))); | ||
| } | ||
| /* | ||
| * Include all of the unmatched names here: | ||
| */ | ||
| while (unmatchedNames.length > 0) { | ||
| self._subcmdOrder.push(unmatchedNames.shift()); | ||
| } | ||
| } | ||
| return; | ||
| } | ||
| /* | ||
| * If this is not a group heading object, it must be the name | ||
| * of a handler to include in the output: | ||
| */ | ||
| assert.string(sc); | ||
| if (matchedNames.indexOf(sc) === -1) { | ||
| throw (new Error('command handler included in help order ' + | ||
| 'but not found: ' + sc)); | ||
| } | ||
| self._subcmdOrder.push(sc); | ||
| }); | ||
| if (unmatchedNames.length > 0) { | ||
| throw (new Error('"helpSubcmds" error: unmatched command ' + | ||
| 'handlers found: ' + unmatchedNames.join(', ') + '.')); | ||
| } | ||
| } else { | ||
| self._subcmdOrder = enumOrder; | ||
| } | ||
| // p('_subcmdOrder:', this._subcmdOrder); | ||
@@ -416,2 +500,3 @@ // p('_handlerFromName: ', this._handlerFromName); | ||
| var indent = helpOpts.indent; | ||
| var gindent = helpOpts.groupIndent; | ||
@@ -444,3 +529,25 @@ var lines = []; | ||
| indent, helpOpts.minHelpCol - indent.length - 2); | ||
| this._subcmdOrder.forEach(function (name) { | ||
| this._subcmdOrder.forEach(function (name, idx) { | ||
| if (typeof (name) === 'object') { | ||
| if (idx > 0) { | ||
| /* | ||
| * If this is not the first line, print a blank line to | ||
| * visually separate this group from previous lines. | ||
| */ | ||
| lines.push(''); | ||
| } | ||
| /* | ||
| * If the group name is not blank, print the group heading. | ||
| * If it is blank, the caller only wants the separator line | ||
| * printed above. | ||
| */ | ||
| assert.string(name.group, 'name.group'); | ||
| if (name.group) { | ||
| lines.push(format('%s%s:', gindent, name.group)); | ||
| } | ||
| return; | ||
| } | ||
| assert.string(name, 'name'); | ||
| var handler = self._handlerFromName[name]; | ||
@@ -454,5 +561,5 @@ if (handler.hidden) { | ||
| } | ||
| var summary = handler.desc | ||
| || (typeof (handler.help) === 'string' && handler.help) | ||
| || ''; | ||
| var summary = handler.desc || | ||
| (typeof (handler.help) === 'string' && handler.help) || | ||
| ''; | ||
| summary = summary.split('\n', 1)[0]; // just leading line | ||
@@ -491,2 +598,42 @@ summary = summary.replace(/{{name}}/g, self.name); | ||
| /** | ||
| * Return the help content for the given sub-command string (aka the | ||
| * subcmd *name*). | ||
| * | ||
| * *Limitation*: If the command has a `.help` *function*, then the function is | ||
| * returned. It is up to the caller to call it, if they like. The `.help` | ||
| * function is defined to be async and is *not* defined to return the | ||
| * string, so running it isn't useful here. | ||
| * | ||
| * @param alias {String} The sub-command name or alias. | ||
| * @throws `UnknownCommandError` if there is no such sub-command. | ||
| * @returns The help string, `null` if no help, or the `.help` function if | ||
| * so defined for that sub-command. | ||
| */ | ||
| Cmdln.prototype.helpFromSubcmd = function helpFromSubcmd(alias) { | ||
| var handler = this.handlerFromSubcmd(alias); | ||
| if (!handler) { | ||
| throw new UnknownCommandError(alias); | ||
| } | ||
| if (!handler.help) { | ||
| return; | ||
| } else if (typeof (handler.help) === 'function') { | ||
| return handler.help; | ||
| } else { | ||
| var help = handler.help; | ||
| help = help.replace(/{{name}}/g, this.name); | ||
| if (~help.indexOf('{{options}}') && handler.options) { | ||
| var parser = new dashdash.Parser({options: handler.options}); | ||
| var helpOpts = (handler.helpOpts | ||
| ? objMerge(this.helpOpts, handler.helpOpts) : this.helpOpts); | ||
| help = help.replace('{{options}}', | ||
| 'Options:\n' + parser.help(helpOpts)); | ||
| } | ||
| help = help.trimRight(); | ||
| return help; | ||
| } | ||
| }; | ||
| /** | ||
| * Dispatch to the appropriate "do_SUBCMD" function. | ||
@@ -518,3 +665,2 @@ */ | ||
| Cmdln.prototype.do_help = function do_help(subcmd, opts, args, callback) { | ||
| var self = this; | ||
| if (args.length === 0) { | ||
@@ -525,4 +671,4 @@ this.printHelp(callback); | ||
| var alias = args[0]; | ||
| var name = this._nameFromAlias[alias]; | ||
| if (!name) { | ||
| var handler = this.handlerFromSubcmd(alias); | ||
| if (!handler) { | ||
| callback(new UnknownCommandError(alias)); | ||
@@ -532,18 +678,13 @@ return; | ||
| var func = this._handlerFromName[name]; | ||
| if (!func.help) { | ||
| try { | ||
| var help = this.helpFromSubcmd(alias); | ||
| } catch (e) { | ||
| callback(e); | ||
| } | ||
| if (!help) { | ||
| callback(new CmdlnError({message: format('no help for "%s"', alias)})); | ||
| } else if (typeof (func.help) === 'function') { | ||
| func.help(subcmd, opts, args, callback); | ||
| } else if (typeof (help) === 'function') { | ||
| help(subcmd, opts, args, callback); | ||
| } else { | ||
| var help = func.help; | ||
| help = help.replace(/{{name}}/g, self.name); | ||
| if (~help.indexOf('{{options}}') && func.options) { | ||
| var parser = new dashdash.Parser({options: func.options}); | ||
| var helpOpts = (func.helpOpts | ||
| ? objMerge(this.helpOpts, func.helpOpts) : this.helpOpts); | ||
| help = help.replace('{{options}}', | ||
| 'Options:\n' + parser.help(helpOpts)); | ||
| } | ||
| console.log(help.trimRight()); | ||
| console.log(help); | ||
| callback(); | ||
@@ -550,0 +691,0 @@ } |
+1
-1
| { | ||
| "name": "cmdln", | ||
| "version": "3.0.2", | ||
| "version": "3.1.0", | ||
| "description": "helper lib for creating CLI tools with subcommands; think `git`, `svn`, `zfs`", | ||
@@ -5,0 +5,0 @@ "author": "Trent Mick (http://trentm.com)", |
+4
-0
@@ -203,3 +203,7 @@ `node-cmdln` is a node.js helper lib for creating CLI tools with subcommands | ||
| - `<Cmdln>.helpFromSubcmd(<subcmd>)` will return the help string for | ||
| that subcmd *or*, if defined, the help function defined for that subcmd. | ||
| This is used by the default `do_help` implementation. | ||
| ## `cmdln.main()` | ||
@@ -206,0 +210,0 @@ |
+0
-1
| # 2.1? | ||
| - support for sub-subcommands | ||
| - bash completion generation support | ||
@@ -5,0 +4,0 @@ - docs: see below |
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
48213
15.45%745
20.55%219
1.86%5
25%