"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/dashdash/lib/dashdash.js" (11 Apr 2017, 35297 Bytes) of package /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /**
    2  * dashdash - A light, featureful and explicit option parsing library for
    3  * node.js.
    4  */
    5 // vim: set ts=4 sts=4 sw=4 et:
    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 var DEBUG = true;
   14 if (DEBUG) {
   15     var debug = console.warn;
   16 } else {
   17     var debug = function () {};
   18 }
   19 
   20 
   21 
   22 // ---- internal support stuff
   23 
   24 // Replace {{variable}} in `s` with the template data in `d`.
   25 function renderTemplate(s, d) {
   26     return s.replace(/{{([a-zA-Z]+)}}/g, function (match, key) {
   27         return d.hasOwnProperty(key) ? d[key] : match;
   28     });
   29 }
   30 
   31 /**
   32  * Return a shallow copy of the given object;
   33  */
   34 function shallowCopy(obj) {
   35     if (!obj) {
   36         return (obj);
   37     }
   38     var copy = {};
   39     Object.keys(obj).forEach(function (k) {
   40         copy[k] = obj[k];
   41     });
   42     return (copy);
   43 }
   44 
   45 
   46 function space(n) {
   47     var s = '';
   48     for (var i = 0; i < n; i++) {
   49         s += ' ';
   50     }
   51     return s;
   52 }
   53 
   54 
   55 function makeIndent(arg, deflen, name) {
   56     if (arg === null || arg === undefined)
   57         return space(deflen);
   58     else if (typeof (arg) === 'number')
   59         return space(arg);
   60     else if (typeof (arg) === 'string')
   61         return arg;
   62     else
   63         assert.fail('invalid "' + name + '": not a string or number: ' + arg);
   64 }
   65 
   66 
   67 /**
   68  * Return an array of lines wrapping the given text to the given width.
   69  * This splits on whitespace. Single tokens longer than `width` are not
   70  * broken up.
   71  */
   72 function textwrap(s, width) {
   73     var words = s.trim().split(/\s+/);
   74     var lines = [];
   75     var line = '';
   76     words.forEach(function (w) {
   77         var newLength = line.length + w.length;
   78         if (line.length > 0)
   79             newLength += 1;
   80         if (newLength > width) {
   81             lines.push(line);
   82             line = '';
   83         }
   84         if (line.length > 0)
   85             line += ' ';
   86         line += w;
   87     });
   88     lines.push(line);
   89     return lines;
   90 }
   91 
   92 
   93 /**
   94  * Transform an option name to a "key" that is used as the field
   95  * on the `opts` object returned from `<parser>.parse()`.
   96  *
   97  * Transformations:
   98  * - '-' -> '_': This allow one to use hyphen in option names (common)
   99  *   but not have to do silly things like `opt["dry-run"]` to access the
  100  *   parsed results.
  101  */
  102 function optionKeyFromName(name) {
  103     return name.replace(/-/g, '_');
  104 }
  105 
  106 
  107 
  108 // ---- Option types
  109 
  110 function parseBool(option, optstr, arg) {
  111     return Boolean(arg);
  112 }
  113 
  114 function parseString(option, optstr, arg) {
  115     assert.string(arg, 'arg');
  116     return arg;
  117 }
  118 
  119 function parseNumber(option, optstr, arg) {
  120     assert.string(arg, 'arg');
  121     var num = Number(arg);
  122     if (isNaN(num)) {
  123         throw new Error(format('arg for "%s" is not a number: "%s"',
  124             optstr, arg));
  125     }
  126     return num;
  127 }
  128 
  129 function parseInteger(option, optstr, arg) {
  130     assert.string(arg, 'arg');
  131     var num = Number(arg);
  132     if (!/^[0-9-]+$/.test(arg) || isNaN(num)) {
  133         throw new Error(format('arg for "%s" is not an integer: "%s"',
  134             optstr, arg));
  135     }
  136     return num;
  137 }
  138 
  139 function parsePositiveInteger(option, optstr, arg) {
  140     assert.string(arg, 'arg');
  141     var num = Number(arg);
  142     if (!/^[0-9]+$/.test(arg) || isNaN(num) || num === 0) {
  143         throw new Error(format('arg for "%s" is not a positive integer: "%s"',
  144             optstr, arg));
  145     }
  146     return num;
  147 }
  148 
  149 /**
  150  * Supported date args:
  151  * - epoch second times (e.g. 1396031701)
  152  * - ISO 8601 format: YYYY-MM-DD[THH:MM:SS[.sss][Z]]
  153  *      2014-03-28T18:35:01.489Z
  154  *      2014-03-28T18:35:01.489
  155  *      2014-03-28T18:35:01Z
  156  *      2014-03-28T18:35:01
  157  *      2014-03-28
  158  */
  159 function parseDate(option, optstr, arg) {
  160     assert.string(arg, 'arg');
  161     var date;
  162     if (/^\d+$/.test(arg)) {
  163         // epoch seconds
  164         date = new Date(Number(arg) * 1000);
  165     /* JSSTYLED */
  166     } else if (/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?Z?)?$/i.test(arg)) {
  167         // ISO 8601 format
  168         date = new Date(arg);
  169     } else {
  170         throw new Error(format('arg for "%s" is not a valid date format: "%s"',
  171             optstr, arg));
  172     }
  173     if (date.toString() === 'Invalid Date') {
  174         throw new Error(format('arg for "%s" is an invalid date: "%s"',
  175             optstr, arg));
  176     }
  177     return date;
  178 }
  179 
  180 var optionTypes = {
  181     bool: {
  182         takesArg: false,
  183         parseArg: parseBool
  184     },
  185     string: {
  186         takesArg: true,
  187         helpArg: 'ARG',
  188         parseArg: parseString
  189     },
  190     number: {
  191         takesArg: true,
  192         helpArg: 'NUM',
  193         parseArg: parseNumber
  194     },
  195     integer: {
  196         takesArg: true,
  197         helpArg: 'INT',
  198         parseArg: parseInteger
  199     },
  200     positiveInteger: {
  201         takesArg: true,
  202         helpArg: 'INT',
  203         parseArg: parsePositiveInteger
  204     },
  205     date: {
  206         takesArg: true,
  207         helpArg: 'DATE',
  208         parseArg: parseDate
  209     },
  210     arrayOfBool: {
  211         takesArg: false,
  212         array: true,
  213         parseArg: parseBool
  214     },
  215     arrayOfString: {
  216         takesArg: true,
  217         helpArg: 'ARG',
  218         array: true,
  219         parseArg: parseString
  220     },
  221     arrayOfNumber: {
  222         takesArg: true,
  223         helpArg: 'NUM',
  224         array: true,
  225         parseArg: parseNumber
  226     },
  227     arrayOfInteger: {
  228         takesArg: true,
  229         helpArg: 'INT',
  230         array: true,
  231         parseArg: parseInteger
  232     },
  233     arrayOfPositiveInteger: {
  234         takesArg: true,
  235         helpArg: 'INT',
  236         array: true,
  237         parseArg: parsePositiveInteger
  238     },
  239     arrayOfDate: {
  240         takesArg: true,
  241         helpArg: 'INT',
  242         array: true,
  243         parseArg: parseDate
  244     },
  245 };
  246 
  247 
  248 
  249 // ---- Parser
  250 
  251 /**
  252  * Parser constructor.
  253  *
  254  * @param config {Object} The parser configuration
  255  *      - options {Array} Array of option specs. See the README for how to
  256  *        specify each option spec.
  257  *      - allowUnknown {Boolean} Default false. Whether to throw on unknown
  258  *        options. If false, then unknown args are included in the _args array.
  259  *      - interspersed {Boolean} Default true. Whether to allow interspersed
  260  *        arguments (non-options) and options. E.g.:
  261  *              node tool.js arg1 arg2 -v
  262  *        '-v' is after some args here. If `interspersed: false` then '-v'
  263  *        would not be parsed out. Note that regardless of `interspersed`
  264  *        the presence of '--' will stop option parsing, as all good
  265  *        option parsers should.
  266  */
  267 function Parser(config) {
  268     assert.object(config, 'config');
  269     assert.arrayOfObject(config.options, 'config.options');
  270     assert.optionalBool(config.interspersed, 'config.interspersed');
  271     var self = this;
  272 
  273     // Allow interspersed arguments (true by default).
  274     this.interspersed = (config.interspersed !== undefined
  275         ? config.interspersed : true);
  276 
  277     // Don't allow unknown flags (true by default).
  278     this.allowUnknown = (config.allowUnknown !== undefined
  279         ? config.allowUnknown : false);
  280 
  281     this.options = config.options.map(function (o) { return shallowCopy(o); });
  282     this.optionFromName = {};
  283     this.optionFromEnv = {};
  284     for (var i = 0; i < this.options.length; i++) {
  285         var o = this.options[i];
  286         if (o.group !== undefined && o.group !== null) {
  287             assert.optionalString(o.group,
  288                 format('config.options.%d.group', i));
  289             continue;
  290         }
  291         assert.ok(optionTypes[o.type],
  292             format('invalid config.options.%d.type: "%s" in %j',
  293                    i, o.type, o));
  294         assert.optionalString(o.name, format('config.options.%d.name', i));
  295         assert.optionalArrayOfString(o.names,
  296             format('config.options.%d.names', i));
  297         assert.ok((o.name || o.names) && !(o.name && o.names),
  298             format('exactly one of "name" or "names" required: %j', o));
  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(o.helpGroup,
  306             format('config.options.%d.helpGroup', i));
  307         assert.optionalBool(o.helpWrap,
  308             format('config.options.%d.helpWrap', i));
  309         assert.optionalBool(o.hidden, format('config.options.%d.hidden', i));
  310 
  311         if (o.name) {
  312             o.names = [o.name];
  313         } else {
  314             assert.string(o.names[0],
  315                 format('config.options.%d.names is empty', i));
  316         }
  317         o.key = optionKeyFromName(o.names[0]);
  318         o.names.forEach(function (n) {
  319             if (self.optionFromName[n]) {
  320                 throw new Error(format(
  321                     'option name collision: "%s" used in %j and %j',
  322                     n, self.optionFromName[n], o));
  323             }
  324             self.optionFromName[n] = o;
  325         });
  326         env.forEach(function (n) {
  327             if (self.optionFromEnv[n]) {
  328                 throw new Error(format(
  329                     'option env collision: "%s" used in %j and %j',
  330                     n, self.optionFromEnv[n], o));
  331             }
  332             self.optionFromEnv[n] = o;
  333         });
  334     }
  335 }
  336 
  337 Parser.prototype.optionTakesArg = function optionTakesArg(option) {
  338     return optionTypes[option.type].takesArg;
  339 };
  340 
  341 /**
  342  * Parse options from the given argv.
  343  *
  344  * @param inputs {Object} Optional.
  345  *      - argv {Array} Optional. The argv to parse. Defaults to
  346  *        `process.argv`.
  347  *      - slice {Number} The index into argv at which options/args begin.
  348  *        Default is 2, as appropriate for `process.argv`.
  349  *      - env {Object} Optional. The env to use for 'env' entries in the
  350  *        option specs. Defaults to `process.env`.
  351  * @returns {Object} Parsed `opts`. It has special keys `_args` (the
  352  *      remaining args from `argv`) and `_order` (gives the order that
  353  *      options were specified).
  354  */
  355 Parser.prototype.parse = function parse(inputs) {
  356     var self = this;
  357 
  358     // Old API was `parse([argv, [slice]])`
  359     if (Array.isArray(arguments[0])) {
  360         inputs = {argv: arguments[0], slice: arguments[1]};
  361     }
  362 
  363     assert.optionalObject(inputs, 'inputs');
  364     if (!inputs) {
  365         inputs = {};
  366     }
  367     assert.optionalArrayOfString(inputs.argv, 'inputs.argv');
  368     //assert.optionalNumber(slice, 'slice');
  369     var argv = inputs.argv || process.argv;
  370     var slice = inputs.slice !== undefined ? inputs.slice : 2;
  371     var args = argv.slice(slice);
  372     var env = inputs.env || process.env;
  373     var opts = {};
  374     var _order = [];
  375 
  376     function addOpt(option, optstr, key, val, from) {
  377         var type = optionTypes[option.type];
  378         var parsedVal = type.parseArg(option, optstr, val);
  379         if (type.array) {
  380             if (!opts[key]) {
  381                 opts[key] = [];
  382             }
  383             if (type.arrayFlatten && Array.isArray(parsedVal)) {
  384                 for (var i = 0; i < parsedVal.length; i++) {
  385                     opts[key].push(parsedVal[i]);
  386                 }
  387             } else {
  388                 opts[key].push(parsedVal);
  389             }
  390         } else {
  391             opts[key] = parsedVal;
  392         }
  393         var item = { key: key, value: parsedVal, from: from };
  394         _order.push(item);
  395     }
  396 
  397     // Parse args.
  398     var _args = [];
  399     var i = 0;
  400     outer: while (i < args.length) {
  401         var arg = args[i];
  402 
  403         // End of options marker.
  404         if (arg === '--') {
  405             i++;
  406             break;
  407 
  408         // Long option
  409         } else if (arg.slice(0, 2) === '--') {
  410             var name = arg.slice(2);
  411             var val = null;
  412             var idx = name.indexOf('=');
  413             if (idx !== -1) {
  414                 val = name.slice(idx + 1);
  415                 name = name.slice(0, idx);
  416             }
  417             var option = this.optionFromName[name];
  418             if (!option) {
  419                 if (!this.allowUnknown)
  420                     throw new Error(format('unknown option: "--%s"', name));
  421                 else if (this.interspersed)
  422                     _args.push(arg);
  423                 else
  424                     break outer;
  425             } else {
  426                 var takesArg = this.optionTakesArg(option);
  427                 if (val !== null && !takesArg) {
  428                     throw new Error(format('argument given to "--%s" option '
  429                         + 'that does not take one: "%s"', name, arg));
  430                 }
  431                 if (!takesArg) {
  432                     addOpt(option, '--'+name, option.key, true, 'argv');
  433                 } else if (val !== null) {
  434                     addOpt(option, '--'+name, option.key, val, 'argv');
  435                 } else if (i + 1 >= args.length) {
  436                     throw new Error(format('do not have enough args for "--%s" '
  437                         + 'option', name));
  438                 } else {
  439                     addOpt(option, '--'+name, option.key, args[i + 1], 'argv');
  440                     i++;
  441                 }
  442             }
  443 
  444         // Short option
  445         } else if (arg[0] === '-' && arg.length > 1) {
  446             var j = 1;
  447             var allFound = true;
  448             while (j < arg.length) {
  449                 var name = arg[j];
  450                 var option = this.optionFromName[name];
  451                 if (!option) {
  452                     allFound = false;
  453                     if (this.allowUnknown) {
  454                         if (this.interspersed) {
  455                             _args.push(arg);
  456                             break;
  457                         } else
  458                             break outer;
  459                     } else if (arg.length > 2) {
  460                         throw new Error(format(
  461                             'unknown option: "-%s" in "%s" group',
  462                             name, arg));
  463                     } else {
  464                         throw new Error(format('unknown option: "-%s"', name));
  465                     }
  466                 } else if (this.optionTakesArg(option)) {
  467                     break;
  468                 }
  469                 j++;
  470             }
  471 
  472             j = 1;
  473             while (allFound && j < arg.length) {
  474                 var name = arg[j];
  475                 var val = arg.slice(j + 1);  // option val if it takes an arg
  476                 var option = this.optionFromName[name];
  477                 var takesArg = this.optionTakesArg(option);
  478                 if (!takesArg) {
  479                     addOpt(option, '-'+name, option.key, true, 'argv');
  480                 } else if (val) {
  481                     addOpt(option, '-'+name, option.key, val, 'argv');
  482                     break;
  483                 } else {
  484                     if (i + 1 >= args.length) {
  485                         throw new Error(format('do not have enough args '
  486                             + 'for "-%s" option', name));
  487                     }
  488                     addOpt(option, '-'+name, option.key, args[i + 1], 'argv');
  489                     i++;
  490                     break;
  491                 }
  492                 j++;
  493             }
  494 
  495         // An interspersed arg
  496         } else if (this.interspersed) {
  497             _args.push(arg);
  498 
  499         // An arg and interspersed args are not allowed, so done options.
  500         } else {
  501             break outer;
  502         }
  503         i++;
  504     }
  505     _args = _args.concat(args.slice(i));
  506 
  507     // Parse environment.
  508     Object.keys(this.optionFromEnv).forEach(function (envname) {
  509         var val = env[envname];
  510         if (val === undefined)
  511             return;
  512         var option = self.optionFromEnv[envname];
  513         if (opts[option.key] !== undefined)
  514             return;
  515         var takesArg = self.optionTakesArg(option);
  516         if (takesArg) {
  517             addOpt(option, envname, option.key, val, 'env');
  518         } else if (val !== '') {
  519             // Boolean envvar handling:
  520             // - VAR=<empty-string>     not set (as if the VAR was not set)
  521             // - VAR=0                  false
  522             // - anything else          true
  523             addOpt(option, envname, option.key, (val !== '0'), 'env');
  524         }
  525     });
  526 
  527     // Apply default values.
  528     this.options.forEach(function (o) {
  529         if (opts[o.key] === undefined) {
  530             if (o.default !== undefined) {
  531                 opts[o.key] = o.default;
  532             } else if (o.type && optionTypes[o.type].default !== undefined) {
  533                 opts[o.key] = optionTypes[o.type].default;
  534             }
  535         }
  536     });
  537 
  538     opts._order = _order;
  539     opts._args = _args;
  540     return opts;
  541 };
  542 
  543 
  544 /**
  545  * Return help output for the current options.
  546  *
  547  * E.g.: if the current options are:
  548  *      [{names: ['help', 'h'], type: 'bool', help: 'Show help and exit.'}]
  549  * then this would return:
  550  *      '  -h, --help     Show help and exit.\n'
  551  *
  552  * @param config {Object} Config for controlling the option help output.
  553  *      - indent {Number|String} Default 4. An indent/prefix to use for
  554  *        each option line.
  555  *      - nameSort {String} Default is 'length'. By default the names are
  556  *        sorted to put the short opts first (i.e. '-h, --help' preferred
  557  *        to '--help, -h'). Set to 'none' to not do this sorting.
  558  *      - maxCol {Number} Default 80. Note that long tokens in a help string
  559  *        can go past this.
  560  *      - helpCol {Number} Set to specify a specific column at which
  561  *        option help will be aligned. By default this is determined
  562  *        automatically.
  563  *      - minHelpCol {Number} Default 20.
  564  *      - maxHelpCol {Number} Default 40.
  565  *      - includeEnv {Boolean} Default false. If true, a note stating the `env`
  566  *        envvar (if specified for this option) will be appended to the help
  567  *        output.
  568  *      - includeDefault {Boolean} Default false. If true, a note stating
  569  *        the `default` for this option, if any, will be appended to the help
  570  *        output.
  571  *      - helpWrap {Boolean} Default true. Wrap help text in helpCol..maxCol
  572  *        bounds.
  573  * @returns {String}
  574  */
  575 Parser.prototype.help = function help(config) {
  576     config = config || {};
  577     assert.object(config, 'config');
  578 
  579     var indent = makeIndent(config.indent, 4, 'config.indent');
  580     var headingIndent = makeIndent(config.headingIndent,
  581         Math.round(indent.length / 2), 'config.headingIndent');
  582 
  583     assert.optionalString(config.nameSort, 'config.nameSort');
  584     var nameSort = config.nameSort || 'length';
  585     assert.ok(~['length', 'none'].indexOf(nameSort),
  586         'invalid "config.nameSort"');
  587     assert.optionalNumber(config.maxCol, 'config.maxCol');
  588     assert.optionalNumber(config.maxHelpCol, 'config.maxHelpCol');
  589     assert.optionalNumber(config.minHelpCol, 'config.minHelpCol');
  590     assert.optionalNumber(config.helpCol, 'config.helpCol');
  591     assert.optionalBool(config.includeEnv, 'config.includeEnv');
  592     assert.optionalBool(config.includeDefault, 'config.includeDefault');
  593     assert.optionalBool(config.helpWrap, 'config.helpWrap');
  594     var maxCol = config.maxCol || 80;
  595     var minHelpCol = config.minHelpCol || 20;
  596     var maxHelpCol = config.maxHelpCol || 40;
  597 
  598     var lines = [];
  599     var maxWidth = 0;
  600     this.options.forEach(function (o) {
  601         if (o.hidden) {
  602             return;
  603         }
  604         if (o.group !== undefined && o.group !== null) {
  605             // We deal with groups in the next pass
  606             lines.push(null);
  607             return;
  608         }
  609         var type = optionTypes[o.type];
  610         var arg = o.helpArg || type.helpArg || 'ARG';
  611         var line = '';
  612         var names = o.names.slice();
  613         if (nameSort === 'length') {
  614             names.sort(function (a, b) {
  615                 if (a.length < b.length)
  616                     return -1;
  617                 else if (b.length < a.length)
  618                     return 1;
  619                 else
  620                     return 0;
  621             })
  622         }
  623         names.forEach(function (name, i) {
  624             if (i > 0)
  625                 line += ', ';
  626             if (name.length === 1) {
  627                 line += '-' + name
  628                 if (type.takesArg)
  629                     line += ' ' + arg;
  630             } else {
  631                 line += '--' + name
  632                 if (type.takesArg)
  633                     line += '=' + arg;
  634             }
  635         });
  636         maxWidth = Math.max(maxWidth, line.length);
  637         lines.push(line);
  638     });
  639 
  640     // Add help strings.
  641     var helpCol = config.helpCol;
  642     if (!helpCol) {
  643         helpCol = maxWidth + indent.length + 2;
  644         helpCol = Math.min(Math.max(helpCol, minHelpCol), maxHelpCol);
  645     }
  646     var i = -1;
  647     this.options.forEach(function (o) {
  648         if (o.hidden) {
  649             return;
  650         }
  651         i++;
  652 
  653         if (o.group !== undefined && o.group !== null) {
  654             if (o.group === '') {
  655                 // Support a empty string "group" to have a blank line between
  656                 // sets of options.
  657                 lines[i] = '';
  658             } else {
  659                 // Render the group heading with the heading-specific indent.
  660                 lines[i] = (i === 0 ? '' : '\n') + headingIndent +
  661                     o.group + ':';
  662             }
  663             return;
  664         }
  665 
  666         var helpDefault;
  667         if (config.includeDefault) {
  668             if (o.default !== undefined) {
  669                 helpDefault = format('Default: %j', o.default);
  670             } else if (o.type && optionTypes[o.type].default !== undefined) {
  671                 helpDefault = format('Default: %j',
  672                     optionTypes[o.type].default);
  673             }
  674         }
  675 
  676         var line = lines[i] = indent + lines[i];
  677         if (!o.help && !(config.includeEnv && o.env) && !helpDefault) {
  678             return;
  679         }
  680         var n = helpCol - line.length;
  681         if (n >= 0) {
  682             line += space(n);
  683         } else {
  684             line += '\n' + space(helpCol);
  685         }
  686 
  687         var helpEnv = '';
  688         if (o.env && o.env.length && config.includeEnv) {
  689             helpEnv += 'Environment: ';
  690             var type = optionTypes[o.type];
  691             var arg = o.helpArg || type.helpArg || 'ARG';
  692             var envs = (Array.isArray(o.env) ? o.env : [o.env]).map(
  693                 function (e) {
  694                     if (type.takesArg) {
  695                         return e + '=' + arg;
  696                     } else {
  697                         return e + '=1';
  698                     }
  699                 }
  700             );
  701             helpEnv += envs.join(', ');
  702         }
  703         var help = (o.help || '').trim();
  704         if (o.helpWrap !== false && config.helpWrap !== false) {
  705             // Wrap help description normally.
  706             if (help.length && !~'.!?"\''.indexOf(help.slice(-1))) {
  707                 help += '.';
  708             }
  709             if (help.length) {
  710                 help += ' ';
  711             }
  712             help += helpEnv;
  713             if (helpDefault) {
  714                 if (helpEnv) {
  715                     help += '. ';
  716                 }
  717                 help += helpDefault;
  718             }
  719             line += textwrap(help, maxCol - helpCol).join(
  720                 '\n' + space(helpCol));
  721         } else {
  722             // Do not wrap help description, but indent newlines appropriately.
  723             var helpLines = help.split('\n').filter(
  724                     function (ln) { return ln.length });
  725             if (helpEnv !== '') {
  726                 helpLines.push(helpEnv);
  727             }
  728             if (helpDefault) {
  729                 helpLines.push(helpDefault);
  730             }
  731             line += helpLines.join('\n' + space(helpCol));
  732         }
  733 
  734         lines[i] = line;
  735     });
  736 
  737     var rv = '';
  738     if (lines.length > 0) {
  739         rv = lines.join('\n') + '\n';
  740     }
  741     return rv;
  742 };
  743 
  744 
  745 /**
  746  * Return a string suitable for a Bash completion file for this tool.
  747  *
  748  * @param args.name {String} The tool name.
  749  * @param args.specExtra {String} Optional. Extra Bash code content to add
  750  *      to the end of the "spec". Typically this is used to append Bash
  751  *      "complete_TYPE" functions for custom option types. See
  752  *      "examples/ddcompletion.js" for an example.
  753  * @param args.argtypes {Array} Optional. Array of completion types for
  754  *      positional args (i.e. non-options). E.g.
  755  *          argtypes = ['fruit', 'veggie', 'file']
  756  *      will result in completion of fruits for the first arg, veggies for the
  757  *      second, and filenames for the third and subsequent positional args.
  758  *      If not given, positional args will use Bash's 'default' completion.
  759  *      See `specExtra` for providing Bash `complete_TYPE` functions, e.g.
  760  *      `complete_fruit` and `complete_veggie` in this example.
  761  */
  762 Parser.prototype.bashCompletion = function bashCompletion(args) {
  763     assert.object(args, 'args');
  764     assert.string(args.name, 'args.name');
  765     assert.optionalString(args.specExtra, 'args.specExtra');
  766     assert.optionalArrayOfString(args.argtypes, 'args.argtypes');
  767 
  768     return bashCompletionFromOptions({
  769         name: args.name,
  770         specExtra: args.specExtra,
  771         argtypes: args.argtypes,
  772         options: this.options
  773     });
  774 };
  775 
  776 
  777 // ---- Bash completion
  778 
  779 const BASH_COMPLETION_TEMPLATE_PATH = path.join(
  780     __dirname, '../etc/dashdash.bash_completion.in');
  781 
  782 /**
  783  * Return the Bash completion "spec" (the string value for the "{{spec}}"
  784  * var in the "dashdash.bash_completion.in" template) for this tool.
  785  *
  786  * The "spec" is Bash code that defines the CLI options and subcmds for
  787  * the template's completion code. It looks something like this:
  788  *
  789  *      local cmd_shortopts="-J ..."
  790  *      local cmd_longopts="--help ..."
  791  *      local cmd_optargs="-p=tritonprofile ..."
  792  *
  793  * @param args.options {Array} The array of dashdash option specs.
  794  * @param args.context {String} Optional. A context string for the "local cmd*"
  795  *      vars in the spec. By default it is the empty string. When used to
  796  *      scope for completion on a *sub-command* (e.g. for "git log" on a "git"
  797  *      tool), then it would have a value (e.g. "__log"). See
  798  *      <http://github.com/trentm/node-cmdln> Bash completion for details.
  799  * @param opts.includeHidden {Boolean} Optional. Default false. By default
  800  *      hidden options and subcmds are "excluded". Here excluded means they
  801  *      won't be offered as a completion, but if used, their argument type
  802  *      will be completed. "Hidden" options and subcmds are ones with the
  803  *      `hidden: true` attribute to exclude them from default help output.
  804  * @param args.argtypes {Array} Optional. Array of completion types for
  805  *      positional args (i.e. non-options). E.g.
  806  *          argtypes = ['fruit', 'veggie', 'file']
  807  *      will result in completion of fruits for the first arg, veggies for the
  808  *      second, and filenames for the third and subsequent positional args.
  809  *      If not given, positional args will use Bash's 'default' completion.
  810  *      See `specExtra` for providing Bash `complete_TYPE` functions, e.g.
  811  *      `complete_fruit` and `complete_veggie` in this example.
  812  */
  813 function bashCompletionSpecFromOptions(args) {
  814     assert.object(args, 'args');
  815     assert.object(args.options, 'args.options');
  816     assert.optionalString(args.context, 'args.context');
  817     assert.optionalBool(args.includeHidden, 'args.includeHidden');
  818     assert.optionalArrayOfString(args.argtypes, 'args.argtypes');
  819 
  820     var context = args.context || '';
  821     var includeHidden = (args.includeHidden === undefined
  822         ? false : args.includeHidden);
  823 
  824     var spec = [];
  825     var shortopts = [];
  826     var longopts = [];
  827     var optargs = [];
  828     (args.options || []).forEach(function (o) {
  829         if (o.group !== undefined && o.group !== null) {
  830             // Skip group headers.
  831             return;
  832         }
  833 
  834         var optNames = o.names || [o.name];
  835         var optType = getOptionType(o.type);
  836         if (optType.takesArg) {
  837             var completionType = o.completionType ||
  838                 optType.completionType || o.type;
  839             optNames.forEach(function (optName) {
  840                 if (optName.length === 1) {
  841                     if (includeHidden || !o.hidden) {
  842                         shortopts.push('-' + optName);
  843                     }
  844                     // Include even hidden options in `optargs` so that bash
  845                     // completion of its arg still works.
  846                     optargs.push('-' + optName + '=' + completionType);
  847                 } else {
  848                     if (includeHidden || !o.hidden) {
  849                         longopts.push('--' + optName);
  850                     }
  851                     optargs.push('--' + optName + '=' + completionType);
  852                 }
  853             });
  854         } else {
  855             optNames.forEach(function (optName) {
  856                 if (includeHidden || !o.hidden) {
  857                     if (optName.length === 1) {
  858                         shortopts.push('-' + optName);
  859                     } else {
  860                         longopts.push('--' + optName);
  861                     }
  862                 }
  863             });
  864         }
  865     });
  866 
  867     spec.push(format('local cmd%s_shortopts="%s"',
  868         context, shortopts.sort().join(' ')));
  869     spec.push(format('local cmd%s_longopts="%s"',
  870         context, longopts.sort().join(' ')));
  871     spec.push(format('local cmd%s_optargs="%s"',
  872         context, optargs.sort().join(' ')));
  873     if (args.argtypes) {
  874         spec.push(format('local cmd%s_argtypes="%s"',
  875             context, args.argtypes.join(' ')));
  876     }
  877     return spec.join('\n');
  878 }
  879 
  880 
  881 /**
  882  * Return a string suitable for a Bash completion file for this tool.
  883  *
  884  * @param args.name {String} The tool name.
  885  * @param args.options {Array} The array of dashdash option specs.
  886  * @param args.specExtra {String} Optional. Extra Bash code content to add
  887  *      to the end of the "spec". Typically this is used to append Bash
  888  *      "complete_TYPE" functions for custom option types. See
  889  *      "examples/ddcompletion.js" for an example.
  890  * @param args.argtypes {Array} Optional. Array of completion types for
  891  *      positional args (i.e. non-options). E.g.
  892  *          argtypes = ['fruit', 'veggie', 'file']
  893  *      will result in completion of fruits for the first arg, veggies for the
  894  *      second, and filenames for the third and subsequent positional args.
  895  *      If not given, positional args will use Bash's 'default' completion.
  896  *      See `specExtra` for providing Bash `complete_TYPE` functions, e.g.
  897  *      `complete_fruit` and `complete_veggie` in this example.
  898  */
  899 function bashCompletionFromOptions(args) {
  900     assert.object(args, 'args');
  901     assert.object(args.options, 'args.options');
  902     assert.string(args.name, 'args.name');
  903     assert.optionalString(args.specExtra, 'args.specExtra');
  904     assert.optionalArrayOfString(args.argtypes, 'args.argtypes');
  905 
  906     // Gather template data.
  907     var data = {
  908         name: args.name,
  909         date: new Date(),
  910         spec: bashCompletionSpecFromOptions({
  911             options: args.options,
  912             argtypes: args.argtypes
  913         }),
  914     };
  915     if (args.specExtra) {
  916         data.spec += '\n\n' + args.specExtra;
  917     }
  918 
  919     // Render template.
  920     var template = fs.readFileSync(BASH_COMPLETION_TEMPLATE_PATH, 'utf8');
  921     return renderTemplate(template, data);
  922 }
  923 
  924 
  925 
  926 // ---- exports
  927 
  928 function createParser(config) {
  929     return new Parser(config);
  930 }
  931 
  932 /**
  933  * Parse argv with the given options.
  934  *
  935  * @param config {Object} A merge of all the available fields from
  936  *      `dashdash.Parser` and `dashdash.Parser.parse`: options, interspersed,
  937  *      argv, env, slice.
  938  */
  939 function parse(config) {
  940     assert.object(config, 'config');
  941     assert.optionalArrayOfString(config.argv, 'config.argv');
  942     assert.optionalObject(config.env, 'config.env');
  943     var config = shallowCopy(config);
  944     var argv = config.argv;
  945     delete config.argv;
  946     var env = config.env;
  947     delete config.env;
  948 
  949     var parser = new Parser(config);
  950     return parser.parse({argv: argv, env: env});
  951 }
  952 
  953 
  954 /**
  955  * Add a new option type.
  956  *
  957  * @params optionType {Object}:
  958  *      - name {String} Required.
  959  *      - takesArg {Boolean} Required. Whether this type of option takes an
  960  *        argument on process.argv. Typically this is true for all but the
  961  *        "bool" type.
  962  *      - helpArg {String} Required iff `takesArg === true`. The string to
  963  *        show in generated help for options of this type.
  964  *      - parseArg {Function} Require. `function (option, optstr, arg)` parser
  965  *        that takes a string argument and returns an instance of the
  966  *        appropriate type, or throws an error if the arg is invalid.
  967  *      - array {Boolean} Optional. Set to true if this is an 'arrayOf' type
  968  *        that collects multiple usages of the option in process.argv and
  969  *        puts results in an array.
  970  *      - arrayFlatten {Boolean} Optional. XXX
  971  *      - default Optional. Default value for options of this type, if no
  972  *        default is specified in the option type usage.
  973  */
  974 function addOptionType(optionType) {
  975     assert.object(optionType, 'optionType');
  976     assert.string(optionType.name, 'optionType.name');
  977     assert.bool(optionType.takesArg, 'optionType.takesArg');
  978     if (optionType.takesArg) {
  979         assert.string(optionType.helpArg, 'optionType.helpArg');
  980     }
  981     assert.func(optionType.parseArg, 'optionType.parseArg');
  982     assert.optionalBool(optionType.array, 'optionType.array');
  983     assert.optionalBool(optionType.arrayFlatten, 'optionType.arrayFlatten');
  984 
  985     optionTypes[optionType.name] = {
  986         takesArg: optionType.takesArg,
  987         helpArg: optionType.helpArg,
  988         parseArg: optionType.parseArg,
  989         array: optionType.array,
  990         arrayFlatten: optionType.arrayFlatten,
  991         default: optionType.default
  992     }
  993 }
  994 
  995 
  996 function getOptionType(name) {
  997     assert.string(name, 'name');
  998     return optionTypes[name];
  999 }
 1000 
 1001 
 1002 /**
 1003  * Return a synopsis string for the given option spec.
 1004  *
 1005  * Examples:
 1006  *      > synopsisFromOpt({names: ['help', 'h'], type: 'bool'});
 1007  *      '[ --help | -h ]'
 1008  *      > synopsisFromOpt({name: 'file', type: 'string', helpArg: 'FILE'});
 1009  *      '[ --file=FILE ]'
 1010  */
 1011 function synopsisFromOpt(o) {
 1012     assert.object(o, 'o');
 1013 
 1014     if (o.hasOwnProperty('group')) {
 1015         return null;
 1016     }
 1017     var names = o.names || [o.name];
 1018     // `type` here could be undefined if, for example, the command has a
 1019     // dashdash option spec with a bogus 'type'.
 1020     var type = getOptionType(o.type);
 1021     var helpArg = o.helpArg || (type && type.helpArg) || 'ARG';
 1022     var parts = [];
 1023     names.forEach(function (name) {
 1024         var part = (name.length === 1 ? '-' : '--') + name;
 1025         if (type && type.takesArg) {
 1026             part += (name.length === 1 ? ' ' + helpArg : '=' + helpArg);
 1027         }
 1028         parts.push(part);
 1029     });
 1030     return ('[ ' + parts.join(' | ') + ' ]');
 1031 };
 1032 
 1033 
 1034 module.exports = {
 1035     createParser: createParser,
 1036     Parser: Parser,
 1037     parse: parse,
 1038     addOptionType: addOptionType,
 1039     getOptionType: getOptionType,
 1040     synopsisFromOpt: synopsisFromOpt,
 1041 
 1042     // Bash completion-related exports
 1043     BASH_COMPLETION_TEMPLATE_PATH: BASH_COMPLETION_TEMPLATE_PATH,
 1044     bashCompletionFromOptions: bashCompletionFromOptions,
 1045     bashCompletionSpecFromOptions: bashCompletionSpecFromOptions,
 1046 
 1047     // Export the parseFoo parsers because they might be useful as primitives
 1048     // for custom option types.
 1049     parseBool: parseBool,
 1050     parseString: parseString,
 1051     parseNumber: parseNumber,
 1052     parseInteger: parseInteger,
 1053     parsePositiveInteger: parsePositiveInteger,
 1054     parseDate: parseDate
 1055 };