diff options
Diffstat (limited to 'node_modules/commander/index.js')
-rw-r--r-- | node_modules/commander/index.js | 462 |
1 files changed, 348 insertions, 114 deletions
diff --git a/node_modules/commander/index.js b/node_modules/commander/index.js index 8378d19a2..a19c05d2e 100644 --- a/node_modules/commander/index.js +++ b/node_modules/commander/index.js @@ -1,19 +1,20 @@ - /** * Module dependencies. */ var EventEmitter = require('events').EventEmitter; var spawn = require('child_process').spawn; +var readlink = require('graceful-readlink').readlinkSync; var path = require('path'); var dirname = path.dirname; var basename = path.basename; +var fs = require('fs'); /** * Expose the root command. */ -exports = module.exports = new Command; +exports = module.exports = new Command(); /** * Expose `Command`. @@ -53,7 +54,7 @@ function Option(flags, description) { * @api private */ -Option.prototype.name = function(){ +Option.prototype.name = function() { return this.long .replace('--', '') .replace('no-', ''); @@ -67,9 +68,8 @@ Option.prototype.name = function(){ * @api private */ -Option.prototype.is = function(arg){ - return arg == this.short - || arg == this.long; +Option.prototype.is = function(arg) { + return arg == this.short || arg == this.long; }; /** @@ -82,9 +82,10 @@ Option.prototype.is = function(arg){ function Command(name) { this.commands = []; this.options = []; - this._execs = []; + this._execs = {}; + this._allowUnknownOption = false; this._args = []; - this._name = name; + this._name = name || ''; } /** @@ -116,46 +117,76 @@ Command.prototype.__proto__ = EventEmitter.prototype; * program * .command('setup') * .description('run remote setup commands') - * .action(function(){ + * .action(function() { * console.log('setup'); * }); * * program * .command('exec <cmd>') * .description('run the given remote command') - * .action(function(cmd){ + * .action(function(cmd) { * console.log('exec "%s"', cmd); * }); * * program + * .command('teardown <dir> [otherDirs...]') + * .description('run teardown commands') + * .action(function(dir, otherDirs) { + * console.log('dir "%s"', dir); + * if (otherDirs) { + * otherDirs.forEach(function (oDir) { + * console.log('dir "%s"', oDir); + * }); + * } + * }); + * + * program * .command('*') * .description('deploy the given env') - * .action(function(env){ + * .action(function(env) { * console.log('deploying "%s"', env); * }); * * program.parse(process.argv); * * @param {String} name - * @param {String} [desc] + * @param {String} [desc] for git-style sub-commands * @return {Command} the new command * @api public */ -Command.prototype.command = function(name, desc) { +Command.prototype.command = function(name, desc, opts) { + opts = opts || {}; var args = name.split(/ +/); var cmd = new Command(args.shift()); - if (desc) cmd.description(desc); - if (desc) this.executables = true; - if (desc) this._execs[cmd._name] = true; + + if (desc) { + cmd.description(desc); + this.executables = true; + this._execs[cmd._name] = true; + if (opts.isDefault) this.defaultExecutable = cmd._name; + } + + cmd._noHelp = !!opts.noHelp; this.commands.push(cmd); cmd.parseExpectedArgs(args); cmd.parent = this; + if (desc) return this; return cmd; }; /** + * Define argument syntax for the top-level command. + * + * @api public + */ + +Command.prototype.arguments = function (desc) { + return this.parseExpectedArgs(desc.split(/ +/)); +}; + +/** * Add an implicit `help [cmd]` subcommand * which invokes `--help` for the given command. * @@ -176,18 +207,33 @@ Command.prototype.addImplicitHelpCommand = function() { * @api public */ -Command.prototype.parseExpectedArgs = function(args){ +Command.prototype.parseExpectedArgs = function(args) { if (!args.length) return; var self = this; - args.forEach(function(arg){ + args.forEach(function(arg) { + var argDetails = { + required: false, + name: '', + variadic: false + }; + switch (arg[0]) { case '<': - self._args.push({ required: true, name: arg.slice(1, -1) }); + argDetails.required = true; + argDetails.name = arg.slice(1, -1); break; case '[': - self._args.push({ required: false, name: arg.slice(1, -1) }); + argDetails.name = arg.slice(1, -1); break; } + + if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') { + argDetails.variadic = true; + argDetails.name = argDetails.name.slice(0, -3); + } + if (argDetails.name) { + self._args.push(argDetails); + } }); return this; }; @@ -200,7 +246,7 @@ Command.prototype.parseExpectedArgs = function(args){ * program * .command('help') * .description('display verbose help') - * .action(function(){ + * .action(function() { * // output help here * }); * @@ -209,9 +255,9 @@ Command.prototype.parseExpectedArgs = function(args){ * @api public */ -Command.prototype.action = function(fn){ +Command.prototype.action = function(fn) { var self = this; - var listener = function(args, unknown){ + var listener = function(args, unknown) { // Parse any so-far unknown options args = args || []; unknown = unknown || []; @@ -231,9 +277,15 @@ Command.prototype.action = function(fn){ // Leftover arguments need to be pushed back. Fixes issue #56 if (parsed.args.length) args = parsed.args.concat(args); - self._args.forEach(function(arg, i){ + self._args.forEach(function(arg, i) { if (arg.required && null == args[i]) { self.missingArgument(arg.name); + } else if (arg.variadic) { + if (i !== self._args.length - 1) { + self.variadicArgNotLast(arg.name); + } + + args[i] = args.splice(i); } }); @@ -246,10 +298,12 @@ Command.prototype.action = function(fn){ args.push(self); } - fn.apply(this, args); + fn.apply(self, args); }; - this.parent.on(this._name, listener); - if (this._alias) this.parent.on(this._alias, listener); + var parent = this.parent || this; + var name = parent === this ? '*' : this._name; + parent.on(name, listener); + if (this._alias) parent.on(this._alias, listener); return this; }; @@ -302,14 +356,26 @@ Command.prototype.action = function(fn){ * @api public */ -Command.prototype.option = function(flags, description, fn, defaultValue){ +Command.prototype.option = function(flags, description, fn, defaultValue) { var self = this , option = new Option(flags, description) , oname = option.name() , name = camelcase(oname); // default as 3rd arg - if ('function' != typeof fn) defaultValue = fn, fn = null; + if (typeof fn != 'function') { + if (fn instanceof RegExp) { + var regex = fn; + fn = function(val, def) { + var m = regex.exec(val); + return m ? m[0] : def; + } + } + else { + defaultValue = fn; + fn = null; + } + } // preassign default value only for --no-*, [optional], or <required> if (false == option.bool || option.optional || option.required) { @@ -324,9 +390,11 @@ Command.prototype.option = function(flags, description, fn, defaultValue){ // when it's passed assign the value // and conditionally invoke the callback - this.on(oname, function(val){ + this.on(oname, function(val) { // coercion - if (null !== val && fn) val = fn(val, undefined === self[name] ? defaultValue : self[name]); + if (null !== val && fn) val = fn(val, undefined === self[name] + ? defaultValue + : self[name]); // unassigned or bool if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) { @@ -348,6 +416,18 @@ Command.prototype.option = function(flags, description, fn, defaultValue){ }; /** + * Allow unknown options on the command line. + * + * @param {Boolean} arg if `true` or omitted, no error will be thrown + * for unknown options. + * @api public + */ +Command.prototype.allowUnknownOption = function(arg) { + this._allowUnknownOption = arguments.length === 0 || arg; + return this; +}; + +/** * Parse `argv`, settings options and invoking commands when defined. * * @param {Array} argv @@ -355,7 +435,7 @@ Command.prototype.option = function(flags, description, fn, defaultValue){ * @api public */ -Command.prototype.parse = function(argv){ +Command.prototype.parse = function(argv) { // implicit help if (this.executables) this.addImplicitHelpCommand(); @@ -365,6 +445,12 @@ Command.prototype.parse = function(argv){ // guess name this._name = this._name || basename(argv[1], '.js'); + // github-style sub-commands with no sub-command + if (this.executables && argv.length < 3 && !this.defaultExecutable) { + // this user needs help + argv.push('--help'); + } + // process argv var parsed = this.parseOptions(this.normalize(argv.slice(2))); var args = this.args = parsed.args; @@ -373,7 +459,13 @@ Command.prototype.parse = function(argv){ // executable sub-commands var name = result.args[0]; - if (this._execs[name]) return this.executeSubCommand(argv, args, parsed.unknown); + if (this._execs[name] && typeof this._execs[name] != "function") { + return this.executeSubCommand(argv, args, parsed.unknown); + } else if (this.defaultExecutable) { + // use the default subcommand + args.unshift(name = this.defaultExecutable); + return this.executeSubCommand(argv, args, parsed.unknown); + } return result; }; @@ -400,24 +492,63 @@ Command.prototype.executeSubCommand = function(argv, args, unknown) { } // executable - var dir = dirname(argv[1]); - var bin = basename(argv[1], '.js') + '-' + args[0]; + var f = argv[1]; + // name of the subcommand, link `pm-install` + var bin = basename(f, '.js') + '-' + args[0]; - // check for ./<bin> first - var local = path.join(dir, bin); - // run it + // In case of globally installed, get the base dir where executable + // subcommand file should be located at + var baseDir + , link = readlink(f); + + // when symbolink is relative path + if (link !== f && link.charAt(0) !== '/') { + link = path.join(dirname(f), link) + } + baseDir = dirname(link); + + // prefer local `./<bin>` to bin in the $PATH + var localBin = path.join(baseDir, bin); + + // whether bin file is a js script with explicit `.js` extension + var isExplicitJS = false; + if (exists(localBin + '.js')) { + bin = localBin + '.js'; + isExplicitJS = true; + } else if (exists(localBin)) { + bin = localBin; + } + args = args.slice(1); - args.unshift(local); - var proc = spawn('node', args, { stdio: 'inherit', customFds: [0, 1, 2] }); - proc.on('error', function(err){ + + var proc; + if (process.platform !== 'win32') { + if (isExplicitJS) { + args.unshift(localBin); + // add executable arguments to spawn + args = (process.execArgv || []).concat(args); + + proc = spawn('node', args, { stdio: 'inherit', customFds: [0, 1, 2] }); + } else { + proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] }); + } + } else { + args.unshift(localBin); + proc = spawn(process.execPath, args, { stdio: 'inherit'}); + } + + proc.on('close', process.exit.bind(process)); + proc.on('error', function(err) { if (err.code == "ENOENT") { console.error('\n %s(1) does not exist, try --help\n', bin); } else if (err.code == "EACCES") { console.error('\n %s(1) not executable. try chmod or run with root\n', bin); } + process.exit(1); }); + // Store the reference to the child process this.runningCommand = proc; }; @@ -431,7 +562,7 @@ Command.prototype.executeSubCommand = function(argv, args, unknown) { * @api private */ -Command.prototype.normalize = function(args){ +Command.prototype.normalize = function(args) { var ret = [] , arg , lastOpt @@ -439,12 +570,18 @@ Command.prototype.normalize = function(args){ for (var i = 0, len = args.length; i < len; ++i) { arg = args[i]; - i > 0 && (lastOpt = this.optionFor(args[i-1])); + if (i > 0) { + lastOpt = this.optionFor(args[i-1]); + } - if (lastOpt && lastOpt.required) { - ret.push(arg); + if (arg === '--') { + // Honor option terminator + ret = ret.concat(args.slice(i)); + break; + } else if (lastOpt && lastOpt.required) { + ret.push(arg); } else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) { - arg.slice(1).split('').forEach(function(c){ + arg.slice(1).split('').forEach(function(c) { ret.push('-' + c); }); } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) { @@ -469,10 +606,8 @@ Command.prototype.normalize = function(args){ * @api private */ -Command.prototype.parseArgs = function(args, unknown){ - var cmds = this.commands - , len = cmds.length - , name; +Command.prototype.parseArgs = function(args, unknown) { + var name; if (args.length) { name = args[0]; @@ -502,7 +637,7 @@ Command.prototype.parseArgs = function(args, unknown){ * @api private */ -Command.prototype.optionFor = function(arg){ +Command.prototype.optionFor = function(arg) { for (var i = 0, len = this.options.length; i < len; ++i) { if (this.options[i].is(arg)) { return this.options[i]; @@ -519,7 +654,7 @@ Command.prototype.optionFor = function(arg){ * @api public */ -Command.prototype.parseOptions = function(argv){ +Command.prototype.parseOptions = function(argv) { var args = [] , len = argv.length , literal @@ -590,13 +725,30 @@ Command.prototype.parseOptions = function(argv){ }; /** + * Return an object containing options as key-value pairs + * + * @return {Object} + * @api public + */ +Command.prototype.opts = function() { + var result = {} + , len = this.options.length; + + for (var i = 0 ; i < len; i++) { + var key = camelcase(this.options[i].name()); + result[key] = key === 'version' ? this._version : this[key]; + } + return result; +}; + +/** * Argument `name` is missing. * * @param {String} name * @api private */ -Command.prototype.missingArgument = function(name){ +Command.prototype.missingArgument = function(name) { console.error(); console.error(" error: missing required argument `%s'", name); console.error(); @@ -611,7 +763,7 @@ Command.prototype.missingArgument = function(name){ * @api private */ -Command.prototype.optionMissingArgument = function(option, flag){ +Command.prototype.optionMissingArgument = function(option, flag) { console.error(); if (flag) { console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag); @@ -629,13 +781,27 @@ Command.prototype.optionMissingArgument = function(option, flag){ * @api private */ -Command.prototype.unknownOption = function(flag){ +Command.prototype.unknownOption = function(flag) { + if (this._allowUnknownOption) return; console.error(); console.error(" error: unknown option `%s'", flag); console.error(); process.exit(1); }; +/** + * Variadic argument with `name` is not the last argument as required. + * + * @param {String} name + * @api private + */ + +Command.prototype.variadicArgNotLast = function(name) { + console.error(); + console.error(" error: variadic arguments must be last `%s'", name); + console.error(); + process.exit(1); +}; /** * Set the program version to `str`. @@ -649,28 +815,28 @@ Command.prototype.unknownOption = function(flag){ * @api public */ -Command.prototype.version = function(str, flags){ +Command.prototype.version = function(str, flags) { if (0 == arguments.length) return this._version; this._version = str; flags = flags || '-V, --version'; this.option(flags, 'output the version number'); - this.on('version', function(){ - console.log(str); + this.on('version', function() { + process.stdout.write(str + '\n'); process.exit(0); }); return this; }; /** - * Set the description `str`. + * Set the description to `str`. * * @param {String} str * @return {String|Command} * @api public */ -Command.prototype.description = function(str){ - if (0 == arguments.length) return this._description; +Command.prototype.description = function(str) { + if (0 === arguments.length) return this._description; this._description = str; return this; }; @@ -683,7 +849,7 @@ Command.prototype.description = function(str){ * @api public */ -Command.prototype.alias = function(alias){ +Command.prototype.alias = function(alias) { if (0 == arguments.length) return this._alias; this._alias = alias; return this; @@ -697,17 +863,14 @@ Command.prototype.alias = function(alias){ * @api public */ -Command.prototype.usage = function(str){ - var args = this._args.map(function(arg){ - return arg.required - ? '<' + arg.name + '>' - : '[' + arg.name + ']'; +Command.prototype.usage = function(str) { + var args = this._args.map(function(arg) { + return humanReadableArgName(arg); }); - var usage = '[options' - + (this.commands.length ? '] [command' : '') - + ']' - + (this._args.length ? ' ' + args : ''); + var usage = '[options]' + + (this.commands.length ? ' [command]' : '') + + (this._args.length ? ' ' + args.join(' ') : ''); if (0 == arguments.length) return this._usage || usage; this._usage = str; @@ -716,14 +879,26 @@ Command.prototype.usage = function(str){ }; /** + * Get the name of the command + * + * @param {String} name + * @return {String|Command} + * @api public + */ + +Command.prototype.name = function() { + return this._name; +}; + +/** * Return the largest option length. * * @return {Number} * @api private */ -Command.prototype.largestOptionLength = function(){ - return this.options.reduce(function(max, option){ +Command.prototype.largestOptionLength = function() { + return this.options.reduce(function(max, option) { return Math.max(max, option.flags.length); }, 0); }; @@ -735,16 +910,15 @@ Command.prototype.largestOptionLength = function(){ * @api private */ -Command.prototype.optionHelp = function(){ +Command.prototype.optionHelp = function() { var width = this.largestOptionLength(); // Prepend the help information return [pad('-h, --help', width) + ' ' + 'output usage information'] - .concat(this.options.map(function(option){ - return pad(option.flags, width) - + ' ' + option.description; + .concat(this.options.map(function(option) { + return pad(option.flags, width) + ' ' + option.description; })) - .join('\n'); + .join('\n'); }; /** @@ -754,30 +928,36 @@ Command.prototype.optionHelp = function(){ * @api private */ -Command.prototype.commandHelp = function(){ +Command.prototype.commandHelp = function() { if (!this.commands.length) return ''; + + var commands = this.commands.filter(function(cmd) { + return !cmd._noHelp; + }).map(function(cmd) { + var args = cmd._args.map(function(arg) { + return humanReadableArgName(arg); + }).join(' '); + + return [ + cmd._name + + (cmd._alias ? '|' + cmd._alias : '') + + (cmd.options.length ? ' [options]' : '') + + ' ' + args + , cmd.description() + ]; + }); + + var width = commands.reduce(function(max, command) { + return Math.max(max, command[0].length); + }, 0); + return [ - '' + '' , ' Commands:' , '' - , this.commands.map(function(cmd){ - var args = cmd._args.map(function(arg){ - return arg.required - ? '<' + arg.name + '>' - : '[' + arg.name + ']'; - }).join(' '); - - return cmd._name - + (cmd._alias - ? '|' + cmd._alias - : '') - + (cmd.options.length - ? ' [options]' - : '') + ' ' + args - + (cmd.description() - ? '\n ' + cmd.description() - : '') - + '\n'; + , commands.map(function(cmd) { + var desc = cmd[1] ? ' ' + cmd[1] : ''; + return pad(cmd[0], width) + desc; }).join('\n').replace(/^/gm, ' ') , '' ].join('\n'); @@ -790,21 +970,42 @@ Command.prototype.commandHelp = function(){ * @api private */ -Command.prototype.helpInformation = function(){ - return [ - '' - , ' Usage: ' + this._name - + (this._alias - ? '|' + this._alias - : '') - + ' ' + this.usage() - , '' + this.commandHelp() - , ' Options:' +Command.prototype.helpInformation = function() { + var desc = []; + if (this._description) { + desc = [ + ' ' + this._description + , '' + ]; + } + + var cmdName = this._name; + if (this._alias) { + cmdName = cmdName + '|' + this._alias; + } + var usage = [ + '' + ,' Usage: ' + cmdName + ' ' + this.usage() + , '' + ]; + + var cmds = []; + var commandHelp = this.commandHelp(); + if (commandHelp) cmds = [commandHelp]; + + var options = [ + ' Options:' , '' , '' + this.optionHelp().replace(/^/gm, ' ') , '' , '' - ].join('\n'); + ]; + + return usage + .concat(cmds) + .concat(desc) + .concat(options) + .join('\n'); }; /** @@ -813,8 +1014,13 @@ Command.prototype.helpInformation = function(){ * @api public */ -Command.prototype.outputHelp = function(){ - process.stdout.write(this.helpInformation()); +Command.prototype.outputHelp = function(cb) { + if (!cb) { + cb = function(passthru) { + return passthru; + } + } + process.stdout.write(cb(this.helpInformation())); this.emit('--help'); }; @@ -824,8 +1030,8 @@ Command.prototype.outputHelp = function(){ * @api public */ -Command.prototype.help = function(){ - this.outputHelp(); +Command.prototype.help = function(cb) { + this.outputHelp(cb); process.exit(); }; @@ -838,7 +1044,7 @@ Command.prototype.help = function(){ */ function camelcase(flag) { - return flag.split('-').reduce(function(str, word){ + return flag.split('-').reduce(function(str, word) { return str + word[0].toUpperCase() + word.slice(1); }); } @@ -874,3 +1080,31 @@ function outputHelpIfNecessary(cmd, options) { } } } + +/** + * Takes an argument an returns its human readable equivalent for help usage. + * + * @param {Object} arg + * @return {String} + * @api private + */ + +function humanReadableArgName(arg) { + var nameOutput = arg.name + (arg.variadic === true ? '...' : ''); + + return arg.required + ? '<' + nameOutput + '>' + : '[' + nameOutput + ']' +} + +// for versions before node v0.8 when there weren't `fs.existsSync` +function exists(file) { + try { + if (fs.statSync(file).isFile()) { + return true; + } + } catch (e) { + return false; + } +} + |