1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var assert = require('assert-plus');
|
8 | var format = require('util').format;
|
9 | var fs = require('fs');
|
10 | var path = require('path');
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | function renderTemplate(s, d) {
|
16 | return s.replace(/{{([a-zA-Z]+)}}/g, function onMatch(match, key) {
|
17 | return Object.prototype.hasOwnProperty.call(d, key) ? d[key] : match;
|
18 | });
|
19 | }
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | function shallowCopy(obj) {
|
25 | if (!obj) {
|
26 | return obj;
|
27 | }
|
28 | var copy = {};
|
29 | Object.keys(obj).forEach(function onK(k) {
|
30 | copy[k] = obj[k];
|
31 | });
|
32 | return copy;
|
33 | }
|
34 |
|
35 | function space(n) {
|
36 | var s = '';
|
37 | for (var i = 0; i < n; i++) {
|
38 | s += ' ';
|
39 | }
|
40 | return s;
|
41 | }
|
42 |
|
43 | function makeIndent(arg, deflen, name) {
|
44 | if (arg === null || arg === undefined) {
|
45 | return space(deflen);
|
46 | } else if (typeof arg === 'number') {
|
47 | return space(arg);
|
48 | } else if (typeof arg === 'string') {
|
49 | return arg;
|
50 | } else {
|
51 | assert.fail('invalid "' + name + '": not a string or number: ' + arg);
|
52 | }
|
53 | }
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | function textwrap(s, width) {
|
61 | var words = s.trim().split(/\s+/);
|
62 | var lines = [];
|
63 | var line = '';
|
64 | words.forEach(function onWord(w) {
|
65 | var newLength = line.length + w.length;
|
66 | if (line.length > 0) {
|
67 | newLength += 1;
|
68 | }
|
69 | if (newLength > width) {
|
70 | lines.push(line);
|
71 | line = '';
|
72 | }
|
73 | if (line.length > 0) {
|
74 | line += ' ';
|
75 | }
|
76 | line += w;
|
77 | });
|
78 | lines.push(line);
|
79 | return lines;
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | function optionKeyFromName(name) {
|
92 | return name.replace(/-/g, '_');
|
93 | }
|
94 |
|
95 |
|
96 |
|
97 | function parseBool(option, optstr, arg) {
|
98 | return Boolean(arg);
|
99 | }
|
100 |
|
101 | function parseString(option, optstr, arg) {
|
102 | assert.string(arg, 'arg');
|
103 | return arg;
|
104 | }
|
105 |
|
106 | function parseNumber(option, optstr, arg) {
|
107 | assert.string(arg, 'arg');
|
108 | var num = Number(arg);
|
109 | if (isNaN(num)) {
|
110 | throw new Error(
|
111 | format('arg for "%s" is not a number: "%s"', optstr, arg)
|
112 | );
|
113 | }
|
114 | return num;
|
115 | }
|
116 |
|
117 | function parseInteger(option, optstr, arg) {
|
118 | assert.string(arg, 'arg');
|
119 | var num = Number(arg);
|
120 | if (!/^[0-9-]+$/.test(arg) || isNaN(num)) {
|
121 | throw new Error(
|
122 | format('arg for "%s" is not an integer: "%s"', optstr, arg)
|
123 | );
|
124 | }
|
125 | return num;
|
126 | }
|
127 |
|
128 | function parsePositiveInteger(option, optstr, arg) {
|
129 | assert.string(arg, 'arg');
|
130 | var num = Number(arg);
|
131 | if (!/^[0-9]+$/.test(arg) || isNaN(num) || num === 0) {
|
132 | throw new Error(
|
133 | format('arg for "%s" is not a positive integer: "%s"', optstr, arg)
|
134 | );
|
135 | }
|
136 | return num;
|
137 | }
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | function parseDate(option, optstr, arg) {
|
150 | assert.string(arg, 'arg');
|
151 | var date;
|
152 | if (/^\d+$/.test(arg)) {
|
153 |
|
154 | date = new Date(Number(arg) * 1000);
|
155 | } else if (
|
156 | /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?Z?)?$/i.test(arg)
|
157 | ) {
|
158 |
|
159 | date = new Date(arg);
|
160 | } else {
|
161 | throw new Error(
|
162 | format('arg for "%s" is not a valid date format: "%s"', optstr, arg)
|
163 | );
|
164 | }
|
165 | if (date.toString() === 'Invalid Date') {
|
166 | throw new Error(
|
167 | format('arg for "%s" is an invalid date: "%s"', optstr, arg)
|
168 | );
|
169 | }
|
170 | return date;
|
171 | }
|
172 |
|
173 | var optionTypes = {
|
174 | bool: {
|
175 | takesArg: false,
|
176 | parseArg: parseBool
|
177 | },
|
178 | string: {
|
179 | takesArg: true,
|
180 | helpArg: 'ARG',
|
181 | parseArg: parseString
|
182 | },
|
183 | number: {
|
184 | takesArg: true,
|
185 | helpArg: 'NUM',
|
186 | parseArg: parseNumber
|
187 | },
|
188 | integer: {
|
189 | takesArg: true,
|
190 | helpArg: 'INT',
|
191 | parseArg: parseInteger
|
192 | },
|
193 | positiveInteger: {
|
194 | takesArg: true,
|
195 | helpArg: 'INT',
|
196 | parseArg: parsePositiveInteger
|
197 | },
|
198 | date: {
|
199 | takesArg: true,
|
200 | helpArg: 'DATE',
|
201 | parseArg: parseDate
|
202 | },
|
203 | arrayOfBool: {
|
204 | takesArg: false,
|
205 | array: true,
|
206 | parseArg: parseBool
|
207 | },
|
208 | arrayOfString: {
|
209 | takesArg: true,
|
210 | helpArg: 'ARG',
|
211 | array: true,
|
212 | parseArg: parseString
|
213 | },
|
214 | arrayOfNumber: {
|
215 | takesArg: true,
|
216 | helpArg: 'NUM',
|
217 | array: true,
|
218 | parseArg: parseNumber
|
219 | },
|
220 | arrayOfInteger: {
|
221 | takesArg: true,
|
222 | helpArg: 'INT',
|
223 | array: true,
|
224 | parseArg: parseInteger
|
225 | },
|
226 | arrayOfPositiveInteger: {
|
227 | takesArg: true,
|
228 | helpArg: 'INT',
|
229 | array: true,
|
230 | parseArg: parsePositiveInteger
|
231 | },
|
232 | arrayOfDate: {
|
233 | takesArg: true,
|
234 | helpArg: 'INT',
|
235 | array: true,
|
236 | parseArg: parseDate
|
237 | }
|
238 | };
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 | function Parser(config) {
|
259 | assert.object(config, 'config');
|
260 | assert.arrayOfObject(config.options, 'config.options');
|
261 | assert.optionalBool(config.interspersed, 'config.interspersed');
|
262 | var self = this;
|
263 |
|
264 |
|
265 | this.interspersed =
|
266 | config.interspersed !== undefined ? config.interspersed : true;
|
267 |
|
268 |
|
269 | this.allowUnknown =
|
270 | config.allowUnknown !== undefined ? config.allowUnknown : false;
|
271 |
|
272 | this.options = config.options.map(function onOpt(o) {
|
273 | return shallowCopy(o);
|
274 | });
|
275 | this.optionFromName = {};
|
276 | this.optionFromEnv = {};
|
277 | for (var i = 0; i < this.options.length; i++) {
|
278 | var o = this.options[i];
|
279 | if (o.group !== undefined && o.group !== null) {
|
280 | assert.optionalString(
|
281 | o.group,
|
282 | format('config.options.%d.group', i)
|
283 | );
|
284 | continue;
|
285 | }
|
286 | assert.ok(
|
287 | optionTypes[o.type],
|
288 | format('invalid config.options.%d.type: "%s" in %j', i, o.type, o)
|
289 | );
|
290 | assert.optionalString(o.name, format('config.options.%d.name', i));
|
291 | assert.optionalArrayOfString(
|
292 | o.names,
|
293 | format('config.options.%d.names', i)
|
294 | );
|
295 | assert.ok(
|
296 | (o.name || o.names) && !(o.name && o.names),
|
297 | format('exactly one of "name" or "names" required: %j', o)
|
298 | );
|
299 | assert.optionalString(o.help, format('config.options.%d.help', i));
|
300 | var env = o.env || [];
|
301 | if (typeof env === 'string') {
|
302 | env = [env];
|
303 | }
|
304 | assert.optionalArrayOfString(env, format('config.options.%d.env', i));
|
305 | assert.optionalString(
|
306 | o.helpGroup,
|
307 | format('config.options.%d.helpGroup', i)
|
308 | );
|
309 | assert.optionalBool(
|
310 | o.helpWrap,
|
311 | format('config.options.%d.helpWrap', i)
|
312 | );
|
313 | assert.optionalBool(o.hidden, format('config.options.%d.hidden', i));
|
314 |
|
315 | if (o.name) {
|
316 | o.names = [o.name];
|
317 | } else {
|
318 | assert.string(
|
319 | o.names[0],
|
320 | format('config.options.%d.names is empty', i)
|
321 | );
|
322 | }
|
323 | o.key = optionKeyFromName(o.names[0]);
|
324 | o.names.forEach(function onName(n) {
|
325 | if (self.optionFromName[n]) {
|
326 | throw new Error(
|
327 | format(
|
328 | 'option name collision: "%s" used in %j and %j',
|
329 | n,
|
330 | self.optionFromName[n],
|
331 | o
|
332 | )
|
333 | );
|
334 | }
|
335 | self.optionFromName[n] = o;
|
336 | });
|
337 | env.forEach(function onName(n) {
|
338 | if (self.optionFromEnv[n]) {
|
339 | throw new Error(
|
340 | format(
|
341 | 'option env collision: "%s" used in %j and %j',
|
342 | n,
|
343 | self.optionFromEnv[n],
|
344 | o
|
345 | )
|
346 | );
|
347 | }
|
348 | self.optionFromEnv[n] = o;
|
349 | });
|
350 | }
|
351 | }
|
352 |
|
353 | Parser.prototype.optionTakesArg = function optionTakesArg(option) {
|
354 | return optionTypes[option.type].takesArg;
|
355 | };
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 | Parser.prototype.parse = function parse(inputs) {
|
372 | var self = this;
|
373 |
|
374 |
|
375 | if (Array.isArray(arguments[0])) {
|
376 | inputs = {argv: arguments[0], slice: arguments[1]};
|
377 | }
|
378 |
|
379 | assert.optionalObject(inputs, 'inputs');
|
380 | if (!inputs) {
|
381 | inputs = {};
|
382 | }
|
383 | assert.optionalArrayOfString(inputs.argv, 'inputs.argv');
|
384 |
|
385 | var argv = inputs.argv || process.argv;
|
386 | var slice = inputs.slice !== undefined ? inputs.slice : 2;
|
387 | var args = argv.slice(slice);
|
388 | var env = inputs.env || process.env;
|
389 | var opts = {};
|
390 | var _order = [];
|
391 |
|
392 | function addOpt(option, optstr, key, val, from) {
|
393 | var type = optionTypes[option.type];
|
394 | var parsedVal = type.parseArg(option, optstr, val);
|
395 | if (type.array) {
|
396 | if (!opts[key]) {
|
397 | opts[key] = [];
|
398 | }
|
399 | if (type.arrayFlatten && Array.isArray(parsedVal)) {
|
400 | for (var i = 0; i < parsedVal.length; i++) {
|
401 | opts[key].push(parsedVal[i]);
|
402 | }
|
403 | } else {
|
404 | opts[key].push(parsedVal);
|
405 | }
|
406 | } else {
|
407 | opts[key] = parsedVal;
|
408 | }
|
409 | var item = {key: key, value: parsedVal, from: from};
|
410 | _order.push(item);
|
411 | }
|
412 |
|
413 |
|
414 | var _args = [];
|
415 | var arg;
|
416 | var i = 0;
|
417 | var idx;
|
418 | var name;
|
419 | var option;
|
420 | var takesArg;
|
421 | var val;
|
422 | outer: while (i < args.length) {
|
423 | arg = args[i];
|
424 |
|
425 |
|
426 | if (arg === '--') {
|
427 | i++;
|
428 | break;
|
429 |
|
430 |
|
431 | } else if (arg.slice(0, 2) === '--') {
|
432 | name = arg.slice(2);
|
433 | val = null;
|
434 | idx = name.indexOf('=');
|
435 | if (idx !== -1) {
|
436 | val = name.slice(idx + 1);
|
437 | name = name.slice(0, idx);
|
438 | }
|
439 | option = this.optionFromName[name];
|
440 | if (!option) {
|
441 | if (!this.allowUnknown) {
|
442 | throw new Error(format('unknown option: "--%s"', name));
|
443 | } else if (this.interspersed) {
|
444 | _args.push(arg);
|
445 | } else {
|
446 | break outer;
|
447 | }
|
448 | } else {
|
449 | takesArg = this.optionTakesArg(option);
|
450 | if (val !== null && !takesArg) {
|
451 | throw new Error(
|
452 | format(
|
453 | 'argument given to "--%s" option ' +
|
454 | 'that does not take one: "%s"',
|
455 | name,
|
456 | arg
|
457 | )
|
458 | );
|
459 | }
|
460 | if (!takesArg) {
|
461 | addOpt(option, '--' + name, option.key, true, 'argv');
|
462 | } else if (val !== null) {
|
463 | addOpt(option, '--' + name, option.key, val, 'argv');
|
464 | } else if (i + 1 >= args.length) {
|
465 | throw new Error(
|
466 | format(
|
467 | 'do not have enough args for "--%s" ' + 'option',
|
468 | name
|
469 | )
|
470 | );
|
471 | } else {
|
472 | addOpt(
|
473 | option,
|
474 | '--' + name,
|
475 | option.key,
|
476 | args[i + 1],
|
477 | 'argv'
|
478 | );
|
479 | i++;
|
480 | }
|
481 | }
|
482 |
|
483 |
|
484 | } else if (arg[0] === '-' && arg.length > 1) {
|
485 | var j = 1;
|
486 | var allFound = true;
|
487 | while (j < arg.length) {
|
488 | name = arg[j];
|
489 | option = this.optionFromName[name];
|
490 | if (!option) {
|
491 | allFound = false;
|
492 | if (this.allowUnknown) {
|
493 | if (this.interspersed) {
|
494 | _args.push(arg);
|
495 | break;
|
496 | } else {
|
497 | break outer;
|
498 | }
|
499 | } else if (arg.length > 2) {
|
500 | throw new Error(
|
501 | format(
|
502 | 'unknown option: "-%s" in "%s" group',
|
503 | name,
|
504 | arg
|
505 | )
|
506 | );
|
507 | } else {
|
508 | throw new Error(format('unknown option: "-%s"', name));
|
509 | }
|
510 | } else if (this.optionTakesArg(option)) {
|
511 | break;
|
512 | }
|
513 | j++;
|
514 | }
|
515 |
|
516 | j = 1;
|
517 | while (allFound && j < arg.length) {
|
518 | name = arg[j];
|
519 | val = arg.slice(j + 1);
|
520 | option = this.optionFromName[name];
|
521 | takesArg = this.optionTakesArg(option);
|
522 | if (!takesArg) {
|
523 | addOpt(option, '-' + name, option.key, true, 'argv');
|
524 | } else if (val) {
|
525 | addOpt(option, '-' + name, option.key, val, 'argv');
|
526 | break;
|
527 | } else {
|
528 | if (i + 1 >= args.length) {
|
529 | throw new Error(
|
530 | format(
|
531 | 'do not have enough args ' + 'for "-%s" option',
|
532 | name
|
533 | )
|
534 | );
|
535 | }
|
536 | addOpt(option, '-' + name, option.key, args[i + 1], 'argv');
|
537 | i++;
|
538 | break;
|
539 | }
|
540 | j++;
|
541 | }
|
542 |
|
543 |
|
544 | } else if (this.interspersed) {
|
545 | _args.push(arg);
|
546 |
|
547 |
|
548 | } else {
|
549 | break outer;
|
550 | }
|
551 | i++;
|
552 | }
|
553 | _args = _args.concat(args.slice(i));
|
554 |
|
555 |
|
556 | Object.keys(this.optionFromEnv).forEach(function onEnvname(envname) {
|
557 | var val = env[envname];
|
558 | if (val === undefined) {
|
559 | return;
|
560 | }
|
561 | var option = self.optionFromEnv[envname];
|
562 | if (opts[option.key] !== undefined) {
|
563 | return;
|
564 | }
|
565 | var takesArg = self.optionTakesArg(option);
|
566 | if (takesArg) {
|
567 | addOpt(option, envname, option.key, val, 'env');
|
568 | } else if (val !== '') {
|
569 |
|
570 |
|
571 |
|
572 |
|
573 | addOpt(option, envname, option.key, val !== '0', 'env');
|
574 | }
|
575 | });
|
576 |
|
577 |
|
578 | this.options.forEach(function onOpt(o) {
|
579 | if (opts[o.key] === undefined) {
|
580 | if (o.default !== undefined) {
|
581 | opts[o.key] = o.default;
|
582 | } else if (o.type && optionTypes[o.type].default !== undefined) {
|
583 | opts[o.key] = optionTypes[o.type].default;
|
584 | }
|
585 | }
|
586 | });
|
587 |
|
588 | opts._order = _order;
|
589 | opts._args = _args;
|
590 | return opts;
|
591 | };
|
592 |
|
593 |
|
594 |
|
595 |
|
596 |
|
597 |
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 |
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 |
|
616 |
|
617 |
|
618 |
|
619 |
|
620 |
|
621 |
|
622 |
|
623 |
|
624 | Parser.prototype.help = function help(config) {
|
625 | config = config || {};
|
626 | assert.object(config, 'config');
|
627 |
|
628 | var indent = makeIndent(config.indent, 4, 'config.indent');
|
629 | var headingIndent = makeIndent(
|
630 | config.headingIndent,
|
631 | Math.round(indent.length / 2),
|
632 | 'config.headingIndent'
|
633 | );
|
634 |
|
635 | assert.optionalString(config.nameSort, 'config.nameSort');
|
636 | var nameSort = config.nameSort || 'length';
|
637 | assert.ok(
|
638 | ~['length', 'none'].indexOf(nameSort),
|
639 | 'invalid "config.nameSort"'
|
640 | );
|
641 | assert.optionalNumber(config.maxCol, 'config.maxCol');
|
642 | assert.optionalNumber(config.maxHelpCol, 'config.maxHelpCol');
|
643 | assert.optionalNumber(config.minHelpCol, 'config.minHelpCol');
|
644 | assert.optionalNumber(config.helpCol, 'config.helpCol');
|
645 | assert.optionalBool(config.includeEnv, 'config.includeEnv');
|
646 | assert.optionalBool(config.includeDefault, 'config.includeDefault');
|
647 | assert.optionalBool(config.helpWrap, 'config.helpWrap');
|
648 | var maxCol = config.maxCol || 80;
|
649 | var minHelpCol = config.minHelpCol || 20;
|
650 | var maxHelpCol = config.maxHelpCol || 40;
|
651 |
|
652 | var lines = [];
|
653 | var maxWidth = 0;
|
654 | this.options.forEach(function onOpt(o) {
|
655 | if (o.hidden) {
|
656 | return;
|
657 | }
|
658 | if (o.group !== undefined && o.group !== null) {
|
659 |
|
660 | lines.push(null);
|
661 | return;
|
662 | }
|
663 | var type = optionTypes[o.type];
|
664 | var arg = o.helpArg || type.helpArg || 'ARG';
|
665 | var line = '';
|
666 | var names = o.names.slice();
|
667 | if (nameSort === 'length') {
|
668 | names.sort(function onCmp(a, b) {
|
669 | if (a.length < b.length) {
|
670 | return -1;
|
671 | } else if (b.length < a.length) {
|
672 | return 1;
|
673 | } else {
|
674 | return 0;
|
675 | }
|
676 | });
|
677 | }
|
678 | names.forEach(function onName(name, i) {
|
679 | if (i > 0) {
|
680 | line += ', ';
|
681 | }
|
682 | if (name.length === 1) {
|
683 | line += '-' + name;
|
684 | if (type.takesArg) {
|
685 | line += ' ' + arg;
|
686 | }
|
687 | } else {
|
688 | line += '--' + name;
|
689 | if (type.takesArg) {
|
690 | line += '=' + arg;
|
691 | }
|
692 | }
|
693 | });
|
694 | maxWidth = Math.max(maxWidth, line.length);
|
695 | lines.push(line);
|
696 | });
|
697 |
|
698 |
|
699 | var helpCol = config.helpCol;
|
700 | if (!helpCol) {
|
701 | helpCol = maxWidth + indent.length + 2;
|
702 | helpCol = Math.min(Math.max(helpCol, minHelpCol), maxHelpCol);
|
703 | }
|
704 | var i = -1;
|
705 | this.options.forEach(function onOpt(o) {
|
706 | if (o.hidden) {
|
707 | return;
|
708 | }
|
709 | i++;
|
710 |
|
711 | if (o.group !== undefined && o.group !== null) {
|
712 | if (o.group === '') {
|
713 |
|
714 |
|
715 | lines[i] = '';
|
716 | } else {
|
717 |
|
718 | lines[i] =
|
719 | (i === 0 ? '' : '\n') + headingIndent + o.group + ':';
|
720 | }
|
721 | return;
|
722 | }
|
723 |
|
724 | var helpDefault;
|
725 | if (config.includeDefault) {
|
726 | if (o.default !== undefined) {
|
727 | helpDefault = format('Default: %j', o.default);
|
728 | } else if (o.type && optionTypes[o.type].default !== undefined) {
|
729 | helpDefault = format(
|
730 | 'Default: %j',
|
731 | optionTypes[o.type].default
|
732 | );
|
733 | }
|
734 | }
|
735 |
|
736 | var line = (lines[i] = indent + lines[i]);
|
737 | if (!o.help && !(config.includeEnv && o.env) && !helpDefault) {
|
738 | return;
|
739 | }
|
740 | var n = helpCol - line.length;
|
741 | if (n >= 0) {
|
742 | line += space(n);
|
743 | } else {
|
744 | line += '\n' + space(helpCol);
|
745 | }
|
746 |
|
747 | var helpEnv = '';
|
748 | if (o.env && o.env.length && config.includeEnv) {
|
749 | helpEnv += 'Environment: ';
|
750 | var type = optionTypes[o.type];
|
751 | var arg = o.helpArg || type.helpArg || 'ARG';
|
752 | var envs = (Array.isArray(o.env) ? o.env : [o.env]).map(
|
753 | function onE(e) {
|
754 | if (type.takesArg) {
|
755 | return e + '=' + arg;
|
756 | } else {
|
757 | return e + '=1';
|
758 | }
|
759 | }
|
760 | );
|
761 | helpEnv += envs.join(', ');
|
762 | }
|
763 | var help = (o.help || '').trim();
|
764 | if (o.helpWrap !== false && config.helpWrap !== false) {
|
765 |
|
766 | if (help.length && !~'.!?"\''.indexOf(help.slice(-1))) {
|
767 | help += '.';
|
768 | }
|
769 | if (help.length) {
|
770 | help += ' ';
|
771 | }
|
772 | help += helpEnv;
|
773 | if (helpDefault) {
|
774 | if (helpEnv) {
|
775 | help += '. ';
|
776 | }
|
777 | help += helpDefault;
|
778 | }
|
779 | line += textwrap(help, maxCol - helpCol).join(
|
780 | '\n' + space(helpCol)
|
781 | );
|
782 | } else {
|
783 |
|
784 | var helpLines = help.split('\n').filter(function onLine(ln) {
|
785 | return ln.length;
|
786 | });
|
787 | if (helpEnv !== '') {
|
788 | helpLines.push(helpEnv);
|
789 | }
|
790 | if (helpDefault) {
|
791 | helpLines.push(helpDefault);
|
792 | }
|
793 | line += helpLines.join('\n' + space(helpCol));
|
794 | }
|
795 |
|
796 | lines[i] = line;
|
797 | });
|
798 |
|
799 | var rv = '';
|
800 | if (lines.length > 0) {
|
801 | rv = lines.join('\n') + '\n';
|
802 | }
|
803 | return rv;
|
804 | };
|
805 |
|
806 |
|
807 |
|
808 |
|
809 |
|
810 |
|
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 |
|
817 |
|
818 |
|
819 |
|
820 |
|
821 |
|
822 |
|
823 | Parser.prototype.bashCompletion = function bashCompletion(args) {
|
824 | assert.object(args, 'args');
|
825 | assert.string(args.name, 'args.name');
|
826 | assert.optionalString(args.specExtra, 'args.specExtra');
|
827 | assert.optionalArrayOfString(args.argtypes, 'args.argtypes');
|
828 |
|
829 | return bashCompletionFromOptions({
|
830 | name: args.name,
|
831 | specExtra: args.specExtra,
|
832 | argtypes: args.argtypes,
|
833 | options: this.options
|
834 | });
|
835 | };
|
836 |
|
837 |
|
838 |
|
839 | const BASH_COMPLETION_TEMPLATE_PATH = path.join(
|
840 | __dirname,
|
841 | '../etc/dashdash.bash_completion.in'
|
842 | );
|
843 |
|
844 |
|
845 |
|
846 |
|
847 |
|
848 |
|
849 |
|
850 |
|
851 |
|
852 |
|
853 |
|
854 |
|
855 |
|
856 |
|
857 |
|
858 |
|
859 |
|
860 |
|
861 |
|
862 |
|
863 |
|
864 |
|
865 |
|
866 |
|
867 |
|
868 |
|
869 |
|
870 |
|
871 |
|
872 |
|
873 |
|
874 |
|
875 | function bashCompletionSpecFromOptions(args) {
|
876 | assert.object(args, 'args');
|
877 | assert.object(args.options, 'args.options');
|
878 | assert.optionalString(args.context, 'args.context');
|
879 | assert.optionalBool(args.includeHidden, 'args.includeHidden');
|
880 | assert.optionalArrayOfString(args.argtypes, 'args.argtypes');
|
881 |
|
882 | var context = args.context || '';
|
883 | var includeHidden =
|
884 | args.includeHidden === undefined ? false : args.includeHidden;
|
885 |
|
886 | var spec = [];
|
887 | var shortopts = [];
|
888 | var longopts = [];
|
889 | var optargs = [];
|
890 | (args.options || []).forEach(function onOpt(o) {
|
891 | if (o.group !== undefined && o.group !== null) {
|
892 |
|
893 | return;
|
894 | }
|
895 |
|
896 | var optNames = o.names || [o.name];
|
897 | var optType = getOptionType(o.type);
|
898 | if (optType.takesArg) {
|
899 | var completionType =
|
900 | o.completionType || optType.completionType || o.type;
|
901 | optNames.forEach(function onOptName(optName) {
|
902 | if (optName.length === 1) {
|
903 | if (includeHidden || !o.hidden) {
|
904 | shortopts.push('-' + optName);
|
905 | }
|
906 |
|
907 |
|
908 | optargs.push('-' + optName + '=' + completionType);
|
909 | } else {
|
910 | if (includeHidden || !o.hidden) {
|
911 | longopts.push('--' + optName);
|
912 | }
|
913 | optargs.push('--' + optName + '=' + completionType);
|
914 | }
|
915 | });
|
916 | } else {
|
917 | optNames.forEach(function onOptName(optName) {
|
918 | if (includeHidden || !o.hidden) {
|
919 | if (optName.length === 1) {
|
920 | shortopts.push('-' + optName);
|
921 | } else {
|
922 | longopts.push('--' + optName);
|
923 | }
|
924 | }
|
925 | });
|
926 | }
|
927 | });
|
928 |
|
929 | spec.push(
|
930 | format(
|
931 | 'local cmd%s_shortopts="%s"',
|
932 | context,
|
933 | shortopts.sort().join(' ')
|
934 | )
|
935 | );
|
936 | spec.push(
|
937 | format('local cmd%s_longopts="%s"', context, longopts.sort().join(' '))
|
938 | );
|
939 | spec.push(
|
940 | format('local cmd%s_optargs="%s"', context, optargs.sort().join(' '))
|
941 | );
|
942 | if (args.argtypes) {
|
943 | spec.push(
|
944 | format(
|
945 | 'local cmd%s_argtypes="%s"',
|
946 | context,
|
947 | args.argtypes.join(' ')
|
948 | )
|
949 | );
|
950 | }
|
951 | return spec.join('\n');
|
952 | }
|
953 |
|
954 |
|
955 |
|
956 |
|
957 |
|
958 |
|
959 |
|
960 |
|
961 |
|
962 |
|
963 |
|
964 |
|
965 |
|
966 |
|
967 |
|
968 |
|
969 |
|
970 |
|
971 |
|
972 | function bashCompletionFromOptions(args) {
|
973 | assert.object(args, 'args');
|
974 | assert.object(args.options, 'args.options');
|
975 | assert.string(args.name, 'args.name');
|
976 | assert.optionalString(args.specExtra, 'args.specExtra');
|
977 | assert.optionalArrayOfString(args.argtypes, 'args.argtypes');
|
978 |
|
979 |
|
980 | var data = {
|
981 | name: args.name,
|
982 | date: new Date(),
|
983 | spec: bashCompletionSpecFromOptions({
|
984 | options: args.options,
|
985 | argtypes: args.argtypes
|
986 | })
|
987 | };
|
988 | if (args.specExtra) {
|
989 | data.spec += '\n\n' + args.specExtra;
|
990 | }
|
991 |
|
992 |
|
993 | var template = fs.readFileSync(BASH_COMPLETION_TEMPLATE_PATH, 'utf8');
|
994 | return renderTemplate(template, data);
|
995 | }
|
996 |
|
997 |
|
998 |
|
999 | function createParser(config) {
|
1000 | return new Parser(config);
|
1001 | }
|
1002 |
|
1003 |
|
1004 |
|
1005 |
|
1006 |
|
1007 |
|
1008 |
|
1009 |
|
1010 | function parse(config_) {
|
1011 | assert.object(config_, 'config');
|
1012 | assert.optionalArrayOfString(config_.argv, 'config.argv');
|
1013 | assert.optionalObject(config_.env, 'config.env');
|
1014 |
|
1015 | var config = shallowCopy(config_);
|
1016 | var argv = config.argv;
|
1017 | delete config.argv;
|
1018 | var env = config.env;
|
1019 | delete config.env;
|
1020 |
|
1021 | var parser = new Parser(config);
|
1022 | return parser.parse({argv: argv, env: env});
|
1023 | }
|
1024 |
|
1025 |
|
1026 |
|
1027 |
|
1028 |
|
1029 |
|
1030 |
|
1031 |
|
1032 |
|
1033 |
|
1034 |
|
1035 |
|
1036 |
|
1037 |
|
1038 |
|
1039 |
|
1040 |
|
1041 |
|
1042 |
|
1043 |
|
1044 |
|
1045 | function addOptionType(optionType) {
|
1046 | assert.object(optionType, 'optionType');
|
1047 | assert.string(optionType.name, 'optionType.name');
|
1048 | assert.bool(optionType.takesArg, 'optionType.takesArg');
|
1049 | if (optionType.takesArg) {
|
1050 | assert.string(optionType.helpArg, 'optionType.helpArg');
|
1051 | }
|
1052 | assert.func(optionType.parseArg, 'optionType.parseArg');
|
1053 | assert.optionalBool(optionType.array, 'optionType.array');
|
1054 | assert.optionalBool(optionType.arrayFlatten, 'optionType.arrayFlatten');
|
1055 |
|
1056 | optionTypes[optionType.name] = {
|
1057 | takesArg: optionType.takesArg,
|
1058 | helpArg: optionType.helpArg,
|
1059 | parseArg: optionType.parseArg,
|
1060 | array: optionType.array,
|
1061 | arrayFlatten: optionType.arrayFlatten,
|
1062 | default: optionType.default
|
1063 | };
|
1064 | }
|
1065 |
|
1066 | function getOptionType(name) {
|
1067 | assert.string(name, 'name');
|
1068 | return optionTypes[name];
|
1069 | }
|
1070 |
|
1071 |
|
1072 |
|
1073 |
|
1074 |
|
1075 |
|
1076 |
|
1077 |
|
1078 |
|
1079 |
|
1080 | function synopsisFromOpt(o) {
|
1081 | assert.object(o, 'o');
|
1082 |
|
1083 | if (Object.prototype.hasOwnProperty.call(o, 'group')) {
|
1084 | return null;
|
1085 | }
|
1086 | var names = o.names || [o.name];
|
1087 |
|
1088 |
|
1089 | var type = getOptionType(o.type);
|
1090 | var helpArg = o.helpArg || (type && type.helpArg) || 'ARG';
|
1091 | var parts = [];
|
1092 | names.forEach(function onName(name) {
|
1093 | var part = (name.length === 1 ? '-' : '--') + name;
|
1094 | if (type && type.takesArg) {
|
1095 | part += name.length === 1 ? ' ' + helpArg : '=' + helpArg;
|
1096 | }
|
1097 | parts.push(part);
|
1098 | });
|
1099 | return '[ ' + parts.join(' | ') + ' ]';
|
1100 | }
|
1101 |
|
1102 | module.exports = {
|
1103 | createParser: createParser,
|
1104 | Parser: Parser,
|
1105 | parse: parse,
|
1106 | addOptionType: addOptionType,
|
1107 | getOptionType: getOptionType,
|
1108 | synopsisFromOpt: synopsisFromOpt,
|
1109 |
|
1110 |
|
1111 | BASH_COMPLETION_TEMPLATE_PATH: BASH_COMPLETION_TEMPLATE_PATH,
|
1112 | bashCompletionFromOptions: bashCompletionFromOptions,
|
1113 | bashCompletionSpecFromOptions: bashCompletionSpecFromOptions,
|
1114 |
|
1115 |
|
1116 |
|
1117 | parseBool: parseBool,
|
1118 | parseString: parseString,
|
1119 | parseNumber: parseNumber,
|
1120 | parseInteger: parseInteger,
|
1121 | parsePositiveInteger: parsePositiveInteger,
|
1122 | parseDate: parseDate
|
1123 | };
|