Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80759 views
1
// this file handles outputting usage instructions,
2
// failures, etc. keeps logging in one place.
3
var decamelize = require('decamelize'),
4
wordwrap = require('wordwrap'),
5
wsize = require('window-size');
6
7
module.exports = function (yargs) {
8
var self = {};
9
10
// methods for ouputting/building failure message.
11
var fails = [];
12
self.failFn = function (f) {
13
fails.push(f);
14
};
15
16
var failMessage = null;
17
var showHelpOnFail = true;
18
self.showHelpOnFail = function (enabled, message) {
19
if (typeof enabled === 'string') {
20
message = enabled;
21
enabled = true;
22
}
23
else if (typeof enabled === 'undefined') {
24
enabled = true;
25
}
26
failMessage = message;
27
showHelpOnFail = enabled;
28
return self;
29
};
30
31
self.fail = function (msg) {
32
if (fails.length) {
33
fails.forEach(function (f) {
34
f(msg);
35
});
36
} else {
37
if (showHelpOnFail) yargs.showHelp("error");
38
if (msg) console.error(msg);
39
if (failMessage) {
40
if (msg) console.error("");
41
console.error(failMessage);
42
}
43
if (yargs.getExitProcess()){
44
process.exit(1);
45
}else{
46
throw new Error(msg);
47
}
48
}
49
};
50
51
// methods for ouputting/building help (usage) message.
52
var usage;
53
self.usage = function (msg) {
54
usage = msg;
55
};
56
57
var examples = [];
58
self.example = function (cmd, description) {
59
examples.push([cmd, description || '']);
60
};
61
62
var commands = [];
63
self.command = function (cmd, description) {
64
commands.push([cmd, description || '']);
65
};
66
self.getCommands = function () {
67
return commands;
68
};
69
70
var descriptions = {};
71
self.describe = function (key, desc) {
72
if (typeof key === 'object') {
73
Object.keys(key).forEach(function (k) {
74
self.describe(k, key[k]);
75
});
76
}
77
else {
78
descriptions[key] = desc;
79
}
80
};
81
self.getDescriptions = function() {
82
return descriptions;
83
}
84
85
var epilog;
86
self.epilog = function (msg) {
87
epilog = msg;
88
};
89
90
var wrap = windowWidth();
91
self.wrap = function (cols) {
92
wrap = cols;
93
};
94
95
self.help = function () {
96
var demanded = yargs.getDemanded(),
97
options = yargs.getOptions(),
98
keys = Object.keys(
99
Object.keys(descriptions)
100
.concat(Object.keys(demanded))
101
.concat(Object.keys(options.default))
102
.reduce(function (acc, key) {
103
if (key !== '_') acc[key] = true;
104
return acc;
105
}, {})
106
);
107
108
var help = keys.length ? [ 'Options:' ] : [];
109
110
// your application's commands, i.e., non-option
111
// arguments populated in '_'.
112
if (commands.length) {
113
help.unshift('');
114
115
var commandsTable = {};
116
commands.forEach(function(command) {
117
commandsTable[command[0]] = {
118
desc: command[1],
119
extra: ''
120
};
121
});
122
123
help = ['Commands:'].concat(formatTable(commandsTable, 5), help);
124
}
125
126
// the usage string.
127
if (usage) {
128
var u = usage.replace(/\$0/g, yargs.$0);
129
if (wrap) u = wordwrap(0, wrap)(u);
130
help.unshift(u, '');
131
}
132
133
// the options table.
134
var aliasKeys = (Object.keys(options.alias) || [])
135
.concat(Object.keys(yargs.parsed.newAliases) || []);
136
137
keys = keys.filter(function(key) {
138
return !yargs.parsed.newAliases[key] && aliasKeys.every(function(alias) {
139
return -1 == (options.alias[alias] || []).indexOf(key);
140
});
141
});
142
143
var switches = keys.reduce(function (acc, key) {
144
acc[key] = [ key ].concat(options.alias[key] || [])
145
.map(function (sw) {
146
return (sw.length > 1 ? '--' : '-') + sw
147
})
148
.join(', ')
149
;
150
return acc;
151
}, {});
152
153
var switchTable = {};
154
keys.forEach(function (key) {
155
var kswitch = switches[key];
156
var desc = descriptions[key] || '';
157
var type = null;
158
159
if (options.boolean[key]) type = '[boolean]';
160
if (options.count[key]) type = '[count]';
161
if (options.string[key]) type = '[string]';
162
if (options.normalize[key]) type = '[string]';
163
164
var extra = [
165
type,
166
demanded[key]
167
? '[required]'
168
: null
169
,
170
defaultString(options.default[key], options.defaultDescription[key])
171
].filter(Boolean).join(' ');
172
173
switchTable[kswitch] = {
174
desc: desc,
175
extra: extra
176
};
177
});
178
help.push.apply(help, formatTable(switchTable, 3));
179
180
if (keys.length) help.push('');
181
182
// describe some common use-cases for your application.
183
if (examples.length) {
184
examples.forEach(function (example) {
185
example[0] = example[0].replace(/\$0/g, yargs.$0);
186
});
187
188
var examplesTable = {};
189
examples.forEach(function(example) {
190
examplesTable[example[0]] = {
191
desc: example[1],
192
extra: ''
193
};
194
});
195
196
help.push.apply(help, ['Examples:'].concat(formatTable(examplesTable, 5), ''));
197
}
198
199
// the usage string.
200
if (epilog) {
201
var e = epilog.replace(/\$0/g, yargs.$0);
202
if (wrap) e = wordwrap(0, wrap)(e);
203
help.push(e, '');
204
}
205
206
return help.join('\n');
207
};
208
209
self.showHelp = function (level) {
210
level = level || 'error';
211
console[level](self.help());
212
}
213
214
// format the default-value-string displayed in
215
// the right-hand column.
216
function defaultString(value, defaultDescription) {
217
var string = '[default: ';
218
219
if (value === undefined) return null;
220
221
if (defaultDescription) {
222
string += defaultDescription;
223
} else {
224
switch (typeof value) {
225
case 'string':
226
string += JSON.stringify(value);
227
break;
228
case 'function':
229
string += '(' + (value.name.length ? decamelize(value.name, '-') : 'generated-value') + ')'
230
break;
231
default:
232
string += value;
233
}
234
}
235
236
return string + ']';
237
}
238
239
// word-wrapped two-column layout used by
240
// examples, options, commands.
241
function formatTable (table, padding) {
242
var output = [];
243
244
// size of left-hand-column.
245
var llen = longest(Object.keys(table));
246
247
// don't allow the left-column to take up
248
// more than half of the screen.
249
if (wrap) {
250
llen = Math.min(llen, parseInt(wrap / 2));
251
}
252
253
// size of right-column.
254
var desclen = longest(Object.keys(table).map(function (k) {
255
return table[k].desc;
256
}));
257
258
Object.keys(table).forEach(function(left) {
259
var desc = table[left].desc,
260
extra = table[left].extra,
261
leftLines = null;
262
263
if (wrap) {
264
desc = wordwrap(llen + padding + 1, wrap)(desc)
265
.slice(llen + padding + 1);
266
}
267
268
// if we need to wrap the left-hand-column,
269
// split it on to multiple lines.
270
if (wrap && left.length > llen) {
271
leftLines = wordwrap(2, llen)(left.trim()).split('\n');
272
left = '';
273
}
274
275
var lpadding = new Array(
276
Math.max(llen - left.length + padding, 0)
277
).join(' ');
278
279
var dpadding = new Array(
280
Math.max(desclen - desc.length + 1, 0)
281
).join(' ');
282
283
if (!wrap && dpadding.length > 0) {
284
desc += dpadding;
285
}
286
287
var prelude = ' ' + left + lpadding;
288
289
var body = [ desc, extra ].filter(Boolean).join(' ');
290
291
if (wrap) {
292
var dlines = desc.split('\n');
293
var dlen = dlines.slice(-1)[0].length
294
+ (dlines.length === 1 ? prelude.length : 0)
295
296
if (extra.length > wrap) {
297
body = desc + '\n' + wordwrap(llen + 4, wrap)(extra)
298
} else {
299
body = desc + (dlen + extra.length > wrap - 2
300
? '\n'
301
+ new Array(wrap - extra.length + 1).join(' ')
302
+ extra
303
: new Array(wrap - extra.length - dlen + 1).join(' ')
304
+ extra
305
);
306
}
307
}
308
309
if (leftLines) { // handle word-wrapping the left-hand-column.
310
var rightLines = body.split('\n'),
311
firstLine = prelude + rightLines[0],
312
lineCount = Math.max(leftLines.length, rightLines.length);
313
314
for (var i = 0; i < lineCount; i++) {
315
var left = leftLines[i],
316
right = i ? rightLines[i] : firstLine;
317
318
output.push(strcpy(left, right, firstLine.length));
319
}
320
} else {
321
output.push(prelude + body);
322
}
323
});
324
325
return output;
326
}
327
328
// find longest string in array of strings.
329
function longest (xs) {
330
return Math.max.apply(
331
null,
332
xs.map(function (x) { return x.length })
333
);
334
}
335
336
// copy one string into another, used when
337
// formatting usage table.
338
function strcpy (source, destination, width) {
339
var str = ''
340
341
source = source || '';
342
destination = destination || new Array(width).join(' ');
343
344
for (var i = 0; i < destination.length; i++) {
345
var char = destination.charAt(i);
346
347
if (char === ' ') char = source.charAt(i) || char;
348
349
str += char;
350
}
351
352
return str;
353
}
354
355
// guess the width of the console window, max-width 100.
356
function windowWidth() {
357
return wsize.width ? Math.min(80, wsize.width) : null;
358
}
359
360
// logic for displaying application version.
361
var version = null;
362
self.version = function (ver, opt, msg) {
363
version = ver;
364
};
365
366
self.showVersion = function() {
367
if (typeof version === 'function') console.log(version());
368
else console.log(version);
369
};
370
371
return self;
372
}
373
374