3952 lines
125 KiB
JavaScript
3952 lines
125 KiB
JavaScript
// Port of python's argparse module, version 3.9.0:
|
|
// https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py
|
|
|
|
'use strict';
|
|
|
|
// Copyright (C) 2010-2020 Python Software Foundation.
|
|
// Copyright (C) 2020 argparse.js authors
|
|
|
|
/*
|
|
* Command-line parsing library
|
|
*
|
|
* This module is an optparse-inspired command-line parsing library that:
|
|
*
|
|
* - handles both optional and positional arguments
|
|
* - produces highly informative usage messages
|
|
* - supports parsers that dispatch to sub-parsers
|
|
*
|
|
* The following is a simple usage example that sums integers from the
|
|
* command-line and writes the result to a file::
|
|
*
|
|
* parser = argparse.ArgumentParser(
|
|
* description='sum the integers at the command line')
|
|
* parser.add_argument(
|
|
* 'integers', metavar='int', nargs='+', type=int,
|
|
* help='an integer to be summed')
|
|
* parser.add_argument(
|
|
* '--log', default=sys.stdout, type=argparse.FileType('w'),
|
|
* help='the file where the sum should be written')
|
|
* args = parser.parse_args()
|
|
* args.log.write('%s' % sum(args.integers))
|
|
* args.log.close()
|
|
*
|
|
* The module contains the following public classes:
|
|
*
|
|
* - ArgumentParser -- The main entry point for command-line parsing. As the
|
|
* example above shows, the add_argument() method is used to populate
|
|
* the parser with actions for optional and positional arguments. Then
|
|
* the parse_args() method is invoked to convert the args at the
|
|
* command-line into an object with attributes.
|
|
*
|
|
* - ArgumentError -- The exception raised by ArgumentParser objects when
|
|
* there are errors with the parser's actions. Errors raised while
|
|
* parsing the command-line are caught by ArgumentParser and emitted
|
|
* as command-line messages.
|
|
*
|
|
* - FileType -- A factory for defining types of files to be created. As the
|
|
* example above shows, instances of FileType are typically passed as
|
|
* the type= argument of add_argument() calls.
|
|
*
|
|
* - Action -- The base class for parser actions. Typically actions are
|
|
* selected by passing strings like 'store_true' or 'append_const' to
|
|
* the action= argument of add_argument(). However, for greater
|
|
* customization of ArgumentParser actions, subclasses of Action may
|
|
* be defined and passed as the action= argument.
|
|
*
|
|
* - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
|
|
* ArgumentDefaultsHelpFormatter -- Formatter classes which
|
|
* may be passed as the formatter_class= argument to the
|
|
* ArgumentParser constructor. HelpFormatter is the default,
|
|
* RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
|
|
* not to change the formatting for help text, and
|
|
* ArgumentDefaultsHelpFormatter adds information about argument defaults
|
|
* to the help.
|
|
*
|
|
* All other classes in this module are considered implementation details.
|
|
* (Also note that HelpFormatter and RawDescriptionHelpFormatter are only
|
|
* considered public as object names -- the API of the formatter objects is
|
|
* still considered an implementation detail.)
|
|
*/
|
|
|
|
const SUPPRESS = '==SUPPRESS==';
|
|
|
|
const OPTIONAL = '?';
|
|
const ZERO_OR_MORE = '*';
|
|
const ONE_OR_MORE = '+';
|
|
const PARSER = 'A...';
|
|
const REMAINDER = '...';
|
|
const _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args';
|
|
|
|
// ==================================
|
|
// Utility functions used for porting
|
|
// ==================================
|
|
const assert = require('assert');
|
|
const util = require('util');
|
|
const fs = require('fs');
|
|
const sub = require('./lib/sub');
|
|
const path = require('path');
|
|
const repr = util.inspect;
|
|
|
|
function get_argv() {
|
|
// omit first argument (which is assumed to be interpreter - `node`, `coffee`, `ts-node`, etc.)
|
|
return process.argv.slice(1);
|
|
}
|
|
|
|
function get_terminal_size() {
|
|
return {
|
|
columns: +process.env.COLUMNS || process.stdout.columns || 80,
|
|
};
|
|
}
|
|
|
|
function hasattr(object, name) {
|
|
return Object.prototype.hasOwnProperty.call(object, name);
|
|
}
|
|
|
|
function getattr(object, name, value) {
|
|
return hasattr(object, name) ? object[name] : value;
|
|
}
|
|
|
|
function setattr(object, name, value) {
|
|
object[name] = value;
|
|
}
|
|
|
|
function setdefault(object, name, value) {
|
|
if (!hasattr(object, name)) object[name] = value;
|
|
return object[name];
|
|
}
|
|
|
|
function delattr(object, name) {
|
|
delete object[name];
|
|
}
|
|
|
|
function range(from, to, step = 1) {
|
|
// range(10) is equivalent to range(0, 10)
|
|
if (arguments.length === 1) [to, from] = [from, 0];
|
|
if (
|
|
typeof from !== 'number' ||
|
|
typeof to !== 'number' ||
|
|
typeof step !== 'number'
|
|
) {
|
|
throw new TypeError('argument cannot be interpreted as an integer');
|
|
}
|
|
if (step === 0) throw new TypeError('range() arg 3 must not be zero');
|
|
|
|
let result = [];
|
|
if (step > 0) {
|
|
for (let i = from; i < to; i += step) result.push(i);
|
|
} else {
|
|
for (let i = from; i > to; i += step) result.push(i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function splitlines(str, keepends = false) {
|
|
let result;
|
|
if (!keepends) {
|
|
result = str.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/);
|
|
} else {
|
|
result = [];
|
|
let parts = str.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/);
|
|
for (let i = 0; i < parts.length; i += 2) {
|
|
result.push(parts[i] + (i + 1 < parts.length ? parts[i + 1] : ''));
|
|
}
|
|
}
|
|
if (!result[result.length - 1]) result.pop();
|
|
return result;
|
|
}
|
|
|
|
function _string_lstrip(string, prefix_chars) {
|
|
let idx = 0;
|
|
while (idx < string.length && prefix_chars.includes(string[idx])) idx++;
|
|
return idx ? string.slice(idx) : string;
|
|
}
|
|
|
|
function _string_split(string, sep, maxsplit) {
|
|
let result = string.split(sep);
|
|
if (result.length > maxsplit) {
|
|
result = result
|
|
.slice(0, maxsplit)
|
|
.concat([result.slice(maxsplit).join(sep)]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function _array_equal(array1, array2) {
|
|
if (array1.length !== array2.length) return false;
|
|
for (let i = 0; i < array1.length; i++) {
|
|
if (array1[i] !== array2[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function _array_remove(array, item) {
|
|
let idx = array.indexOf(item);
|
|
if (idx === -1) throw new TypeError(sub('%r not in list', item));
|
|
array.splice(idx, 1);
|
|
}
|
|
|
|
// normalize choices to array;
|
|
// this isn't required in python because `in` and `map` operators work with anything,
|
|
// but in js dealing with multiple types here is too clunky
|
|
function _choices_to_array(choices) {
|
|
if (choices === undefined) {
|
|
return [];
|
|
} else if (Array.isArray(choices)) {
|
|
return choices;
|
|
} else if (
|
|
choices !== null &&
|
|
typeof choices[Symbol.iterator] === 'function'
|
|
) {
|
|
return Array.from(choices);
|
|
} else if (typeof choices === 'object' && choices !== null) {
|
|
return Object.keys(choices);
|
|
} else {
|
|
throw new Error(sub('invalid choices value: %r', choices));
|
|
}
|
|
}
|
|
|
|
// decorator that allows a class to be called without new
|
|
function _callable(cls) {
|
|
let result = {
|
|
// object is needed for inferred class name
|
|
[cls.name]: function (...args) {
|
|
let this_class = new.target === result || !new.target;
|
|
return Reflect.construct(cls, args, this_class ? cls : new.target);
|
|
},
|
|
};
|
|
result[cls.name].prototype = cls.prototype;
|
|
// fix default tag for toString, e.g. [object Action] instead of [object Object]
|
|
cls.prototype[Symbol.toStringTag] = cls.name;
|
|
return result[cls.name];
|
|
}
|
|
|
|
function _alias(object, from, to) {
|
|
try {
|
|
let name = object.constructor.name;
|
|
Object.defineProperty(object, from, {
|
|
value: util.deprecate(
|
|
object[to],
|
|
sub('%s.%s() is renamed to %s.%s()', name, from, name, to)
|
|
),
|
|
enumerable: false,
|
|
});
|
|
} catch {}
|
|
}
|
|
|
|
// decorator that allows snake_case class methods to be called with camelCase and vice versa
|
|
function _camelcase_alias(_class) {
|
|
for (let name of Object.getOwnPropertyNames(_class.prototype)) {
|
|
let camelcase = name.replace(/\w_[a-z]/g, (s) => s[0] + s[2].toUpperCase());
|
|
if (camelcase !== name) _alias(_class.prototype, camelcase, name);
|
|
}
|
|
return _class;
|
|
}
|
|
|
|
function _to_legacy_name(key) {
|
|
key = key.replace(/\w_[a-z]/g, (s) => s[0] + s[2].toUpperCase());
|
|
if (key === 'default') key = 'defaultValue';
|
|
if (key === 'const') key = 'constant';
|
|
return key;
|
|
}
|
|
|
|
function _to_new_name(key) {
|
|
if (key === 'defaultValue') key = 'default';
|
|
if (key === 'constant') key = 'const';
|
|
key = key.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase());
|
|
return key;
|
|
}
|
|
|
|
// parse options
|
|
let no_default = Symbol('no_default_value');
|
|
function _parse_opts(args, descriptor) {
|
|
function get_name() {
|
|
let stack = new Error().stack
|
|
.split('\n')
|
|
.map((x) => x.match(/^ at (.*) \(.*\)$/))
|
|
.filter(Boolean)
|
|
.map((m) => m[1])
|
|
.map((fn) => fn.match(/[^ .]*$/)[0]);
|
|
|
|
if (stack.length && stack[0] === get_name.name) stack.shift();
|
|
if (stack.length && stack[0] === _parse_opts.name) stack.shift();
|
|
return stack.length ? stack[0] : '';
|
|
}
|
|
|
|
args = Array.from(args);
|
|
let kwargs = {};
|
|
let result = [];
|
|
let last_opt = args.length && args[args.length - 1];
|
|
|
|
if (
|
|
typeof last_opt === 'object' &&
|
|
last_opt !== null &&
|
|
!Array.isArray(last_opt) &&
|
|
(!last_opt.constructor || last_opt.constructor.name === 'Object')
|
|
) {
|
|
kwargs = Object.assign({}, args.pop());
|
|
}
|
|
|
|
// LEGACY (v1 compatibility): camelcase
|
|
let renames = [];
|
|
for (let key of Object.keys(descriptor)) {
|
|
let old_name = _to_legacy_name(key);
|
|
if (old_name !== key && old_name in kwargs) {
|
|
if (key in kwargs) {
|
|
// default and defaultValue specified at the same time, happens often in old tests
|
|
//throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key))
|
|
} else {
|
|
kwargs[key] = kwargs[old_name];
|
|
}
|
|
renames.push([old_name, key]);
|
|
delete kwargs[old_name];
|
|
}
|
|
}
|
|
if (renames.length) {
|
|
let name = get_name();
|
|
deprecate(
|
|
'camelcase_' + name,
|
|
sub(
|
|
'%s(): following options are renamed: %s',
|
|
name,
|
|
renames.map(([a, b]) => sub('%r -> %r', a, b))
|
|
)
|
|
);
|
|
}
|
|
// end
|
|
|
|
let missing_positionals = [];
|
|
let positional_count = args.length;
|
|
|
|
for (let [key, def] of Object.entries(descriptor)) {
|
|
if (key[0] === '*') {
|
|
if (key.length > 0 && key[1] === '*') {
|
|
// LEGACY (v1 compatibility): camelcase
|
|
let renames = [];
|
|
for (let key of Object.keys(kwargs)) {
|
|
let new_name = _to_new_name(key);
|
|
if (new_name !== key && key in kwargs) {
|
|
if (new_name in kwargs) {
|
|
// default and defaultValue specified at the same time, happens often in old tests
|
|
//throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), new_name))
|
|
} else {
|
|
kwargs[new_name] = kwargs[key];
|
|
}
|
|
renames.push([key, new_name]);
|
|
delete kwargs[key];
|
|
}
|
|
}
|
|
if (renames.length) {
|
|
let name = get_name();
|
|
deprecate(
|
|
'camelcase_' + name,
|
|
sub(
|
|
'%s(): following options are renamed: %s',
|
|
name,
|
|
renames.map(([a, b]) => sub('%r -> %r', a, b))
|
|
)
|
|
);
|
|
}
|
|
// end
|
|
result.push(kwargs);
|
|
kwargs = {};
|
|
} else {
|
|
result.push(args);
|
|
args = [];
|
|
}
|
|
} else if (key in kwargs && args.length > 0) {
|
|
throw new TypeError(
|
|
sub('%s() got multiple values for argument %r', get_name(), key)
|
|
);
|
|
} else if (key in kwargs) {
|
|
result.push(kwargs[key]);
|
|
delete kwargs[key];
|
|
} else if (args.length > 0) {
|
|
result.push(args.shift());
|
|
} else if (def !== no_default) {
|
|
result.push(def);
|
|
} else {
|
|
missing_positionals.push(key);
|
|
}
|
|
}
|
|
|
|
if (Object.keys(kwargs).length) {
|
|
throw new TypeError(
|
|
sub(
|
|
'%s() got an unexpected keyword argument %r',
|
|
get_name(),
|
|
Object.keys(kwargs)[0]
|
|
)
|
|
);
|
|
}
|
|
|
|
if (args.length) {
|
|
let from = Object.entries(descriptor).filter(
|
|
([k, v]) => k[0] !== '*' && v !== no_default
|
|
).length;
|
|
let to = Object.entries(descriptor).filter(([k]) => k[0] !== '*').length;
|
|
throw new TypeError(
|
|
sub(
|
|
'%s() takes %s positional argument%s but %s %s given',
|
|
get_name(),
|
|
from === to ? sub('from %s to %s', from, to) : to,
|
|
from === to && to === 1 ? '' : 's',
|
|
positional_count,
|
|
positional_count === 1 ? 'was' : 'were'
|
|
)
|
|
);
|
|
}
|
|
|
|
if (missing_positionals.length) {
|
|
let strs = missing_positionals.map(repr);
|
|
if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1];
|
|
let str_joined = strs.join(strs.length === 2 ? '' : ', ');
|
|
throw new TypeError(
|
|
sub(
|
|
'%s() missing %i required positional argument%s: %s',
|
|
get_name(),
|
|
strs.length,
|
|
strs.length === 1 ? '' : 's',
|
|
str_joined
|
|
)
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
let _deprecations = {};
|
|
function deprecate(id, string) {
|
|
_deprecations[id] = _deprecations[id] || util.deprecate(() => {}, string);
|
|
_deprecations[id]();
|
|
}
|
|
|
|
// =============================
|
|
// Utility functions and classes
|
|
// =============================
|
|
function _AttributeHolder(cls = Object) {
|
|
/*
|
|
* Abstract base class that provides __repr__.
|
|
*
|
|
* The __repr__ method returns a string in the format::
|
|
* ClassName(attr=name, attr=name, ...)
|
|
* The attributes are determined either by a class-level attribute,
|
|
* '_kwarg_names', or by inspecting the instance __dict__.
|
|
*/
|
|
|
|
return class _AttributeHolder extends cls {
|
|
[util.inspect.custom]() {
|
|
let type_name = this.constructor.name;
|
|
let arg_strings = [];
|
|
let star_args = {};
|
|
for (let arg of this._get_args()) {
|
|
arg_strings.push(repr(arg));
|
|
}
|
|
for (let [name, value] of this._get_kwargs()) {
|
|
if (/^[a-z_][a-z0-9_$]*$/i.test(name)) {
|
|
arg_strings.push(sub('%s=%r', name, value));
|
|
} else {
|
|
star_args[name] = value;
|
|
}
|
|
}
|
|
if (Object.keys(star_args).length) {
|
|
arg_strings.push(sub('**%s', repr(star_args)));
|
|
}
|
|
return sub('%s(%s)', type_name, arg_strings.join(', '));
|
|
}
|
|
|
|
toString() {
|
|
return this[util.inspect.custom]();
|
|
}
|
|
|
|
_get_kwargs() {
|
|
return Object.entries(this);
|
|
}
|
|
|
|
_get_args() {
|
|
return [];
|
|
}
|
|
};
|
|
}
|
|
|
|
function _copy_items(items) {
|
|
if (items === undefined) {
|
|
return [];
|
|
}
|
|
return items.slice(0);
|
|
}
|
|
|
|
// ===============
|
|
// Formatting Help
|
|
// ===============
|
|
const HelpFormatter = _camelcase_alias(
|
|
_callable(
|
|
class HelpFormatter {
|
|
/*
|
|
* Formatter for generating usage messages and argument help strings.
|
|
*
|
|
* Only the name of this class is considered a public API. All the methods
|
|
* provided by the class are considered an implementation detail.
|
|
*/
|
|
|
|
constructor() {
|
|
let [prog, indent_increment, max_help_position, width] = _parse_opts(
|
|
arguments,
|
|
{
|
|
prog: no_default,
|
|
indent_increment: 2,
|
|
max_help_position: 24,
|
|
width: undefined,
|
|
}
|
|
);
|
|
|
|
// default setting for width
|
|
if (width === undefined) {
|
|
width = get_terminal_size().columns;
|
|
width -= 2;
|
|
}
|
|
|
|
this._prog = prog;
|
|
this._indent_increment = indent_increment;
|
|
this._max_help_position = Math.min(
|
|
max_help_position,
|
|
Math.max(width - 20, indent_increment * 2)
|
|
);
|
|
this._width = width;
|
|
|
|
this._current_indent = 0;
|
|
this._level = 0;
|
|
this._action_max_length = 0;
|
|
|
|
this._root_section = this._Section(this, undefined);
|
|
this._current_section = this._root_section;
|
|
|
|
this._whitespace_matcher = /[ \t\n\r\f\v]+/g; // equivalent to python /\s+/ with ASCII flag
|
|
this._long_break_matcher = /\n\n\n+/g;
|
|
}
|
|
|
|
// ===============================
|
|
// Section and indentation methods
|
|
// ===============================
|
|
_indent() {
|
|
this._current_indent += this._indent_increment;
|
|
this._level += 1;
|
|
}
|
|
|
|
_dedent() {
|
|
this._current_indent -= this._indent_increment;
|
|
assert(this._current_indent >= 0, 'Indent decreased below 0.');
|
|
this._level -= 1;
|
|
}
|
|
|
|
_add_item(func, args) {
|
|
this._current_section.items.push([func, args]);
|
|
}
|
|
|
|
// ========================
|
|
// Message building methods
|
|
// ========================
|
|
start_section(heading) {
|
|
this._indent();
|
|
let section = this._Section(this, this._current_section, heading);
|
|
this._add_item(section.format_help.bind(section), []);
|
|
this._current_section = section;
|
|
}
|
|
|
|
end_section() {
|
|
this._current_section = this._current_section.parent;
|
|
this._dedent();
|
|
}
|
|
|
|
add_text(text) {
|
|
if (text !== SUPPRESS && text !== undefined) {
|
|
this._add_item(this._format_text.bind(this), [text]);
|
|
}
|
|
}
|
|
|
|
add_usage(usage, actions, groups, prefix = undefined) {
|
|
if (usage !== SUPPRESS) {
|
|
let args = [usage, actions, groups, prefix];
|
|
this._add_item(this._format_usage.bind(this), args);
|
|
}
|
|
}
|
|
|
|
add_argument(action) {
|
|
if (action.help !== SUPPRESS) {
|
|
// find all invocations
|
|
let invocations = [this._format_action_invocation(action)];
|
|
for (let subaction of this._iter_indented_subactions(action)) {
|
|
invocations.push(this._format_action_invocation(subaction));
|
|
}
|
|
|
|
// update the maximum item length
|
|
let invocation_length = Math.max(
|
|
...invocations.map((invocation) => invocation.length)
|
|
);
|
|
let action_length = invocation_length + this._current_indent;
|
|
this._action_max_length = Math.max(
|
|
this._action_max_length,
|
|
action_length
|
|
);
|
|
|
|
// add the item to the list
|
|
this._add_item(this._format_action.bind(this), [action]);
|
|
}
|
|
}
|
|
|
|
add_arguments(actions) {
|
|
for (let action of actions) {
|
|
this.add_argument(action);
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// Help-formatting methods
|
|
// =======================
|
|
format_help() {
|
|
let help = this._root_section.format_help();
|
|
if (help) {
|
|
help = help.replace(this._long_break_matcher, '\n\n');
|
|
help = help.replace(/^\n+|\n+$/g, '') + '\n';
|
|
}
|
|
return help;
|
|
}
|
|
|
|
_join_parts(part_strings) {
|
|
return part_strings
|
|
.filter((part) => part && part !== SUPPRESS)
|
|
.join('');
|
|
}
|
|
|
|
_format_usage(usage, actions, groups, prefix) {
|
|
if (prefix === undefined) {
|
|
prefix = 'usage: ';
|
|
}
|
|
|
|
// if usage is specified, use that
|
|
if (usage !== undefined) {
|
|
usage = sub(usage, { prog: this._prog });
|
|
|
|
// if no optionals or positionals are available, usage is just prog
|
|
} else if (usage === undefined && !actions.length) {
|
|
usage = sub('%(prog)s', { prog: this._prog });
|
|
|
|
// if optionals and positionals are available, calculate usage
|
|
} else if (usage === undefined) {
|
|
let prog = sub('%(prog)s', { prog: this._prog });
|
|
|
|
// split optionals from positionals
|
|
let optionals = [];
|
|
let positionals = [];
|
|
for (let action of actions) {
|
|
if (action.option_strings.length) {
|
|
optionals.push(action);
|
|
} else {
|
|
positionals.push(action);
|
|
}
|
|
}
|
|
|
|
// build full usage string
|
|
let action_usage = this._format_actions_usage(
|
|
[].concat(optionals).concat(positionals),
|
|
groups
|
|
);
|
|
usage = [prog, action_usage].map(String).join(' ');
|
|
|
|
// wrap the usage parts if it's too long
|
|
let text_width = this._width - this._current_indent;
|
|
if (prefix.length + usage.length > text_width) {
|
|
// break usage into wrappable parts
|
|
let part_regexp = /\(.*?\)+(?=\s|$)|\[.*?\]+(?=\s|$)|\S+/g;
|
|
let opt_usage = this._format_actions_usage(optionals, groups);
|
|
let pos_usage = this._format_actions_usage(positionals, groups);
|
|
let opt_parts = opt_usage.match(part_regexp) || [];
|
|
let pos_parts = pos_usage.match(part_regexp) || [];
|
|
assert(opt_parts.join(' ') === opt_usage);
|
|
assert(pos_parts.join(' ') === pos_usage);
|
|
|
|
// helper for wrapping lines
|
|
let get_lines = (parts, indent, prefix = undefined) => {
|
|
let lines = [];
|
|
let line = [];
|
|
let line_len;
|
|
if (prefix !== undefined) {
|
|
line_len = prefix.length - 1;
|
|
} else {
|
|
line_len = indent.length - 1;
|
|
}
|
|
for (let part of parts) {
|
|
if (line_len + 1 + part.length > text_width && line) {
|
|
lines.push(indent + line.join(' '));
|
|
line = [];
|
|
line_len = indent.length - 1;
|
|
}
|
|
line.push(part);
|
|
line_len += part.length + 1;
|
|
}
|
|
if (line.length) {
|
|
lines.push(indent + line.join(' '));
|
|
}
|
|
if (prefix !== undefined) {
|
|
lines[0] = lines[0].slice(indent.length);
|
|
}
|
|
return lines;
|
|
};
|
|
|
|
let lines;
|
|
|
|
// if prog is short, follow it with optionals or positionals
|
|
if (prefix.length + prog.length <= 0.75 * text_width) {
|
|
let indent = ' '.repeat(prefix.length + prog.length + 1);
|
|
if (opt_parts.length) {
|
|
lines = get_lines([prog].concat(opt_parts), indent, prefix);
|
|
lines = lines.concat(get_lines(pos_parts, indent));
|
|
} else if (pos_parts.length) {
|
|
lines = get_lines([prog].concat(pos_parts), indent, prefix);
|
|
} else {
|
|
lines = [prog];
|
|
}
|
|
|
|
// if prog is long, put it on its own line
|
|
} else {
|
|
let indent = ' '.repeat(prefix.length);
|
|
let parts = [].concat(opt_parts).concat(pos_parts);
|
|
lines = get_lines(parts, indent);
|
|
if (lines.length > 1) {
|
|
lines = [];
|
|
lines = lines.concat(get_lines(opt_parts, indent));
|
|
lines = lines.concat(get_lines(pos_parts, indent));
|
|
}
|
|
lines = [prog].concat(lines);
|
|
}
|
|
|
|
// join lines into usage
|
|
usage = lines.join('\n');
|
|
}
|
|
}
|
|
|
|
// prefix with 'usage:'
|
|
return sub('%s%s\n\n', prefix, usage);
|
|
}
|
|
|
|
_format_actions_usage(actions, groups) {
|
|
// find group indices and identify actions in groups
|
|
let group_actions = new Set();
|
|
let inserts = {};
|
|
for (let group of groups) {
|
|
let start = actions.indexOf(group._group_actions[0]);
|
|
if (start === -1) {
|
|
continue;
|
|
} else {
|
|
let end = start + group._group_actions.length;
|
|
if (_array_equal(actions.slice(start, end), group._group_actions)) {
|
|
for (let action of group._group_actions) {
|
|
group_actions.add(action);
|
|
}
|
|
if (!group.required) {
|
|
if (start in inserts) {
|
|
inserts[start] += ' [';
|
|
} else {
|
|
inserts[start] = '[';
|
|
}
|
|
if (end in inserts) {
|
|
inserts[end] += ']';
|
|
} else {
|
|
inserts[end] = ']';
|
|
}
|
|
} else {
|
|
if (start in inserts) {
|
|
inserts[start] += ' (';
|
|
} else {
|
|
inserts[start] = '(';
|
|
}
|
|
if (end in inserts) {
|
|
inserts[end] += ')';
|
|
} else {
|
|
inserts[end] = ')';
|
|
}
|
|
}
|
|
for (let i of range(start + 1, end)) {
|
|
inserts[i] = '|';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// collect all actions format strings
|
|
let parts = [];
|
|
for (let [i, action] of Object.entries(actions)) {
|
|
// suppressed arguments are marked with None
|
|
// remove | separators for suppressed arguments
|
|
if (action.help === SUPPRESS) {
|
|
parts.push(undefined);
|
|
if (inserts[+i] === '|') {
|
|
delete inserts[+i];
|
|
} else if (inserts[+i + 1] === '|') {
|
|
delete inserts[+i + 1];
|
|
}
|
|
|
|
// produce all arg strings
|
|
} else if (!action.option_strings.length) {
|
|
let default_value =
|
|
this._get_default_metavar_for_positional(action);
|
|
let part = this._format_args(action, default_value);
|
|
|
|
// if it's in a group, strip the outer []
|
|
if (group_actions.has(action)) {
|
|
if (part[0] === '[' && part[part.length - 1] === ']') {
|
|
part = part.slice(1, -1);
|
|
}
|
|
}
|
|
|
|
// add the action string to the list
|
|
parts.push(part);
|
|
|
|
// produce the first way to invoke the option in brackets
|
|
} else {
|
|
let option_string = action.option_strings[0];
|
|
let part;
|
|
|
|
// if the Optional doesn't take a value, format is:
|
|
// -s or --long
|
|
if (action.nargs === 0) {
|
|
part = action.format_usage();
|
|
|
|
// if the Optional takes a value, format is:
|
|
// -s ARGS or --long ARGS
|
|
} else {
|
|
let default_value =
|
|
this._get_default_metavar_for_optional(action);
|
|
let args_string = this._format_args(action, default_value);
|
|
part = sub('%s %s', option_string, args_string);
|
|
}
|
|
|
|
// make it look optional if it's not required or in a group
|
|
if (!action.required && !group_actions.has(action)) {
|
|
part = sub('[%s]', part);
|
|
}
|
|
|
|
// add the action string to the list
|
|
parts.push(part);
|
|
}
|
|
}
|
|
|
|
// insert things at the necessary indices
|
|
for (let i of Object.keys(inserts)
|
|
.map(Number)
|
|
.sort((a, b) => b - a)) {
|
|
parts.splice(+i, 0, inserts[+i]);
|
|
}
|
|
|
|
// join all the action items with spaces
|
|
let text = parts.filter(Boolean).join(' ');
|
|
|
|
// clean up separators for mutually exclusive groups
|
|
text = text.replace(/([\[(]) /g, '$1');
|
|
text = text.replace(/ ([\])])/g, '$1');
|
|
text = text.replace(/[\[(] *[\])]/g, '');
|
|
text = text.replace(/\(([^|]*)\)/g, '$1', text);
|
|
text = text.trim();
|
|
|
|
// return the text
|
|
return text;
|
|
}
|
|
|
|
_format_text(text) {
|
|
if (text.includes('%(prog)')) {
|
|
text = sub(text, { prog: this._prog });
|
|
}
|
|
let text_width = Math.max(this._width - this._current_indent, 11);
|
|
let indent = ' '.repeat(this._current_indent);
|
|
return this._fill_text(text, text_width, indent) + '\n\n';
|
|
}
|
|
|
|
_format_action(action) {
|
|
// determine the required width and the entry label
|
|
let help_position = Math.min(
|
|
this._action_max_length + 2,
|
|
this._max_help_position
|
|
);
|
|
let help_width = Math.max(this._width - help_position, 11);
|
|
let action_width = help_position - this._current_indent - 2;
|
|
let action_header = this._format_action_invocation(action);
|
|
let indent_first;
|
|
|
|
// no help; start on same line and add a final newline
|
|
if (!action.help) {
|
|
let tup = [this._current_indent, '', action_header];
|
|
action_header = sub('%*s%s\n', ...tup);
|
|
|
|
// short action name; start on the same line and pad two spaces
|
|
} else if (action_header.length <= action_width) {
|
|
let tup = [this._current_indent, '', action_width, action_header];
|
|
action_header = sub('%*s%-*s ', ...tup);
|
|
indent_first = 0;
|
|
|
|
// long action name; start on the next line
|
|
} else {
|
|
let tup = [this._current_indent, '', action_header];
|
|
action_header = sub('%*s%s\n', ...tup);
|
|
indent_first = help_position;
|
|
}
|
|
|
|
// collect the pieces of the action help
|
|
let parts = [action_header];
|
|
|
|
// if there was help for the action, add lines of help text
|
|
if (action.help) {
|
|
let help_text = this._expand_help(action);
|
|
let help_lines = this._split_lines(help_text, help_width);
|
|
parts.push(sub('%*s%s\n', indent_first, '', help_lines[0]));
|
|
for (let line of help_lines.slice(1)) {
|
|
parts.push(sub('%*s%s\n', help_position, '', line));
|
|
}
|
|
|
|
// or add a newline if the description doesn't end with one
|
|
} else if (!action_header.endsWith('\n')) {
|
|
parts.push('\n');
|
|
}
|
|
|
|
// if there are any sub-actions, add their help as well
|
|
for (let subaction of this._iter_indented_subactions(action)) {
|
|
parts.push(this._format_action(subaction));
|
|
}
|
|
|
|
// return a single string
|
|
return this._join_parts(parts);
|
|
}
|
|
|
|
_format_action_invocation(action) {
|
|
if (!action.option_strings.length) {
|
|
let default_value = this._get_default_metavar_for_positional(action);
|
|
let metavar = this._metavar_formatter(action, default_value)(1)[0];
|
|
return metavar;
|
|
} else {
|
|
let parts = [];
|
|
|
|
// if the Optional doesn't take a value, format is:
|
|
// -s, --long
|
|
if (action.nargs === 0) {
|
|
parts = parts.concat(action.option_strings);
|
|
|
|
// if the Optional takes a value, format is:
|
|
// -s ARGS, --long ARGS
|
|
} else {
|
|
let default_value = this._get_default_metavar_for_optional(action);
|
|
let args_string = this._format_args(action, default_value);
|
|
for (let option_string of action.option_strings) {
|
|
parts.push(sub('%s %s', option_string, args_string));
|
|
}
|
|
}
|
|
|
|
return parts.join(', ');
|
|
}
|
|
}
|
|
|
|
_metavar_formatter(action, default_metavar) {
|
|
let result;
|
|
if (action.metavar !== undefined) {
|
|
result = action.metavar;
|
|
} else if (action.choices !== undefined) {
|
|
let choice_strs = _choices_to_array(action.choices).map(String);
|
|
result = sub('{%s}', choice_strs.join(','));
|
|
} else {
|
|
result = default_metavar;
|
|
}
|
|
|
|
function format(tuple_size) {
|
|
if (Array.isArray(result)) {
|
|
return result;
|
|
} else {
|
|
return Array(tuple_size).fill(result);
|
|
}
|
|
}
|
|
return format;
|
|
}
|
|
|
|
_format_args(action, default_metavar) {
|
|
let get_metavar = this._metavar_formatter(action, default_metavar);
|
|
let result;
|
|
if (action.nargs === undefined) {
|
|
result = sub('%s', ...get_metavar(1));
|
|
} else if (action.nargs === OPTIONAL) {
|
|
result = sub('[%s]', ...get_metavar(1));
|
|
} else if (action.nargs === ZERO_OR_MORE) {
|
|
let metavar = get_metavar(1);
|
|
if (metavar.length === 2) {
|
|
result = sub('[%s [%s ...]]', ...metavar);
|
|
} else {
|
|
result = sub('[%s ...]', ...metavar);
|
|
}
|
|
} else if (action.nargs === ONE_OR_MORE) {
|
|
result = sub('%s [%s ...]', ...get_metavar(2));
|
|
} else if (action.nargs === REMAINDER) {
|
|
result = '...';
|
|
} else if (action.nargs === PARSER) {
|
|
result = sub('%s ...', ...get_metavar(1));
|
|
} else if (action.nargs === SUPPRESS) {
|
|
result = '';
|
|
} else {
|
|
let formats;
|
|
try {
|
|
formats = range(action.nargs).map(() => '%s');
|
|
} catch (err) {
|
|
throw new TypeError('invalid nargs value');
|
|
}
|
|
result = sub(formats.join(' '), ...get_metavar(action.nargs));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
_expand_help(action) {
|
|
let params = Object.assign({ prog: this._prog }, action);
|
|
for (let name of Object.keys(params)) {
|
|
if (params[name] === SUPPRESS) {
|
|
delete params[name];
|
|
}
|
|
}
|
|
for (let name of Object.keys(params)) {
|
|
if (params[name] && params[name].name) {
|
|
params[name] = params[name].name;
|
|
}
|
|
}
|
|
if (params.choices !== undefined) {
|
|
let choices_str = _choices_to_array(params.choices)
|
|
.map(String)
|
|
.join(', ');
|
|
params.choices = choices_str;
|
|
}
|
|
// LEGACY (v1 compatibility): camelcase
|
|
for (let key of Object.keys(params)) {
|
|
let old_name = _to_legacy_name(key);
|
|
if (old_name !== key) {
|
|
params[old_name] = params[key];
|
|
}
|
|
}
|
|
// end
|
|
return sub(this._get_help_string(action), params);
|
|
}
|
|
|
|
*_iter_indented_subactions(action) {
|
|
if (typeof action._get_subactions === 'function') {
|
|
this._indent();
|
|
yield* action._get_subactions();
|
|
this._dedent();
|
|
}
|
|
}
|
|
|
|
_split_lines(text, width) {
|
|
text = text.replace(this._whitespace_matcher, ' ').trim();
|
|
// The textwrap module is used only for formatting help.
|
|
// Delay its import for speeding up the common usage of argparse.
|
|
let textwrap = require('./lib/textwrap');
|
|
return textwrap.wrap(text, { width });
|
|
}
|
|
|
|
_fill_text(text, width, indent) {
|
|
text = text.replace(this._whitespace_matcher, ' ').trim();
|
|
let textwrap = require('./lib/textwrap');
|
|
return textwrap.fill(text, {
|
|
width,
|
|
initial_indent: indent,
|
|
subsequent_indent: indent,
|
|
});
|
|
}
|
|
|
|
_get_help_string(action) {
|
|
return action.help;
|
|
}
|
|
|
|
_get_default_metavar_for_optional(action) {
|
|
return action.dest.toUpperCase();
|
|
}
|
|
|
|
_get_default_metavar_for_positional(action) {
|
|
return action.dest;
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
HelpFormatter.prototype._Section = _callable(
|
|
class _Section {
|
|
constructor(formatter, parent, heading = undefined) {
|
|
this.formatter = formatter;
|
|
this.parent = parent;
|
|
this.heading = heading;
|
|
this.items = [];
|
|
}
|
|
|
|
format_help() {
|
|
// format the indented section
|
|
if (this.parent !== undefined) {
|
|
this.formatter._indent();
|
|
}
|
|
let item_help = this.formatter._join_parts(
|
|
this.items.map(([func, args]) => func.apply(null, args))
|
|
);
|
|
if (this.parent !== undefined) {
|
|
this.formatter._dedent();
|
|
}
|
|
|
|
// return nothing if the section was empty
|
|
if (!item_help) {
|
|
return '';
|
|
}
|
|
|
|
// add the heading if the section was non-empty
|
|
let heading;
|
|
if (this.heading !== SUPPRESS && this.heading !== undefined) {
|
|
let current_indent = this.formatter._current_indent;
|
|
heading = sub('%*s%s:\n', current_indent, '', this.heading);
|
|
} else {
|
|
heading = '';
|
|
}
|
|
|
|
// join the section-initial newline, the heading and the help
|
|
return this.formatter._join_parts(['\n', heading, item_help, '\n']);
|
|
}
|
|
}
|
|
);
|
|
|
|
const RawDescriptionHelpFormatter = _camelcase_alias(
|
|
_callable(
|
|
class RawDescriptionHelpFormatter extends HelpFormatter {
|
|
/*
|
|
* Help message formatter which retains any formatting in descriptions.
|
|
*
|
|
* Only the name of this class is considered a public API. All the methods
|
|
* provided by the class are considered an implementation detail.
|
|
*/
|
|
|
|
_fill_text(text, width, indent) {
|
|
return splitlines(text, true)
|
|
.map((line) => indent + line)
|
|
.join('');
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
const RawTextHelpFormatter = _camelcase_alias(
|
|
_callable(
|
|
class RawTextHelpFormatter extends RawDescriptionHelpFormatter {
|
|
/*
|
|
* Help message formatter which retains formatting of all help text.
|
|
*
|
|
* Only the name of this class is considered a public API. All the methods
|
|
* provided by the class are considered an implementation detail.
|
|
*/
|
|
|
|
_split_lines(text /*, width*/) {
|
|
return splitlines(text);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
const ArgumentDefaultsHelpFormatter = _camelcase_alias(
|
|
_callable(
|
|
class ArgumentDefaultsHelpFormatter extends HelpFormatter {
|
|
/*
|
|
* Help message formatter which adds default values to argument help.
|
|
*
|
|
* Only the name of this class is considered a public API. All the methods
|
|
* provided by the class are considered an implementation detail.
|
|
*/
|
|
|
|
_get_help_string(action) {
|
|
let help = action.help;
|
|
// LEGACY (v1 compatibility): additional check for defaultValue needed
|
|
if (
|
|
!action.help.includes('%(default)') &&
|
|
!action.help.includes('%(defaultValue)')
|
|
) {
|
|
if (action.default !== SUPPRESS) {
|
|
let defaulting_nargs = [OPTIONAL, ZERO_OR_MORE];
|
|
if (
|
|
action.option_strings.length ||
|
|
defaulting_nargs.includes(action.nargs)
|
|
) {
|
|
help += ' (default: %(default)s)';
|
|
}
|
|
}
|
|
}
|
|
return help;
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
const MetavarTypeHelpFormatter = _camelcase_alias(
|
|
_callable(
|
|
class MetavarTypeHelpFormatter extends HelpFormatter {
|
|
/*
|
|
* Help message formatter which uses the argument 'type' as the default
|
|
* metavar value (instead of the argument 'dest')
|
|
*
|
|
* Only the name of this class is considered a public API. All the methods
|
|
* provided by the class are considered an implementation detail.
|
|
*/
|
|
|
|
_get_default_metavar_for_optional(action) {
|
|
return typeof action.type === 'function' ?
|
|
action.type.name
|
|
: action.type;
|
|
}
|
|
|
|
_get_default_metavar_for_positional(action) {
|
|
return typeof action.type === 'function' ?
|
|
action.type.name
|
|
: action.type;
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// =====================
|
|
// Options and Arguments
|
|
// =====================
|
|
function _get_action_name(argument) {
|
|
if (argument === undefined) {
|
|
return undefined;
|
|
} else if (argument.option_strings.length) {
|
|
return argument.option_strings.join('/');
|
|
} else if (![undefined, SUPPRESS].includes(argument.metavar)) {
|
|
return argument.metavar;
|
|
} else if (![undefined, SUPPRESS].includes(argument.dest)) {
|
|
return argument.dest;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
const ArgumentError = _callable(
|
|
class ArgumentError extends Error {
|
|
/*
|
|
* An error from creating or using an argument (optional or positional).
|
|
*
|
|
* The string value of this exception is the message, augmented with
|
|
* information about the argument that caused it.
|
|
*/
|
|
|
|
constructor(argument, message) {
|
|
super();
|
|
this.name = 'ArgumentError';
|
|
this._argument_name = _get_action_name(argument);
|
|
this._message = message;
|
|
this.message = this.str();
|
|
}
|
|
|
|
str() {
|
|
let format;
|
|
if (this._argument_name === undefined) {
|
|
format = '%(message)s';
|
|
} else {
|
|
format = 'argument %(argument_name)s: %(message)s';
|
|
}
|
|
return sub(format, {
|
|
message: this._message,
|
|
argument_name: this._argument_name,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
const ArgumentTypeError = _callable(
|
|
class ArgumentTypeError extends Error {
|
|
/*
|
|
* An error from trying to convert a command line string to a type.
|
|
*/
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'ArgumentTypeError';
|
|
}
|
|
}
|
|
);
|
|
|
|
// ==============
|
|
// Action classes
|
|
// ==============
|
|
const Action = _camelcase_alias(
|
|
_callable(
|
|
class Action extends _AttributeHolder(Function) {
|
|
/*
|
|
* Information about how to convert command line strings to Python objects.
|
|
*
|
|
* Action objects are used by an ArgumentParser to represent the information
|
|
* needed to parse a single argument from one or more strings from the
|
|
* command line. The keyword arguments to the Action constructor are also
|
|
* all attributes of Action instances.
|
|
*
|
|
* Keyword Arguments:
|
|
*
|
|
* - option_strings -- A list of command-line option strings which
|
|
* should be associated with this action.
|
|
*
|
|
* - dest -- The name of the attribute to hold the created object(s)
|
|
*
|
|
* - nargs -- The number of command-line arguments that should be
|
|
* consumed. By default, one argument will be consumed and a single
|
|
* value will be produced. Other values include:
|
|
* - N (an integer) consumes N arguments (and produces a list)
|
|
* - '?' consumes zero or one arguments
|
|
* - '*' consumes zero or more arguments (and produces a list)
|
|
* - '+' consumes one or more arguments (and produces a list)
|
|
* Note that the difference between the default and nargs=1 is that
|
|
* with the default, a single value will be produced, while with
|
|
* nargs=1, a list containing a single value will be produced.
|
|
*
|
|
* - const -- The value to be produced if the option is specified and the
|
|
* option uses an action that takes no values.
|
|
*
|
|
* - default -- The value to be produced if the option is not specified.
|
|
*
|
|
* - type -- A callable that accepts a single string argument, and
|
|
* returns the converted value. The standard Python types str, int,
|
|
* float, and complex are useful examples of such callables. If None,
|
|
* str is used.
|
|
*
|
|
* - choices -- A container of values that should be allowed. If not None,
|
|
* after a command-line argument has been converted to the appropriate
|
|
* type, an exception will be raised if it is not a member of this
|
|
* collection.
|
|
*
|
|
* - required -- True if the action must always be specified at the
|
|
* command line. This is only meaningful for optional command-line
|
|
* arguments.
|
|
*
|
|
* - help -- The help string describing the argument.
|
|
*
|
|
* - metavar -- The name to be used for the option's argument with the
|
|
* help string. If None, the 'dest' value will be used as the name.
|
|
*/
|
|
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
dest,
|
|
nargs,
|
|
const_value,
|
|
default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
nargs: undefined,
|
|
const: undefined,
|
|
default: undefined,
|
|
type: undefined,
|
|
choices: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
// when this class is called as a function, redirect it to .call() method of itself
|
|
super(
|
|
'return arguments.callee.call.apply(arguments.callee, arguments)'
|
|
);
|
|
|
|
this.option_strings = option_strings;
|
|
this.dest = dest;
|
|
this.nargs = nargs;
|
|
this.const = const_value;
|
|
this.default = default_value;
|
|
this.type = type;
|
|
this.choices = choices;
|
|
this.required = required;
|
|
this.help = help;
|
|
this.metavar = metavar;
|
|
}
|
|
|
|
_get_kwargs() {
|
|
let names = [
|
|
'option_strings',
|
|
'dest',
|
|
'nargs',
|
|
'const',
|
|
'default',
|
|
'type',
|
|
'choices',
|
|
'help',
|
|
'metavar',
|
|
];
|
|
return names.map((name) => [name, getattr(this, name)]);
|
|
}
|
|
|
|
format_usage() {
|
|
return this.option_strings[0];
|
|
}
|
|
|
|
call(/*parser, namespace, values, option_string = undefined*/) {
|
|
throw new Error('.call() not defined');
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
const BooleanOptionalAction = _camelcase_alias(
|
|
_callable(
|
|
class BooleanOptionalAction extends Action {
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
dest,
|
|
default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
default: undefined,
|
|
type: undefined,
|
|
choices: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
let _option_strings = [];
|
|
for (let option_string of option_strings) {
|
|
_option_strings.push(option_string);
|
|
|
|
if (option_string.startsWith('--')) {
|
|
option_string = '--no-' + option_string.slice(2);
|
|
_option_strings.push(option_string);
|
|
}
|
|
}
|
|
|
|
if (help !== undefined && default_value !== undefined) {
|
|
help += ` (default: ${default_value})`;
|
|
}
|
|
|
|
super({
|
|
option_strings: _option_strings,
|
|
dest,
|
|
nargs: 0,
|
|
default: default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
});
|
|
}
|
|
|
|
call(parser, namespace, values, option_string = undefined) {
|
|
if (this.option_strings.includes(option_string)) {
|
|
setattr(namespace, this.dest, !option_string.startsWith('--no-'));
|
|
}
|
|
}
|
|
|
|
format_usage() {
|
|
return this.option_strings.join(' | ');
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
const _StoreAction = _callable(
|
|
class _StoreAction extends Action {
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
dest,
|
|
nargs,
|
|
const_value,
|
|
default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
nargs: undefined,
|
|
const: undefined,
|
|
default: undefined,
|
|
type: undefined,
|
|
choices: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
if (nargs === 0) {
|
|
throw new TypeError(
|
|
'nargs for store actions must be != 0; if you ' +
|
|
'have nothing to store, actions such as store ' +
|
|
'true or store const may be more appropriate'
|
|
);
|
|
}
|
|
if (const_value !== undefined && nargs !== OPTIONAL) {
|
|
throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL));
|
|
}
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
nargs,
|
|
const: const_value,
|
|
default: default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
});
|
|
}
|
|
|
|
call(parser, namespace, values /*, option_string = undefined*/) {
|
|
setattr(namespace, this.dest, values);
|
|
}
|
|
}
|
|
);
|
|
|
|
const _StoreConstAction = _callable(
|
|
class _StoreConstAction extends Action {
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
dest,
|
|
const_value,
|
|
default_value,
|
|
required,
|
|
help,
|
|
//, metavar
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
const: no_default,
|
|
default: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
nargs: 0,
|
|
const: const_value,
|
|
default: default_value,
|
|
required,
|
|
help,
|
|
});
|
|
}
|
|
|
|
call(parser, namespace /*, values, option_string = undefined*/) {
|
|
setattr(namespace, this.dest, this.const);
|
|
}
|
|
}
|
|
);
|
|
|
|
const _StoreTrueAction = _callable(
|
|
class _StoreTrueAction extends _StoreConstAction {
|
|
constructor() {
|
|
let [option_strings, dest, default_value, required, help] = _parse_opts(
|
|
arguments,
|
|
{
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
default: false,
|
|
required: false,
|
|
help: undefined,
|
|
}
|
|
);
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
const: true,
|
|
default: default_value,
|
|
required,
|
|
help,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
const _StoreFalseAction = _callable(
|
|
class _StoreFalseAction extends _StoreConstAction {
|
|
constructor() {
|
|
let [option_strings, dest, default_value, required, help] = _parse_opts(
|
|
arguments,
|
|
{
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
default: true,
|
|
required: false,
|
|
help: undefined,
|
|
}
|
|
);
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
const: false,
|
|
default: default_value,
|
|
required,
|
|
help,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
const _AppendAction = _callable(
|
|
class _AppendAction extends Action {
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
dest,
|
|
nargs,
|
|
const_value,
|
|
default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
nargs: undefined,
|
|
const: undefined,
|
|
default: undefined,
|
|
type: undefined,
|
|
choices: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
if (nargs === 0) {
|
|
throw new TypeError(
|
|
'nargs for append actions must be != 0; if arg ' +
|
|
'strings are not supplying the value to append, ' +
|
|
'the append const action may be more appropriate'
|
|
);
|
|
}
|
|
if (const_value !== undefined && nargs !== OPTIONAL) {
|
|
throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL));
|
|
}
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
nargs,
|
|
const: const_value,
|
|
default: default_value,
|
|
type,
|
|
choices,
|
|
required,
|
|
help,
|
|
metavar,
|
|
});
|
|
}
|
|
|
|
call(parser, namespace, values /*, option_string = undefined*/) {
|
|
let items = getattr(namespace, this.dest, undefined);
|
|
items = _copy_items(items);
|
|
items.push(values);
|
|
setattr(namespace, this.dest, items);
|
|
}
|
|
}
|
|
);
|
|
|
|
const _AppendConstAction = _callable(
|
|
class _AppendConstAction extends Action {
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
dest,
|
|
const_value,
|
|
default_value,
|
|
required,
|
|
help,
|
|
metavar,
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
const: no_default,
|
|
default: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
nargs: 0,
|
|
const: const_value,
|
|
default: default_value,
|
|
required,
|
|
help,
|
|
metavar,
|
|
});
|
|
}
|
|
|
|
call(parser, namespace /*, values, option_string = undefined*/) {
|
|
let items = getattr(namespace, this.dest, undefined);
|
|
items = _copy_items(items);
|
|
items.push(this.const);
|
|
setattr(namespace, this.dest, items);
|
|
}
|
|
}
|
|
);
|
|
|
|
const _CountAction = _callable(
|
|
class _CountAction extends Action {
|
|
constructor() {
|
|
let [option_strings, dest, default_value, required, help] = _parse_opts(
|
|
arguments,
|
|
{
|
|
option_strings: no_default,
|
|
dest: no_default,
|
|
default: undefined,
|
|
required: false,
|
|
help: undefined,
|
|
}
|
|
);
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
nargs: 0,
|
|
default: default_value,
|
|
required,
|
|
help,
|
|
});
|
|
}
|
|
|
|
call(parser, namespace /*, values, option_string = undefined*/) {
|
|
let count = getattr(namespace, this.dest, undefined);
|
|
if (count === undefined) {
|
|
count = 0;
|
|
}
|
|
setattr(namespace, this.dest, count + 1);
|
|
}
|
|
}
|
|
);
|
|
|
|
const _HelpAction = _callable(
|
|
class _HelpAction extends Action {
|
|
constructor() {
|
|
let [option_strings, dest, default_value, help] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
dest: SUPPRESS,
|
|
default: SUPPRESS,
|
|
help: undefined,
|
|
});
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
default: default_value,
|
|
nargs: 0,
|
|
help,
|
|
});
|
|
}
|
|
|
|
call(parser /*, namespace, values, option_string = undefined*/) {
|
|
parser.print_help();
|
|
parser.exit();
|
|
}
|
|
}
|
|
);
|
|
|
|
const _VersionAction = _callable(
|
|
class _VersionAction extends Action {
|
|
constructor() {
|
|
let [option_strings, version, dest, default_value, help] = _parse_opts(
|
|
arguments,
|
|
{
|
|
option_strings: no_default,
|
|
version: undefined,
|
|
dest: SUPPRESS,
|
|
default: SUPPRESS,
|
|
help: "show program's version number and exit",
|
|
}
|
|
);
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
default: default_value,
|
|
nargs: 0,
|
|
help,
|
|
});
|
|
this.version = version;
|
|
}
|
|
|
|
call(parser /*, namespace, values, option_string = undefined*/) {
|
|
let version = this.version;
|
|
if (version === undefined) {
|
|
version = parser.version;
|
|
}
|
|
let formatter = parser._get_formatter();
|
|
formatter.add_text(version);
|
|
parser._print_message(formatter.format_help(), process.stdout);
|
|
parser.exit();
|
|
}
|
|
}
|
|
);
|
|
|
|
const _SubParsersAction = _camelcase_alias(
|
|
_callable(
|
|
class _SubParsersAction extends Action {
|
|
constructor() {
|
|
let [
|
|
option_strings,
|
|
prog,
|
|
parser_class,
|
|
dest,
|
|
required,
|
|
help,
|
|
metavar,
|
|
] = _parse_opts(arguments, {
|
|
option_strings: no_default,
|
|
prog: no_default,
|
|
parser_class: no_default,
|
|
dest: SUPPRESS,
|
|
required: false,
|
|
help: undefined,
|
|
metavar: undefined,
|
|
});
|
|
|
|
let name_parser_map = {};
|
|
|
|
super({
|
|
option_strings,
|
|
dest,
|
|
nargs: PARSER,
|
|
choices: name_parser_map,
|
|
required,
|
|
help,
|
|
metavar,
|
|
});
|
|
|
|
this._prog_prefix = prog;
|
|
this._parser_class = parser_class;
|
|
this._name_parser_map = name_parser_map;
|
|
this._choices_actions = [];
|
|
}
|
|
|
|
add_parser() {
|
|
let [name, kwargs] = _parse_opts(arguments, {
|
|
name: no_default,
|
|
'**kwargs': no_default,
|
|
});
|
|
|
|
// set prog from the existing prefix
|
|
if (kwargs.prog === undefined) {
|
|
kwargs.prog = sub('%s %s', this._prog_prefix, name);
|
|
}
|
|
|
|
let aliases = getattr(kwargs, 'aliases', []);
|
|
delete kwargs.aliases;
|
|
|
|
// create a pseudo-action to hold the choice help
|
|
if ('help' in kwargs) {
|
|
let help = kwargs.help;
|
|
delete kwargs.help;
|
|
let choice_action = this._ChoicesPseudoAction(name, aliases, help);
|
|
this._choices_actions.push(choice_action);
|
|
}
|
|
|
|
// create the parser and add it to the map
|
|
let parser = new this._parser_class(kwargs);
|
|
this._name_parser_map[name] = parser;
|
|
|
|
// make parser available under aliases also
|
|
for (let alias of aliases) {
|
|
this._name_parser_map[alias] = parser;
|
|
}
|
|
|
|
return parser;
|
|
}
|
|
|
|
_get_subactions() {
|
|
return this._choices_actions;
|
|
}
|
|
|
|
call(parser, namespace, values /*, option_string = undefined*/) {
|
|
let parser_name = values[0];
|
|
let arg_strings = values.slice(1);
|
|
|
|
// set the parser name if requested
|
|
if (this.dest !== SUPPRESS) {
|
|
setattr(namespace, this.dest, parser_name);
|
|
}
|
|
|
|
// select the parser
|
|
if (hasattr(this._name_parser_map, parser_name)) {
|
|
parser = this._name_parser_map[parser_name];
|
|
} else {
|
|
let args = { parser_name, choices: this._name_parser_map.join(', ') };
|
|
let msg = sub(
|
|
'unknown parser %(parser_name)r (choices: %(choices)s)',
|
|
args
|
|
);
|
|
throw new ArgumentError(this, msg);
|
|
}
|
|
|
|
// parse all the remaining options into the namespace
|
|
// store any unrecognized options on the object, so that the top
|
|
// level parser can decide what to do with them
|
|
|
|
// In case this subparser defines new defaults, we parse them
|
|
// in a new namespace object and then update the original
|
|
// namespace for the relevant parts.
|
|
let subnamespace;
|
|
[subnamespace, arg_strings] = parser.parse_known_args(
|
|
arg_strings,
|
|
undefined
|
|
);
|
|
for (let [key, value] of Object.entries(subnamespace)) {
|
|
setattr(namespace, key, value);
|
|
}
|
|
|
|
if (arg_strings.length) {
|
|
setdefault(namespace, _UNRECOGNIZED_ARGS_ATTR, []);
|
|
getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).push(...arg_strings);
|
|
}
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
_SubParsersAction.prototype._ChoicesPseudoAction = _callable(
|
|
class _ChoicesPseudoAction extends Action {
|
|
constructor(name, aliases, help) {
|
|
let metavar = name,
|
|
dest = name;
|
|
if (aliases.length) {
|
|
metavar += sub(' (%s)', aliases.join(', '));
|
|
}
|
|
super({ option_strings: [], dest, help, metavar });
|
|
}
|
|
}
|
|
);
|
|
|
|
const _ExtendAction = _callable(
|
|
class _ExtendAction extends _AppendAction {
|
|
call(parser, namespace, values /*, option_string = undefined*/) {
|
|
let items = getattr(namespace, this.dest, undefined);
|
|
items = _copy_items(items);
|
|
items = items.concat(values);
|
|
setattr(namespace, this.dest, items);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ==============
|
|
// Type classes
|
|
// ==============
|
|
const FileType = _callable(
|
|
class FileType extends Function {
|
|
/*
|
|
* Factory for creating file object types
|
|
*
|
|
* Instances of FileType are typically passed as type= arguments to the
|
|
* ArgumentParser add_argument() method.
|
|
*
|
|
* Keyword Arguments:
|
|
* - mode -- A string indicating how the file is to be opened. Accepts the
|
|
* same values as the builtin open() function.
|
|
* - bufsize -- The file's desired buffer size. Accepts the same values as
|
|
* the builtin open() function.
|
|
* - encoding -- The file's encoding. Accepts the same values as the
|
|
* builtin open() function.
|
|
* - errors -- A string indicating how encoding and decoding errors are to
|
|
* be handled. Accepts the same value as the builtin open() function.
|
|
*/
|
|
|
|
constructor() {
|
|
let [
|
|
flags,
|
|
encoding,
|
|
mode,
|
|
autoClose,
|
|
emitClose,
|
|
start,
|
|
end,
|
|
highWaterMark,
|
|
fs,
|
|
] = _parse_opts(arguments, {
|
|
flags: 'r',
|
|
encoding: undefined,
|
|
mode: undefined, // 0o666
|
|
autoClose: undefined, // true
|
|
emitClose: undefined, // false
|
|
start: undefined, // 0
|
|
end: undefined, // Infinity
|
|
highWaterMark: undefined, // 64 * 1024
|
|
fs: undefined,
|
|
});
|
|
|
|
// when this class is called as a function, redirect it to .call() method of itself
|
|
super('return arguments.callee.call.apply(arguments.callee, arguments)');
|
|
|
|
Object.defineProperty(this, 'name', {
|
|
get() {
|
|
return sub('FileType(%r)', flags);
|
|
},
|
|
});
|
|
this._flags = flags;
|
|
this._options = {};
|
|
if (encoding !== undefined) this._options.encoding = encoding;
|
|
if (mode !== undefined) this._options.mode = mode;
|
|
if (autoClose !== undefined) this._options.autoClose = autoClose;
|
|
if (emitClose !== undefined) this._options.emitClose = emitClose;
|
|
if (start !== undefined) this._options.start = start;
|
|
if (end !== undefined) this._options.end = end;
|
|
if (highWaterMark !== undefined)
|
|
this._options.highWaterMark = highWaterMark;
|
|
if (fs !== undefined) this._options.fs = fs;
|
|
}
|
|
|
|
call(string) {
|
|
// the special argument "-" means sys.std{in,out}
|
|
if (string === '-') {
|
|
if (this._flags.includes('r')) {
|
|
return process.stdin;
|
|
} else if (this._flags.includes('w')) {
|
|
return process.stdout;
|
|
} else {
|
|
let msg = sub('argument "-" with mode %r', this._flags);
|
|
throw new TypeError(msg);
|
|
}
|
|
}
|
|
|
|
// all other arguments are used as file names
|
|
let fd;
|
|
try {
|
|
fd = fs.openSync(string, this._flags, this._options.mode);
|
|
} catch (e) {
|
|
let args = { filename: string, error: e.message };
|
|
let message = "can't open '%(filename)s': %(error)s";
|
|
throw new ArgumentTypeError(sub(message, args));
|
|
}
|
|
|
|
let options = Object.assign({ fd, flags: this._flags }, this._options);
|
|
if (this._flags.includes('r')) {
|
|
return fs.createReadStream(undefined, options);
|
|
} else if (this._flags.includes('w')) {
|
|
return fs.createWriteStream(undefined, options);
|
|
} else {
|
|
let msg = sub('argument "%s" with mode %r', string, this._flags);
|
|
throw new TypeError(msg);
|
|
}
|
|
}
|
|
|
|
[util.inspect.custom]() {
|
|
let args = [this._flags];
|
|
let kwargs = Object.entries(this._options).map(([k, v]) => {
|
|
if (k === 'mode')
|
|
v = {
|
|
value: v,
|
|
[util.inspect.custom]() {
|
|
return '0o' + this.value.toString(8);
|
|
},
|
|
};
|
|
return [k, v];
|
|
});
|
|
let args_str = []
|
|
.concat(args.filter((arg) => arg !== -1).map(repr))
|
|
.concat(
|
|
kwargs
|
|
.filter(([, /*kw*/ arg]) => arg !== undefined)
|
|
.map(([kw, arg]) => sub('%s=%r', kw, arg))
|
|
)
|
|
.join(', ');
|
|
return sub('%s(%s)', this.constructor.name, args_str);
|
|
}
|
|
|
|
toString() {
|
|
return this[util.inspect.custom]();
|
|
}
|
|
}
|
|
);
|
|
|
|
// ===========================
|
|
// Optional and Positional Parsing
|
|
// ===========================
|
|
const Namespace = _callable(
|
|
class Namespace extends _AttributeHolder() {
|
|
/*
|
|
* Simple object for storing attributes.
|
|
*
|
|
* Implements equality by attribute names and values, and provides a simple
|
|
* string representation.
|
|
*/
|
|
|
|
constructor(options = {}) {
|
|
super();
|
|
Object.assign(this, options);
|
|
}
|
|
}
|
|
);
|
|
|
|
// unset string tag to mimic plain object
|
|
Namespace.prototype[Symbol.toStringTag] = undefined;
|
|
|
|
const _ActionsContainer = _camelcase_alias(
|
|
_callable(
|
|
class _ActionsContainer {
|
|
constructor() {
|
|
let [description, prefix_chars, argument_default, conflict_handler] =
|
|
_parse_opts(arguments, {
|
|
description: no_default,
|
|
prefix_chars: no_default,
|
|
argument_default: no_default,
|
|
conflict_handler: no_default,
|
|
});
|
|
|
|
this.description = description;
|
|
this.argument_default = argument_default;
|
|
this.prefix_chars = prefix_chars;
|
|
this.conflict_handler = conflict_handler;
|
|
|
|
// set up registries
|
|
this._registries = {};
|
|
|
|
// register actions
|
|
this.register('action', undefined, _StoreAction);
|
|
this.register('action', 'store', _StoreAction);
|
|
this.register('action', 'store_const', _StoreConstAction);
|
|
this.register('action', 'store_true', _StoreTrueAction);
|
|
this.register('action', 'store_false', _StoreFalseAction);
|
|
this.register('action', 'append', _AppendAction);
|
|
this.register('action', 'append_const', _AppendConstAction);
|
|
this.register('action', 'count', _CountAction);
|
|
this.register('action', 'help', _HelpAction);
|
|
this.register('action', 'version', _VersionAction);
|
|
this.register('action', 'parsers', _SubParsersAction);
|
|
this.register('action', 'extend', _ExtendAction);
|
|
// LEGACY (v1 compatibility): camelcase variants
|
|
['storeConst', 'storeTrue', 'storeFalse', 'appendConst'].forEach(
|
|
(old_name) => {
|
|
let new_name = _to_new_name(old_name);
|
|
this.register(
|
|
'action',
|
|
old_name,
|
|
util.deprecate(
|
|
this._registry_get('action', new_name),
|
|
sub(
|
|
'{action: "%s"} is renamed to {action: "%s"}',
|
|
old_name,
|
|
new_name
|
|
)
|
|
)
|
|
);
|
|
}
|
|
);
|
|
// end
|
|
|
|
// raise an exception if the conflict handler is invalid
|
|
this._get_handler();
|
|
|
|
// action storage
|
|
this._actions = [];
|
|
this._option_string_actions = {};
|
|
|
|
// groups
|
|
this._action_groups = [];
|
|
this._mutually_exclusive_groups = [];
|
|
|
|
// defaults storage
|
|
this._defaults = {};
|
|
|
|
// determines whether an "option" looks like a negative number
|
|
this._negative_number_matcher = /^-\d+$|^-\d*\.\d+$/;
|
|
|
|
// whether or not there are any optionals that look like negative
|
|
// numbers -- uses a list so it can be shared and edited
|
|
this._has_negative_number_optionals = [];
|
|
}
|
|
|
|
// ====================
|
|
// Registration methods
|
|
// ====================
|
|
register(registry_name, value, object) {
|
|
let registry = setdefault(this._registries, registry_name, {});
|
|
registry[value] = object;
|
|
}
|
|
|
|
_registry_get(registry_name, value, default_value = undefined) {
|
|
return getattr(this._registries[registry_name], value, default_value);
|
|
}
|
|
|
|
// ==================================
|
|
// Namespace default accessor methods
|
|
// ==================================
|
|
set_defaults(kwargs) {
|
|
Object.assign(this._defaults, kwargs);
|
|
|
|
// if these defaults match any existing arguments, replace
|
|
// the previous default on the object with the new one
|
|
for (let action of this._actions) {
|
|
if (action.dest in kwargs) {
|
|
action.default = kwargs[action.dest];
|
|
}
|
|
}
|
|
}
|
|
|
|
get_default(dest) {
|
|
for (let action of this._actions) {
|
|
if (action.dest === dest && action.default !== undefined) {
|
|
return action.default;
|
|
}
|
|
}
|
|
return this._defaults[dest];
|
|
}
|
|
|
|
// =======================
|
|
// Adding argument actions
|
|
// =======================
|
|
add_argument() {
|
|
/*
|
|
* add_argument(dest, ..., name=value, ...)
|
|
* add_argument(option_string, option_string, ..., name=value, ...)
|
|
*/
|
|
let [args, kwargs] = _parse_opts(arguments, {
|
|
'*args': no_default,
|
|
'**kwargs': no_default,
|
|
});
|
|
// LEGACY (v1 compatibility), old-style add_argument([ args ], { options })
|
|
if (args.length === 1 && Array.isArray(args[0])) {
|
|
args = args[0];
|
|
deprecate(
|
|
'argument-array',
|
|
sub(
|
|
'use add_argument(%(args)s, {...}) instead of add_argument([ %(args)s ], { ... })',
|
|
{
|
|
args: args.map(repr).join(', '),
|
|
}
|
|
)
|
|
);
|
|
}
|
|
// end
|
|
|
|
// if no positional args are supplied or only one is supplied and
|
|
// it doesn't look like an option string, parse a positional
|
|
// argument
|
|
let chars = this.prefix_chars;
|
|
if (
|
|
!args.length ||
|
|
(args.length === 1 && !chars.includes(args[0][0]))
|
|
) {
|
|
if (args.length && 'dest' in kwargs) {
|
|
throw new TypeError('dest supplied twice for positional argument');
|
|
}
|
|
kwargs = this._get_positional_kwargs(...args, kwargs);
|
|
|
|
// otherwise, we're adding an optional argument
|
|
} else {
|
|
kwargs = this._get_optional_kwargs(...args, kwargs);
|
|
}
|
|
|
|
// if no default was supplied, use the parser-level default
|
|
if (!('default' in kwargs)) {
|
|
let dest = kwargs.dest;
|
|
if (dest in this._defaults) {
|
|
kwargs.default = this._defaults[dest];
|
|
} else if (this.argument_default !== undefined) {
|
|
kwargs.default = this.argument_default;
|
|
}
|
|
}
|
|
|
|
// create the action object, and add it to the parser
|
|
let action_class = this._pop_action_class(kwargs);
|
|
if (typeof action_class !== 'function') {
|
|
throw new TypeError(sub('unknown action "%s"', action_class));
|
|
}
|
|
// eslint-disable-next-line new-cap
|
|
let action = new action_class(kwargs);
|
|
|
|
// raise an error if the action type is not callable
|
|
let type_func = this._registry_get('type', action.type, action.type);
|
|
if (typeof type_func !== 'function') {
|
|
throw new TypeError(sub('%r is not callable', type_func));
|
|
}
|
|
|
|
if (type_func === FileType) {
|
|
throw new TypeError(
|
|
sub(
|
|
'%r is a FileType class object, instance of it' +
|
|
' must be passed',
|
|
type_func
|
|
)
|
|
);
|
|
}
|
|
|
|
// raise an error if the metavar does not match the type
|
|
if ('_get_formatter' in this) {
|
|
try {
|
|
this._get_formatter()._format_args(action, undefined);
|
|
} catch (err) {
|
|
// check for 'invalid nargs value' is an artifact of TypeError and ValueError in js being the same
|
|
if (
|
|
err instanceof TypeError &&
|
|
err.message !== 'invalid nargs value'
|
|
) {
|
|
throw new TypeError(
|
|
'length of metavar tuple does not match nargs'
|
|
);
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return this._add_action(action);
|
|
}
|
|
|
|
add_argument_group() {
|
|
let group = _ArgumentGroup(this, ...arguments);
|
|
this._action_groups.push(group);
|
|
return group;
|
|
}
|
|
|
|
add_mutually_exclusive_group() {
|
|
// eslint-disable-next-line no-use-before-define
|
|
let group = _MutuallyExclusiveGroup(this, ...arguments);
|
|
this._mutually_exclusive_groups.push(group);
|
|
return group;
|
|
}
|
|
|
|
_add_action(action) {
|
|
// resolve any conflicts
|
|
this._check_conflict(action);
|
|
|
|
// add to actions list
|
|
this._actions.push(action);
|
|
action.container = this;
|
|
|
|
// index the action by any option strings it has
|
|
for (let option_string of action.option_strings) {
|
|
this._option_string_actions[option_string] = action;
|
|
}
|
|
|
|
// set the flag if any option strings look like negative numbers
|
|
for (let option_string of action.option_strings) {
|
|
if (this._negative_number_matcher.test(option_string)) {
|
|
if (!this._has_negative_number_optionals.length) {
|
|
this._has_negative_number_optionals.push(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the created action
|
|
return action;
|
|
}
|
|
|
|
_remove_action(action) {
|
|
_array_remove(this._actions, action);
|
|
}
|
|
|
|
_add_container_actions(container) {
|
|
// collect groups by titles
|
|
let title_group_map = {};
|
|
for (let group of this._action_groups) {
|
|
if (group.title in title_group_map) {
|
|
let msg = 'cannot merge actions - two groups are named %r';
|
|
throw new TypeError(sub(msg, group.title));
|
|
}
|
|
title_group_map[group.title] = group;
|
|
}
|
|
|
|
// map each action to its group
|
|
let group_map = new Map();
|
|
for (let group of container._action_groups) {
|
|
// if a group with the title exists, use that, otherwise
|
|
// create a new group matching the container's group
|
|
if (!(group.title in title_group_map)) {
|
|
title_group_map[group.title] = this.add_argument_group({
|
|
title: group.title,
|
|
description: group.description,
|
|
conflict_handler: group.conflict_handler,
|
|
});
|
|
}
|
|
|
|
// map the actions to their new group
|
|
for (let action of group._group_actions) {
|
|
group_map.set(action, title_group_map[group.title]);
|
|
}
|
|
}
|
|
|
|
// add container's mutually exclusive groups
|
|
// NOTE: if add_mutually_exclusive_group ever gains title= and
|
|
// description= then this code will need to be expanded as above
|
|
for (let group of container._mutually_exclusive_groups) {
|
|
let mutex_group = this.add_mutually_exclusive_group({
|
|
required: group.required,
|
|
});
|
|
|
|
// map the actions to their new mutex group
|
|
for (let action of group._group_actions) {
|
|
group_map.set(action, mutex_group);
|
|
}
|
|
}
|
|
|
|
// add all actions to this container or their group
|
|
for (let action of container._actions) {
|
|
group_map.get(action)._add_action(action);
|
|
}
|
|
}
|
|
|
|
_get_positional_kwargs() {
|
|
let [dest, kwargs] = _parse_opts(arguments, {
|
|
dest: no_default,
|
|
'**kwargs': no_default,
|
|
});
|
|
|
|
// make sure required is not specified
|
|
if ('required' in kwargs) {
|
|
let msg = "'required' is an invalid argument for positionals";
|
|
throw new TypeError(msg);
|
|
}
|
|
|
|
// mark positional arguments as required if at least one is
|
|
// always required
|
|
if (![OPTIONAL, ZERO_OR_MORE].includes(kwargs.nargs)) {
|
|
kwargs.required = true;
|
|
}
|
|
if (kwargs.nargs === ZERO_OR_MORE && !('default' in kwargs)) {
|
|
kwargs.required = true;
|
|
}
|
|
|
|
// return the keyword arguments with no option strings
|
|
return Object.assign(kwargs, { dest, option_strings: [] });
|
|
}
|
|
|
|
_get_optional_kwargs() {
|
|
let [args, kwargs] = _parse_opts(arguments, {
|
|
'*args': no_default,
|
|
'**kwargs': no_default,
|
|
});
|
|
|
|
// determine short and long option strings
|
|
let option_strings = [];
|
|
let long_option_strings = [];
|
|
let option_string;
|
|
for (option_string of args) {
|
|
// error on strings that don't start with an appropriate prefix
|
|
if (!this.prefix_chars.includes(option_string[0])) {
|
|
let args = {
|
|
option: option_string,
|
|
prefix_chars: this.prefix_chars,
|
|
};
|
|
let msg =
|
|
'invalid option string %(option)r: ' +
|
|
'must start with a character %(prefix_chars)r';
|
|
throw new TypeError(sub(msg, args));
|
|
}
|
|
|
|
// strings starting with two prefix characters are long options
|
|
option_strings.push(option_string);
|
|
if (
|
|
option_string.length > 1 &&
|
|
this.prefix_chars.includes(option_string[1])
|
|
) {
|
|
long_option_strings.push(option_string);
|
|
}
|
|
}
|
|
|
|
// infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
|
|
let dest = kwargs.dest;
|
|
delete kwargs.dest;
|
|
if (dest === undefined) {
|
|
let dest_option_string;
|
|
if (long_option_strings.length) {
|
|
dest_option_string = long_option_strings[0];
|
|
} else {
|
|
dest_option_string = option_strings[0];
|
|
}
|
|
dest = _string_lstrip(dest_option_string, this.prefix_chars);
|
|
if (!dest) {
|
|
let msg = 'dest= is required for options like %r';
|
|
throw new TypeError(sub(msg, option_string));
|
|
}
|
|
dest = dest.replace(/-/g, '_');
|
|
}
|
|
|
|
// return the updated keyword arguments
|
|
return Object.assign(kwargs, { dest, option_strings });
|
|
}
|
|
|
|
_pop_action_class(kwargs, default_value = undefined) {
|
|
let action = getattr(kwargs, 'action', default_value);
|
|
delete kwargs.action;
|
|
return this._registry_get('action', action, action);
|
|
}
|
|
|
|
_get_handler() {
|
|
// determine function from conflict handler string
|
|
let handler_func_name = sub(
|
|
'_handle_conflict_%s',
|
|
this.conflict_handler
|
|
);
|
|
if (typeof this[handler_func_name] === 'function') {
|
|
return this[handler_func_name];
|
|
} else {
|
|
let msg = 'invalid conflict_resolution value: %r';
|
|
throw new TypeError(sub(msg, this.conflict_handler));
|
|
}
|
|
}
|
|
|
|
_check_conflict(action) {
|
|
// find all options that conflict with this option
|
|
let confl_optionals = [];
|
|
for (let option_string of action.option_strings) {
|
|
if (hasattr(this._option_string_actions, option_string)) {
|
|
let confl_optional = this._option_string_actions[option_string];
|
|
confl_optionals.push([option_string, confl_optional]);
|
|
}
|
|
}
|
|
|
|
// resolve any conflicts
|
|
if (confl_optionals.length) {
|
|
let conflict_handler = this._get_handler();
|
|
conflict_handler.call(this, action, confl_optionals);
|
|
}
|
|
}
|
|
|
|
_handle_conflict_error(action, conflicting_actions) {
|
|
let message =
|
|
conflicting_actions.length === 1 ?
|
|
'conflicting option string: %s'
|
|
: 'conflicting option strings: %s';
|
|
let conflict_string = conflicting_actions
|
|
.map(([option_string /*, action*/]) => option_string)
|
|
.join(', ');
|
|
throw new ArgumentError(action, sub(message, conflict_string));
|
|
}
|
|
|
|
_handle_conflict_resolve(action, conflicting_actions) {
|
|
// remove all conflicting options
|
|
for (let [option_string, action] of conflicting_actions) {
|
|
// remove the conflicting option
|
|
_array_remove(action.option_strings, option_string);
|
|
delete this._option_string_actions[option_string];
|
|
|
|
// if the option now has no option string, remove it from the
|
|
// container holding it
|
|
if (!action.option_strings.length) {
|
|
action.container._remove_action(action);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
const _ArgumentGroup = _callable(
|
|
class _ArgumentGroup extends _ActionsContainer {
|
|
constructor() {
|
|
let [container, title, description, kwargs] = _parse_opts(arguments, {
|
|
container: no_default,
|
|
title: undefined,
|
|
description: undefined,
|
|
'**kwargs': no_default,
|
|
});
|
|
|
|
// add any missing keyword arguments by checking the container
|
|
setdefault(kwargs, 'conflict_handler', container.conflict_handler);
|
|
setdefault(kwargs, 'prefix_chars', container.prefix_chars);
|
|
setdefault(kwargs, 'argument_default', container.argument_default);
|
|
super(Object.assign({ description }, kwargs));
|
|
|
|
// group attributes
|
|
this.title = title;
|
|
this._group_actions = [];
|
|
|
|
// share most attributes with the container
|
|
this._registries = container._registries;
|
|
this._actions = container._actions;
|
|
this._option_string_actions = container._option_string_actions;
|
|
this._defaults = container._defaults;
|
|
this._has_negative_number_optionals =
|
|
container._has_negative_number_optionals;
|
|
this._mutually_exclusive_groups = container._mutually_exclusive_groups;
|
|
}
|
|
|
|
_add_action(action) {
|
|
action = super._add_action(action);
|
|
this._group_actions.push(action);
|
|
return action;
|
|
}
|
|
|
|
_remove_action(action) {
|
|
super._remove_action(action);
|
|
_array_remove(this._group_actions, action);
|
|
}
|
|
}
|
|
);
|
|
|
|
const _MutuallyExclusiveGroup = _callable(
|
|
class _MutuallyExclusiveGroup extends _ArgumentGroup {
|
|
constructor() {
|
|
let [container, required] = _parse_opts(arguments, {
|
|
container: no_default,
|
|
required: false,
|
|
});
|
|
|
|
super(container);
|
|
this.required = required;
|
|
this._container = container;
|
|
}
|
|
|
|
_add_action(action) {
|
|
if (action.required) {
|
|
let msg = 'mutually exclusive arguments must be optional';
|
|
throw new TypeError(msg);
|
|
}
|
|
action = this._container._add_action(action);
|
|
this._group_actions.push(action);
|
|
return action;
|
|
}
|
|
|
|
_remove_action(action) {
|
|
this._container._remove_action(action);
|
|
_array_remove(this._group_actions, action);
|
|
}
|
|
}
|
|
);
|
|
|
|
const ArgumentParser = _camelcase_alias(
|
|
_callable(
|
|
class ArgumentParser extends _AttributeHolder(_ActionsContainer) {
|
|
/*
|
|
* Object for parsing command line strings into Python objects.
|
|
*
|
|
* Keyword Arguments:
|
|
* - prog -- The name of the program (default: sys.argv[0])
|
|
* - usage -- A usage message (default: auto-generated from arguments)
|
|
* - description -- A description of what the program does
|
|
* - epilog -- Text following the argument descriptions
|
|
* - parents -- Parsers whose arguments should be copied into this one
|
|
* - formatter_class -- HelpFormatter class for printing help messages
|
|
* - prefix_chars -- Characters that prefix optional arguments
|
|
* - fromfile_prefix_chars -- Characters that prefix files containing
|
|
* additional arguments
|
|
* - argument_default -- The default value for all arguments
|
|
* - conflict_handler -- String indicating how to handle conflicts
|
|
* - add_help -- Add a -h/-help option
|
|
* - allow_abbrev -- Allow long options to be abbreviated unambiguously
|
|
* - exit_on_error -- Determines whether or not ArgumentParser exits with
|
|
* error info when an error occurs
|
|
*/
|
|
|
|
constructor() {
|
|
let [
|
|
prog,
|
|
usage,
|
|
description,
|
|
epilog,
|
|
parents,
|
|
formatter_class,
|
|
prefix_chars,
|
|
fromfile_prefix_chars,
|
|
argument_default,
|
|
conflict_handler,
|
|
add_help,
|
|
allow_abbrev,
|
|
exit_on_error,
|
|
debug, // LEGACY (v1 compatibility), debug mode
|
|
version, // LEGACY (v1 compatibility), version
|
|
] = _parse_opts(arguments, {
|
|
prog: undefined,
|
|
usage: undefined,
|
|
description: undefined,
|
|
epilog: undefined,
|
|
parents: [],
|
|
formatter_class: HelpFormatter,
|
|
prefix_chars: '-',
|
|
fromfile_prefix_chars: undefined,
|
|
argument_default: undefined,
|
|
conflict_handler: 'error',
|
|
add_help: true,
|
|
allow_abbrev: true,
|
|
exit_on_error: true,
|
|
debug: undefined, // LEGACY (v1 compatibility), debug mode
|
|
version: undefined, // LEGACY (v1 compatibility), version
|
|
});
|
|
|
|
// LEGACY (v1 compatibility)
|
|
if (debug !== undefined) {
|
|
deprecate(
|
|
'debug',
|
|
'The "debug" argument to ArgumentParser is deprecated. Please ' +
|
|
'override ArgumentParser.exit function instead.'
|
|
);
|
|
}
|
|
|
|
if (version !== undefined) {
|
|
deprecate(
|
|
'version',
|
|
'The "version" argument to ArgumentParser is deprecated. Please use ' +
|
|
"add_argument(..., { action: 'version', version: 'N', ... }) instead."
|
|
);
|
|
}
|
|
// end
|
|
|
|
super({
|
|
description,
|
|
prefix_chars,
|
|
argument_default,
|
|
conflict_handler,
|
|
});
|
|
|
|
// default setting for prog
|
|
if (prog === undefined) {
|
|
prog = path.basename(get_argv()[0] || '');
|
|
}
|
|
|
|
this.prog = prog;
|
|
this.usage = usage;
|
|
this.epilog = epilog;
|
|
this.formatter_class = formatter_class;
|
|
this.fromfile_prefix_chars = fromfile_prefix_chars;
|
|
this.add_help = add_help;
|
|
this.allow_abbrev = allow_abbrev;
|
|
this.exit_on_error = exit_on_error;
|
|
// LEGACY (v1 compatibility), debug mode
|
|
this.debug = debug;
|
|
// end
|
|
|
|
this._positionals = this.add_argument_group('positional arguments');
|
|
this._optionals = this.add_argument_group('optional arguments');
|
|
this._subparsers = undefined;
|
|
|
|
// register types
|
|
function identity(string) {
|
|
return string;
|
|
}
|
|
this.register('type', undefined, identity);
|
|
this.register('type', null, identity);
|
|
this.register('type', 'auto', identity);
|
|
this.register('type', 'int', function (x) {
|
|
let result = Number(x);
|
|
if (!Number.isInteger(result)) {
|
|
throw new TypeError(sub('could not convert string to int: %r', x));
|
|
}
|
|
return result;
|
|
});
|
|
this.register('type', 'float', function (x) {
|
|
let result = Number(x);
|
|
if (isNaN(result)) {
|
|
throw new TypeError(
|
|
sub('could not convert string to float: %r', x)
|
|
);
|
|
}
|
|
return result;
|
|
});
|
|
this.register('type', 'str', String);
|
|
// LEGACY (v1 compatibility): custom types
|
|
this.register(
|
|
'type',
|
|
'string',
|
|
util.deprecate(
|
|
String,
|
|
'use {type:"str"} or {type:String} instead of {type:"string"}'
|
|
)
|
|
);
|
|
// end
|
|
|
|
// add help argument if necessary
|
|
// (using explicit default to override global argument_default)
|
|
let default_prefix = prefix_chars.includes('-') ? '-' : prefix_chars[0];
|
|
if (this.add_help) {
|
|
this.add_argument(
|
|
default_prefix + 'h',
|
|
default_prefix.repeat(2) + 'help',
|
|
{
|
|
action: 'help',
|
|
default: SUPPRESS,
|
|
help: 'show this help message and exit',
|
|
}
|
|
);
|
|
}
|
|
// LEGACY (v1 compatibility), version
|
|
if (version) {
|
|
this.add_argument(
|
|
default_prefix + 'v',
|
|
default_prefix.repeat(2) + 'version',
|
|
{
|
|
action: 'version',
|
|
default: SUPPRESS,
|
|
version: this.version,
|
|
help: "show program's version number and exit",
|
|
}
|
|
);
|
|
}
|
|
// end
|
|
|
|
// add parent arguments and defaults
|
|
for (let parent of parents) {
|
|
this._add_container_actions(parent);
|
|
Object.assign(this._defaults, parent._defaults);
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// Pretty __repr__ methods
|
|
// =======================
|
|
_get_kwargs() {
|
|
let names = [
|
|
'prog',
|
|
'usage',
|
|
'description',
|
|
'formatter_class',
|
|
'conflict_handler',
|
|
'add_help',
|
|
];
|
|
return names.map((name) => [name, getattr(this, name)]);
|
|
}
|
|
|
|
// ==================================
|
|
// Optional/Positional adding methods
|
|
// ==================================
|
|
add_subparsers() {
|
|
let [kwargs] = _parse_opts(arguments, {
|
|
'**kwargs': no_default,
|
|
});
|
|
|
|
if (this._subparsers !== undefined) {
|
|
this.error('cannot have multiple subparser arguments');
|
|
}
|
|
|
|
// add the parser class to the arguments if it's not present
|
|
setdefault(kwargs, 'parser_class', this.constructor);
|
|
|
|
if ('title' in kwargs || 'description' in kwargs) {
|
|
let title = getattr(kwargs, 'title', 'subcommands');
|
|
let description = getattr(kwargs, 'description', undefined);
|
|
delete kwargs.title;
|
|
delete kwargs.description;
|
|
this._subparsers = this.add_argument_group(title, description);
|
|
} else {
|
|
this._subparsers = this._positionals;
|
|
}
|
|
|
|
// prog defaults to the usage message of this parser, skipping
|
|
// optional arguments and with no "usage:" prefix
|
|
if (kwargs.prog === undefined) {
|
|
let formatter = this._get_formatter();
|
|
let positionals = this._get_positional_actions();
|
|
let groups = this._mutually_exclusive_groups;
|
|
formatter.add_usage(this.usage, positionals, groups, '');
|
|
kwargs.prog = formatter.format_help().trim();
|
|
}
|
|
|
|
// create the parsers action and add it to the positionals list
|
|
let parsers_class = this._pop_action_class(kwargs, 'parsers');
|
|
// eslint-disable-next-line new-cap
|
|
let action = new parsers_class(
|
|
Object.assign({ option_strings: [] }, kwargs)
|
|
);
|
|
this._subparsers._add_action(action);
|
|
|
|
// return the created parsers action
|
|
return action;
|
|
}
|
|
|
|
_add_action(action) {
|
|
if (action.option_strings.length) {
|
|
this._optionals._add_action(action);
|
|
} else {
|
|
this._positionals._add_action(action);
|
|
}
|
|
return action;
|
|
}
|
|
|
|
_get_optional_actions() {
|
|
return this._actions.filter((action) => action.option_strings.length);
|
|
}
|
|
|
|
_get_positional_actions() {
|
|
return this._actions.filter((action) => !action.option_strings.length);
|
|
}
|
|
|
|
// =====================================
|
|
// Command line argument parsing methods
|
|
// =====================================
|
|
parse_args(args = undefined, namespace = undefined) {
|
|
let argv;
|
|
[args, argv] = this.parse_known_args(args, namespace);
|
|
if (argv && argv.length > 0) {
|
|
let msg = 'unrecognized arguments: %s';
|
|
this.error(sub(msg, argv.join(' ')));
|
|
}
|
|
return args;
|
|
}
|
|
|
|
parse_known_args(args = undefined, namespace = undefined) {
|
|
if (args === undefined) {
|
|
args = get_argv().slice(1);
|
|
}
|
|
|
|
// default Namespace built from parser defaults
|
|
if (namespace === undefined) {
|
|
namespace = new Namespace();
|
|
}
|
|
|
|
// add any action defaults that aren't present
|
|
for (let action of this._actions) {
|
|
if (action.dest !== SUPPRESS) {
|
|
if (!hasattr(namespace, action.dest)) {
|
|
if (action.default !== SUPPRESS) {
|
|
setattr(namespace, action.dest, action.default);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add any parser defaults that aren't present
|
|
for (let dest of Object.keys(this._defaults)) {
|
|
if (!hasattr(namespace, dest)) {
|
|
setattr(namespace, dest, this._defaults[dest]);
|
|
}
|
|
}
|
|
|
|
// parse the arguments and exit if there are any errors
|
|
if (this.exit_on_error) {
|
|
try {
|
|
[namespace, args] = this._parse_known_args(args, namespace);
|
|
} catch (err) {
|
|
if (err instanceof ArgumentError) {
|
|
this.error(err.message);
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
} else {
|
|
[namespace, args] = this._parse_known_args(args, namespace);
|
|
}
|
|
|
|
if (hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) {
|
|
args = args.concat(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR));
|
|
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR);
|
|
}
|
|
|
|
return [namespace, args];
|
|
}
|
|
|
|
_parse_known_args(arg_strings, namespace) {
|
|
// replace arg strings that are file references
|
|
if (this.fromfile_prefix_chars !== undefined) {
|
|
arg_strings = this._read_args_from_files(arg_strings);
|
|
}
|
|
|
|
// map all mutually exclusive arguments to the other arguments
|
|
// they can't occur with
|
|
let action_conflicts = new Map();
|
|
for (let mutex_group of this._mutually_exclusive_groups) {
|
|
let group_actions = mutex_group._group_actions;
|
|
for (let [i, mutex_action] of Object.entries(
|
|
mutex_group._group_actions
|
|
)) {
|
|
let conflicts = action_conflicts.get(mutex_action) || [];
|
|
conflicts = conflicts.concat(group_actions.slice(0, +i));
|
|
conflicts = conflicts.concat(group_actions.slice(+i + 1));
|
|
action_conflicts.set(mutex_action, conflicts);
|
|
}
|
|
}
|
|
|
|
// find all option indices, and determine the arg_string_pattern
|
|
// which has an 'O' if there is an option at an index,
|
|
// an 'A' if there is an argument, or a '-' if there is a '--'
|
|
let option_string_indices = {};
|
|
let arg_string_pattern_parts = [];
|
|
let arg_strings_iter = Object.entries(arg_strings)[Symbol.iterator]();
|
|
for (let [i, arg_string] of arg_strings_iter) {
|
|
// all args after -- are non-options
|
|
if (arg_string === '--') {
|
|
arg_string_pattern_parts.push('-');
|
|
for ([i, arg_string] of arg_strings_iter) {
|
|
arg_string_pattern_parts.push('A');
|
|
}
|
|
|
|
// otherwise, add the arg to the arg strings
|
|
// and note the index if it was an option
|
|
} else {
|
|
let option_tuple = this._parse_optional(arg_string);
|
|
let pattern;
|
|
if (option_tuple === undefined) {
|
|
pattern = 'A';
|
|
} else {
|
|
option_string_indices[i] = option_tuple;
|
|
pattern = 'O';
|
|
}
|
|
arg_string_pattern_parts.push(pattern);
|
|
}
|
|
}
|
|
|
|
// join the pieces together to form the pattern
|
|
let arg_strings_pattern = arg_string_pattern_parts.join('');
|
|
|
|
// converts arg strings to the appropriate and then takes the action
|
|
let seen_actions = new Set();
|
|
let seen_non_default_actions = new Set();
|
|
let extras;
|
|
|
|
let take_action = (
|
|
action,
|
|
argument_strings,
|
|
option_string = undefined
|
|
) => {
|
|
seen_actions.add(action);
|
|
let argument_values = this._get_values(action, argument_strings);
|
|
|
|
// error if this argument is not allowed with other previously
|
|
// seen arguments, assuming that actions that use the default
|
|
// value don't really count as "present"
|
|
if (argument_values !== action.default) {
|
|
seen_non_default_actions.add(action);
|
|
for (let conflict_action of action_conflicts.get(action) || []) {
|
|
if (seen_non_default_actions.has(conflict_action)) {
|
|
let msg = 'not allowed with argument %s';
|
|
let action_name = _get_action_name(conflict_action);
|
|
throw new ArgumentError(action, sub(msg, action_name));
|
|
}
|
|
}
|
|
}
|
|
|
|
// take the action if we didn't receive a SUPPRESS value
|
|
// (e.g. from a default)
|
|
if (argument_values !== SUPPRESS) {
|
|
action(this, namespace, argument_values, option_string);
|
|
}
|
|
};
|
|
|
|
// function to convert arg_strings into an optional action
|
|
let consume_optional = (start_index) => {
|
|
// get the optional identified at this index
|
|
let option_tuple = option_string_indices[start_index];
|
|
let [action, option_string, explicit_arg] = option_tuple;
|
|
|
|
// identify additional optionals in the same arg string
|
|
// (e.g. -xyz is the same as -x -y -z if no args are required)
|
|
let action_tuples = [];
|
|
let stop;
|
|
for (;;) {
|
|
// if we found no optional action, skip it
|
|
if (action === undefined) {
|
|
extras.push(arg_strings[start_index]);
|
|
return start_index + 1;
|
|
}
|
|
|
|
// if there is an explicit argument, try to match the
|
|
// optional's string arguments to only this
|
|
if (explicit_arg !== undefined) {
|
|
let arg_count = this._match_argument(action, 'A');
|
|
|
|
// if the action is a single-dash option and takes no
|
|
// arguments, try to parse more single-dash options out
|
|
// of the tail of the option string
|
|
let chars = this.prefix_chars;
|
|
if (arg_count === 0 && !chars.includes(option_string[1])) {
|
|
action_tuples.push([action, [], option_string]);
|
|
let char = option_string[0];
|
|
option_string = char + explicit_arg[0];
|
|
let new_explicit_arg = explicit_arg.slice(1) || undefined;
|
|
let optionals_map = this._option_string_actions;
|
|
if (hasattr(optionals_map, option_string)) {
|
|
action = optionals_map[option_string];
|
|
explicit_arg = new_explicit_arg;
|
|
} else {
|
|
let msg = 'ignored explicit argument %r';
|
|
throw new ArgumentError(action, sub(msg, explicit_arg));
|
|
}
|
|
|
|
// if the action expect exactly one argument, we've
|
|
// successfully matched the option; exit the loop
|
|
} else if (arg_count === 1) {
|
|
stop = start_index + 1;
|
|
let args = [explicit_arg];
|
|
action_tuples.push([action, args, option_string]);
|
|
break;
|
|
|
|
// error if a double-dash option did not use the
|
|
// explicit argument
|
|
} else {
|
|
let msg = 'ignored explicit argument %r';
|
|
throw new ArgumentError(action, sub(msg, explicit_arg));
|
|
}
|
|
|
|
// if there is no explicit argument, try to match the
|
|
// optional's string arguments with the following strings
|
|
// if successful, exit the loop
|
|
} else {
|
|
let start = start_index + 1;
|
|
let selected_patterns = arg_strings_pattern.slice(start);
|
|
let arg_count = this._match_argument(action, selected_patterns);
|
|
stop = start + arg_count;
|
|
let args = arg_strings.slice(start, stop);
|
|
action_tuples.push([action, args, option_string]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// add the Optional to the list and return the index at which
|
|
// the Optional's string args stopped
|
|
assert(action_tuples.length);
|
|
for (let [action, args, option_string] of action_tuples) {
|
|
take_action(action, args, option_string);
|
|
}
|
|
return stop;
|
|
};
|
|
|
|
// the list of Positionals left to be parsed; this is modified
|
|
// by consume_positionals()
|
|
let positionals = this._get_positional_actions();
|
|
|
|
// function to convert arg_strings into positional actions
|
|
let consume_positionals = (start_index) => {
|
|
// match as many Positionals as possible
|
|
let selected_pattern = arg_strings_pattern.slice(start_index);
|
|
let arg_counts = this._match_arguments_partial(
|
|
positionals,
|
|
selected_pattern
|
|
);
|
|
|
|
// slice off the appropriate arg strings for each Positional
|
|
// and add the Positional and its args to the list
|
|
for (
|
|
let i = 0;
|
|
i < positionals.length && i < arg_counts.length;
|
|
i++
|
|
) {
|
|
let action = positionals[i];
|
|
let arg_count = arg_counts[i];
|
|
let args = arg_strings.slice(start_index, start_index + arg_count);
|
|
start_index += arg_count;
|
|
take_action(action, args);
|
|
}
|
|
|
|
// slice off the Positionals that we just parsed and return the
|
|
// index at which the Positionals' string args stopped
|
|
positionals = positionals.slice(arg_counts.length);
|
|
return start_index;
|
|
};
|
|
|
|
// consume Positionals and Optionals alternately, until we have
|
|
// passed the last option string
|
|
extras = [];
|
|
let start_index = 0;
|
|
let max_option_string_index = Math.max(
|
|
-1,
|
|
...Object.keys(option_string_indices).map(Number)
|
|
);
|
|
while (start_index <= max_option_string_index) {
|
|
// consume any Positionals preceding the next option
|
|
let next_option_string_index = Math.min(
|
|
// eslint-disable-next-line no-loop-func
|
|
...Object.keys(option_string_indices)
|
|
.map(Number)
|
|
.filter((index) => index >= start_index)
|
|
);
|
|
if (start_index !== next_option_string_index) {
|
|
let positionals_end_index = consume_positionals(start_index);
|
|
|
|
// only try to parse the next optional if we didn't consume
|
|
// the option string during the positionals parsing
|
|
if (positionals_end_index > start_index) {
|
|
start_index = positionals_end_index;
|
|
continue;
|
|
} else {
|
|
start_index = positionals_end_index;
|
|
}
|
|
}
|
|
|
|
// if we consumed all the positionals we could and we're not
|
|
// at the index of an option string, there were extra arguments
|
|
if (!(start_index in option_string_indices)) {
|
|
let strings = arg_strings.slice(
|
|
start_index,
|
|
next_option_string_index
|
|
);
|
|
extras = extras.concat(strings);
|
|
start_index = next_option_string_index;
|
|
}
|
|
|
|
// consume the next optional and any arguments for it
|
|
start_index = consume_optional(start_index);
|
|
}
|
|
|
|
// consume any positionals following the last Optional
|
|
let stop_index = consume_positionals(start_index);
|
|
|
|
// if we didn't consume all the argument strings, there were extras
|
|
extras = extras.concat(arg_strings.slice(stop_index));
|
|
|
|
// make sure all required actions were present and also convert
|
|
// action defaults which were not given as arguments
|
|
let required_actions = [];
|
|
for (let action of this._actions) {
|
|
if (!seen_actions.has(action)) {
|
|
if (action.required) {
|
|
required_actions.push(_get_action_name(action));
|
|
} else {
|
|
// Convert action default now instead of doing it before
|
|
// parsing arguments to avoid calling convert functions
|
|
// twice (which may fail) if the argument was given, but
|
|
// only if it was defined already in the namespace
|
|
if (
|
|
action.default !== undefined &&
|
|
typeof action.default === 'string' &&
|
|
hasattr(namespace, action.dest) &&
|
|
action.default === getattr(namespace, action.dest)
|
|
) {
|
|
setattr(
|
|
namespace,
|
|
action.dest,
|
|
this._get_value(action, action.default)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (required_actions.length) {
|
|
this.error(
|
|
sub(
|
|
'the following arguments are required: %s',
|
|
required_actions.join(', ')
|
|
)
|
|
);
|
|
}
|
|
|
|
// make sure all required groups had one option present
|
|
for (let group of this._mutually_exclusive_groups) {
|
|
if (group.required) {
|
|
let no_actions_used = true;
|
|
for (let action of group._group_actions) {
|
|
if (seen_non_default_actions.has(action)) {
|
|
no_actions_used = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if no actions were used, report the error
|
|
if (no_actions_used) {
|
|
let names = group._group_actions
|
|
.filter((action) => action.help !== SUPPRESS)
|
|
.map((action) => _get_action_name(action));
|
|
let msg = 'one of the arguments %s is required';
|
|
this.error(sub(msg, names.join(' ')));
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the updated namespace and the extra arguments
|
|
return [namespace, extras];
|
|
}
|
|
|
|
_read_args_from_files(arg_strings) {
|
|
// expand arguments referencing files
|
|
let new_arg_strings = [];
|
|
for (let arg_string of arg_strings) {
|
|
// for regular arguments, just add them back into the list
|
|
if (
|
|
!arg_string ||
|
|
!this.fromfile_prefix_chars.includes(arg_string[0])
|
|
) {
|
|
new_arg_strings.push(arg_string);
|
|
|
|
// replace arguments referencing files with the file content
|
|
} else {
|
|
try {
|
|
let args_file = fs.readFileSync(arg_string.slice(1), 'utf8');
|
|
let arg_strings = [];
|
|
for (let arg_line of splitlines(args_file)) {
|
|
for (let arg of this.convert_arg_line_to_args(arg_line)) {
|
|
arg_strings.push(arg);
|
|
}
|
|
}
|
|
arg_strings = this._read_args_from_files(arg_strings);
|
|
new_arg_strings = new_arg_strings.concat(arg_strings);
|
|
} catch (err) {
|
|
this.error(err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the modified argument list
|
|
return new_arg_strings;
|
|
}
|
|
|
|
convert_arg_line_to_args(arg_line) {
|
|
return [arg_line];
|
|
}
|
|
|
|
_match_argument(action, arg_strings_pattern) {
|
|
// match the pattern for this action to the arg strings
|
|
let nargs_pattern = this._get_nargs_pattern(action);
|
|
let match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern));
|
|
|
|
// raise an exception if we weren't able to find a match
|
|
if (match === null) {
|
|
let nargs_errors = {
|
|
undefined: 'expected one argument',
|
|
[OPTIONAL]: 'expected at most one argument',
|
|
[ONE_OR_MORE]: 'expected at least one argument',
|
|
};
|
|
let msg = nargs_errors[action.nargs];
|
|
if (msg === undefined) {
|
|
msg = sub(
|
|
action.nargs === 1 ?
|
|
'expected %s argument'
|
|
: 'expected %s arguments',
|
|
action.nargs
|
|
);
|
|
}
|
|
throw new ArgumentError(action, msg);
|
|
}
|
|
|
|
// return the number of arguments matched
|
|
return match[1].length;
|
|
}
|
|
|
|
_match_arguments_partial(actions, arg_strings_pattern) {
|
|
// progressively shorten the actions list by slicing off the
|
|
// final actions until we find a match
|
|
let result = [];
|
|
for (let i of range(actions.length, 0, -1)) {
|
|
let actions_slice = actions.slice(0, i);
|
|
let pattern = actions_slice
|
|
.map((action) => this._get_nargs_pattern(action))
|
|
.join('');
|
|
let match = arg_strings_pattern.match(new RegExp('^' + pattern));
|
|
if (match !== null) {
|
|
result = result.concat(
|
|
match.slice(1).map((string) => string.length)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// return the list of arg string counts
|
|
return result;
|
|
}
|
|
|
|
_parse_optional(arg_string) {
|
|
// if it's an empty string, it was meant to be a positional
|
|
if (!arg_string) {
|
|
return undefined;
|
|
}
|
|
|
|
// if it doesn't start with a prefix, it was meant to be positional
|
|
if (!this.prefix_chars.includes(arg_string[0])) {
|
|
return undefined;
|
|
}
|
|
|
|
// if the option string is present in the parser, return the action
|
|
if (arg_string in this._option_string_actions) {
|
|
let action = this._option_string_actions[arg_string];
|
|
return [action, arg_string, undefined];
|
|
}
|
|
|
|
// if it's just a single character, it was meant to be positional
|
|
if (arg_string.length === 1) {
|
|
return undefined;
|
|
}
|
|
|
|
// if the option string before the "=" is present, return the action
|
|
if (arg_string.includes('=')) {
|
|
let [option_string, explicit_arg] = _string_split(arg_string, '=', 1);
|
|
if (option_string in this._option_string_actions) {
|
|
let action = this._option_string_actions[option_string];
|
|
return [action, option_string, explicit_arg];
|
|
}
|
|
}
|
|
|
|
// search through all possible prefixes of the option string
|
|
// and all actions in the parser for possible interpretations
|
|
let option_tuples = this._get_option_tuples(arg_string);
|
|
|
|
// if multiple actions match, the option string was ambiguous
|
|
if (option_tuples.length > 1) {
|
|
let options = option_tuples
|
|
.map(
|
|
([, /*action*/ option_string /*, explicit_arg*/]) => option_string
|
|
)
|
|
.join(', ');
|
|
let args = { option: arg_string, matches: options };
|
|
let msg = 'ambiguous option: %(option)s could match %(matches)s';
|
|
this.error(sub(msg, args));
|
|
|
|
// if exactly one action matched, this segmentation is good,
|
|
// so return the parsed action
|
|
} else if (option_tuples.length === 1) {
|
|
let [option_tuple] = option_tuples;
|
|
return option_tuple;
|
|
}
|
|
|
|
// if it was not found as an option, but it looks like a negative
|
|
// number, it was meant to be positional
|
|
// unless there are negative-number-like options
|
|
if (this._negative_number_matcher.test(arg_string)) {
|
|
if (!this._has_negative_number_optionals.length) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// if it contains a space, it was meant to be a positional
|
|
if (arg_string.includes(' ')) {
|
|
return undefined;
|
|
}
|
|
|
|
// it was meant to be an optional but there is no such option
|
|
// in this parser (though it might be a valid option in a subparser)
|
|
return [undefined, arg_string, undefined];
|
|
}
|
|
|
|
_get_option_tuples(option_string) {
|
|
let result = [];
|
|
|
|
// option strings starting with two prefix characters are only
|
|
// split at the '='
|
|
let chars = this.prefix_chars;
|
|
if (
|
|
chars.includes(option_string[0]) &&
|
|
chars.includes(option_string[1])
|
|
) {
|
|
if (this.allow_abbrev) {
|
|
let option_prefix, explicit_arg;
|
|
if (option_string.includes('=')) {
|
|
[option_prefix, explicit_arg] = _string_split(
|
|
option_string,
|
|
'=',
|
|
1
|
|
);
|
|
} else {
|
|
option_prefix = option_string;
|
|
explicit_arg = undefined;
|
|
}
|
|
for (let option_string of Object.keys(
|
|
this._option_string_actions
|
|
)) {
|
|
if (option_string.startsWith(option_prefix)) {
|
|
let action = this._option_string_actions[option_string];
|
|
let tup = [action, option_string, explicit_arg];
|
|
result.push(tup);
|
|
}
|
|
}
|
|
}
|
|
|
|
// single character options can be concatenated with their arguments
|
|
// but multiple character options always have to have their argument
|
|
// separate
|
|
} else if (
|
|
chars.includes(option_string[0]) &&
|
|
!chars.includes(option_string[1])
|
|
) {
|
|
let option_prefix = option_string;
|
|
let explicit_arg = undefined;
|
|
let short_option_prefix = option_string.slice(0, 2);
|
|
let short_explicit_arg = option_string.slice(2);
|
|
|
|
for (let option_string of Object.keys(this._option_string_actions)) {
|
|
if (option_string === short_option_prefix) {
|
|
let action = this._option_string_actions[option_string];
|
|
let tup = [action, option_string, short_explicit_arg];
|
|
result.push(tup);
|
|
} else if (option_string.startsWith(option_prefix)) {
|
|
let action = this._option_string_actions[option_string];
|
|
let tup = [action, option_string, explicit_arg];
|
|
result.push(tup);
|
|
}
|
|
}
|
|
|
|
// shouldn't ever get here
|
|
} else {
|
|
this.error(sub('unexpected option string: %s', option_string));
|
|
}
|
|
|
|
// return the collected option tuples
|
|
return result;
|
|
}
|
|
|
|
_get_nargs_pattern(action) {
|
|
// in all examples below, we have to allow for '--' args
|
|
// which are represented as '-' in the pattern
|
|
let nargs = action.nargs;
|
|
let nargs_pattern;
|
|
|
|
// the default (None) is assumed to be a single argument
|
|
if (nargs === undefined) {
|
|
nargs_pattern = '(-*A-*)';
|
|
|
|
// allow zero or one arguments
|
|
} else if (nargs === OPTIONAL) {
|
|
nargs_pattern = '(-*A?-*)';
|
|
|
|
// allow zero or more arguments
|
|
} else if (nargs === ZERO_OR_MORE) {
|
|
nargs_pattern = '(-*[A-]*)';
|
|
|
|
// allow one or more arguments
|
|
} else if (nargs === ONE_OR_MORE) {
|
|
nargs_pattern = '(-*A[A-]*)';
|
|
|
|
// allow any number of options or arguments
|
|
} else if (nargs === REMAINDER) {
|
|
nargs_pattern = '([-AO]*)';
|
|
|
|
// allow one argument followed by any number of options or arguments
|
|
} else if (nargs === PARSER) {
|
|
nargs_pattern = '(-*A[-AO]*)';
|
|
|
|
// suppress action, like nargs=0
|
|
} else if (nargs === SUPPRESS) {
|
|
nargs_pattern = '(-*-*)';
|
|
|
|
// all others should be integers
|
|
} else {
|
|
nargs_pattern = sub(
|
|
'(-*%s-*)',
|
|
'A'.repeat(nargs).split('').join('-*')
|
|
);
|
|
}
|
|
|
|
// if this is an optional action, -- is not allowed
|
|
if (action.option_strings.length) {
|
|
nargs_pattern = nargs_pattern.replace(/-\*/g, '');
|
|
nargs_pattern = nargs_pattern.replace(/-/g, '');
|
|
}
|
|
|
|
// return the pattern
|
|
return nargs_pattern;
|
|
}
|
|
|
|
// ========================
|
|
// Alt command line argument parsing, allowing free intermix
|
|
// ========================
|
|
|
|
parse_intermixed_args(args = undefined, namespace = undefined) {
|
|
let argv;
|
|
[args, argv] = this.parse_known_intermixed_args(args, namespace);
|
|
if (argv.length) {
|
|
let msg = 'unrecognized arguments: %s';
|
|
this.error(sub(msg, argv.join(' ')));
|
|
}
|
|
return args;
|
|
}
|
|
|
|
parse_known_intermixed_args(args = undefined, namespace = undefined) {
|
|
// returns a namespace and list of extras
|
|
//
|
|
// positional can be freely intermixed with optionals. optionals are
|
|
// first parsed with all positional arguments deactivated. The 'extras'
|
|
// are then parsed. If the parser definition is incompatible with the
|
|
// intermixed assumptions (e.g. use of REMAINDER, subparsers) a
|
|
// TypeError is raised.
|
|
//
|
|
// positionals are 'deactivated' by setting nargs and default to
|
|
// SUPPRESS. This blocks the addition of that positional to the
|
|
// namespace
|
|
|
|
let extras;
|
|
let positionals = this._get_positional_actions();
|
|
let a = positionals.filter((action) =>
|
|
[PARSER, REMAINDER].includes(action.nargs)
|
|
);
|
|
if (a.length) {
|
|
throw new TypeError(
|
|
sub(
|
|
'parse_intermixed_args: positional arg' + ' with nargs=%s',
|
|
a[0].nargs
|
|
)
|
|
);
|
|
}
|
|
|
|
for (let group of this._mutually_exclusive_groups) {
|
|
for (let action of group._group_actions) {
|
|
if (positionals.includes(action)) {
|
|
throw new TypeError(
|
|
'parse_intermixed_args: positional in' +
|
|
' mutuallyExclusiveGroup'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
let save_usage;
|
|
try {
|
|
save_usage = this.usage;
|
|
let remaining_args;
|
|
try {
|
|
if (this.usage === undefined) {
|
|
// capture the full usage for use in error messages
|
|
this.usage = this.format_usage().slice(7);
|
|
}
|
|
for (let action of positionals) {
|
|
// deactivate positionals
|
|
action.save_nargs = action.nargs;
|
|
// action.nargs = 0
|
|
action.nargs = SUPPRESS;
|
|
action.save_default = action.default;
|
|
action.default = SUPPRESS;
|
|
}
|
|
[namespace, remaining_args] = this.parse_known_args(
|
|
args,
|
|
namespace
|
|
);
|
|
for (let action of positionals) {
|
|
// remove the empty positional values from namespace
|
|
let attr = getattr(namespace, action.dest);
|
|
if (Array.isArray(attr) && attr.length === 0) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn(
|
|
sub('Do not expect %s in %s', action.dest, namespace)
|
|
);
|
|
delattr(namespace, action.dest);
|
|
}
|
|
}
|
|
} finally {
|
|
// restore nargs and usage before exiting
|
|
for (let action of positionals) {
|
|
action.nargs = action.save_nargs;
|
|
action.default = action.save_default;
|
|
}
|
|
}
|
|
let optionals = this._get_optional_actions();
|
|
try {
|
|
// parse positionals. optionals aren't normally required, but
|
|
// they could be, so make sure they aren't.
|
|
for (let action of optionals) {
|
|
action.save_required = action.required;
|
|
action.required = false;
|
|
}
|
|
for (let group of this._mutually_exclusive_groups) {
|
|
group.save_required = group.required;
|
|
group.required = false;
|
|
}
|
|
[namespace, extras] = this.parse_known_args(
|
|
remaining_args,
|
|
namespace
|
|
);
|
|
} finally {
|
|
// restore parser values before exiting
|
|
for (let action of optionals) {
|
|
action.required = action.save_required;
|
|
}
|
|
for (let group of this._mutually_exclusive_groups) {
|
|
group.required = group.save_required;
|
|
}
|
|
}
|
|
} finally {
|
|
this.usage = save_usage;
|
|
}
|
|
return [namespace, extras];
|
|
}
|
|
|
|
// ========================
|
|
// Value conversion methods
|
|
// ========================
|
|
_get_values(action, arg_strings) {
|
|
// for everything but PARSER, REMAINDER args, strip out first '--'
|
|
if (![PARSER, REMAINDER].includes(action.nargs)) {
|
|
try {
|
|
_array_remove(arg_strings, '--');
|
|
} catch (err) {}
|
|
}
|
|
|
|
let value;
|
|
// optional argument produces a default when not present
|
|
if (!arg_strings.length && action.nargs === OPTIONAL) {
|
|
if (action.option_strings.length) {
|
|
value = action.const;
|
|
} else {
|
|
value = action.default;
|
|
}
|
|
if (typeof value === 'string') {
|
|
value = this._get_value(action, value);
|
|
this._check_value(action, value);
|
|
}
|
|
|
|
// when nargs='*' on a positional, if there were no command-line
|
|
// args, use the default if it is anything other than None
|
|
} else if (
|
|
!arg_strings.length &&
|
|
action.nargs === ZERO_OR_MORE &&
|
|
!action.option_strings.length
|
|
) {
|
|
if (action.default !== undefined) {
|
|
value = action.default;
|
|
} else {
|
|
value = arg_strings;
|
|
}
|
|
this._check_value(action, value);
|
|
|
|
// single argument or optional argument produces a single value
|
|
} else if (
|
|
arg_strings.length === 1 &&
|
|
[undefined, OPTIONAL].includes(action.nargs)
|
|
) {
|
|
let arg_string = arg_strings[0];
|
|
value = this._get_value(action, arg_string);
|
|
this._check_value(action, value);
|
|
|
|
// REMAINDER arguments convert all values, checking none
|
|
} else if (action.nargs === REMAINDER) {
|
|
value = arg_strings.map((v) => this._get_value(action, v));
|
|
|
|
// PARSER arguments convert all values, but check only the first
|
|
} else if (action.nargs === PARSER) {
|
|
value = arg_strings.map((v) => this._get_value(action, v));
|
|
this._check_value(action, value[0]);
|
|
|
|
// SUPPRESS argument does not put anything in the namespace
|
|
} else if (action.nargs === SUPPRESS) {
|
|
value = SUPPRESS;
|
|
|
|
// all other types of nargs produce a list
|
|
} else {
|
|
value = arg_strings.map((v) => this._get_value(action, v));
|
|
for (let v of value) {
|
|
this._check_value(action, v);
|
|
}
|
|
}
|
|
|
|
// return the converted value
|
|
return value;
|
|
}
|
|
|
|
_get_value(action, arg_string) {
|
|
let type_func = this._registry_get('type', action.type, action.type);
|
|
if (typeof type_func !== 'function') {
|
|
let msg = '%r is not callable';
|
|
throw new ArgumentError(action, sub(msg, type_func));
|
|
}
|
|
|
|
// convert the value to the appropriate type
|
|
let result;
|
|
try {
|
|
try {
|
|
result = type_func(arg_string);
|
|
} catch (err) {
|
|
// Dear TC39, why would you ever consider making es6 classes not callable?
|
|
// We had one universal interface, [[Call]], which worked for anything
|
|
// (with familiar this-instanceof guard for classes). Now we have two.
|
|
if (
|
|
err instanceof TypeError &&
|
|
/Class constructor .* cannot be invoked without 'new'/.test(
|
|
err.message
|
|
)
|
|
) {
|
|
// eslint-disable-next-line new-cap
|
|
result = new type_func(arg_string);
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// ArgumentTypeErrors indicate errors
|
|
if (err instanceof ArgumentTypeError) {
|
|
//let name = getattr(action.type, 'name', repr(action.type))
|
|
let msg = err.message;
|
|
throw new ArgumentError(action, msg);
|
|
|
|
// TypeErrors or ValueErrors also indicate errors
|
|
} else if (err instanceof TypeError) {
|
|
let name = getattr(action.type, 'name', repr(action.type));
|
|
let args = { type: name, value: arg_string };
|
|
let msg = 'invalid %(type)s value: %(value)r';
|
|
throw new ArgumentError(action, sub(msg, args));
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// return the converted value
|
|
return result;
|
|
}
|
|
|
|
_check_value(action, value) {
|
|
// converted value must be one of the choices (if specified)
|
|
if (
|
|
action.choices !== undefined &&
|
|
!_choices_to_array(action.choices).includes(value)
|
|
) {
|
|
let args = {
|
|
value,
|
|
choices: _choices_to_array(action.choices).map(repr).join(', '),
|
|
};
|
|
let msg = 'invalid choice: %(value)r (choose from %(choices)s)';
|
|
throw new ArgumentError(action, sub(msg, args));
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// Help-formatting methods
|
|
// =======================
|
|
format_usage() {
|
|
let formatter = this._get_formatter();
|
|
formatter.add_usage(
|
|
this.usage,
|
|
this._actions,
|
|
this._mutually_exclusive_groups
|
|
);
|
|
return formatter.format_help();
|
|
}
|
|
|
|
format_help() {
|
|
let formatter = this._get_formatter();
|
|
|
|
// usage
|
|
formatter.add_usage(
|
|
this.usage,
|
|
this._actions,
|
|
this._mutually_exclusive_groups
|
|
);
|
|
|
|
// description
|
|
formatter.add_text(this.description);
|
|
|
|
// positionals, optionals and user-defined groups
|
|
for (let action_group of this._action_groups) {
|
|
formatter.start_section(action_group.title);
|
|
formatter.add_text(action_group.description);
|
|
formatter.add_arguments(action_group._group_actions);
|
|
formatter.end_section();
|
|
}
|
|
|
|
// epilog
|
|
formatter.add_text(this.epilog);
|
|
|
|
// determine help from format above
|
|
return formatter.format_help();
|
|
}
|
|
|
|
_get_formatter() {
|
|
// eslint-disable-next-line new-cap
|
|
return new this.formatter_class({ prog: this.prog });
|
|
}
|
|
|
|
// =====================
|
|
// Help-printing methods
|
|
// =====================
|
|
print_usage(file = undefined) {
|
|
if (file === undefined) file = process.stdout;
|
|
this._print_message(this.format_usage(), file);
|
|
}
|
|
|
|
print_help(file = undefined) {
|
|
if (file === undefined) file = process.stdout;
|
|
this._print_message(this.format_help(), file);
|
|
}
|
|
|
|
_print_message(message, file = undefined) {
|
|
if (message) {
|
|
if (file === undefined) file = process.stderr;
|
|
file.write(message);
|
|
}
|
|
}
|
|
|
|
// ===============
|
|
// Exiting methods
|
|
// ===============
|
|
exit(status = 0, message = undefined) {
|
|
if (message) {
|
|
this._print_message(message, process.stderr);
|
|
}
|
|
process.exit(status);
|
|
}
|
|
|
|
error(message) {
|
|
/*
|
|
* error(message: string)
|
|
*
|
|
* Prints a usage message incorporating the message to stderr and
|
|
* exits.
|
|
*
|
|
* If you override this in a subclass, it should not return -- it
|
|
* should either exit or raise an exception.
|
|
*/
|
|
|
|
// LEGACY (v1 compatibility), debug mode
|
|
if (this.debug === true) throw new Error(message);
|
|
// end
|
|
this.print_usage(process.stderr);
|
|
let args = { prog: this.prog, message: message };
|
|
this.exit(2, sub('%(prog)s: error: %(message)s\n', args));
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
module.exports = {
|
|
ArgumentParser,
|
|
ArgumentError,
|
|
ArgumentTypeError,
|
|
BooleanOptionalAction,
|
|
FileType,
|
|
HelpFormatter,
|
|
ArgumentDefaultsHelpFormatter,
|
|
RawDescriptionHelpFormatter,
|
|
RawTextHelpFormatter,
|
|
MetavarTypeHelpFormatter,
|
|
Namespace,
|
|
Action,
|
|
ONE_OR_MORE,
|
|
OPTIONAL,
|
|
PARSER,
|
|
REMAINDER,
|
|
SUPPRESS,
|
|
ZERO_OR_MORE,
|
|
};
|
|
|
|
// LEGACY (v1 compatibility), Const alias
|
|
Object.defineProperty(module.exports, 'Const', {
|
|
get() {
|
|
let result = {};
|
|
Object.entries({
|
|
ONE_OR_MORE,
|
|
OPTIONAL,
|
|
PARSER,
|
|
REMAINDER,
|
|
SUPPRESS,
|
|
ZERO_OR_MORE,
|
|
}).forEach(([n, v]) => {
|
|
Object.defineProperty(result, n, {
|
|
get() {
|
|
deprecate(
|
|
n,
|
|
sub('use argparse.%s instead of argparse.Const.%s', n, n)
|
|
);
|
|
return v;
|
|
},
|
|
});
|
|
});
|
|
Object.entries({ _UNRECOGNIZED_ARGS_ATTR }).forEach(([n, v]) => {
|
|
Object.defineProperty(result, n, {
|
|
get() {
|
|
deprecate(
|
|
n,
|
|
sub(
|
|
'argparse.Const.%s is an internal symbol and will no longer be available',
|
|
n
|
|
)
|
|
);
|
|
return v;
|
|
},
|
|
});
|
|
});
|
|
return result;
|
|
},
|
|
enumerable: false,
|
|
});
|
|
// end
|