tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

argparse.js (3676B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // Command-line argument parser, modeled after but not identical to Python's
      6 // argparse.
      7 
      8 var ArgParser = class {
      9  constructor(desc) {
     10    this._params = [];
     11    this._doc = desc;
     12 
     13    this.add_argument("--help", {
     14      help: "display this help message",
     15    });
     16  }
     17 
     18  // name is '--foo', '-f', or an array of aliases.
     19  //
     20  // spec is an options object with keys:
     21  //   dest: key name to store the result in (optional for long options)
     22  //   default: value to use if not passed on command line (optional)
     23  //   help: description of the option to show in --help
     24  //   options: array of valid choices
     25  //
     26  // Prefixes of long option names are allowed. If a prefix is ambiguous, it
     27  // will match the first parameter added to the ArgParser.
     28  add_argument(name, spec) {
     29    const names = Array.isArray(name) ? name : [name];
     30 
     31    spec = Object.assign({}, spec);
     32    spec.aliases = names;
     33    for (const name of names) {
     34      if (!name.startsWith("-")) {
     35        throw new Error(`unhandled argument syntax '${name}'`);
     36      }
     37      if (name.startsWith("--")) {
     38        spec.dest = spec.dest || name.substr(2);
     39      }
     40      this._params.push({ name, spec });
     41    }
     42  }
     43 
     44  parse_args(args) {
     45    const opts = {};
     46    const rest = [];
     47 
     48    for (const { spec } of this._params) {
     49      if (spec.default !== undefined) {
     50        opts[spec.dest] = spec.default;
     51      }
     52    }
     53 
     54    const seen = new Set();
     55    for (let i = 0; i < args.length; i++) {
     56      const arg = args[i];
     57      if (!arg.startsWith("-")) {
     58        rest.push(arg);
     59        continue;
     60      } else if (arg === "--") {
     61        rest.push(args.slice(i+1));
     62        break;
     63      }
     64 
     65      if (arg == "--help" || arg == "-h") {
     66        this.help();
     67      }
     68 
     69      let parameter;
     70      let [passed, value] = arg.split("=");
     71      for (const { name, spec } of this._params) {
     72        if (passed.startsWith("--")) {
     73          if (name.startsWith(passed)) {
     74            parameter = spec;
     75          }
     76        } else if (passed.startsWith("-") && passed === name) {
     77          parameter = spec;
     78        }
     79        if (parameter) {
     80          if (value === undefined) {
     81            value = args[++i];
     82          }
     83          opts[parameter.dest] = value;
     84          break;
     85        }
     86      }
     87 
     88      if (parameter) {
     89        if (seen.has(parameter)) {
     90          throw new Error(`${parameter.aliases[0]} given multiple times`);
     91        }
     92        seen.add(parameter);
     93      } else {
     94        throw new Error(`invalid command-line argument '${arg}'`);
     95      }
     96    }
     97 
     98    for (const { name, spec } of this._params) {
     99      if (spec.options && !spec.options.includes(opts[spec.dest])) {
    100        throw new Error(`invalid ${name} value '${opts[spec.dest]}'`);
    101        opts[spec.dest] = spec.default;
    102      }
    103    }
    104 
    105    return { opts, rest };
    106  }
    107 
    108  help() {
    109    print(`Usage: ${this._doc}`);
    110    const specs = new Set(this._params.map(p => p.spec));
    111    const optstrs = [...specs].map(p => p.aliases.join(", "));
    112    let maxlen = Math.max(...optstrs.map(s => s.length));
    113    for (const spec of specs) {
    114      const name = spec.aliases[0];
    115      let helptext = spec.help ?? "undocumented";
    116      if ("options" in spec) {
    117        helptext += ` (one of ${spec.options.map(x => `'${x}'`).join(", ")})`;
    118      }
    119      if ("default" in spec) {
    120        helptext += ` (default '${spec.default}')`;
    121      }
    122      const optstr = spec.aliases.join(", ");
    123      print(`  ${optstr.padEnd(maxlen)}  ${helptext}`);
    124    }
    125    quit(0);
    126  }
    127 };