1 | 'use strict'
|
2 |
|
3 |
|
4 | const decamelize = require('decamelize')
|
5 | const stringWidth = require('string-width')
|
6 | const objFilter = require('./obj-filter')
|
7 | const path = require('path')
|
8 | const setBlocking = require('set-blocking')
|
9 | const YError = require('./yerror')
|
10 |
|
11 | module.exports = function usage (yargs, y18n) {
|
12 | const __ = y18n.__
|
13 | const self = {}
|
14 |
|
15 |
|
16 | const fails = []
|
17 | self.failFn = function failFn (f) {
|
18 | fails.push(f)
|
19 | }
|
20 |
|
21 | let failMessage = null
|
22 | let showHelpOnFail = true
|
23 | self.showHelpOnFail = function showHelpOnFailFn (enabled, message) {
|
24 | if (typeof enabled === 'string') {
|
25 | message = enabled
|
26 | enabled = true
|
27 | } else if (typeof enabled === 'undefined') {
|
28 | enabled = true
|
29 | }
|
30 | failMessage = message
|
31 | showHelpOnFail = enabled
|
32 | return self
|
33 | }
|
34 |
|
35 | let failureOutput = false
|
36 | self.fail = function fail (msg, err) {
|
37 | const logger = yargs._getLoggerInstance()
|
38 |
|
39 | if (fails.length) {
|
40 | for (let i = fails.length - 1; i >= 0; --i) {
|
41 | fails[i](msg, err, self)
|
42 | }
|
43 | } else {
|
44 | if (yargs.getExitProcess()) setBlocking(true)
|
45 |
|
46 |
|
47 | if (!failureOutput) {
|
48 | failureOutput = true
|
49 | if (showHelpOnFail) {
|
50 | yargs.showHelp('error')
|
51 | logger.error()
|
52 | }
|
53 | if (msg || err) logger.error(msg || err)
|
54 | if (failMessage) {
|
55 | if (msg || err) logger.error('')
|
56 | logger.error(failMessage)
|
57 | }
|
58 | }
|
59 |
|
60 | err = err || new YError(msg)
|
61 | if (yargs.getExitProcess()) {
|
62 | return yargs.exit(1)
|
63 | } else if (yargs._hasParseCallback()) {
|
64 | return yargs.exit(1, err)
|
65 | } else {
|
66 | throw err
|
67 | }
|
68 | }
|
69 | }
|
70 |
|
71 |
|
72 | let usages = []
|
73 | let usageDisabled = false
|
74 | self.usage = (msg, description) => {
|
75 | if (msg === null) {
|
76 | usageDisabled = true
|
77 | usages = []
|
78 | return
|
79 | }
|
80 | usageDisabled = false
|
81 | usages.push([msg, description || ''])
|
82 | return self
|
83 | }
|
84 | self.getUsage = () => {
|
85 | return usages
|
86 | }
|
87 | self.getUsageDisabled = () => {
|
88 | return usageDisabled
|
89 | }
|
90 |
|
91 | self.getPositionalGroupName = () => {
|
92 | return __('Positionals:')
|
93 | }
|
94 |
|
95 | let examples = []
|
96 | self.example = (cmd, description) => {
|
97 | examples.push([cmd, description || ''])
|
98 | }
|
99 |
|
100 | let commands = []
|
101 | self.command = function command (cmd, description, isDefault, aliases) {
|
102 |
|
103 | if (isDefault) {
|
104 | commands = commands.map((cmdArray) => {
|
105 | cmdArray[2] = false
|
106 | return cmdArray
|
107 | })
|
108 | }
|
109 | commands.push([cmd, description || '', isDefault, aliases])
|
110 | }
|
111 | self.getCommands = () => commands
|
112 |
|
113 | let descriptions = {}
|
114 | self.describe = function describe (key, desc) {
|
115 | if (typeof key === 'object') {
|
116 | Object.keys(key).forEach((k) => {
|
117 | self.describe(k, key[k])
|
118 | })
|
119 | } else {
|
120 | descriptions[key] = desc
|
121 | }
|
122 | }
|
123 | self.getDescriptions = () => descriptions
|
124 |
|
125 | let epilogs = []
|
126 | self.epilog = (msg) => {
|
127 | epilogs.push(msg)
|
128 | }
|
129 |
|
130 | let wrapSet = false
|
131 | let wrap
|
132 | self.wrap = (cols) => {
|
133 | wrapSet = true
|
134 | wrap = cols
|
135 | }
|
136 |
|
137 | function getWrap () {
|
138 | if (!wrapSet) {
|
139 | wrap = windowWidth()
|
140 | wrapSet = true
|
141 | }
|
142 |
|
143 | return wrap
|
144 | }
|
145 |
|
146 | const deferY18nLookupPrefix = '__yargsString__:'
|
147 | self.deferY18nLookup = str => deferY18nLookupPrefix + str
|
148 |
|
149 | const defaultGroup = __('Options:')
|
150 | self.help = function help () {
|
151 | if (cachedHelpMessage) return cachedHelpMessage
|
152 | normalizeAliases()
|
153 |
|
154 |
|
155 | const base$0 = yargs.customScriptName ? yargs.$0 : path.basename(yargs.$0)
|
156 | const demandedOptions = yargs.getDemandedOptions()
|
157 | const demandedCommands = yargs.getDemandedCommands()
|
158 | const deprecatedOptions = yargs.getDeprecatedOptions()
|
159 | const groups = yargs.getGroups()
|
160 | const options = yargs.getOptions()
|
161 |
|
162 | let keys = []
|
163 | keys = keys.concat(Object.keys(descriptions))
|
164 | keys = keys.concat(Object.keys(demandedOptions))
|
165 | keys = keys.concat(Object.keys(demandedCommands))
|
166 | keys = keys.concat(Object.keys(options.default))
|
167 | keys = keys.filter(filterHiddenOptions)
|
168 | keys = Object.keys(keys.reduce((acc, key) => {
|
169 | if (key !== '_') acc[key] = true
|
170 | return acc
|
171 | }, {}))
|
172 |
|
173 | const theWrap = getWrap()
|
174 | const ui = require('cliui')({
|
175 | width: theWrap,
|
176 | wrap: !!theWrap
|
177 | })
|
178 |
|
179 |
|
180 | if (!usageDisabled) {
|
181 | if (usages.length) {
|
182 |
|
183 | usages.forEach((usage) => {
|
184 | ui.div(`${usage[0].replace(/\$0/g, base$0)}`)
|
185 | if (usage[1]) {
|
186 | ui.div({ text: `${usage[1]}`, padding: [1, 0, 0, 0] })
|
187 | }
|
188 | })
|
189 | ui.div()
|
190 | } else if (commands.length) {
|
191 | let u = null
|
192 |
|
193 | if (demandedCommands._) {
|
194 | u = `${base$0} <${__('command')}>\n`
|
195 | } else {
|
196 | u = `${base$0} [${__('command')}]\n`
|
197 | }
|
198 | ui.div(`${u}`)
|
199 | }
|
200 | }
|
201 |
|
202 |
|
203 |
|
204 | if (commands.length) {
|
205 | ui.div(__('Commands:'))
|
206 |
|
207 | const context = yargs.getContext()
|
208 | const parentCommands = context.commands.length ? `${context.commands.join(' ')} ` : ''
|
209 |
|
210 | if (yargs.getParserConfiguration()['sort-commands'] === true) {
|
211 | commands = commands.sort((a, b) => a[0].localeCompare(b[0]))
|
212 | }
|
213 |
|
214 | commands.forEach((command) => {
|
215 | const commandString = `${base$0} ${parentCommands}${command[0].replace(/^\$0 ?/, '')}`
|
216 | ui.span(
|
217 | {
|
218 | text: commandString,
|
219 | padding: [0, 2, 0, 2],
|
220 | width: maxWidth(commands, theWrap, `${base$0}${parentCommands}`) + 4
|
221 | },
|
222 | { text: command[1] }
|
223 | )
|
224 | const hints = []
|
225 | if (command[2]) hints.push(`[${__('default')}]`)
|
226 | if (command[3] && command[3].length) {
|
227 | hints.push(`[${__('aliases:')} ${command[3].join(', ')}]`)
|
228 | }
|
229 | if (hints.length) {
|
230 | ui.div({ text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right' })
|
231 | } else {
|
232 | ui.div()
|
233 | }
|
234 | })
|
235 |
|
236 | ui.div()
|
237 | }
|
238 |
|
239 |
|
240 |
|
241 | const aliasKeys = (Object.keys(options.alias) || [])
|
242 | .concat(Object.keys(yargs.parsed.newAliases) || [])
|
243 |
|
244 | keys = keys.filter(key => !yargs.parsed.newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1))
|
245 |
|
246 |
|
247 |
|
248 | if (!groups[defaultGroup]) groups[defaultGroup] = []
|
249 | addUngroupedKeys(keys, options.alias, groups)
|
250 |
|
251 |
|
252 | Object.keys(groups).forEach((groupName) => {
|
253 | if (!groups[groupName].length) return
|
254 |
|
255 |
|
256 |
|
257 | const normalizedKeys = groups[groupName].filter(filterHiddenOptions).map((key) => {
|
258 | if (~aliasKeys.indexOf(key)) return key
|
259 | for (let i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) {
|
260 | if (~(options.alias[aliasKey] || []).indexOf(key)) return aliasKey
|
261 | }
|
262 | return key
|
263 | })
|
264 |
|
265 | if (normalizedKeys.length < 1) return
|
266 |
|
267 | ui.div(groupName)
|
268 |
|
269 |
|
270 | const switches = normalizedKeys.reduce((acc, key) => {
|
271 | acc[key] = [key].concat(options.alias[key] || [])
|
272 | .map(sw => {
|
273 |
|
274 |
|
275 | if (groupName === self.getPositionalGroupName()) return sw
|
276 | else {
|
277 | return (
|
278 |
|
279 |
|
280 | /^[0-9]$/.test(sw)
|
281 | ? ~options.boolean.indexOf(key) ? '-' : '--'
|
282 | : sw.length > 1 ? '--' : '-'
|
283 | ) + sw
|
284 | }
|
285 | })
|
286 | .join(', ')
|
287 |
|
288 | return acc
|
289 | }, {})
|
290 |
|
291 | normalizedKeys.forEach((key) => {
|
292 | const kswitch = switches[key]
|
293 | let desc = descriptions[key] || ''
|
294 | let type = null
|
295 |
|
296 | if (~desc.lastIndexOf(deferY18nLookupPrefix)) desc = __(desc.substring(deferY18nLookupPrefix.length))
|
297 |
|
298 | if (~options.boolean.indexOf(key)) type = `[${__('boolean')}]`
|
299 | if (~options.count.indexOf(key)) type = `[${__('count')}]`
|
300 | if (~options.string.indexOf(key)) type = `[${__('string')}]`
|
301 | if (~options.normalize.indexOf(key)) type = `[${__('string')}]`
|
302 | if (~options.array.indexOf(key)) type = `[${__('array')}]`
|
303 | if (~options.number.indexOf(key)) type = `[${__('number')}]`
|
304 |
|
305 | const extra = [
|
306 | (key in deprecatedOptions) ? (
|
307 | typeof deprecatedOptions[key] === 'string'
|
308 | ? `[${__('deprecated: %s', deprecatedOptions[key])}]`
|
309 | : `[${__('deprecated')}]`
|
310 | ) : null,
|
311 | type,
|
312 | (key in demandedOptions) ? `[${__('required')}]` : null,
|
313 | options.choices && options.choices[key] ? `[${__('choices:')} ${
|
314 | self.stringifiedValues(options.choices[key])}]` : null,
|
315 | defaultString(options.default[key], options.defaultDescription[key])
|
316 | ].filter(Boolean).join(' ')
|
317 |
|
318 | ui.span(
|
319 | { text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches, theWrap) + 4 },
|
320 | desc
|
321 | )
|
322 |
|
323 | if (extra) ui.div({ text: extra, padding: [0, 0, 0, 2], align: 'right' })
|
324 | else ui.div()
|
325 | })
|
326 |
|
327 | ui.div()
|
328 | })
|
329 |
|
330 |
|
331 | if (examples.length) {
|
332 | ui.div(__('Examples:'))
|
333 |
|
334 | examples.forEach((example) => {
|
335 | example[0] = example[0].replace(/\$0/g, base$0)
|
336 | })
|
337 |
|
338 | examples.forEach((example) => {
|
339 | if (example[1] === '') {
|
340 | ui.div(
|
341 | {
|
342 | text: example[0],
|
343 | padding: [0, 2, 0, 2]
|
344 | }
|
345 | )
|
346 | } else {
|
347 | ui.div(
|
348 | {
|
349 | text: example[0],
|
350 | padding: [0, 2, 0, 2],
|
351 | width: maxWidth(examples, theWrap) + 4
|
352 | }, {
|
353 | text: example[1]
|
354 | }
|
355 | )
|
356 | }
|
357 | })
|
358 |
|
359 | ui.div()
|
360 | }
|
361 |
|
362 |
|
363 | if (epilogs.length > 0) {
|
364 | const e = epilogs.map(epilog => epilog.replace(/\$0/g, base$0)).join('\n')
|
365 | ui.div(`${e}\n`)
|
366 | }
|
367 |
|
368 |
|
369 | return ui.toString().replace(/\s*$/, '')
|
370 | }
|
371 |
|
372 |
|
373 |
|
374 | function maxWidth (table, theWrap, modifier) {
|
375 | let width = 0
|
376 |
|
377 |
|
378 |
|
379 | if (!Array.isArray(table)) {
|
380 | table = Object.keys(table).map(key => [table[key]])
|
381 | }
|
382 |
|
383 | table.forEach((v) => {
|
384 | width = Math.max(
|
385 | stringWidth(modifier ? `${modifier} ${v[0]}` : v[0]),
|
386 | width
|
387 | )
|
388 | })
|
389 |
|
390 |
|
391 |
|
392 | if (theWrap) width = Math.min(width, parseInt(theWrap * 0.5, 10))
|
393 |
|
394 | return width
|
395 | }
|
396 |
|
397 |
|
398 |
|
399 | function normalizeAliases () {
|
400 |
|
401 | const demandedOptions = yargs.getDemandedOptions()
|
402 | const options = yargs.getOptions()
|
403 |
|
404 | ;(Object.keys(options.alias) || []).forEach((key) => {
|
405 | options.alias[key].forEach((alias) => {
|
406 |
|
407 | if (descriptions[alias]) self.describe(key, descriptions[alias])
|
408 |
|
409 | if (alias in demandedOptions) yargs.demandOption(key, demandedOptions[alias])
|
410 |
|
411 | if (~options.boolean.indexOf(alias)) yargs.boolean(key)
|
412 | if (~options.count.indexOf(alias)) yargs.count(key)
|
413 | if (~options.string.indexOf(alias)) yargs.string(key)
|
414 | if (~options.normalize.indexOf(alias)) yargs.normalize(key)
|
415 | if (~options.array.indexOf(alias)) yargs.array(key)
|
416 | if (~options.number.indexOf(alias)) yargs.number(key)
|
417 | })
|
418 | })
|
419 | }
|
420 |
|
421 |
|
422 |
|
423 | let cachedHelpMessage
|
424 | self.cacheHelpMessage = function () {
|
425 | cachedHelpMessage = this.help()
|
426 | }
|
427 |
|
428 |
|
429 |
|
430 | self.clearCachedHelpMessage = function () {
|
431 | cachedHelpMessage = undefined
|
432 | }
|
433 |
|
434 |
|
435 |
|
436 | function addUngroupedKeys (keys, aliases, groups) {
|
437 | let groupedKeys = []
|
438 | let toCheck = null
|
439 | Object.keys(groups).forEach((group) => {
|
440 | groupedKeys = groupedKeys.concat(groups[group])
|
441 | })
|
442 |
|
443 | keys.forEach((key) => {
|
444 | toCheck = [key].concat(aliases[key])
|
445 | if (!toCheck.some(k => groupedKeys.indexOf(k) !== -1)) {
|
446 | groups[defaultGroup].push(key)
|
447 | }
|
448 | })
|
449 | return groupedKeys
|
450 | }
|
451 |
|
452 | function filterHiddenOptions (key) {
|
453 | return yargs.getOptions().hiddenOptions.indexOf(key) < 0 || yargs.parsed.argv[yargs.getOptions().showHiddenOpt]
|
454 | }
|
455 |
|
456 | self.showHelp = (level) => {
|
457 | const logger = yargs._getLoggerInstance()
|
458 | if (!level) level = 'error'
|
459 | const emit = typeof level === 'function' ? level : logger[level]
|
460 | emit(self.help())
|
461 | }
|
462 |
|
463 | self.functionDescription = (fn) => {
|
464 | const description = fn.name ? decamelize(fn.name, '-') : __('generated-value')
|
465 | return ['(', description, ')'].join('')
|
466 | }
|
467 |
|
468 | self.stringifiedValues = function stringifiedValues (values, separator) {
|
469 | let string = ''
|
470 | const sep = separator || ', '
|
471 | const array = [].concat(values)
|
472 |
|
473 | if (!values || !array.length) return string
|
474 |
|
475 | array.forEach((value) => {
|
476 | if (string.length) string += sep
|
477 | string += JSON.stringify(value)
|
478 | })
|
479 |
|
480 | return string
|
481 | }
|
482 |
|
483 |
|
484 |
|
485 | function defaultString (value, defaultDescription) {
|
486 | let string = `[${__('default:')} `
|
487 |
|
488 | if (value === undefined && !defaultDescription) return null
|
489 |
|
490 | if (defaultDescription) {
|
491 | string += defaultDescription
|
492 | } else {
|
493 | switch (typeof value) {
|
494 | case 'string':
|
495 | string += `"${value}"`
|
496 | break
|
497 | case 'object':
|
498 | string += JSON.stringify(value)
|
499 | break
|
500 | default:
|
501 | string += value
|
502 | }
|
503 | }
|
504 |
|
505 | return `${string}]`
|
506 | }
|
507 |
|
508 |
|
509 | function windowWidth () {
|
510 | const maxWidth = 80
|
511 |
|
512 |
|
513 | if (typeof process === 'object' && process.stdout && process.stdout.columns) {
|
514 | return Math.min(maxWidth, process.stdout.columns)
|
515 | } else {
|
516 | return maxWidth
|
517 | }
|
518 | }
|
519 |
|
520 |
|
521 | let version = null
|
522 | self.version = (ver) => {
|
523 | version = ver
|
524 | }
|
525 |
|
526 | self.showVersion = () => {
|
527 | const logger = yargs._getLoggerInstance()
|
528 | logger.log(version)
|
529 | }
|
530 |
|
531 | self.reset = function reset (localLookup) {
|
532 |
|
533 |
|
534 | failMessage = null
|
535 | failureOutput = false
|
536 | usages = []
|
537 | usageDisabled = false
|
538 | epilogs = []
|
539 | examples = []
|
540 | commands = []
|
541 | descriptions = objFilter(descriptions, (k, v) => !localLookup[k])
|
542 | return self
|
543 | }
|
544 |
|
545 | const frozens = []
|
546 | self.freeze = function freeze () {
|
547 | const frozen = {}
|
548 | frozens.push(frozen)
|
549 | frozen.failMessage = failMessage
|
550 | frozen.failureOutput = failureOutput
|
551 | frozen.usages = usages
|
552 | frozen.usageDisabled = usageDisabled
|
553 | frozen.epilogs = epilogs
|
554 | frozen.examples = examples
|
555 | frozen.commands = commands
|
556 | frozen.descriptions = descriptions
|
557 | }
|
558 | self.unfreeze = function unfreeze () {
|
559 | const frozen = frozens.pop()
|
560 | failMessage = frozen.failMessage
|
561 | failureOutput = frozen.failureOutput
|
562 | usages = frozen.usages
|
563 | usageDisabled = frozen.usageDisabled
|
564 | epilogs = frozen.epilogs
|
565 | examples = frozen.examples
|
566 | commands = frozen.commands
|
567 | descriptions = frozen.descriptions
|
568 | }
|
569 |
|
570 | return self
|
571 | }
|