Initial commit
[yaffs-website] / node_modules / yargs / lib / usage.js
1 // this file handles outputting usage instructions,
2 // failures, etc. keeps logging in one place.
3 const cliui = require('cliui')
4 const decamelize = require('decamelize')
5 const stringWidth = require('string-width')
6 const wsize = require('window-size')
7 const objFilter = require('./obj-filter')
8 const setBlocking = require('set-blocking')
9
10 module.exports = function (yargs, y18n) {
11   const __ = y18n.__
12   const self = {}
13
14   // methods for ouputting/building failure message.
15   var fails = []
16   self.failFn = function (f) {
17     fails.push(f)
18   }
19
20   var failMessage = null
21   var showHelpOnFail = true
22   self.showHelpOnFail = function (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   var failureOutput = false
35   self.fail = function (msg, err) {
36     if (fails.length) {
37       fails.forEach(function (f) {
38         f(msg, err)
39       })
40     } else {
41       if (yargs.getExitProcess()) setBlocking(true)
42
43       // don't output failure message more than once
44       if (!failureOutput) {
45         failureOutput = true
46         if (showHelpOnFail) yargs.showHelp('error')
47         if (msg) console.error(msg)
48         if (failMessage) {
49           if (msg) console.error('')
50           console.error(failMessage)
51         }
52       }
53       if (yargs.getExitProcess()) {
54         process.exit(1)
55       } else {
56         throw err || new Error(msg)
57       }
58     }
59   }
60
61   // methods for ouputting/building help (usage) message.
62   var usage
63   self.usage = function (msg) {
64     usage = msg
65   }
66   self.getUsage = function () {
67     return usage
68   }
69
70   var examples = []
71   self.example = function (cmd, description) {
72     examples.push([cmd, description || ''])
73   }
74
75   var commands = []
76   self.command = function (cmd, description) {
77     commands.push([cmd, description || ''])
78   }
79   self.getCommands = function () {
80     return commands
81   }
82
83   var descriptions = {}
84   self.describe = function (key, desc) {
85     if (typeof key === 'object') {
86       Object.keys(key).forEach(function (k) {
87         self.describe(k, key[k])
88       })
89     } else {
90       descriptions[key] = desc
91     }
92   }
93   self.getDescriptions = function () {
94     return descriptions
95   }
96
97   var epilog
98   self.epilog = function (msg) {
99     epilog = msg
100   }
101
102   var wrap = windowWidth()
103   self.wrap = function (cols) {
104     wrap = cols
105   }
106
107   var deferY18nLookupPrefix = '__yargsString__:'
108   self.deferY18nLookup = function (str) {
109     return deferY18nLookupPrefix + str
110   }
111
112   var defaultGroup = 'Options:'
113   self.help = function () {
114     normalizeAliases()
115
116     var demanded = yargs.getDemanded()
117     var groups = yargs.getGroups()
118     var options = yargs.getOptions()
119     var keys = Object.keys(
120       Object.keys(descriptions)
121       .concat(Object.keys(demanded))
122       .concat(Object.keys(options.default))
123       .reduce(function (acc, key) {
124         if (key !== '_') acc[key] = true
125         return acc
126       }, {})
127     )
128     var ui = cliui({
129       width: wrap,
130       wrap: !!wrap
131     })
132
133     // the usage string.
134     if (usage) {
135       var u = usage.replace(/\$0/g, yargs.$0)
136       ui.div(u + '\n')
137     }
138
139     // your application's commands, i.e., non-option
140     // arguments populated in '_'.
141     if (commands.length) {
142       ui.div(__('Commands:'))
143
144       commands.forEach(function (command) {
145         ui.div(
146           {text: command[0], padding: [0, 2, 0, 2], width: maxWidth(commands) + 4},
147           {text: command[1]}
148         )
149       })
150
151       ui.div()
152     }
153
154     // perform some cleanup on the keys array, making it
155     // only include top-level keys not their aliases.
156     var aliasKeys = (Object.keys(options.alias) || [])
157       .concat(Object.keys(yargs.parsed.newAliases) || [])
158
159     keys = keys.filter(function (key) {
160       return !yargs.parsed.newAliases[key] && aliasKeys.every(function (alias) {
161         return (options.alias[alias] || []).indexOf(key) === -1
162       })
163     })
164
165     // populate 'Options:' group with any keys that have not
166     // explicitly had a group set.
167     if (!groups[defaultGroup]) groups[defaultGroup] = []
168     addUngroupedKeys(keys, options.alias, groups)
169
170     // display 'Options:' table along with any custom tables:
171     Object.keys(groups).forEach(function (groupName) {
172       if (!groups[groupName].length) return
173
174       ui.div(__(groupName))
175
176       // if we've grouped the key 'f', but 'f' aliases 'foobar',
177       // normalizedKeys should contain only 'foobar'.
178       var normalizedKeys = groups[groupName].map(function (key) {
179         if (~aliasKeys.indexOf(key)) return key
180         for (var i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) {
181           if (~(options.alias[aliasKey] || []).indexOf(key)) return aliasKey
182         }
183         return key
184       })
185
186       // actually generate the switches string --foo, -f, --bar.
187       var switches = normalizedKeys.reduce(function (acc, key) {
188         acc[key] = [ key ].concat(options.alias[key] || [])
189           .map(function (sw) {
190             return (sw.length > 1 ? '--' : '-') + sw
191           })
192           .join(', ')
193
194         return acc
195       }, {})
196
197       normalizedKeys.forEach(function (key) {
198         var kswitch = switches[key]
199         var desc = descriptions[key] || ''
200         var type = null
201
202         if (~desc.lastIndexOf(deferY18nLookupPrefix)) desc = __(desc.substring(deferY18nLookupPrefix.length))
203
204         if (~options.boolean.indexOf(key)) type = '[' + __('boolean') + ']'
205         if (~options.count.indexOf(key)) type = '[' + __('count') + ']'
206         if (~options.string.indexOf(key)) type = '[' + __('string') + ']'
207         if (~options.normalize.indexOf(key)) type = '[' + __('string') + ']'
208         if (~options.array.indexOf(key)) type = '[' + __('array') + ']'
209         if (~options.number.indexOf(key)) type = '[' + __('number') + ']'
210
211         var extra = [
212           type,
213           demanded[key] ? '[' + __('required') + ']' : null,
214           options.choices && options.choices[key] ? '[' + __('choices:') + ' ' +
215             self.stringifiedValues(options.choices[key]) + ']' : null,
216           defaultString(options.default[key], options.defaultDescription[key])
217         ].filter(Boolean).join(' ')
218
219         ui.span(
220           {text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches) + 4},
221           desc
222         )
223
224         if (extra) ui.div({text: extra, padding: [0, 0, 0, 2], align: 'right'})
225         else ui.div()
226       })
227
228       ui.div()
229     })
230
231     // describe some common use-cases for your application.
232     if (examples.length) {
233       ui.div(__('Examples:'))
234
235       examples.forEach(function (example) {
236         example[0] = example[0].replace(/\$0/g, yargs.$0)
237       })
238
239       examples.forEach(function (example) {
240         ui.div(
241           {text: example[0], padding: [0, 2, 0, 2], width: maxWidth(examples) + 4},
242           example[1]
243         )
244       })
245
246       ui.div()
247     }
248
249     // the usage string.
250     if (epilog) {
251       var e = epilog.replace(/\$0/g, yargs.$0)
252       ui.div(e + '\n')
253     }
254
255     return ui.toString()
256   }
257
258   // return the maximum width of a string
259   // in the left-hand column of a table.
260   function maxWidth (table) {
261     var width = 0
262
263     // table might be of the form [leftColumn],
264     // or {key: leftColumn}}
265     if (!Array.isArray(table)) {
266       table = Object.keys(table).map(function (key) {
267         return [table[key]]
268       })
269     }
270
271     table.forEach(function (v) {
272       width = Math.max(stringWidth(v[0]), width)
273     })
274
275     // if we've enabled 'wrap' we should limit
276     // the max-width of the left-column.
277     if (wrap) width = Math.min(width, parseInt(wrap * 0.5, 10))
278
279     return width
280   }
281
282   // make sure any options set for aliases,
283   // are copied to the keys being aliased.
284   function normalizeAliases () {
285     var demanded = yargs.getDemanded()
286     var options = yargs.getOptions()
287
288     ;(Object.keys(options.alias) || []).forEach(function (key) {
289       options.alias[key].forEach(function (alias) {
290         // copy descriptions.
291         if (descriptions[alias]) self.describe(key, descriptions[alias])
292         // copy demanded.
293         if (demanded[alias]) yargs.demand(key, demanded[alias].msg)
294         // type messages.
295         if (~options.boolean.indexOf(alias)) yargs.boolean(key)
296         if (~options.count.indexOf(alias)) yargs.count(key)
297         if (~options.string.indexOf(alias)) yargs.string(key)
298         if (~options.normalize.indexOf(alias)) yargs.normalize(key)
299         if (~options.array.indexOf(alias)) yargs.array(key)
300         if (~options.number.indexOf(alias)) yargs.number(key)
301       })
302     })
303   }
304
305   // given a set of keys, place any keys that are
306   // ungrouped under the 'Options:' grouping.
307   function addUngroupedKeys (keys, aliases, groups) {
308     var groupedKeys = []
309     var toCheck = null
310     Object.keys(groups).forEach(function (group) {
311       groupedKeys = groupedKeys.concat(groups[group])
312     })
313
314     keys.forEach(function (key) {
315       toCheck = [key].concat(aliases[key])
316       if (!toCheck.some(function (k) {
317         return groupedKeys.indexOf(k) !== -1
318       })) {
319         groups[defaultGroup].push(key)
320       }
321     })
322     return groupedKeys
323   }
324
325   self.showHelp = function (level) {
326     if (!level) level = 'error'
327     var emit = typeof level === 'function' ? level : console[ level ]
328     emit(self.help())
329   }
330
331   self.functionDescription = function (fn) {
332     var description = fn.name ? decamelize(fn.name, '-') : __('generated-value')
333     return ['(', description, ')'].join('')
334   }
335
336   self.stringifiedValues = function (values, separator) {
337     var string = ''
338     var sep = separator || ', '
339     var array = [].concat(values)
340
341     if (!values || !array.length) return string
342
343     array.forEach(function (value) {
344       if (string.length) string += sep
345       string += JSON.stringify(value)
346     })
347
348     return string
349   }
350
351   // format the default-value-string displayed in
352   // the right-hand column.
353   function defaultString (value, defaultDescription) {
354     var string = '[' + __('default:') + ' '
355
356     if (value === undefined && !defaultDescription) return null
357
358     if (defaultDescription) {
359       string += defaultDescription
360     } else {
361       switch (typeof value) {
362         case 'string':
363           string += JSON.stringify(value)
364           break
365         case 'object':
366           string += JSON.stringify(value)
367           break
368         default:
369           string += value
370       }
371     }
372
373     return string + ']'
374   }
375
376   // guess the width of the console window, max-width 80.
377   function windowWidth () {
378     return wsize.width ? Math.min(80, wsize.width) : null
379   }
380
381   // logic for displaying application version.
382   var version = null
383   self.version = function (ver) {
384     version = ver
385   }
386
387   self.showVersion = function () {
388     if (typeof version === 'function') console.log(version())
389     else console.log(version)
390   }
391
392   self.reset = function (globalLookup) {
393     // do not reset wrap here
394     fails = []
395     failMessage = null
396     failureOutput = false
397     usage = undefined
398     epilog = undefined
399     examples = []
400     commands = []
401     descriptions = objFilter(descriptions, function (k, v) {
402       return globalLookup[k]
403     })
404     return self
405   }
406
407   return self
408 }