.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / yargs / lib / usage.js
1 'use strict'
2 // this file handles outputting usage instructions,
3 // failures, etc. keeps logging in one place.
4 const stringWidth = require('string-width')
5 const objFilter = require('./obj-filter')
6 const path = require('path')
7 const setBlocking = require('set-blocking')
8 const YError = require('./yerror')
9
10 module.exports = function usage (yargs, y18n) {
11   const __ = y18n.__
12   const self = {}
13
14   // methods for ouputting/building failure message.
15   const fails = []
16   self.failFn = function failFn (f) {
17     fails.push(f)
18   }
19
20   let failMessage = null
21   let showHelpOnFail = true
22   self.showHelpOnFail = function showHelpOnFailFn (enabled, message) {
23     if (typeof enabled === 'string') {
24       message = enabled
25       enabled = true
26     } else if (typeof enabled === 'undefined') {
27       enabled = true
28     }
29     failMessage = message
30     showHelpOnFail = enabled
31     return self
32   }
33
34   let failureOutput = false
35   self.fail = function fail (msg, err) {
36     const logger = yargs._getLoggerInstance()
37
38     if (fails.length) {
39       for (let i = fails.length - 1; i >= 0; --i) {
40         fails[i](msg, err, self)
41       }
42     } else {
43       if (yargs.getExitProcess()) setBlocking(true)
44
45       // don't output failure message more than once
46       if (!failureOutput) {
47         failureOutput = true
48         if (showHelpOnFail) yargs.showHelp('error')
49         if (msg || err) logger.error(msg || err)
50         if (failMessage) {
51           if (msg || err) logger.error('')
52           logger.error(failMessage)
53         }
54       }
55
56       err = err || new YError(msg)
57       if (yargs.getExitProcess()) {
58         return yargs.exit(1)
59       } else if (yargs._hasParseCallback()) {
60         return yargs.exit(1, err)
61       } else {
62         throw err
63       }
64     }
65   }
66
67   // methods for ouputting/building help (usage) message.
68   let usages = []
69   let usageDisabled = false
70   self.usage = (msg, description) => {
71     if (msg === null) {
72       usageDisabled = true
73       usages = []
74       return
75     }
76     usageDisabled = false
77     usages.push([msg, description || ''])
78     return self
79   }
80   self.getUsage = () => {
81     return usages
82   }
83   self.getUsageDisabled = () => {
84     return usageDisabled
85   }
86
87   self.getPositionalGroupName = () => {
88     return __('Positionals:')
89   }
90
91   let examples = []
92   self.example = (cmd, description) => {
93     examples.push([cmd, description || ''])
94   }
95
96   let commands = []
97   self.command = function command (cmd, description, isDefault, aliases) {
98     // the last default wins, so cancel out any previously set default
99     if (isDefault) {
100       commands = commands.map((cmdArray) => {
101         cmdArray[2] = false
102         return cmdArray
103       })
104     }
105     commands.push([cmd, description || '', isDefault, aliases])
106   }
107   self.getCommands = () => commands
108
109   let descriptions = {}
110   self.describe = function describe (key, desc) {
111     if (typeof key === 'object') {
112       Object.keys(key).forEach((k) => {
113         self.describe(k, key[k])
114       })
115     } else {
116       descriptions[key] = desc
117     }
118   }
119   self.getDescriptions = () => descriptions
120
121   let epilog
122   self.epilog = (msg) => {
123     epilog = msg
124   }
125
126   let wrapSet = false
127   let wrap
128   self.wrap = (cols) => {
129     wrapSet = true
130     wrap = cols
131   }
132
133   function getWrap () {
134     if (!wrapSet) {
135       wrap = windowWidth()
136       wrapSet = true
137     }
138
139     return wrap
140   }
141
142   const deferY18nLookupPrefix = '__yargsString__:'
143   self.deferY18nLookup = str => deferY18nLookupPrefix + str
144
145   const defaultGroup = 'Options:'
146   self.help = function help () {
147     normalizeAliases()
148
149     // handle old demanded API
150     const base$0 = path.basename(yargs.$0)
151     const demandedOptions = yargs.getDemandedOptions()
152     const demandedCommands = yargs.getDemandedCommands()
153     const groups = yargs.getGroups()
154     const options = yargs.getOptions()
155     let keys = Object.keys(
156       Object.keys(descriptions)
157       .concat(Object.keys(demandedOptions))
158       .concat(Object.keys(demandedCommands))
159       .concat(Object.keys(options.default))
160       .reduce((acc, key) => {
161         if (key !== '_') acc[key] = true
162         return acc
163       }, {})
164     )
165
166     const theWrap = getWrap()
167     const ui = require('cliui')({
168       width: theWrap,
169       wrap: !!theWrap
170     })
171
172     // the usage string.
173     if (!usageDisabled) {
174       if (usages.length) {
175         // user-defined usage.
176         usages.forEach((usage) => {
177           ui.div(`${usage[0].replace(/\$0/g, base$0)}`)
178           if (usage[1]) {
179             ui.div({text: `${usage[1]}`, padding: [1, 0, 0, 0]})
180           }
181         })
182         ui.div()
183       } else if (commands.length) {
184         let u = null
185         // demonstrate how commands are used.
186         if (demandedCommands._) {
187           u = `${base$0} <${__('command')}>\n`
188         } else {
189           u = `${base$0} [${__('command')}]\n`
190         }
191         ui.div(`${u}`)
192       }
193     }
194
195     // your application's commands, i.e., non-option
196     // arguments populated in '_'.
197     if (commands.length) {
198       ui.div(__('Commands:'))
199
200       const context = yargs.getContext()
201       const parentCommands = context.commands.length ? `${context.commands.join(' ')} ` : ''
202
203       commands.forEach((command) => {
204         const commandString = `${base$0} ${parentCommands}${command[0].replace(/^\$0 ?/, '')}` // drop $0 from default commands.
205         ui.span(
206           {
207             text: commandString,
208             padding: [0, 2, 0, 2],
209             width: maxWidth(commands, theWrap, `${base$0}${parentCommands}`) + 4
210           },
211           {text: command[1]}
212         )
213         const hints = []
214         if (command[2]) hints.push(`[${__('default:').slice(0, -1)}]`) // TODO hacking around i18n here
215         if (command[3] && command[3].length) {
216           hints.push(`[${__('aliases:')} ${command[3].join(', ')}]`)
217         }
218         if (hints.length) {
219           ui.div({text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right'})
220         } else {
221           ui.div()
222         }
223       })
224
225       ui.div()
226     }
227
228     // perform some cleanup on the keys array, making it
229     // only include top-level keys not their aliases.
230     const aliasKeys = (Object.keys(options.alias) || [])
231       .concat(Object.keys(yargs.parsed.newAliases) || [])
232
233     keys = keys.filter(key => !yargs.parsed.newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1))
234
235     // populate 'Options:' group with any keys that have not
236     // explicitly had a group set.
237     if (!groups[defaultGroup]) groups[defaultGroup] = []
238     addUngroupedKeys(keys, options.alias, groups)
239
240     // display 'Options:' table along with any custom tables:
241     Object.keys(groups).forEach((groupName) => {
242       if (!groups[groupName].length) return
243
244       ui.div(__(groupName))
245
246       // if we've grouped the key 'f', but 'f' aliases 'foobar',
247       // normalizedKeys should contain only 'foobar'.
248       const normalizedKeys = groups[groupName].map((key) => {
249         if (~aliasKeys.indexOf(key)) return key
250         for (let i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) {
251           if (~(options.alias[aliasKey] || []).indexOf(key)) return aliasKey
252         }
253         return key
254       })
255
256       // actually generate the switches string --foo, -f, --bar.
257       const switches = normalizedKeys.reduce((acc, key) => {
258         acc[key] = [ key ].concat(options.alias[key] || [])
259           .map(sw => {
260             // for the special positional group don't
261             // add '--' or '-' prefix.
262             if (groupName === self.getPositionalGroupName()) return sw
263             else return (sw.length > 1 ? '--' : '-') + sw
264           })
265           .join(', ')
266
267         return acc
268       }, {})
269
270       normalizedKeys.forEach((key) => {
271         const kswitch = switches[key]
272         let desc = descriptions[key] || ''
273         let type = null
274
275         if (~desc.lastIndexOf(deferY18nLookupPrefix)) desc = __(desc.substring(deferY18nLookupPrefix.length))
276
277         if (~options.boolean.indexOf(key)) type = `[${__('boolean')}]`
278         if (~options.count.indexOf(key)) type = `[${__('count')}]`
279         if (~options.string.indexOf(key)) type = `[${__('string')}]`
280         if (~options.normalize.indexOf(key)) type = `[${__('string')}]`
281         if (~options.array.indexOf(key)) type = `[${__('array')}]`
282         if (~options.number.indexOf(key)) type = `[${__('number')}]`
283
284         const extra = [
285           type,
286           (key in demandedOptions) ? `[${__('required')}]` : null,
287           options.choices && options.choices[key] ? `[${__('choices:')} ${
288             self.stringifiedValues(options.choices[key])}]` : null,
289           defaultString(options.default[key], options.defaultDescription[key])
290         ].filter(Boolean).join(' ')
291
292         ui.span(
293           {text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches, theWrap) + 4},
294           desc
295         )
296
297         if (extra) ui.div({text: extra, padding: [0, 0, 0, 2], align: 'right'})
298         else ui.div()
299       })
300
301       ui.div()
302     })
303
304     // describe some common use-cases for your application.
305     if (examples.length) {
306       ui.div(__('Examples:'))
307
308       examples.forEach((example) => {
309         example[0] = example[0].replace(/\$0/g, base$0)
310       })
311
312       examples.forEach((example) => {
313         if (example[1] === '') {
314           ui.div(
315             {
316               text: example[0],
317               padding: [0, 2, 0, 2]
318             }
319           )
320         } else {
321           ui.div(
322             {
323               text: example[0],
324               padding: [0, 2, 0, 2],
325               width: maxWidth(examples, theWrap) + 4
326             }, {
327               text: example[1]
328             }
329           )
330         }
331       })
332
333       ui.div()
334     }
335
336     // the usage string.
337     if (epilog) {
338       const e = epilog.replace(/\$0/g, base$0)
339       ui.div(`${e}\n`)
340     }
341
342     return ui.toString()
343   }
344
345   // return the maximum width of a string
346   // in the left-hand column of a table.
347   function maxWidth (table, theWrap, modifier) {
348     let width = 0
349
350     // table might be of the form [leftColumn],
351     // or {key: leftColumn}
352     if (!Array.isArray(table)) {
353       table = Object.keys(table).map(key => [table[key]])
354     }
355
356     table.forEach((v) => {
357       width = Math.max(
358         stringWidth(modifier ? `${modifier} ${v[0]}` : v[0]),
359         width
360       )
361     })
362
363     // if we've enabled 'wrap' we should limit
364     // the max-width of the left-column.
365     if (theWrap) width = Math.min(width, parseInt(theWrap * 0.5, 10))
366
367     return width
368   }
369
370   // make sure any options set for aliases,
371   // are copied to the keys being aliased.
372   function normalizeAliases () {
373     // handle old demanded API
374     const demandedOptions = yargs.getDemandedOptions()
375     const options = yargs.getOptions()
376
377     ;(Object.keys(options.alias) || []).forEach((key) => {
378       options.alias[key].forEach((alias) => {
379         // copy descriptions.
380         if (descriptions[alias]) self.describe(key, descriptions[alias])
381         // copy demanded.
382         if (alias in demandedOptions) yargs.demandOption(key, demandedOptions[alias])
383         // type messages.
384         if (~options.boolean.indexOf(alias)) yargs.boolean(key)
385         if (~options.count.indexOf(alias)) yargs.count(key)
386         if (~options.string.indexOf(alias)) yargs.string(key)
387         if (~options.normalize.indexOf(alias)) yargs.normalize(key)
388         if (~options.array.indexOf(alias)) yargs.array(key)
389         if (~options.number.indexOf(alias)) yargs.number(key)
390       })
391     })
392   }
393
394   // given a set of keys, place any keys that are
395   // ungrouped under the 'Options:' grouping.
396   function addUngroupedKeys (keys, aliases, groups) {
397     let groupedKeys = []
398     let toCheck = null
399     Object.keys(groups).forEach((group) => {
400       groupedKeys = groupedKeys.concat(groups[group])
401     })
402
403     keys.forEach((key) => {
404       toCheck = [key].concat(aliases[key])
405       if (!toCheck.some(k => groupedKeys.indexOf(k) !== -1)) {
406         groups[defaultGroup].push(key)
407       }
408     })
409     return groupedKeys
410   }
411
412   self.showHelp = (level) => {
413     const logger = yargs._getLoggerInstance()
414     if (!level) level = 'error'
415     const emit = typeof level === 'function' ? level : logger[level]
416     emit(self.help())
417   }
418
419   self.functionDescription = (fn) => {
420     const description = fn.name ? require('decamelize')(fn.name, '-') : __('generated-value')
421     return ['(', description, ')'].join('')
422   }
423
424   self.stringifiedValues = function stringifiedValues (values, separator) {
425     let string = ''
426     const sep = separator || ', '
427     const array = [].concat(values)
428
429     if (!values || !array.length) return string
430
431     array.forEach((value) => {
432       if (string.length) string += sep
433       string += JSON.stringify(value)
434     })
435
436     return string
437   }
438
439   // format the default-value-string displayed in
440   // the right-hand column.
441   function defaultString (value, defaultDescription) {
442     let string = `[${__('default:')} `
443
444     if (value === undefined && !defaultDescription) return null
445
446     if (defaultDescription) {
447       string += defaultDescription
448     } else {
449       switch (typeof value) {
450         case 'string':
451           string += `"${value}"`
452           break
453         case 'object':
454           string += JSON.stringify(value)
455           break
456         default:
457           string += value
458       }
459     }
460
461     return `${string}]`
462   }
463
464   // guess the width of the console window, max-width 80.
465   function windowWidth () {
466     const maxWidth = 80
467     if (typeof process === 'object' && process.stdout && process.stdout.columns) {
468       return Math.min(maxWidth, process.stdout.columns)
469     } else {
470       return maxWidth
471     }
472   }
473
474   // logic for displaying application version.
475   let version = null
476   self.version = (ver) => {
477     version = ver
478   }
479
480   self.showVersion = () => {
481     const logger = yargs._getLoggerInstance()
482     logger.log(version)
483   }
484
485   self.reset = function reset (localLookup) {
486     // do not reset wrap here
487     // do not reset fails here
488     failMessage = null
489     failureOutput = false
490     usages = []
491     usageDisabled = false
492     epilog = undefined
493     examples = []
494     commands = []
495     descriptions = objFilter(descriptions, (k, v) => !localLookup[k])
496     return self
497   }
498
499   let frozen
500   self.freeze = function freeze () {
501     frozen = {}
502     frozen.failMessage = failMessage
503     frozen.failureOutput = failureOutput
504     frozen.usages = usages
505     frozen.usageDisabled = usageDisabled
506     frozen.epilog = epilog
507     frozen.examples = examples
508     frozen.commands = commands
509     frozen.descriptions = descriptions
510   }
511   self.unfreeze = function unfreeze () {
512     failMessage = frozen.failMessage
513     failureOutput = frozen.failureOutput
514     usages = frozen.usages
515     usageDisabled = frozen.usageDisabled
516     epilog = frozen.epilog
517     examples = frozen.examples
518     commands = frozen.commands
519     descriptions = frozen.descriptions
520     frozen = undefined
521   }
522
523   return self
524 }