1/*!2* commander3* Copyright(c) 2011 TJ Holowaychuk <[email protected]>4* MIT Licensed5*/67/*!8* Console9*/10var util = require('util');11var fs = require("fs");1213/* Monkey patching */14if (!util.format) {15var formatRegExp = /%[sdj%]/g;16util.format = function(f) {17if (typeof f !== 'string') {18var objects = [];19for (var i = 0; i < arguments.length; i++) {20objects.push(util.inspect(arguments[i]));21}22return objects.join(' ');23}2425var i = 1;26var args = arguments;27var len = args.length;28var str = String(f).replace(formatRegExp, function(x) {29if (i >= len) return x;30switch (x) {31case '%s': return String(args[i++]);32case '%d': return Number(args[i++]);33case '%j': return JSON.stringify(args[i++]);34case '%%': return '%';35default:36return x;37}38});39for (var x = args[i]; i < len; x = args[++i]) {40if (x === null || typeof x !== 'object') {41str += ' ' + x;42} else {43str += ' ' + util.inspect(x);44}45}46return str;47}48}4950var consoleFlush = function(data) {51if (!Buffer.isBuffer(data)) {52data= new Buffer(''+ data);53}54if (data.length) {55var written= 0;56do {57try {58var len = data.length- written;59written += fs.writeSync(process.stdout.fd, data, written, len, -1);60}61catch (e) {62}63} while(written < data.length);64}65};6667/**68* Module dependencies.69*/7071var EventEmitter = require('events').EventEmitter72, path = require('path')73, tty = require('tty')74, basename = path.basename;7576/**77* Expose the root command.78*/7980exports = module.exports = new Command;8182/**83* Expose `Command`.84*/8586exports.Command = Command;8788/**89* Expose `Option`.90*/9192exports.Option = Option;9394/**95* Initialize a new `Option` with the given `flags` and `description`.96*97* @param {String} flags98* @param {String} description99* @api public100*/101102function Option(flags, description) {103this.flags = flags;104this.required = ~flags.indexOf('<');105this.optional = ~flags.indexOf('[');106this.bool = !~flags.indexOf('-no-');107flags = flags.split(/[ ,|]+/);108if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();109this.long = flags.shift();110this.description = description;111}112113/**114* Return option name.115*116* @return {String}117* @api private118*/119120Option.prototype.name = function(){121return this.long122.replace('--', '')123.replace('no-', '');124};125126/**127* Check if `arg` matches the short or long flag.128*129* @param {String} arg130* @return {Boolean}131* @api private132*/133134Option.prototype.is = function(arg){135return arg == this.short136|| arg == this.long;137};138139/**140* Initialize a new `Command`.141*142* @param {String} name143* @api public144*/145146function Command(name) {147this.commands = [];148this.options = [];149this.args = [];150this.name = name;151this.opts = {};152}153154/**155* Inherit from `EventEmitter.prototype`.156*/157158Command.prototype.__proto__ = EventEmitter.prototype;159160/**161* Add command `name`.162*163* The `.action()` callback is invoked when the164* command `name` is specified via __ARGV__,165* and the remaining arguments are applied to the166* function for access.167*168* When the `name` is "*" an un-matched command169* will be passed as the first arg, followed by170* the rest of __ARGV__ remaining.171*172* Examples:173*174* program175* .version('0.0.1')176* .option('-C, --chdir <path>', 'change the working directory')177* .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')178* .option('-T, --no-tests', 'ignore test hook')179*180* program181* .command('setup')182* .description('run remote setup commands')183* .action(function(){184* console.log('setup');185* });186*187* program188* .command('exec <cmd>')189* .description('run the given remote command')190* .action(function(cmd){191* console.log('exec "%s"', cmd);192* });193*194* program195* .command('*')196* .description('deploy the given env')197* .action(function(env){198* console.log('deploying "%s"', env);199* });200*201* program.parse(process.argv);202*203* @param {String} name204* @return {Command} the new command205* @api public206*/207208Command.prototype.command = function(name){209var args = name.split(/ +/);210var cmd = new Command(args.shift());211this.commands.push(cmd);212cmd.parseExpectedArgs(args);213cmd.parent = this;214return cmd;215};216217/**218* Parse expected `args`.219*220* For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.221*222* @param {Array} args223* @return {Command} for chaining224* @api public225*/226227Command.prototype.parseExpectedArgs = function(args){228if (!args.length) return;229var self = this;230args.forEach(function(arg){231switch (arg[0]) {232case '<':233self.args.push({ required: true, name: arg.slice(1, -1) });234break;235case '[':236self.args.push({ required: false, name: arg.slice(1, -1) });237break;238}239});240return this;241};242243/**244* Register callback `fn` for the command.245*246* Examples:247*248* program249* .command('help')250* .description('display verbose help')251* .action(function(){252* // output help here253* });254*255* @param {Function} fn256* @return {Command} for chaining257* @api public258*/259260Command.prototype.action = function(fn){261var self = this;262this.parent.on(this.name, function(args, unknown){263264args = args.slice();265// Parse any so-far unknown options266unknown = unknown || [];267var parsed = self.parseOptions(unknown);268269// Output help if necessary270outputHelpIfNecessary(self, parsed.unknown);271272// If there are still any unknown options, then we simply273// die, unless someone asked for help, in which case we give it274// to them, and then we die.275if (parsed.unknown.length > 0) {276self.unknownOption(parsed.unknown[0]);277}278279// If we were expecting a required option and we missed it,280// error out281self.options.forEach(function(option, i) {282var oname = option.name();283var name = camelcase(oname);284if (option.isPresenceRequired && self[name] === undefined && !parsed.required[oname]) {285self.optionMissing(option);286}287});288289self.args.forEach(function(arg, i){290if (arg.required && null == args[i]) {291self.missingArgument(arg.name);292}293});294295// Always append ourselves to the end of the arguments,296// to make sure we match the number of arguments the user297// expects298// If we have expected arguments and we have at most the number of299// expected arguments, then add it to the end. If not, push us300// at the end (for the case of varargs).301if (self.args.length && (args.length <= self.args.length - 1)) {302args[self.args.length] = self;303} else {304args.push(self);305}306307fn.apply(this, args);308});309return this;310};311312/**313* Define option with `flags`, `description` and optional314* coercion `fn`.315*316* The `flags` string should contain both the short and long flags,317* separated by comma, a pipe or space. The following are all valid318* all will output this way when `--help` is used.319*320* "-p, --pepper"321* "-p|--pepper"322* "-p --pepper"323*324* Examples:325*326* // simple boolean defaulting to false327* program.option('-p, --pepper', 'add pepper');328*329* --pepper330* program.pepper331* // => Boolean332*333* // simple boolean defaulting to false334* program.option('-C, --no-cheese', 'remove cheese');335*336* program.cheese337* // => true338*339* --no-cheese340* program.cheese341* // => true342*343* // required argument344* program.option('-C, --chdir <path>', 'change the working directory');345*346* --chdir /tmp347* program.chdir348* // => "/tmp"349*350* // optional argument351* program.option('-c, --cheese [type]', 'add cheese [marble]');352*353* @param {String} flags354* @param {String} description355* @param {Function|Mixed} fn or default356* @param {Mixed} defaultValue357* @return {Command} for chaining358* @api public359*/360361Command.prototype.option = function(flags, description, fn, defaultValue, isRequired){362var self = this363, option = new Option(flags, description)364, oname = option.name()365, name = camelcase(oname);366367// default as 3rd arg368if ('function' != typeof fn) isRequired = defaultValue, defaultValue = fn, fn = null;369370// preassign default value only for --no-*, [optional], or <required>371if (false == option.bool || option.optional || option.required) {372// when --no-* we make sure default is true373if (false == option.bool) defaultValue = true;374// preassign only if we have a default375if (undefined !== defaultValue) self[name] = defaultValue;376}377378option.isPresenceRequired = isRequired;379380// register the option381this.options.push(option);382383// when it's passed assign the value384// and conditionally invoke the callback385this.on(oname, function(val){386// coercion387if (null != val && fn) val = fn(val);388389// unassigned or bool390if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {391// if no value, bool true, and we have a default, then use it!392if (null == val) {393self[name] = self.opts[name] = option.bool394? defaultValue || true395: false;396} else {397self[name] = self.opts[name] = val;398}399} else if (null !== val) {400// reassign401self[name] = self.opts[name] = val;402}403});404405return this;406};407408/**409* Parse `argv`, settings options and invoking commands when defined.410*411* @param {Array} argv412* @return {Command} for chaining413* @api public414*/415416Command.prototype.parse = function(argv){417// store raw args418this.rawArgs = argv;419420// guess name421if (!this.name) this.name = basename(argv[1]);422423// process argv424var parsed = this.parseOptions(this.normalize(argv.slice(2)));425this.args = parsed.args;426return this.parseArgs(this.args, parsed.unknown, parsed.required);427};428429/**430* Normalize `args`, splitting joined short flags. For example431* the arg "-abc" is equivalent to "-a -b -c".432*433* @param {Array} args434* @return {Array}435* @api private436*/437438Command.prototype.normalize = function(args){439var ret = []440, arg;441442for (var i = 0, len = args.length; i < len; ++i) {443arg = args[i];444if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {445arg.slice(1).split('').forEach(function(c){446ret.push('-' + c);447});448} else {449ret.push(arg);450}451}452453return ret;454};455456/**457* Parse command `args`.458*459* When listener(s) are available those460* callbacks are invoked, otherwise the "*"461* event is emitted and those actions are invoked.462*463* @param {Array} args464* @return {Command} for chaining465* @api private466*/467468Command.prototype.parseArgs = function(args, unknown, required){469var cmds = this.commands470, len = cmds.length471, self = this472, name;473474if (args.length) {475name = args[0];476if (this.listeners(name).length) {477var commandName = args.shift();478this.executedCommand = commandName;479this.emit(commandName, args, unknown);480} else {481this.executedCommand = "*";482this.emit('*', args);483}484} else {485outputHelpIfNecessary(this, unknown);486487// If there were no args and we have unknown options,488// then they are extraneous and we need to error.489if (unknown.length > 0) {490this.unknownOption(unknown[0]);491}492493// If we were expecting a required option and we missed it,494// error out495this.options.forEach(function(option, i) {496var oname = option.name();497var name = camelcase(oname);498if (option.isPresenceRequired && self[name] === undefined && !required[oname]) {499self.optionMissing(option);500}501});502}503504return this;505};506507/**508* Return an option matching `arg` if any.509*510* @param {String} arg511* @return {Option}512* @api private513*/514515Command.prototype.optionFor = function(arg){516for (var i = 0, len = this.options.length; i < len; ++i) {517if (this.options[i].is(arg)) {518return this.options[i];519}520}521};522523/**524* Parse options from `argv` returning `argv`525* void of these options.526*527* @param {Array} argv528* @return {Array}529* @api public530*/531532Command.prototype.parseOptions = function(argv){533var args = []534, len = argv.length535, literal536, option537, arg;538539var unknownOptions = [];540var required = {};541542// parse options543for (var i = 0; i < len; ++i) {544arg = argv[i];545546// literal args after --547if ('--' == arg) {548literal = true;549continue;550}551552if (literal) {553args.push(arg);554continue;555}556557// find matching Option558option = this.optionFor(arg);559560// option is defined561if (option) {562if (option.isPresenceRequired) {563required[option.name()] = true;564}565566// requires arg567if (option.required) {568arg = argv[++i];569if (null == arg) return this.optionMissingArgument(option);570if ('-' == arg[0]) return this.optionMissingArgument(option, arg);571this.emit(option.name(), arg);572// optional arg573} else if (option.optional) {574arg = argv[i+1];575if (null == arg || '-' == arg[0]) {576arg = null;577} else {578++i;579}580this.emit(option.name(), arg);581// bool582} else {583this.emit(option.name());584}585continue;586}587588// looks like an option589if (arg.length > 1 && '-' == arg[0]) {590unknownOptions.push(arg);591592// If the next argument looks like it might be593// an argument for this option, we pass it on.594// If it isn't, then it'll simply be ignored595if (argv[i+1] && '-' != argv[i+1][0]) {596unknownOptions.push(argv[++i]);597}598continue;599}600601// arg602args.push(arg);603}604605return { args: args, unknown: unknownOptions, required: required };606};607608/**609* Argument `name` is missing.610*611* @param {String} name612* @api private613*/614615Command.prototype.missingArgument = function(name){616console.error();617console.error(" error: missing required argument `%s'", name);618console.error();619process.exit(1);620};621622/**623* `Option` is missing an argument, but received `flag` or nothing.624*625* @param {String} option626* @param {String} flag627* @api private628*/629630Command.prototype.optionMissingArgument = function(option, flag){631console.error();632if (flag) {633console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag);634} else {635console.error(" error: option `%s' argument missing", option.flags);636}637console.error();638process.exit(1);639};640641/**642* `Option` is missing.643*644* @param {String} option645* @param {String} flag646* @api private647*/648649Command.prototype.optionMissing = function(option){650console.error();651console.error(" error: option `%s' is missing", option.name());652console.error();653process.exit(1);654};655656/**657* Unknown option `flag`.658*659* @param {String} flag660* @api private661*/662663Command.prototype.unknownOption = function(flag){664console.error();665console.error(" error: unknown option `%s'", flag);666console.error();667process.exit(1);668};669670/**671* Set the program version to `str`.672*673* This method auto-registers the "-V, --version" flag674* which will print the version number when passed.675*676* @param {String} str677* @param {String} flags678* @return {Command} for chaining679* @api public680*/681682Command.prototype.version = function(str, flags){683if (0 == arguments.length) return this._version;684this._version = str;685flags = flags || '-V, --version';686this.option(flags, 'output the version number');687this.on('version', function(){688console.log(str);689process.exit(0);690});691return this;692};693694/**695* Set the description `str`.696*697* @param {String} str698* @return {String|Command}699* @api public700*/701702Command.prototype.description = function(str){703if (0 == arguments.length) return this._description;704this._description = str;705return this;706};707708/**709* Set / get the command usage `str`.710*711* @param {String} str712* @return {String|Command}713* @api public714*/715716Command.prototype.usage = function(str){717var args = this.args.map(function(arg){718return arg.required719? '<' + arg.name + '>'720: '[' + arg.name + ']';721});722723var usage = '[options'724+ (this.commands.length ? '] [command' : '')725+ ']'726+ (this.args.length ? ' ' + args : '');727if (0 == arguments.length) return this._usage || usage;728this._usage = str;729730return this;731};732733/**734* Return the largest option length.735*736* @return {Number}737* @api private738*/739740Command.prototype.largestOptionLength = function(){741return this.options.reduce(function(max, option){742return Math.max(max, option.flags.length);743}, 0);744};745746/**747* Return help for options.748*749* @return {String}750* @api private751*/752753Command.prototype.optionHelp = function(){754var width = this.largestOptionLength();755756// Prepend the help information757return [pad('-h, --help', width) + ' ' + 'output usage information']758.concat(this.options.map(function(option){759return pad(option.flags, width)760+ ' ' + option.description;761}))762.join('\n');763};764765/**766* Return command help documentation.767*768* @return {String}769* @api private770*/771772Command.prototype.commandHelp = function(){773if (!this.commands.length) return '';774return [775''776, ' Commands:'777, ''778, this.commands.map(function(cmd){779var args = cmd.args.map(function(arg){780return arg.required781? '<' + arg.name + '>'782: '[' + arg.name + ']';783}).join(' ');784785return cmd.name786+ (cmd.options.length787? ' [options]'788: '') + ' ' + args789+ (cmd.description()790? '\n' + cmd.description()791: '');792}).join('\n\n').replace(/^/gm, ' ')793, ''794].join('\n');795};796797/**798* Return program help documentation.799*800* @return {String}801* @api private802*/803804Command.prototype.helpInformation = function(){805return [806''807, ' Usage: ' + this.name + ' ' + this.usage()808, '' + this.commandHelp()809, ' Options:'810, ''811, '' + this.optionHelp().replace(/^/gm, ' ')812, ''813, ''814].join('\n');815};816817/**818* Prompt for a `Number`.819*820* @param {String} str821* @param {Function} fn822* @api private823*/824825Command.prototype.promptForNumber = function(str, fn){826var self = this;827this.promptSingleLine(str, function parseNumber(val){828val = Number(val);829if (isNaN(val)) return self.promptSingleLine(str + '(must be a number) ', parseNumber);830fn(val);831});832};833834/**835* Prompt for a `Date`.836*837* @param {String} str838* @param {Function} fn839* @api private840*/841842Command.prototype.promptForDate = function(str, fn){843var self = this;844this.promptSingleLine(str, function parseDate(val){845val = new Date(val);846if (isNaN(val.getTime())) return self.promptSingleLine(str + '(must be a date) ', parseDate);847fn(val);848});849};850851/**852* Single-line prompt.853*854* @param {String} str855* @param {Function} fn856* @api private857*/858859Command.prototype.promptSingleLine = function(str, fn){860if ('function' == typeof arguments[2]) {861return this['promptFor' + (fn.name || fn)](str, arguments[2]);862}863864process.stdout.write(str);865process.stdin.setEncoding('utf8');866process.stdin.once('data', function(val){867fn(val.trim());868}).resume();869};870871/**872* Multi-line prompt.873*874* @param {String} str875* @param {Function} fn876* @api private877*/878879Command.prototype.promptMultiLine = function(str, fn){880var buf = [];881console.log(str);882process.stdin.setEncoding('utf8');883process.stdin.on('data', function(val){884if ('\n' == val || '\r\n' == val) {885process.stdin.removeAllListeners('data');886fn(buf.join('\n'));887} else {888buf.push(val.trimRight());889}890}).resume();891};892893/**894* Prompt `str` and callback `fn(val)`895*896* Commander supports single-line and multi-line prompts.897* To issue a single-line prompt simply add white-space898* to the end of `str`, something like "name: ", whereas899* for a multi-line prompt omit this "description:".900*901*902* Examples:903*904* program.prompt('Username: ', function(name){905* console.log('hi %s', name);906* });907*908* program.prompt('Description:', function(desc){909* console.log('description was "%s"', desc.trim());910* });911*912* @param {String} str913* @param {Function} fn914* @api public915*/916917Command.prototype.prompt = function(str, fn){918if (/ $/.test(str)) return this.promptSingleLine.apply(this, arguments);919this.promptMultiLine(str, fn);920};921922/**923* Prompt for password with `str`, `mask` char and callback `fn(val)`.924*925* The mask string defaults to '', aka no output is926* written while typing, you may want to use "*" etc.927*928* Examples:929*930* program.password('Password: ', function(pass){931* console.log('got "%s"', pass);932* process.stdin.destroy();933* });934*935* program.password('Password: ', '*', function(pass){936* console.log('got "%s"', pass);937* process.stdin.destroy();938* });939*940* @param {String} str941* @param {String} mask942* @param {Function} fn943* @api public944*/945946Command.prototype.password = function(str, mask, fn){947var self = this948, buf = '';949950// default mask951if ('function' == typeof mask) {952fn = mask;953mask = '';954}955956tty.setRawMode(true);957process.stdout.write(str);958959// keypress960process.stdin.on('keypress', function(c, key){961if (key && 'enter' == key.name) {962console.log();963process.stdin.removeAllListeners('keypress');964tty.setRawMode(false);965if (!buf.trim().length) return self.password(str, mask, fn);966fn(buf);967return;968}969970if (key && key.ctrl && 'c' == key.name) {971console.log('%s', buf);972process.exit();973}974975process.stdout.write(mask);976buf += c;977}).resume();978};979980/**981* Confirmation prompt with `str` and callback `fn(bool)`982*983* Examples:984*985* program.confirm('continue? ', function(ok){986* console.log(' got %j', ok);987* process.stdin.destroy();988* });989*990* @param {String} str991* @param {Function} fn992* @api public993*/994995996Command.prototype.confirm = function(str, fn){997var self = this;998this.prompt(str, function(ok){999if (!ok.trim()) {1000return self.confirm(str, fn);1001}1002fn(parseBool(ok));1003});1004};10051006/**1007* Choice prompt with `list` of items and callback `fn(index, item)`1008*1009* Examples:1010*1011* var list = ['tobi', 'loki', 'jane', 'manny', 'luna'];1012*1013* console.log('Choose the coolest pet:');1014* program.choose(list, function(i){1015* console.log('you chose %d "%s"', i, list[i]);1016* process.stdin.destroy();1017* });1018*1019* @param {Array} list1020* @param {Function} fn1021* @api public1022*/10231024Command.prototype.choose = function(list, fn){1025var self = this;10261027list.forEach(function(item, i){1028console.log(' %d) %s', i + 1, item);1029});10301031function again() {1032self.prompt(' : ', function(val){1033val = parseInt(val, 10) - 1;1034if (null == list[val]) {1035again();1036} else {1037fn(val, list[val]);1038}1039});1040}10411042again();1043};10441045/**1046* Camel-case the given `flag`1047*1048* @param {String} flag1049* @return {String}1050* @api private1051*/10521053function camelcase(flag) {1054return flag.split('-').reduce(function(str, word){1055return str + word[0].toUpperCase() + word.slice(1);1056});1057}10581059/**1060* Parse a boolean `str`.1061*1062* @param {String} str1063* @return {Boolean}1064* @api private1065*/10661067function parseBool(str) {1068return /^y|yes|ok|true$/i.test(str);1069}10701071/**1072* Pad `str` to `width`.1073*1074* @param {String} str1075* @param {Number} width1076* @return {String}1077* @api private1078*/10791080function pad(str, width) {1081var len = Math.max(0, width - str.length);1082return str + Array(len + 1).join(' ');1083}10841085/**1086* Output help information if necessary1087*1088* @param {Command} command to output help for1089* @param {Array} array of options to search for -h or --help1090* @api private1091*/10921093function outputHelpIfNecessary(cmd, options) {1094options = options || [];1095for (var i = 0; i < options.length; i++) {1096if (options[i] == '--help' || options[i] == '-h') {1097process.on('exit', function() {1098consoleFlush(cmd.helpInformation());1099cmd.emit('--help');1100consoleFlush("");1101});1102process.exit();1103}11041105return true;1106}1107}110811091110