/**1* Module dependencies.2*/34var EventEmitter = require('events').EventEmitter;5var spawn = require('child_process').spawn;6var readlink = require('graceful-readlink').readlinkSync;7var path = require('path');8var dirname = path.dirname;9var basename = path.basename;10var fs = require('fs');1112/**13* Expose the root command.14*/1516exports = module.exports = new Command();1718/**19* Expose `Command`.20*/2122exports.Command = Command;2324/**25* Expose `Option`.26*/2728exports.Option = Option;2930/**31* Initialize a new `Option` with the given `flags` and `description`.32*33* @param {String} flags34* @param {String} description35* @api public36*/3738function Option(flags, description) {39this.flags = flags;40this.required = ~flags.indexOf('<');41this.optional = ~flags.indexOf('[');42this.bool = !~flags.indexOf('-no-');43flags = flags.split(/[ ,|]+/);44if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();45this.long = flags.shift();46this.description = description || '';47}4849/**50* Return option name.51*52* @return {String}53* @api private54*/5556Option.prototype.name = function() {57return this.long58.replace('--', '')59.replace('no-', '');60};6162/**63* Check if `arg` matches the short or long flag.64*65* @param {String} arg66* @return {Boolean}67* @api private68*/6970Option.prototype.is = function(arg) {71return arg == this.short || arg == this.long;72};7374/**75* Initialize a new `Command`.76*77* @param {String} name78* @api public79*/8081function Command(name) {82this.commands = [];83this.options = [];84this._execs = {};85this._allowUnknownOption = false;86this._args = [];87this._name = name || '';88}8990/**91* Inherit from `EventEmitter.prototype`.92*/9394Command.prototype.__proto__ = EventEmitter.prototype;9596/**97* Add command `name`.98*99* The `.action()` callback is invoked when the100* command `name` is specified via __ARGV__,101* and the remaining arguments are applied to the102* function for access.103*104* When the `name` is "*" an un-matched command105* will be passed as the first arg, followed by106* the rest of __ARGV__ remaining.107*108* Examples:109*110* program111* .version('0.0.1')112* .option('-C, --chdir <path>', 'change the working directory')113* .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')114* .option('-T, --no-tests', 'ignore test hook')115*116* program117* .command('setup')118* .description('run remote setup commands')119* .action(function() {120* console.log('setup');121* });122*123* program124* .command('exec <cmd>')125* .description('run the given remote command')126* .action(function(cmd) {127* console.log('exec "%s"', cmd);128* });129*130* program131* .command('teardown <dir> [otherDirs...]')132* .description('run teardown commands')133* .action(function(dir, otherDirs) {134* console.log('dir "%s"', dir);135* if (otherDirs) {136* otherDirs.forEach(function (oDir) {137* console.log('dir "%s"', oDir);138* });139* }140* });141*142* program143* .command('*')144* .description('deploy the given env')145* .action(function(env) {146* console.log('deploying "%s"', env);147* });148*149* program.parse(process.argv);150*151* @param {String} name152* @param {String} [desc] for git-style sub-commands153* @return {Command} the new command154* @api public155*/156157Command.prototype.command = function(name, desc, opts) {158opts = opts || {};159var args = name.split(/ +/);160var cmd = new Command(args.shift());161162if (desc) {163cmd.description(desc);164this.executables = true;165this._execs[cmd._name] = true;166if (opts.isDefault) this.defaultExecutable = cmd._name;167}168169cmd._noHelp = !!opts.noHelp;170this.commands.push(cmd);171cmd.parseExpectedArgs(args);172cmd.parent = this;173174if (desc) return this;175return cmd;176};177178/**179* Define argument syntax for the top-level command.180*181* @api public182*/183184Command.prototype.arguments = function (desc) {185return this.parseExpectedArgs(desc.split(/ +/));186};187188/**189* Add an implicit `help [cmd]` subcommand190* which invokes `--help` for the given command.191*192* @api private193*/194195Command.prototype.addImplicitHelpCommand = function() {196this.command('help [cmd]', 'display help for [cmd]');197};198199/**200* Parse expected `args`.201*202* For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.203*204* @param {Array} args205* @return {Command} for chaining206* @api public207*/208209Command.prototype.parseExpectedArgs = function(args) {210if (!args.length) return;211var self = this;212args.forEach(function(arg) {213var argDetails = {214required: false,215name: '',216variadic: false217};218219switch (arg[0]) {220case '<':221argDetails.required = true;222argDetails.name = arg.slice(1, -1);223break;224case '[':225argDetails.name = arg.slice(1, -1);226break;227}228229if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {230argDetails.variadic = true;231argDetails.name = argDetails.name.slice(0, -3);232}233if (argDetails.name) {234self._args.push(argDetails);235}236});237return this;238};239240/**241* Register callback `fn` for the command.242*243* Examples:244*245* program246* .command('help')247* .description('display verbose help')248* .action(function() {249* // output help here250* });251*252* @param {Function} fn253* @return {Command} for chaining254* @api public255*/256257Command.prototype.action = function(fn) {258var self = this;259var listener = function(args, unknown) {260// Parse any so-far unknown options261args = args || [];262unknown = unknown || [];263264var parsed = self.parseOptions(unknown);265266// Output help if necessary267outputHelpIfNecessary(self, parsed.unknown);268269// If there are still any unknown options, then we simply270// die, unless someone asked for help, in which case we give it271// to them, and then we die.272if (parsed.unknown.length > 0) {273self.unknownOption(parsed.unknown[0]);274}275276// Leftover arguments need to be pushed back. Fixes issue #56277if (parsed.args.length) args = parsed.args.concat(args);278279self._args.forEach(function(arg, i) {280if (arg.required && null == args[i]) {281self.missingArgument(arg.name);282} else if (arg.variadic) {283if (i !== self._args.length - 1) {284self.variadicArgNotLast(arg.name);285}286287args[i] = args.splice(i);288}289});290291// Always append ourselves to the end of the arguments,292// to make sure we match the number of arguments the user293// expects294if (self._args.length) {295args[self._args.length] = self;296} else {297args.push(self);298}299300fn.apply(self, args);301};302var parent = this.parent || this;303var name = parent === this ? '*' : this._name;304parent.on(name, listener);305if (this._alias) parent.on(this._alias, listener);306return this;307};308309/**310* Define option with `flags`, `description` and optional311* coercion `fn`.312*313* The `flags` string should contain both the short and long flags,314* separated by comma, a pipe or space. The following are all valid315* all will output this way when `--help` is used.316*317* "-p, --pepper"318* "-p|--pepper"319* "-p --pepper"320*321* Examples:322*323* // simple boolean defaulting to false324* program.option('-p, --pepper', 'add pepper');325*326* --pepper327* program.pepper328* // => Boolean329*330* // simple boolean defaulting to true331* program.option('-C, --no-cheese', 'remove cheese');332*333* program.cheese334* // => true335*336* --no-cheese337* program.cheese338* // => false339*340* // required argument341* program.option('-C, --chdir <path>', 'change the working directory');342*343* --chdir /tmp344* program.chdir345* // => "/tmp"346*347* // optional argument348* program.option('-c, --cheese [type]', 'add cheese [marble]');349*350* @param {String} flags351* @param {String} description352* @param {Function|*} [fn] or default353* @param {*} [defaultValue]354* @return {Command} for chaining355* @api public356*/357358Command.prototype.option = function(flags, description, fn, defaultValue) {359var self = this360, option = new Option(flags, description)361, oname = option.name()362, name = camelcase(oname);363364// default as 3rd arg365if (typeof fn != 'function') {366if (fn instanceof RegExp) {367var regex = fn;368fn = function(val, def) {369var m = regex.exec(val);370return m ? m[0] : def;371}372}373else {374defaultValue = fn;375fn = null;376}377}378379// preassign default value only for --no-*, [optional], or <required>380if (false == option.bool || option.optional || option.required) {381// when --no-* we make sure default is true382if (false == option.bool) defaultValue = true;383// preassign only if we have a default384if (undefined !== defaultValue) self[name] = defaultValue;385}386387// register the option388this.options.push(option);389390// when it's passed assign the value391// and conditionally invoke the callback392this.on(oname, function(val) {393// coercion394if (null !== val && fn) val = fn(val, undefined === self[name]395? defaultValue396: self[name]);397398// unassigned or bool399if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {400// if no value, bool true, and we have a default, then use it!401if (null == val) {402self[name] = option.bool403? defaultValue || true404: false;405} else {406self[name] = val;407}408} else if (null !== val) {409// reassign410self[name] = val;411}412});413414return this;415};416417/**418* Allow unknown options on the command line.419*420* @param {Boolean} arg if `true` or omitted, no error will be thrown421* for unknown options.422* @api public423*/424Command.prototype.allowUnknownOption = function(arg) {425this._allowUnknownOption = arguments.length === 0 || arg;426return this;427};428429/**430* Parse `argv`, settings options and invoking commands when defined.431*432* @param {Array} argv433* @return {Command} for chaining434* @api public435*/436437Command.prototype.parse = function(argv) {438// implicit help439if (this.executables) this.addImplicitHelpCommand();440441// store raw args442this.rawArgs = argv;443444// guess name445this._name = this._name || basename(argv[1], '.js');446447// github-style sub-commands with no sub-command448if (this.executables && argv.length < 3 && !this.defaultExecutable) {449// this user needs help450argv.push('--help');451}452453// process argv454var parsed = this.parseOptions(this.normalize(argv.slice(2)));455var args = this.args = parsed.args;456457var result = this.parseArgs(this.args, parsed.unknown);458459// executable sub-commands460var name = result.args[0];461462var aliasCommand = null;463// check alias of sub commands464if (name) {465aliasCommand = this.commands.filter(function(command) {466return command.alias() === name;467})[0];468}469470if (this._execs[name] && typeof this._execs[name] != "function") {471return this.executeSubCommand(argv, args, parsed.unknown);472} else if (aliasCommand) {473// is alias of a subCommand474args[0] = aliasCommand._name;475return this.executeSubCommand(argv, args, parsed.unknown);476} else if (this.defaultExecutable) {477// use the default subcommand478args.unshift(this.defaultExecutable);479return this.executeSubCommand(argv, args, parsed.unknown);480}481482return result;483};484485/**486* Execute a sub-command executable.487*488* @param {Array} argv489* @param {Array} args490* @param {Array} unknown491* @api private492*/493494Command.prototype.executeSubCommand = function(argv, args, unknown) {495args = args.concat(unknown);496497if (!args.length) this.help();498if ('help' == args[0] && 1 == args.length) this.help();499500// <cmd> --help501if ('help' == args[0]) {502args[0] = args[1];503args[1] = '--help';504}505506// executable507var f = argv[1];508// name of the subcommand, link `pm-install`509var bin = basename(f, '.js') + '-' + args[0];510511512// In case of globally installed, get the base dir where executable513// subcommand file should be located at514var baseDir515, link = readlink(f);516517// when symbolink is relative path518if (link !== f && link.charAt(0) !== '/') {519link = path.join(dirname(f), link)520}521baseDir = dirname(link);522523// prefer local `./<bin>` to bin in the $PATH524var localBin = path.join(baseDir, bin);525526// whether bin file is a js script with explicit `.js` extension527var isExplicitJS = false;528if (exists(localBin + '.js')) {529bin = localBin + '.js';530isExplicitJS = true;531} else if (exists(localBin)) {532bin = localBin;533}534535args = args.slice(1);536537var proc;538if (process.platform !== 'win32') {539if (isExplicitJS) {540args.unshift(bin);541// add executable arguments to spawn542args = (process.execArgv || []).concat(args);543544proc = spawn('node', args, { stdio: 'inherit', customFds: [0, 1, 2] });545} else {546proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });547}548} else {549args.unshift(bin);550proc = spawn(process.execPath, args, { stdio: 'inherit'});551}552553proc.on('close', process.exit.bind(process));554proc.on('error', function(err) {555if (err.code == "ENOENT") {556console.error('\n %s(1) does not exist, try --help\n', bin);557} else if (err.code == "EACCES") {558console.error('\n %s(1) not executable. try chmod or run with root\n', bin);559}560process.exit(1);561});562563// Store the reference to the child process564this.runningCommand = proc;565};566567/**568* Normalize `args`, splitting joined short flags. For example569* the arg "-abc" is equivalent to "-a -b -c".570* This also normalizes equal sign and splits "--abc=def" into "--abc def".571*572* @param {Array} args573* @return {Array}574* @api private575*/576577Command.prototype.normalize = function(args) {578var ret = []579, arg580, lastOpt581, index;582583for (var i = 0, len = args.length; i < len; ++i) {584arg = args[i];585if (i > 0) {586lastOpt = this.optionFor(args[i-1]);587}588589if (arg === '--') {590// Honor option terminator591ret = ret.concat(args.slice(i));592break;593} else if (lastOpt && lastOpt.required) {594ret.push(arg);595} else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {596arg.slice(1).split('').forEach(function(c) {597ret.push('-' + c);598});599} else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {600ret.push(arg.slice(0, index), arg.slice(index + 1));601} else {602ret.push(arg);603}604}605606return ret;607};608609/**610* Parse command `args`.611*612* When listener(s) are available those613* callbacks are invoked, otherwise the "*"614* event is emitted and those actions are invoked.615*616* @param {Array} args617* @return {Command} for chaining618* @api private619*/620621Command.prototype.parseArgs = function(args, unknown) {622var name;623624if (args.length) {625name = args[0];626if (this.listeners(name).length) {627this.emit(args.shift(), args, unknown);628} else {629this.emit('*', args);630}631} else {632outputHelpIfNecessary(this, unknown);633634// If there were no args and we have unknown options,635// then they are extraneous and we need to error.636if (unknown.length > 0) {637this.unknownOption(unknown[0]);638}639}640641return this;642};643644/**645* Return an option matching `arg` if any.646*647* @param {String} arg648* @return {Option}649* @api private650*/651652Command.prototype.optionFor = function(arg) {653for (var i = 0, len = this.options.length; i < len; ++i) {654if (this.options[i].is(arg)) {655return this.options[i];656}657}658};659660/**661* Parse options from `argv` returning `argv`662* void of these options.663*664* @param {Array} argv665* @return {Array}666* @api public667*/668669Command.prototype.parseOptions = function(argv) {670var args = []671, len = argv.length672, literal673, option674, arg;675676var unknownOptions = [];677678// parse options679for (var i = 0; i < len; ++i) {680arg = argv[i];681682// literal args after --683if (literal) {684args.push(arg);685continue;686}687688if ('--' == arg) {689literal = true;690continue;691}692693// find matching Option694option = this.optionFor(arg);695696// option is defined697if (option) {698// requires arg699if (option.required) {700arg = argv[++i];701if (null == arg) return this.optionMissingArgument(option);702this.emit(option.name(), arg);703// optional arg704} else if (option.optional) {705arg = argv[i+1];706if (null == arg || ('-' == arg[0] && '-' != arg)) {707arg = null;708} else {709++i;710}711this.emit(option.name(), arg);712// bool713} else {714this.emit(option.name());715}716continue;717}718719// looks like an option720if (arg.length > 1 && '-' == arg[0]) {721unknownOptions.push(arg);722723// If the next argument looks like it might be724// an argument for this option, we pass it on.725// If it isn't, then it'll simply be ignored726if (argv[i+1] && '-' != argv[i+1][0]) {727unknownOptions.push(argv[++i]);728}729continue;730}731732// arg733args.push(arg);734}735736return { args: args, unknown: unknownOptions };737};738739/**740* Return an object containing options as key-value pairs741*742* @return {Object}743* @api public744*/745Command.prototype.opts = function() {746var result = {}747, len = this.options.length;748749for (var i = 0 ; i < len; i++) {750var key = camelcase(this.options[i].name());751result[key] = key === 'version' ? this._version : this[key];752}753return result;754};755756/**757* Argument `name` is missing.758*759* @param {String} name760* @api private761*/762763Command.prototype.missingArgument = function(name) {764console.error();765console.error(" error: missing required argument `%s'", name);766console.error();767process.exit(1);768};769770/**771* `Option` is missing an argument, but received `flag` or nothing.772*773* @param {String} option774* @param {String} flag775* @api private776*/777778Command.prototype.optionMissingArgument = function(option, flag) {779console.error();780if (flag) {781console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag);782} else {783console.error(" error: option `%s' argument missing", option.flags);784}785console.error();786process.exit(1);787};788789/**790* Unknown option `flag`.791*792* @param {String} flag793* @api private794*/795796Command.prototype.unknownOption = function(flag) {797if (this._allowUnknownOption) return;798console.error();799console.error(" error: unknown option `%s'", flag);800console.error();801process.exit(1);802};803804/**805* Variadic argument with `name` is not the last argument as required.806*807* @param {String} name808* @api private809*/810811Command.prototype.variadicArgNotLast = function(name) {812console.error();813console.error(" error: variadic arguments must be last `%s'", name);814console.error();815process.exit(1);816};817818/**819* Set the program version to `str`.820*821* This method auto-registers the "-V, --version" flag822* which will print the version number when passed.823*824* @param {String} str825* @param {String} [flags]826* @return {Command} for chaining827* @api public828*/829830Command.prototype.version = function(str, flags) {831if (0 == arguments.length) return this._version;832this._version = str;833flags = flags || '-V, --version';834this.option(flags, 'output the version number');835this.on('version', function() {836process.stdout.write(str + '\n');837process.exit(0);838});839return this;840};841842/**843* Set the description to `str`.844*845* @param {String} str846* @return {String|Command}847* @api public848*/849850Command.prototype.description = function(str) {851if (0 === arguments.length) return this._description;852this._description = str;853return this;854};855856/**857* Set an alias for the command858*859* @param {String} alias860* @return {String|Command}861* @api public862*/863864Command.prototype.alias = function(alias) {865var command = this;866if(this.commands.length !== 0) {867command = this.commands[this.commands.length - 1]868}869870if (arguments.length === 0) return command._alias;871872command._alias = alias;873return this;874};875876/**877* Set / get the command usage `str`.878*879* @param {String} str880* @return {String|Command}881* @api public882*/883884Command.prototype.usage = function(str) {885var args = this._args.map(function(arg) {886return humanReadableArgName(arg);887});888889var usage = '[options]'890+ (this.commands.length ? ' [command]' : '')891+ (this._args.length ? ' ' + args.join(' ') : '');892893if (0 == arguments.length) return this._usage || usage;894this._usage = str;895896return this;897};898899/**900* Get the name of the command901*902* @param {String} name903* @return {String|Command}904* @api public905*/906907Command.prototype.name = function() {908return this._name;909};910911/**912* Return the largest option length.913*914* @return {Number}915* @api private916*/917918Command.prototype.largestOptionLength = function() {919return this.options.reduce(function(max, option) {920return Math.max(max, option.flags.length);921}, 0);922};923924/**925* Return help for options.926*927* @return {String}928* @api private929*/930931Command.prototype.optionHelp = function() {932var width = this.largestOptionLength();933934// Prepend the help information935return [pad('-h, --help', width) + ' ' + 'output usage information']936.concat(this.options.map(function(option) {937return pad(option.flags, width) + ' ' + option.description;938}))939.join('\n');940};941942/**943* Return command help documentation.944*945* @return {String}946* @api private947*/948949Command.prototype.commandHelp = function() {950if (!this.commands.length) return '';951952var commands = this.commands.filter(function(cmd) {953return !cmd._noHelp;954}).map(function(cmd) {955var args = cmd._args.map(function(arg) {956return humanReadableArgName(arg);957}).join(' ');958959return [960cmd._name961+ (cmd._alias ? '|' + cmd._alias : '')962+ (cmd.options.length ? ' [options]' : '')963+ ' ' + args964, cmd._description965];966});967968var width = commands.reduce(function(max, command) {969return Math.max(max, command[0].length);970}, 0);971972return [973''974, ' Commands:'975, ''976, commands.map(function(cmd) {977var desc = cmd[1] ? ' ' + cmd[1] : '';978return pad(cmd[0], width) + desc;979}).join('\n').replace(/^/gm, ' ')980, ''981].join('\n');982};983984/**985* Return program help documentation.986*987* @return {String}988* @api private989*/990991Command.prototype.helpInformation = function() {992var desc = [];993if (this._description) {994desc = [995' ' + this._description996, ''997];998}9991000var cmdName = this._name;1001if (this._alias) {1002cmdName = cmdName + '|' + this._alias;1003}1004var usage = [1005''1006,' Usage: ' + cmdName + ' ' + this.usage()1007, ''1008];10091010var cmds = [];1011var commandHelp = this.commandHelp();1012if (commandHelp) cmds = [commandHelp];10131014var options = [1015' Options:'1016, ''1017, '' + this.optionHelp().replace(/^/gm, ' ')1018, ''1019, ''1020];10211022return usage1023.concat(cmds)1024.concat(desc)1025.concat(options)1026.join('\n');1027};10281029/**1030* Output help information for this command1031*1032* @api public1033*/10341035Command.prototype.outputHelp = function(cb) {1036if (!cb) {1037cb = function(passthru) {1038return passthru;1039}1040}1041process.stdout.write(cb(this.helpInformation()));1042this.emit('--help');1043};10441045/**1046* Output help information and exit.1047*1048* @api public1049*/10501051Command.prototype.help = function(cb) {1052this.outputHelp(cb);1053process.exit();1054};10551056/**1057* Camel-case the given `flag`1058*1059* @param {String} flag1060* @return {String}1061* @api private1062*/10631064function camelcase(flag) {1065return flag.split('-').reduce(function(str, word) {1066return str + word[0].toUpperCase() + word.slice(1);1067});1068}10691070/**1071* Pad `str` to `width`.1072*1073* @param {String} str1074* @param {Number} width1075* @return {String}1076* @api private1077*/10781079function pad(str, width) {1080var len = Math.max(0, width - str.length);1081return str + Array(len + 1).join(' ');1082}10831084/**1085* Output help information if necessary1086*1087* @param {Command} command to output help for1088* @param {Array} array of options to search for -h or --help1089* @api private1090*/10911092function outputHelpIfNecessary(cmd, options) {1093options = options || [];1094for (var i = 0; i < options.length; i++) {1095if (options[i] == '--help' || options[i] == '-h') {1096cmd.outputHelp();1097process.exit(0);1098}1099}1100}11011102/**1103* Takes an argument an returns its human readable equivalent for help usage.1104*1105* @param {Object} arg1106* @return {String}1107* @api private1108*/11091110function humanReadableArgName(arg) {1111var nameOutput = arg.name + (arg.variadic === true ? '...' : '');11121113return arg.required1114? '<' + nameOutput + '>'1115: '[' + nameOutput + ']'1116}11171118// for versions before node v0.8 when there weren't `fs.existsSync`1119function exists(file) {1120try {1121if (fs.statSync(file).isFile()) {1122return true;1123}1124} catch (e) {1125return false;1126}1127}1128112911301131