1 var assert = require('assert')
2 var Completion = require('./lib/completion')
3 var Parser = require('./lib/parser')
4 var path = require('path')
5 var tokenizeArgString = require('./lib/tokenize-arg-string')
6 var Usage = require('./lib/usage')
7 var Validation = require('./lib/validation')
8 var Y18n = require('y18n')
10 Argv(process.argv.slice(2))
12 var exports = module.exports = Argv
13 function Argv (processArgs, cwd) {
14 processArgs = processArgs || [] // handle calling yargs().
21 directory: path.resolve(__dirname, './locales'),
25 if (!cwd) cwd = process.cwd()
27 self.$0 = process.argv
29 .map(function (x, i) {
30 // ignore the node bin, specify this in your
31 // bin file with #!/usr/bin/env node
32 if (i === 0 && /\b(node|iojs)$/.test(x)) return
33 var b = rebase(cwd, x)
34 return x.match(/^\//) && b.length < x.length ? b : x
38 if (process.env._ !== undefined && process.argv[1] === process.env._) {
39 self.$0 = process.env._.replace(
40 path.dirname(process.execPath) + '/', ''
45 self.resetOptions = self.reset = function () {
46 // put yargs back into its initial
47 // state, this is useful for creating a
57 defaultDescription: {},
66 usage = Usage(self, y18n) // handle usage output.
67 validation = Validation(self, usage, y18n) // handle arg validation.
68 completion = Completion(self, usage)
84 self.boolean = function (bools) {
85 options.boolean.push.apply(options.boolean, [].concat(bools))
89 self.array = function (arrays) {
90 options.array.push.apply(options.array, [].concat(arrays))
94 self.nargs = function (key, n) {
95 if (typeof key === 'object') {
96 Object.keys(key).forEach(function (k) {
100 options.narg[key] = n
105 self.choices = function (key, values) {
106 if (typeof key === 'object') {
107 Object.keys(key).forEach(function (k) {
108 self.choices(k, key[k])
111 options.choices[key] = (options.choices[key] || []).concat(values)
116 self.normalize = function (strings) {
117 options.normalize.push.apply(options.normalize, [].concat(strings))
121 self.config = function (key, msg, parseFn) {
122 if (typeof msg === 'function') {
126 self.describe(key, msg || usage.deferY18nLookup('Path to JSON config file'))
127 ;(Array.isArray(key) ? key : [key]).forEach(function (k) {
128 options.config[k] = parseFn || true
133 self.example = function (cmd, description) {
134 usage.example(cmd, description)
138 self.command = function (cmd, description, fn) {
139 if (description !== false) {
140 usage.command(cmd, description)
142 if (fn) commandHandlers[cmd] = fn
146 var commandHandlers = {}
147 self.getCommandHandlers = function () {
148 return commandHandlers
151 self.string = function (strings) {
152 options.string.push.apply(options.string, [].concat(strings))
156 self.default = function (key, value, defaultDescription) {
157 if (typeof key === 'object') {
158 Object.keys(key).forEach(function (k) {
159 self.default(k, key[k])
162 if (defaultDescription) options.defaultDescription[key] = defaultDescription
163 if (typeof value === 'function') {
164 if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
167 options.default[key] = value
172 self.alias = function (x, y) {
173 if (typeof x === 'object') {
174 Object.keys(x).forEach(function (key) {
175 self.alias(key, x[key])
178 // perhaps 'x' is already an alias in another list?
179 // if so we should append to x's list.
181 Object.keys(options.alias).forEach(function (key) {
182 if (~options.alias[key].indexOf(x)) aliases = options.alias[key]
185 if (aliases) { // x was an alias itself.
187 } else { // x is a new alias key.
188 options.alias[x] = (options.alias[x] || []).concat(y)
191 // wait! perhaps we've created two lists of aliases
192 // that reference each other?
193 if (options.alias[y]) {
194 Array.prototype.push.apply((options.alias[x] || aliases), options.alias[y])
195 delete options.alias[y]
201 self.count = function (counts) {
202 options.count.push.apply(options.count, [].concat(counts))
207 self.demand = self.required = self.require = function (keys, max, msg) {
208 // you can optionally provide a 'max' key,
209 // which will raise an exception if too many '_'
210 // options are provided.
211 if (typeof max !== 'number') {
216 if (typeof keys === 'number') {
217 if (!demanded._) demanded._ = { count: 0, msg: null, max: max }
218 demanded._.count = keys
220 } else if (Array.isArray(keys)) {
221 keys.forEach(function (key) {
222 self.demand(key, msg)
225 if (typeof msg === 'string') {
226 demanded[keys] = { msg: msg }
227 } else if (msg === true || typeof msg === 'undefined') {
228 demanded[keys] = { msg: undefined }
234 self.getDemanded = function () {
238 self.requiresArg = function (requiresArgs) {
239 options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs))
243 self.implies = function (key, value) {
244 validation.implies(key, value)
248 self.usage = function (msg, opts) {
249 if (!opts && typeof msg === 'object') {
256 if (opts) self.options(opts)
261 self.epilogue = self.epilog = function (msg) {
266 self.fail = function (f) {
271 self.check = function (f) {
276 self.defaults = self.default
278 self.describe = function (key, desc) {
279 options.key[key] = true
280 usage.describe(key, desc)
284 self.parse = function (args) {
285 return parseArgs(args)
288 self.option = self.options = function (key, opt) {
289 if (typeof key === 'object') {
290 Object.keys(key).forEach(function (k) {
291 self.options(k, key[k])
294 assert(typeof opt === 'object', 'second argument to option must be an object')
296 options.key[key] = true // track manually set keys.
298 if (opt.alias) self.alias(key, opt.alias)
300 var demand = opt.demand || opt.required || opt.require
303 self.demand(key, demand)
304 } if ('config' in opt) {
305 self.config(key, opt.configParser)
306 } if ('default' in opt) {
307 self.default(key, opt.default)
308 } if ('nargs' in opt) {
309 self.nargs(key, opt.nargs)
310 } if ('choices' in opt) {
311 self.choices(key, opt.choices)
312 } if ('group' in opt) {
313 self.group(key, opt.group)
314 } if (opt.boolean || opt.type === 'boolean') {
316 if (opt.alias) self.boolean(opt.alias)
317 } if (opt.array || opt.type === 'array') {
319 if (opt.alias) self.array(opt.alias)
320 } if (opt.string || opt.type === 'string') {
322 if (opt.alias) self.string(opt.alias)
323 } if (opt.count || opt.type === 'count') {
325 } if (opt.defaultDescription) {
326 options.defaultDescription[key] = opt.defaultDescription
329 var desc = opt.describe || opt.description || opt.desc
331 self.describe(key, desc)
334 if (opt.requiresArg) {
335 self.requiresArg(key)
341 self.getOptions = function () {
346 self.group = function (opts, groupName) {
348 groups[groupName] = (groups[groupName] || []).concat(opts).filter(function (key) {
349 if (seen[key]) return false
350 return (seen[key] = true)
354 self.getGroups = function () {
358 // as long as options.envPrefix is not undefined,
359 // parser will apply env vars matching prefix to argv
360 self.env = function (prefix) {
361 if (prefix === false) options.envPrefix = undefined
362 else options.envPrefix = prefix || ''
366 self.wrap = function (cols) {
372 self.strict = function () {
376 self.getStrict = function () {
380 self.showHelp = function (level) {
381 if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed.
382 usage.showHelp(level)
386 var versionOpt = null
387 self.version = function (ver, opt, msg) {
388 versionOpt = opt || 'version'
390 self.boolean(versionOpt)
391 self.describe(versionOpt, msg || usage.deferY18nLookup('Show version number'))
396 self.addHelpOpt = function (opt, msg) {
399 self.describe(opt, msg || usage.deferY18nLookup('Show help'))
403 self.showHelpOnFail = function (enabled, message) {
404 usage.showHelpOnFail(enabled, message)
408 var exitProcess = true
409 self.exitProcess = function (enabled) {
410 if (typeof enabled !== 'boolean') {
413 exitProcess = enabled
416 self.getExitProcess = function () {
420 self.help = function () {
421 if (arguments.length > 0) return self.addHelpOpt.apply(self, arguments)
423 if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed.
428 var completionCommand = null
429 self.completion = function (cmd, desc, fn) {
430 // a function to execute when generating
431 // completions can be provided as the second
432 // or third argument to completion.
433 if (typeof desc === 'function') {
438 // register the completion command.
439 completionCommand = cmd || 'completion'
440 if (!desc && desc !== false) {
441 desc = 'generate bash completion script'
443 self.command(completionCommand, desc)
445 // a function can be provided
446 if (fn) completion.registerFunction(fn)
451 self.showCompletionScript = function ($0) {
453 console.log(completion.generateCompletionScript($0))
457 self.locale = function (locale) {
458 if (arguments.length === 0) {
460 return y18n.getLocale()
463 y18n.setLocale(locale)
467 self.updateStrings = self.updateLocale = function (obj) {
469 y18n.updateLocale(obj)
473 var detectLocale = true
474 self.detectLocale = function (detect) {
475 detectLocale = detect
478 self.getDetectLocale = function () {
482 self.getUsageInstance = function () {
486 self.getValidationInstance = function () {
490 self.terminalWidth = function () {
491 return require('window-size').width
494 Object.defineProperty(self, 'argv', {
499 args = parseArgs(processArgs)
501 usage.fail(err.message)
509 function parseArgs (args) {
510 args = normalizeArgs(args)
512 var parsed = Parser(args, options, y18n)
513 var argv = parsed.argv
514 var aliases = parsed.aliases
520 guessLocale() // guess locale lazily, so that it can be turned off in chain.
522 // while building up the argv object, there
523 // are two passes through the parser. If completion
524 // is being performed short-circuit on the first pass.
525 if (completionCommand &&
526 (process.argv.join(' ')).indexOf(completion.completionKey) !== -1 &&
527 !argv[completion.completionKey]) {
531 // if there's a handler associated with a
532 // command defer processing to it.
533 var handlerKeys = Object.keys(self.getCommandHandlers())
534 for (var i = 0, command; (command = handlerKeys[i]) !== undefined; i++) {
535 if (~argv._.indexOf(command)) {
536 runCommand(command, self, argv)
541 // generate a completion script for adding to ~/.bashrc.
542 if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
543 self.showCompletionScript()
549 // we must run completions first, a user might
550 // want to complete the --help or --version option.
551 if (completion.completionKey in argv) {
552 // we allow for asynchronous completions,
553 // e.g., loading in a list of commands from an API.
554 completion.getCompletion(function (completions) {
555 ;(completions || []).forEach(function (completion) {
556 console.log(completion)
566 var helpOrVersion = false
567 Object.keys(argv).forEach(function (key) {
568 if (key === helpOpt && argv[key]) {
574 } else if (key === versionOpt && argv[key]) {
583 // If the help or version options where used and exitProcess is false,
584 // we won't run validations
585 if (!helpOrVersion) {
586 if (parsed.error) throw parsed.error
588 // if we're executed via bash completion, don't
589 // bother with validation.
590 if (!argv[completion.completionKey]) {
591 validation.nonOptionCount(argv)
592 validation.missingArgumentValue(argv)
593 validation.requiredArguments(argv)
594 if (strict) validation.unknownArguments(argv, aliases)
595 validation.customChecks(argv, aliases)
596 validation.limitedChoices(argv)
597 validation.implications(argv)
601 setPlaceholderKeys(argv)
606 function guessLocale () {
607 if (!detectLocale) return
610 var osLocale = require('os-locale')
611 self.locale(osLocale.sync({ spawn: false }))
613 // if we explode looking up locale just noop
614 // we'll keep using the default language 'en'.
618 function runCommand (command, yargs, argv) {
619 setPlaceholderKeys(argv)
620 yargs.getCommandHandlers()[command](yargs.reset(), argv)
623 function setPlaceholderKeys (argv) {
624 Object.keys(options.key).forEach(function (key) {
625 // don't set placeholder keys for dot
626 // notation options 'foo.bar'.
627 if (~key.indexOf('.')) return
628 if (typeof argv[key] === 'undefined') argv[key] = undefined
632 function normalizeArgs (args) {
633 if (typeof args === 'string') {
634 return tokenizeArgString(args)
643 // rebase an absolute path to a relative one with respect to a base directory
644 // exported for tests
645 exports.rebase = rebase
646 function rebase (base, dir) {
647 return path.relative(base, dir)
650 /* Hack an instance of Argv with process.argv into Argv
652 require('yargs')(['--beeble=1','-z','zizzle']).argv
653 to parse a list of args and
654 require('yargs').argv
655 to get a parsed version of process.argv.
657 function singletonify (inst) {
658 Object.keys(inst).forEach(function (key) {
659 if (key === 'argv') {
660 Argv.__defineGetter__(key, inst.__lookupGetter__(key))
662 Argv[key] = typeof inst[key] === 'function' ? inst[key].bind(inst) : inst[key]