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