UNPKG

argparse

Version:

CLI arguments parser. Native port of python's argparse.

1,439 lines (1,263 loc) 135 kB
// Port of python's argparse module, version 3.14.6: // https://github.com/python/cpython/blob/v3.14.6/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', * help='the file where the sum should be written') * args = parser.parse_args() * with (open(args.log, 'w') if args.log is not None * else contextlib.nullcontext(sys.stdout)) as log: * log.write('%s' % sum(args.integers)) * * 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('./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') const 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 = [] const 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 _string_to_number (string) { if (typeof string === 'string' && !/(?:^|\D)_|_(?:\D|$)/.test(string)) { string = string.replace(/_/g, '') } return Number(string) } function _array_remove (array, item) { const 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 instanceof Map) { return Array.from(choices.keys()) } 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) { const result = { // object is needed for inferred class name [cls.name]: function (...args) { const 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] } // parse options const no_default = Symbol('no_default_value') function _parse_opts (args, descriptor) { function get_name () { const 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 = {} const result = [] const 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()) } const missing_positionals = [] const positional_count = args.length let keyword_only = false for (const [key, def] of Object.entries(descriptor)) { if (key === '*') { keyword_only = true } else if (key[0] === '*') { if (key.length > 0 && key[1] === '*') { 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 && !keyword_only) { 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) { const entries = Object.entries(descriptor) const marker = entries.findIndex(([k]) => k === '*') const positional_entries = (marker === -1 ? entries : entries.slice(0, marker)) .filter(([k]) => k[0] !== '*') const from = positional_entries.filter(([, v]) => v !== no_default).length const to = positional_entries.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) { const strs = missing_positionals.map(repr) if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1] const 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 } // ============================= // 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] () { const type_name = this.constructor.name const arg_strings = [] const star_args = {} for (const arg of this._get_args()) { arg_strings.push(repr(arg)) } for (const [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 || items === null) { return [] } return items.slice(0) } function _identity (value) { return value } // =============== // Formatting Help // =============== const HelpFormatter = _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, color ] = _parse_opts(arguments, { prog: no_default, indent_increment: 2, max_help_position: 24, width: undefined, color: true }) // default setting for width if (width === undefined) { width = get_terminal_size().columns width -= 2 } this._set_color(color) 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 } _set_color (color) { const { can_colorize, decolor, get_theme } = require('./_colorize') if (color && can_colorize()) { this._theme = get_theme({ force_color: true }).argparse this._decolor = decolor } else { this._theme = get_theme({ force_no_color: true }).argparse this._decolor = _identity } } // =============================== // 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() const 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) { const args = [usage, actions, groups, prefix] this._add_item(this._format_usage.bind(this), args) } } add_argument (action) { if (action.help !== SUPPRESS) { // find all invocations const get_invocation = action => this._decolor(this._format_action_invocation(action)) const invocation_lengths = [ get_invocation(action).length + this._current_indent ] for (const subaction of this._iter_indented_subactions(action)) { invocation_lengths.push( get_invocation(subaction).length + this._current_indent ) } // update the maximum item length const action_length = Math.max(...invocation_lengths) 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 (const 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) { const t = this._theme if (prefix === undefined) { prefix = 'usage: ' } // if usage is specified, use that if (usage !== undefined) { usage = t.prog_extra + sub(usage, { prog: t.prog + this._prog + t.reset + t.prog_extra }) + t.reset // if no optionals or positionals are available, usage is just prog } else if (usage === undefined && !actions.length) { usage = t.prog + this._prog + t.reset // if optionals and positionals are available, calculate usage } else if (usage === undefined) { const prog = sub('%(prog)s', { prog: this._prog }) const [parts, pos_start] = this._get_actions_usage_parts(actions, groups) // build full usage string usage = [prog].concat(parts).filter(Boolean).join(' ') // wrap the usage parts if it's too long const text_width = this._width - this._current_indent if (prefix.length + this._decolor(usage).length > text_width) { // break usage into wrappable parts const opt_parts = parts.slice(0, pos_start) const pos_parts = parts.slice(pos_start) // helper for wrapping lines const get_lines = (parts, indent, prefix = undefined) => { const lines = [] let line = [] const indent_length = indent.length let line_len if (prefix !== undefined) { line_len = prefix.length - 1 } else { line_len = indent_length - 1 } for (const part of parts) { const part_len = this._decolor(part).length if (line_len + 1 + part_len > text_width && line.length) { lines.push(indent + line.join(' ')) line = [] line_len = indent_length - 1 } line.push(part) line_len += part_len + 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 const prog_len = this._decolor(prog).length if (prefix.length + prog_len <= 0.75 * text_width) { const indent = ' '.repeat(prefix.length + prog_len + 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 { const indent = ' '.repeat(prefix.length) const 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') } if (usage.startsWith(prog)) usage = usage.slice(prog.length) usage = t.prog + prog + t.reset + usage } // prefix with 'usage:' return t.usage + prefix + t.reset + usage + '\n\n' } _is_long_option (string) { return string.length > 2 } _get_actions_usage_parts (actions, groups) { /* Get usage parts with split index for optionals/positionals. * Returns [parts, pos_start] where pos_start is the index in parts * where positionals begin. This preserves mutually exclusive group * formatting across the optionals/positionals boundary (gh-75949). */ actions = actions.filter(action => action.help !== SUPPRESS) // group actions by mutually exclusive groups const action_groups = new Map(actions.map(action => [action, undefined])) for (const group of groups) { for (const action of group._group_actions) { if (action_groups.has(action)) { action_groups.set(action, group) } } } // positional arguments keep their position const positionals = [] for (const action of actions) { if (!action.option_strings.length) { const group = action_groups.get(action) action_groups.delete(action) if (group) { const group_actions = [] for (const action2 of group._group_actions) { if (action2.option_strings.length && action_groups.has(action2)) { const action_group = action_groups.get(action2) action_groups.delete(action2) if (action_group) { group_actions.push(action2) } } } group_actions.push(action) positionals.push([group.required, group_actions]) } else { positionals.push([undefined, [action]]) } } } // the remaining optional arguments are sorted by the position of // the first option in the group const optionals = [] for (const action of actions) { if (action.option_strings.length && action_groups.has(action)) { const group = action_groups.get(action) action_groups.delete(action) if (group) { const group_actions = [action] for (const action2 of group._group_actions) { if (action2.option_strings.length && action_groups.has(action2)) { const action_group = action_groups.get(action2) action_groups.delete(action2) if (action_group) { group_actions.push(action2) } } } optionals.push([group.required, group_actions]) } else { optionals.push([undefined, [action]]) } } } // collect all actions format strings const parts = [] const t = this._theme let pos_start const action_sets = optionals.concat(positionals) for (let i = 0; i < action_sets.length; i++) { const [required, group] = action_sets[i] const start = parts.length if (i === optionals.length) { pos_start = start } const in_group = group.length > 1 for (const action of group) { let part // produce all arg strings if (!action.option_strings.length) { const default_value = this._get_default_metavar_for_positional(action) part = this._format_args(action, default_value) // if it's in a group, strip the outer [] if (in_group && part[0] === '[' && part[part.length - 1] === ']') { part = part.slice(1, -1) } part = t.summary_action + part + t.reset // produce the first way to invoke the option in brackets } else { const option_string = action.option_strings[0] const option_color = this._is_long_option(option_string) ? t.summary_long_option : t.summary_short_option // if the Optional doesn't take a value, format is: // -s or --long if (action.nargs === 0) { part = action.format_usage() part = option_color + part + t.reset // if the Optional takes a value, format is: // -s ARGS or --long ARGS } else { const default_value = this._get_default_metavar_for_optional(action) const args_string = this._format_args(action, default_value) part = option_color + option_string + ' ' + t.summary_label + args_string + t.reset } // make it look optional if it's not required or in a group if (!(action.required || required || in_group)) { part = sub('[%s]', part) } } // add the action string to the list parts.push(part) } if (in_group) { parts[start] = (required ? '(' : '[') + parts[start] for (let i = start; i < parts.length - 1; i++) { parts[i] += ' |' } parts[parts.length - 1] += required ? ')' : ']' } } if (pos_start === undefined) { pos_start = parts.length } return [parts, pos_start] } _format_text (text) { if (text.includes('%(prog)')) { text = sub(text, { prog: this._prog }) } const text_width = Math.max(this._width - this._current_indent, 11) const 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 const help_position = Math.min(this._action_max_length + 2, this._max_help_position) const help_width = Math.max(this._width - help_position, 11) const action_width = help_position - this._current_indent - 2 let action_header = this._format_action_invocation(action) const action_header_no_color = this._decolor(action_header) let indent_first // no help; start on same line and add a final newline if (!action.help) { const 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_no_color.length <= action_width) { // calculate widths without color codes const action_header_color = action_header const tup = [this._current_indent, '', action_width, action_header_no_color] action_header = sub('%*s%-*s ', ...tup) // swap in the colored header action_header = action_header.replace(action_header_no_color, action_header_color) indent_first = 0 // long action name; start on the next line } else { const tup = [this._current_indent, '', action_header] action_header = sub('%*s%s\n', ...tup) indent_first = help_position } // collect the pieces of the action help const parts = [action_header] // if there was help for the action, add lines of help text if (action.help && action.help.trim()) { const help_text = this._expand_help(action) if (help_text) { const help_lines = this._split_lines(help_text, help_width) parts.push(sub('%*s%s\n', indent_first, '', help_lines[0])) for (const 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 (const 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) { const t = this._theme if (!action.option_strings.length) { const default_value = this._get_default_metavar_for_positional(action) return t.action + this._metavar_formatter(action, default_value)(1).join(' ') + t.reset } else { const color_option_strings = strings => strings.map(string => { const color = this._is_long_option(string) ? t.long_option : t.short_option return color + string + t.reset }) // if the Optional doesn't take a value, format is: // -s, --long if (action.nargs === 0) { return color_option_strings(action.option_strings).join(', ') // if the Optional takes a value, format is: // -s, --long ARGS } else { const default_value = this._get_default_metavar_for_optional(action) const option_strings = color_option_strings(action.option_strings) const args_string = t.label + this._format_args(action, default_value) + t.reset return option_strings.join(', ') + ' ' + args_string } } } _metavar_formatter (action, default_metavar) { let result if (action.metavar !== undefined) { result = action.metavar } else if (action.choices !== undefined) { const 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) { const 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) { const 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) { const help_string = this._get_help_string(action) if (!help_string.includes('%')) { return help_string } const params = Object.assign({}, action, { prog: this._prog }) for (const name of Object.keys(params)) { const value = params[name] if (value === SUPPRESS) { delete params[name] } else if (value !== undefined && value !== null && 'name' in Object(value)) { params[name] = value.name } } if (params.choices !== undefined) { const choices_str = _choices_to_array(params.choices).map(String).join(', ') params.choices = choices_str } return sub(help_string, 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. const textwrap = require('./textwrap') return textwrap.wrap(text, { width }) } _fill_text (text, width, indent) { text = text.replace(this._whitespace_matcher, ' ').trim() const textwrap = require('./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() } const 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) { const current_indent = this.formatter._current_indent const t = this.formatter._theme const heading_text = this.heading + ':' heading = ' '.repeat(current_indent) + t.heading + heading_text + t.reset + '\n' } else { heading = '' } // join the section-initial newline, the heading and the help return this.formatter._join_parts(['\n', heading, item_help, '\n']) } }) const RawDescriptionHelpFormatter = _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 = _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 = _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 if (help === undefined) { help = '' } if (!help.includes('%(default)') && action.default !== SUPPRESS && !action.required) { const defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] if (action.option_strings.length || defaulting_nargs.includes(action.nargs)) { help += ' (default: %(default)s)' } } return help } }) const MetavarTypeHelpFormatter = _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)) { const metavar = argument.metavar if (!Array.isArray(metavar)) { return metavar } if (argument.nargs === ZERO_OR_MORE && metavar.length === 2) { return sub('%s[, %s]', ...metavar) } else if (argument.nargs === ONE_OR_MORE) { return sub('%s[, %s]', ...metavar) } else { return metavar.join(', ') } } else if (![undefined, SUPPRESS].includes(argument.dest)) { return argument.dest } else if (argument.choices) { const choices = _choices_to_array(argument.choices) if (choices.length) { return sub('{%s}', choices.map(String).join(',')) } } 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 = _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 () { const [ option_strings, dest, nargs, const_value, default_value, type, choices, required, help, metavar, deprecated ] = _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, deprecated: false }) // 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 this.deprecated = deprecated } _get_kwargs () { const names = [ 'option_strings', 'dest', 'nargs', 'const', 'default', 'type', 'choices', 'required', 'help', 'metavar', 'deprecated' ] 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 = _callable(class BooleanOptionalAction extends Action { constructor () { const [ option_strings, dest, default_value, required, help, deprecated ] = _parse_opts(arguments, { option_strings: no_default, dest: no_default, default: undefined, required: false, help: undefined, deprecated: false }) const _option_strings = [] for (let option_string of option_strings) { _option_strings.push(option_string) if (option_string.startsWith('--')) { if (option_string.startsWith('--no-')) { throw new TypeError(sub('invalid option name %r ' + 'for BooleanOptionalAction', option_string)) } option_string = '--no-' + option_string.slice(2) _option_strings.push(option_string) } } super({ option_strings: _option_strings, dest, nargs: 0, default: default_value, required, help, deprecated }) } 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 () { const [ option_strings, dest, nargs, const_value, default_value, type, choices, required, help, metavar, deprecated ] = _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, deprecated: false }) 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, deprecated }) } call (parser, namespace, values/*, option_string = undefined */) { setattr(namespace, this.dest, values) } }) const _StoreConstAction = _callable(class _StoreConstAction extends Action { constructor () { const [ option_strings, dest, const_value, default_value, required, help, deprecated ] = _parse_opts(arguments, { option_strings: no_default, dest: no_default, const: undefined, default: undefined, required: false, help: undefined, deprecated: false, metavar: undefined }) super({ option_strings, dest, nargs: 0, const: const_value, default: default_value, required, help, deprecated }) } call (parser, namespace/*, val