Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80728 views
1
var path = require('path');
2
var wordwrap = require('wordwrap');
3
4
/* Hack an instance of Argv with process.argv into Argv
5
so people can do
6
require('optimist')(['--beeble=1','-z','zizzle']).argv
7
to parse a list of args and
8
require('optimist').argv
9
to get a parsed version of process.argv.
10
*/
11
12
var inst = Argv(process.argv.slice(2));
13
Object.keys(inst).forEach(function (key) {
14
Argv[key] = typeof inst[key] == 'function'
15
? inst[key].bind(inst)
16
: inst[key];
17
});
18
19
var exports = module.exports = Argv;
20
function Argv (args, cwd) {
21
var self = {};
22
if (!cwd) cwd = process.cwd();
23
24
self.$0 = process.argv
25
.slice(0,2)
26
.map(function (x) {
27
var b = rebase(cwd, x);
28
return x.match(/^\//) && b.length < x.length
29
? b : x
30
})
31
.join(' ')
32
;
33
34
if (process.env._ != undefined && process.argv[1] == process.env._) {
35
self.$0 = process.env._.replace(
36
path.dirname(process.execPath) + '/', ''
37
);
38
}
39
40
var flags = { bools : {}, strings : {} };
41
42
self.boolean = function (bools) {
43
if (!Array.isArray(bools)) {
44
bools = [].slice.call(arguments);
45
}
46
47
bools.forEach(function (name) {
48
flags.bools[name] = true;
49
});
50
51
return self;
52
};
53
54
self.string = function (strings) {
55
if (!Array.isArray(strings)) {
56
strings = [].slice.call(arguments);
57
}
58
59
strings.forEach(function (name) {
60
flags.strings[name] = true;
61
});
62
63
return self;
64
};
65
66
var aliases = {};
67
self.alias = function (x, y) {
68
if (typeof x === 'object') {
69
Object.keys(x).forEach(function (key) {
70
self.alias(key, x[key]);
71
});
72
}
73
else if (Array.isArray(y)) {
74
y.forEach(function (yy) {
75
self.alias(x, yy);
76
});
77
}
78
else {
79
var zs = (aliases[x] || []).concat(aliases[y] || []).concat(x, y);
80
aliases[x] = zs.filter(function (z) { return z != x });
81
aliases[y] = zs.filter(function (z) { return z != y });
82
}
83
84
return self;
85
};
86
87
var demanded = {};
88
self.demand = function (keys) {
89
if (typeof keys == 'number') {
90
if (!demanded._) demanded._ = 0;
91
demanded._ += keys;
92
}
93
else if (Array.isArray(keys)) {
94
keys.forEach(function (key) {
95
self.demand(key);
96
});
97
}
98
else {
99
demanded[keys] = true;
100
}
101
102
return self;
103
};
104
105
var usage;
106
self.usage = function (msg, opts) {
107
if (!opts && typeof msg === 'object') {
108
opts = msg;
109
msg = null;
110
}
111
112
usage = msg;
113
114
if (opts) self.options(opts);
115
116
return self;
117
};
118
119
function fail (msg) {
120
self.showHelp();
121
if (msg) console.error(msg);
122
process.exit(1);
123
}
124
125
var checks = [];
126
self.check = function (f) {
127
checks.push(f);
128
return self;
129
};
130
131
var defaults = {};
132
self.default = function (key, value) {
133
if (typeof key === 'object') {
134
Object.keys(key).forEach(function (k) {
135
self.default(k, key[k]);
136
});
137
}
138
else {
139
defaults[key] = value;
140
}
141
142
return self;
143
};
144
145
var descriptions = {};
146
self.describe = function (key, desc) {
147
if (typeof key === 'object') {
148
Object.keys(key).forEach(function (k) {
149
self.describe(k, key[k]);
150
});
151
}
152
else {
153
descriptions[key] = desc;
154
}
155
return self;
156
};
157
158
self.parse = function (args) {
159
return Argv(args).argv;
160
};
161
162
self.option = self.options = function (key, opt) {
163
if (typeof key === 'object') {
164
Object.keys(key).forEach(function (k) {
165
self.options(k, key[k]);
166
});
167
}
168
else {
169
if (opt.alias) self.alias(key, opt.alias);
170
if (opt.demand) self.demand(key);
171
if (typeof opt.default !== 'undefined') {
172
self.default(key, opt.default);
173
}
174
175
if (opt.boolean || opt.type === 'boolean') {
176
self.boolean(key);
177
}
178
if (opt.string || opt.type === 'string') {
179
self.string(key);
180
}
181
182
var desc = opt.describe || opt.description || opt.desc;
183
if (desc) {
184
self.describe(key, desc);
185
}
186
}
187
188
return self;
189
};
190
191
var wrap = null;
192
self.wrap = function (cols) {
193
wrap = cols;
194
return self;
195
};
196
197
self.showHelp = function (fn) {
198
if (!fn) fn = console.error;
199
fn(self.help());
200
};
201
202
self.help = function () {
203
var keys = Object.keys(
204
Object.keys(descriptions)
205
.concat(Object.keys(demanded))
206
.concat(Object.keys(defaults))
207
.reduce(function (acc, key) {
208
if (key !== '_') acc[key] = true;
209
return acc;
210
}, {})
211
);
212
213
var help = keys.length ? [ 'Options:' ] : [];
214
215
if (usage) {
216
help.unshift(usage.replace(/\$0/g, self.$0), '');
217
}
218
219
var switches = keys.reduce(function (acc, key) {
220
acc[key] = [ key ].concat(aliases[key] || [])
221
.map(function (sw) {
222
return (sw.length > 1 ? '--' : '-') + sw
223
})
224
.join(', ')
225
;
226
return acc;
227
}, {});
228
229
var switchlen = longest(Object.keys(switches).map(function (s) {
230
return switches[s] || '';
231
}));
232
233
var desclen = longest(Object.keys(descriptions).map(function (d) {
234
return descriptions[d] || '';
235
}));
236
237
keys.forEach(function (key) {
238
var kswitch = switches[key];
239
var desc = descriptions[key] || '';
240
241
if (wrap) {
242
desc = wordwrap(switchlen + 4, wrap)(desc)
243
.slice(switchlen + 4)
244
;
245
}
246
247
var spadding = new Array(
248
Math.max(switchlen - kswitch.length + 3, 0)
249
).join(' ');
250
251
var dpadding = new Array(
252
Math.max(desclen - desc.length + 1, 0)
253
).join(' ');
254
255
var type = null;
256
257
if (flags.bools[key]) type = '[boolean]';
258
if (flags.strings[key]) type = '[string]';
259
260
if (!wrap && dpadding.length > 0) {
261
desc += dpadding;
262
}
263
264
var prelude = ' ' + kswitch + spadding;
265
var extra = [
266
type,
267
demanded[key]
268
? '[required]'
269
: null
270
,
271
defaults[key] !== undefined
272
? '[default: ' + JSON.stringify(defaults[key]) + ']'
273
: null
274
,
275
].filter(Boolean).join(' ');
276
277
var body = [ desc, extra ].filter(Boolean).join(' ');
278
279
if (wrap) {
280
var dlines = desc.split('\n');
281
var dlen = dlines.slice(-1)[0].length
282
+ (dlines.length === 1 ? prelude.length : 0)
283
284
body = desc + (dlen + extra.length > wrap - 2
285
? '\n'
286
+ new Array(wrap - extra.length + 1).join(' ')
287
+ extra
288
: new Array(wrap - extra.length - dlen + 1).join(' ')
289
+ extra
290
);
291
}
292
293
help.push(prelude + body);
294
});
295
296
help.push('');
297
return help.join('\n');
298
};
299
300
Object.defineProperty(self, 'argv', {
301
get : parseArgs,
302
enumerable : true,
303
});
304
305
function parseArgs () {
306
var argv = { _ : [], $0 : self.$0 };
307
Object.keys(flags.bools).forEach(function (key) {
308
setArg(key, defaults[key] || false);
309
});
310
311
function setArg (key, val) {
312
var num = Number(val);
313
var value = typeof val !== 'string' || isNaN(num) ? val : num;
314
if (flags.strings[key]) value = val;
315
316
setKey(argv, key.split('.'), value);
317
318
(aliases[key] || []).forEach(function (x) {
319
argv[x] = argv[key];
320
});
321
}
322
323
for (var i = 0; i < args.length; i++) {
324
var arg = args[i];
325
326
if (arg === '--') {
327
argv._.push.apply(argv._, args.slice(i + 1));
328
break;
329
}
330
else if (arg.match(/^--.+=/)) {
331
// Using [\s\S] instead of . because js doesn't support the
332
// 'dotall' regex modifier. See:
333
// http://stackoverflow.com/a/1068308/13216
334
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
335
setArg(m[1], m[2]);
336
}
337
else if (arg.match(/^--no-.+/)) {
338
var key = arg.match(/^--no-(.+)/)[1];
339
setArg(key, false);
340
}
341
else if (arg.match(/^--.+/)) {
342
var key = arg.match(/^--(.+)/)[1];
343
var next = args[i + 1];
344
if (next !== undefined && !next.match(/^-/)
345
&& !flags.bools[key]
346
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
347
setArg(key, next);
348
i++;
349
}
350
else if (/^(true|false)$/.test(next)) {
351
setArg(key, next === 'true');
352
i++;
353
}
354
else {
355
setArg(key, true);
356
}
357
}
358
else if (arg.match(/^-[^-]+/)) {
359
var letters = arg.slice(1,-1).split('');
360
361
var broken = false;
362
for (var j = 0; j < letters.length; j++) {
363
if (letters[j+1] && letters[j+1].match(/\W/)) {
364
setArg(letters[j], arg.slice(j+2));
365
broken = true;
366
break;
367
}
368
else {
369
setArg(letters[j], true);
370
}
371
}
372
373
if (!broken) {
374
var key = arg.slice(-1)[0];
375
376
if (args[i+1] && !args[i+1].match(/^-/)
377
&& !flags.bools[key]
378
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
379
setArg(key, args[i+1]);
380
i++;
381
}
382
else if (args[i+1] && /true|false/.test(args[i+1])) {
383
setArg(key, args[i+1] === 'true');
384
i++;
385
}
386
else {
387
setArg(key, true);
388
}
389
}
390
}
391
else {
392
var n = Number(arg);
393
argv._.push(flags.strings['_'] || isNaN(n) ? arg : n);
394
}
395
}
396
397
Object.keys(defaults).forEach(function (key) {
398
if (!(key in argv)) {
399
argv[key] = defaults[key];
400
if (key in aliases) {
401
argv[aliases[key]] = defaults[key];
402
}
403
}
404
});
405
406
if (demanded._ && argv._.length < demanded._) {
407
fail('Not enough non-option arguments: got '
408
+ argv._.length + ', need at least ' + demanded._
409
);
410
}
411
412
var missing = [];
413
Object.keys(demanded).forEach(function (key) {
414
if (!argv[key]) missing.push(key);
415
});
416
417
if (missing.length) {
418
fail('Missing required arguments: ' + missing.join(', '));
419
}
420
421
checks.forEach(function (f) {
422
try {
423
if (f(argv) === false) {
424
fail('Argument check failed: ' + f.toString());
425
}
426
}
427
catch (err) {
428
fail(err)
429
}
430
});
431
432
return argv;
433
}
434
435
function longest (xs) {
436
return Math.max.apply(
437
null,
438
xs.map(function (x) { return x.length })
439
);
440
}
441
442
return self;
443
};
444
445
// rebase an absolute path to a relative one with respect to a base directory
446
// exported for tests
447
exports.rebase = rebase;
448
function rebase (base, dir) {
449
var ds = path.normalize(dir).split('/').slice(1);
450
var bs = path.normalize(base).split('/').slice(1);
451
452
for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
453
ds.splice(0, i); bs.splice(0, i);
454
455
var p = path.normalize(
456
bs.map(function () { return '..' }).concat(ds).join('/')
457
).replace(/\/$/,'').replace(/^$/, '.');
458
return p.match(/^[.\/]/) ? p : './' + p;
459
};
460
461
function setKey (obj, keys, value) {
462
var o = obj;
463
keys.slice(0,-1).forEach(function (key) {
464
if (o[key] === undefined) o[key] = {};
465
o = o[key];
466
});
467
468
var key = keys[keys.length - 1];
469
if (o[key] === undefined || typeof o[key] === 'boolean') {
470
o[key] = value;
471
}
472
else if (Array.isArray(o[key])) {
473
o[key].push(value);
474
}
475
else {
476
o[key] = [ o[key], value ];
477
}
478
}
479
480