index.js 12 KB

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