1var assert = require('assert'), 2 path = require('path'), 3 Completion = require('./lib/completion'), 4 Parser = require('./lib/parser'), 5 Usage = require('./lib/usage'), 6 Validation = require('./lib/validation') 7 8Argv(process.argv.slice(2)) 9 10var exports = module.exports = Argv 11function Argv (processArgs, cwd) { 12 processArgs = processArgs || [] // handle calling yargs(). 13 14 var self = {} 15 var completion = null 16 var usage = null 17 var validation = null 18 19 if (!cwd) cwd = process.cwd() 20 21 self.$0 = process.argv 22 .slice(0, 2) 23 .map(function (x, i) { 24 // ignore the node bin, specify this in your 25 // bin file with #!/usr/bin/env node 26 if (i === 0 && /\b(node|iojs)$/.test(x)) return 27 var b = rebase(cwd, x) 28 return x.match(/^\//) && b.length < x.length 29 ? b : x 30 }) 31 .join(' ').trim() 32 33 if (process.env._ !== undefined && process.argv[1] === process.env._) { 34 self.$0 = process.env._.replace( 35 path.dirname(process.execPath) + '/', '' 36 ) 37 } 38 39 var options 40 self.resetOptions = self.reset = function () { 41 // put yargs back into its initial 42 // state, this is useful for creating a 43 // nested CLI. 44 options = { 45 array: [], 46 boolean: [], 47 string: [], 48 narg: {}, 49 key: {}, 50 alias: {}, 51 default: {}, 52 defaultDescription: {}, 53 requiresArg: [], 54 count: [], 55 normalize: [], 56 config: [] 57 } 58 59 usage = Usage(self) // handle usage output. 60 validation = Validation(self, usage) // handle arg validation. 61 completion = Completion(self, usage) 62 63 demanded = {} 64 65 exitProcess = true 66 strict = false 67 helpOpt = null 68 versionOpt = null 69 completionOpt = null 70 commandHandlers = {} 71 self.parsed = false 72 73 return self 74 } 75 self.resetOptions() 76 77 self.boolean = function (bools) { 78 options.boolean.push.apply(options.boolean, [].concat(bools)) 79 return self 80 } 81 82 self.array = function (arrays) { 83 options.array.push.apply(options.array, [].concat(arrays)) 84 return self 85 } 86 87 self.nargs = function (key, n) { 88 if (typeof key === 'object') { 89 Object.keys(key).forEach(function (k) { 90 self.nargs(k, key[k]) 91 }) 92 } else { 93 options.narg[key] = n 94 } 95 return self 96 } 97 98 self.normalize = function (strings) { 99 options.normalize.push.apply(options.normalize, [].concat(strings)) 100 return self 101 } 102 103 self.config = function (configs) { 104 options.config.push.apply(options.config, [].concat(configs)) 105 return self 106 } 107 108 self.example = function (cmd, description) { 109 usage.example(cmd, description) 110 return self 111 } 112 113 self.command = function (cmd, description, fn) { 114 usage.command(cmd, description) 115 if (fn) commandHandlers[cmd] = fn 116 return self 117 } 118 119 var commandHandlers = {} 120 self.getCommandHandlers = function () { 121 return commandHandlers 122 } 123 124 self.string = function (strings) { 125 options.string.push.apply(options.string, [].concat(strings)) 126 return self 127 } 128 129 self.default = function (key, value, defaultDescription) { 130 if (typeof key === 'object') { 131 Object.keys(key).forEach(function (k) { 132 self.default(k, key[k]) 133 }) 134 } else { 135 if (typeof value === 'function') { 136 defaultDescription = usage.functionDescription(value, defaultDescription) 137 value = value.call() 138 } 139 options.defaultDescription[key] = defaultDescription 140 options.default[key] = value 141 } 142 return self 143 } 144 145 self.alias = function (x, y) { 146 if (typeof x === 'object') { 147 Object.keys(x).forEach(function (key) { 148 self.alias(key, x[key]) 149 }) 150 } else { 151 options.alias[x] = (options.alias[x] || []).concat(y) 152 } 153 return self 154 } 155 156 self.count = function (counts) { 157 options.count.push.apply(options.count, [].concat(counts)) 158 return self 159 } 160 161 var demanded = {} 162 self.demand = self.required = self.require = function (keys, msg) { 163 if (typeof keys === 'number') { 164 if (!demanded._) demanded._ = { count: 0, msg: null } 165 demanded._.count += keys 166 demanded._.msg = msg 167 } else if (Array.isArray(keys)) { 168 keys.forEach(function (key) { 169 self.demand(key, msg) 170 }) 171 } else { 172 if (typeof msg === 'string') { 173 demanded[keys] = { msg: msg } 174 } else if (msg === true || typeof msg === 'undefined') { 175 demanded[keys] = { msg: undefined } 176 } 177 } 178 179 return self 180 } 181 self.getDemanded = function () { 182 return demanded 183 } 184 185 self.requiresArg = function (requiresArgs) { 186 options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs)) 187 return self 188 } 189 190 self.implies = function (key, value) { 191 validation.implies(key, value) 192 return self 193 } 194 195 self.usage = function (msg, opts) { 196 if (!opts && typeof msg === 'object') { 197 opts = msg 198 msg = null 199 } 200 201 usage.usage(msg) 202 203 if (opts) self.options(opts) 204 205 return self 206 } 207 208 self.epilogue = self.epilog = function (msg) { 209 usage.epilog(msg) 210 return self 211 } 212 213 self.fail = function (f) { 214 usage.failFn(f) 215 return self 216 } 217 218 self.check = function (f) { 219 validation.check(f) 220 return self 221 } 222 223 self.defaults = self.default 224 225 self.describe = function (key, desc) { 226 options.key[key] = true 227 usage.describe(key, desc) 228 return self 229 } 230 231 self.parse = function (args) { 232 return parseArgs(args) 233 } 234 235 self.option = self.options = function (key, opt) { 236 if (typeof key === 'object') { 237 Object.keys(key).forEach(function (k) { 238 self.options(k, key[k]) 239 }) 240 } else { 241 assert(typeof opt === 'object', 'second argument to option must be an object') 242 243 options.key[key] = true // track manually set keys. 244 245 if (opt.alias) self.alias(key, opt.alias) 246 247 var demand = opt.demand || opt.required || opt.require 248 249 if (demand) { 250 self.demand(key, demand) 251 } if ('default' in opt) { 252 self.default(key, opt.default) 253 } if ('nargs' in opt) { 254 self.nargs(key, opt.nargs) 255 } if (opt.boolean || opt.type === 'boolean') { 256 self.boolean(key) 257 if (opt.alias) self.boolean(opt.alias) 258 } if (opt.array || opt.type === 'array') { 259 self.array(key) 260 if (opt.alias) self.array(opt.alias) 261 } if (opt.string || opt.type === 'string') { 262 self.string(key) 263 if (opt.alias) self.string(opt.alias) 264 } if (opt.count || opt.type === 'count') { 265 self.count(key) 266 } 267 268 var desc = opt.describe || opt.description || opt.desc 269 if (desc) { 270 self.describe(key, desc) 271 } 272 273 if (opt.requiresArg) { 274 self.requiresArg(key) 275 } 276 } 277 278 return self 279 } 280 self.getOptions = function () { 281 return options 282 } 283 284 self.wrap = function (cols) { 285 usage.wrap(cols) 286 return self 287 } 288 289 var strict = false 290 self.strict = function () { 291 strict = true 292 return self 293 } 294 self.getStrict = function () { 295 return strict 296 } 297 298 self.showHelp = function (level) { 299 if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed. 300 usage.showHelp(level) 301 return self 302 } 303 304 var versionOpt = null 305 self.version = function (ver, opt, msg) { 306 versionOpt = opt || 'version' 307 usage.version(ver) 308 self.boolean(versionOpt) 309 self.describe(versionOpt, msg || 'Show version number') 310 return self 311 } 312 313 var helpOpt = null 314 self.addHelpOpt = function (opt, msg) { 315 helpOpt = opt 316 self.boolean(opt) 317 self.describe(opt, msg || 'Show help') 318 return self 319 } 320 321 self.showHelpOnFail = function (enabled, message) { 322 usage.showHelpOnFail(enabled, message) 323 return self 324 } 325 326 var exitProcess = true 327 self.exitProcess = function (enabled) { 328 if (typeof enabled !== 'boolean') { 329 enabled = true 330 } 331 exitProcess = enabled 332 return self 333 } 334 self.getExitProcess = function () { 335 return exitProcess 336 } 337 338 self.help = function () { 339 if (arguments.length > 0) return self.addHelpOpt.apply(self, arguments) 340 341 if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed. 342 343 return usage.help() 344 } 345 346 var completionOpt = null, 347 completionCommand = null 348 self.completion = function (cmd, desc, fn) { 349 // a function to execute when generating 350 // completions can be provided as the second 351 // or third argument to completion. 352 if (typeof desc === 'function') { 353 fn = desc 354 desc = null 355 } 356 357 // register the completion command. 358 completionCommand = cmd 359 completionOpt = completion.completionKey 360 self.command(completionCommand, desc || 'generate bash completion script') 361 362 // a function can be provided 363 if (fn) completion.registerFunction(fn) 364 365 return self 366 } 367 368 self.showCompletionScript = function ($0) { 369 $0 = $0 || self.$0 370 console.log(completion.generateCompletionScript($0)) 371 return self 372 } 373 374 self.getUsageInstance = function () { 375 return usage 376 } 377 378 self.getValidationInstance = function () { 379 return validation 380 } 381 382 self.terminalWidth = function () { 383 return require('window-size').width 384 } 385 386 Object.defineProperty(self, 'argv', { 387 get: function () { 388 var args = null 389 390 try { 391 args = parseArgs(processArgs) 392 } catch (err) { 393 usage.fail(err.message) 394 } 395 396 return args 397 }, 398 enumerable: true 399 }) 400 401 function parseArgs (args) { 402 var parsed = Parser(args, options), 403 argv = parsed.argv, 404 aliases = parsed.aliases 405 406 argv.$0 = self.$0 407 408 self.parsed = parsed 409 410 // generate a completion script for adding to ~/.bashrc. 411 if (completionCommand && ~argv._.indexOf(completionCommand)) { 412 self.showCompletionScript() 413 if (exitProcess) { 414 process.exit(0) 415 } 416 } 417 418 // if there's a handler associated with a 419 // command defer processing to it. 420 var handlerKeys = Object.keys(self.getCommandHandlers()) 421 for (var i = 0, command; (command = handlerKeys[i]) !== undefined; i++) { 422 if (~argv._.indexOf(command)) { 423 self.getCommandHandlers()[command](self.reset()) 424 return self.argv 425 } 426 } 427 428 Object.keys(argv).forEach(function (key) { 429 if (key === helpOpt && argv[key]) { 430 self.showHelp('log') 431 if (exitProcess) { 432 process.exit(0) 433 } 434 } else if (key === versionOpt && argv[key]) { 435 usage.showVersion() 436 if (exitProcess) { 437 process.exit(0) 438 } 439 } else if (key === completionOpt) { 440 // we allow for asynchronous completions, 441 // e.g., loading in a list of commands from an API. 442 completion.getCompletion(function (completions) { 443 ;(completions || []).forEach(function (completion) { 444 console.log(completion) 445 }) 446 447 if (exitProcess) { 448 process.exit(0) 449 } 450 }) 451 return 452 } 453 }) 454 455 validation.nonOptionCount(argv) 456 validation.missingArgumentValue(argv) 457 validation.requiredArguments(argv) 458 459 if (strict) { 460 validation.unknownArguments(argv, aliases) 461 } 462 463 validation.customChecks(argv, aliases) 464 validation.implications(argv) 465 setPlaceholderKeys(argv) 466 467 return argv 468 } 469 470 function setPlaceholderKeys (argv) { 471 Object.keys(options.key).forEach(function (key) { 472 if (typeof argv[key] === 'undefined') argv[key] = undefined 473 }) 474 } 475 476 sigletonify(self) 477 return self 478} 479 480// rebase an absolute path to a relative one with respect to a base directory 481// exported for tests 482exports.rebase = rebase 483function rebase (base, dir) { 484 return path.relative(base, dir) 485} 486 487/* Hack an instance of Argv with process.argv into Argv 488 so people can do 489 require('yargs')(['--beeble=1','-z','zizzle']).argv 490 to parse a list of args and 491 require('yargs').argv 492 to get a parsed version of process.argv. 493*/ 494function sigletonify (inst) { 495 Object.keys(inst).forEach(function (key) { 496 if (key === 'argv') { 497 Argv.__defineGetter__(key, inst.__lookupGetter__(key)) 498 } else { 499 Argv[key] = typeof inst[key] === 'function' 500 ? inst[key].bind(inst) 501 : inst[key] 502 } 503 }) 504} 505