argparse
Version:
CLI arguments parser. Native port of python's argparse.
3,805 lines • 135 kB
JavaScript
// 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/*, values, option_string = undefined */) {
setattr(namespace, this.dest, this.const)
}
})
const _StoreTrueAction = _callable(class _StoreTrueAction extends _StoreConstAction {
constructor () {
const [
option_strings,
dest,
default_value,
required,
help,
deprecated
] = _parse_opts(arguments, {
option_strings: no_default,
dest: no_default,
default: false,
required: false,
help: undefined,
deprecated: false
})
super({
option_strings,
dest,
const: true,
default: default_value,
required,
help,
deprecated
})
}
})
const _StoreFalseAction = _callable(class _StoreFalseAction extends _StoreConstAction {
constructor () {
const [
option_strings,
dest,
default_value,
required,
help,
deprecated
] = _parse_opts(arguments, {
option_strings: no_default,
dest: no_default,
default: true,
required: false,
help: undefined,
deprecated: false
})
super({
option_strings,
dest,
const: false,
default: default_value,
required,
help,
deprecated
})
}
})
const _AppendAction = _callable(class _AppendAction 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 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,
deprecated
})
}
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 () {
const [
option_strings,
dest,
const_value,
default_value,
required,
help,
metavar,
deprecated
] = _parse_opts(arguments, {
option_strings: no_default,
dest: no_default,
const: undefined,
default: undefined,
required: false,
help: undefined,
metavar: undefined,
deprecated: false
})
super({
option_strings,
dest,
nargs: 0,
const: const_value,
default: default_value,
required,
help,
metavar,
deprecated
})
}
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 () {
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
})
super({
option_strings,
dest,
nargs: 0,
default: default_value,
required,
help,
deprecated
})
}
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 () {
const [
option_strings,
dest,
default_value,
help,
deprecated
] = _parse_opts(arguments, {
option_strings: no_default,
dest: SUPPRESS,
default: SUPPRESS,
help: undefined,
deprecated: false
})
super({
option_strings,
dest,
default: default_value,
nargs: 0,
help,
deprecated
})
}
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",
deprecated: false
})
if (help === undefined || help === null) {
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
}
const formatter = parser._get_formatter()
formatter.add_text(version)
parser._print_message(formatter.format_help(), process.stdout)
parser.exit()
}
})
const _SubParsersAction = _callable(class _SubParsersAction extends Action {
constructor () {
const [
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
})
const name_parser_map = Object.create(null)
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 = []
this._deprecated = new Set()
this._color = true
}
add_parser () {
const [
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)
}
// set color
if (kwargs.color === undefined) {
kwargs.color = this._color
}
const aliases = getattr(kwargs, 'aliases', [])
delete kwargs.aliases
const deprecated = getattr(kwargs, 'deprecated', false)
delete kwargs.deprecated
if (hasattr(this._name_parser_map, name)) {
throw new TypeError(sub('conflicting subparser: %s', name))
}
for (const alias of aliases) {
if (hasattr(this._name_parser_map, alias)) {
throw new TypeError(sub('conflicting subparser alias: %s', alias))
}
}
// create a pseudo-action to hold the choice help
let choice_action
if ('help' in kwargs) {
const help = kwargs.help
delete kwargs.help
choice_action = this._ChoicesPseudoAction(name, aliases, help)
this._choices_actions.push(choice_action)
}
// create the parser and add it to the map
const parser = new this._parser_class(kwargs)
if (choice_action !== undefined) {
parser._check_help(choice_action)
}
this._name_parser_map[name] = parser
// make parser available under aliases also
for (const alias of aliases) {
this._name_parser_map[alias] = parser
}
if (deprecated) {
this._deprecated.add(name)
for (const alias of aliases) {
this._deprecated.add(alias)
}
}
return parser
}
_get_subactions () {
return this._choices_actions
}
call (parser, namespace, values/*, option_string = undefined */) {
const 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
let subparser
if (hasattr(this._name_parser_map, parser_name)) {
subparser = this._name_parser_map[parser_name]
} else {
const args = {parser_name,
choices: Object.keys(this._name_parser_map).join(', ')}
const msg = sub('unknown parser %(parser_name)r (choices: %(choices)s)', args)
throw new ArgumentError(this, msg)
}
if (this._deprecated.has(parser_name)) {
parser._warning(sub("command '%(parser_name)s' is deprecated", { parser_name }))
}
// 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] = subparser.parse_known_args(arg_strings, undefined)
for (const [key, value] of Object.entries(subnamespace)) {
setattr(namespace, key, value)
}
if (arg_strings.length) {
if (!hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) {
setattr(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
const 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 {
/*
* Deprecated 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 () {
const [
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
})
process.emitWarning(
'FileType is deprecated. Simply open files after parsing arguments.',
'PendingDeprecationWarning')
// 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 ([...'wax'].some(flag => this._flags.includes(flag))) {
return process.stdout
} else {
const 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) {
if (e instanceof TypeError) {
throw e
}
const args = { filename: string, error: e.message }
const message = "can't open '%(filename)s': %(error)s"
throw new ArgumentTypeError(sub(message, args))
}
const options = Object.assign({ fd, flags: this._flags }, this._options)
if (this._flags.includes('r')) {
const file = fs.createReadStream(string, options)
file.path = string
return file
} else if (this._flags.includes('w')) {
const file = fs.createWriteStream(string, options)
file.path = string
return file
} else {
fs.closeSync(fd)
const msg = sub('argument "%s" with mode %r', string, this._flags)
throw new TypeError(msg)
}
}
[util.inspect.custom] () {
const args = [this._flags]
const 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]
})
const 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 = _callable(class _ActionsContainer {
constructor () {
const [
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 = Object.create(null)
// 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)
// raise an exception if the conflict handler is invalid
this._get_handler()
// action storage
this._actions = []
this._option_string_actions = Object.create(null)
// groups
this._action_groups = []
this._mutually_exclusive_groups = []
// defaults storage
this._defaults = Object.create(null)
// determines whether an "option" looks like a negative number
this._negative_number_matcher = /^-\.?\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) {
const registry = setdefault(this._registries, registry_name, Object.create(null))
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 (const action of this._actions) {
if (action.dest in kwargs) {
action.default = kwargs[action.dest]
}
}
}
get_default (dest) {
for (const 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
})
// argparse v1 accepted an array of argument names here. Python argparse
// accepts only individual string names and rejects a list; reject it
// explicitly because JS would otherwise coerce the array to a string.
if (args.some(arg => typeof arg !== 'string')) {
throw new TypeError('argument name must be a string')
}
if (args.some(arg => !arg.length)) {
throw new TypeError('argument name must not be empty')
}
// if no positional args are supplied or only one is supplied and
// it doesn't look like an option string, parse a positional
// argument
const 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,' +
' did you mean metavar?')
}
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)) {
const 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
const action_name = kwargs.action
const action_class = this._pop_action_class(kwargs)
if (typeof action_class !== 'function') {
throw new TypeError(sub('unknown action %r', action_class))
}
// eslint-disable-next-line new-cap
const action = new action_class(kwargs)
// raise an error if action for positional argument does not
// consume arguments
if (!action.option_strings.length && action.nargs === 0) {
throw new TypeError(sub('action %r is not valid for positional arguments', action_name))
}
// raise an error if the action type is not callable
const 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_validation_formatter' in this) {
const formatter = this._get_validation_formatter()
try {
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
}
}
}
this._check_help(action)
return this._add_action(action)
}
add_argument_group () {
const group = _ArgumentGroup(this, ...arguments)
this._action_groups.push(group)
return group
}
add_mutually_exclusive_group () {
const 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 (const 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 (const 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
const title_group_map = Object.create(null)
for (const group of this._action_groups) {
if (group.title in title_group_map) {
// This branch could happen if a derived class added
// groups with duplicated titles in constructor.
const 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
const group_map = new Map()
for (const 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 (const 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 (const group of container._mutually_exclusive_groups) {
let cont
if (group._container === container) {
cont = this
} else {
cont = title_group_map[group._container.title]
}
const mutex_group = cont.add_mutually_exclusive_group({
required: group.required
})
// map the actions to their new mutex group
for (const action of group._group_actions) {
group_map.set(action, mutex_group)
}
}
// add all actions to this container or their group
for (const action of container._actions) {
const action_group = group_map.get(action) || this
action_group._add_action(action)
}
}
_get_positional_kwargs () {
const [
dest,
kwargs
] = _parse_opts(arguments, {
dest: no_default,
'**kwargs': no_default
})
// make sure required is not specified
if ('required' in kwargs) {
const msg = "'required' is an invalid argument for positionals"
throw new TypeError(msg)
}
// mark positional arguments as required if at least one is
// always required
const nargs = kwargs.nargs
if (nargs === 0) {
throw new TypeError('nargs for positionals must be != 0')
}
if (![OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS].includes(nargs)) {
kwargs.required = true
}
// return the keyword arguments with no option strings
return Object.assign(kwargs, { dest, option_strings: [] })
}
_get_optional_kwargs () {
const [
args,
kwargs
] = _parse_opts(arguments, {
'*args': no_default,
'**kwargs': no_default
})
// determine short and long option strings
const option_strings = []
const 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])) {
const args = {option: option_string,
prefix_chars: this.prefix_chars}
const 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) {
const 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) {
const action = getattr(kwargs, 'action', default_value)
delete kwargs.action
return this._registry_get('action', action, action)
}
_get_handler () {
// determine function from conflict handler string
const handler_func_name = sub('_handle_conflict_%s', this.conflict_handler)
if (typeof this[handler_func_name] === 'function') {
return this[handler_func_name]
} else {
const 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
const confl_optionals = []
for (const option_string of action.option_strings) {
if (hasattr(this._option_string_actions, option_string)) {
const confl_optional = this._option_string_actions[option_string]
confl_optionals.push([option_string, confl_optional])
}
}
// resolve any conflicts
if (confl_optionals.length) {
const conflict_handler = this._get_handler()
conflict_handler.call(this, action, confl_optionals)
}
}
_handle_conflict_error (action, conflicting_actions) {
const message = conflicting_actions.length === 1 ?
'conflicting option string: %s' :
'conflicting option strings: %s'
const conflict_string = conflicting_actions.map(([option_string]) => option_string).join(', ')
throw new ArgumentError(action, sub(message, conflict_string))
}
_handle_conflict_resolve (action, conflicting_actions) {
// remove all conflicting options
for (const [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)
}
}
}
_check_help (action) {
if (action.help && '_get_validation_formatter' in this) {
const formatter = this._get_validation_formatter()
try {
formatter._expand_help(action)
} catch (err) {
if (err instanceof TypeError) {
throw new TypeError('badly formed help string', { cause: err })
}
throw err
}
}
}
})
const _ArgumentGroup = _callable(class _ArgumentGroup extends _ActionsContainer {
constructor () {
const [
container,
title,
description,
kwargs
] = _parse_opts(arguments, {
container: no_default,
title: undefined,
description: undefined,
'**kwargs': no_default
})
if ('prefix_chars' in kwargs) {
const depr_msg =
"The use of the undocumented 'prefix_chars' parameter in " +
'ArgumentParser.add_argument_group() is deprecated.'
process.emitWarning(depr_msg, 'DeprecationWarning')
}
// 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)
}
add_argument_group () {
throw new TypeError('argument groups cannot be nested')
}
})
const _MutuallyExclusiveGroup = _callable(class _MutuallyExclusiveGroup extends _ArgumentGroup {
constructor () {
const [
container,
required
] = _parse_opts(arguments, {
container: no_default,
required: false
})
super(container)
this.required = required
this._container = container
}
_add_action (action) {
if (action.required) {
const 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)
}
add_mutually_exclusive_group () {
throw new TypeError('mutually exclusive groups cannot be nested')
}
})
const ArgumentParser = _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
* - suggest_on_error - Enables suggestions for mistyped argument choices
* and subparser names. (default: false)
* - color - Allow color output in help messages (default: true)
*/
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,
suggest_on_error,
color
] = _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,
'*': no_default,
suggest_on_error: false,
color: true
})
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
this.suggest_on_error = suggest_on_error
this.color = color
// Cached formatter for validation (avoids repeated _set_color calls)
this._cached_formatter = undefined
this._positionals = this.add_argument_group('positional arguments')
this._optionals = this.add_argument_group('options')
this._subparsers = undefined
// register types
function identity (string) {
return string
}
this.register('type', undefined, identity)
this.register('type', null, identity)
this.register('type', 'int', function (x) {
if (typeof x === 'string' && !/^[+-]?\d+(?:_?\d+)*$/.test(x.trim())) {
throw new TypeError(sub('could not convert string to int: %r', x))
}
const result = _string_to_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) {
const result = _string_to_number(x)
if (isNaN(result)) {
throw new TypeError(sub('could not convert string to float: %r', x))
}
return result
})
this.register('type', 'str', String)
// add help argument if necessary
// (using explicit default to override global argument_default)
const 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'
}
)
}
// add parent arguments and defaults
for (const parent of parents) {
if (!(parent instanceof ArgumentParser)) {
throw new TypeError('parents must be a list of ArgumentParser')
}
this._add_container_actions(parent)
Object.assign(this._defaults, parent._defaults)
}
}
// =======================
// Pretty __repr__ methods
// =======================
_get_kwargs () {
const names = [
'prog',
'usage',
'description',
'formatter_class',
'conflict_handler',
'add_help'
]
return names.map(name => [name, getattr(this, name)])
}
// ==================================
// Optional/Positional adding methods
// ==================================
add_subparsers () {
const [
kwargs
] = _parse_opts(arguments, {
'**kwargs': no_default
})
if (this._subparsers !== undefined) {
throw new TypeError('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) {
const title = getattr(kwargs, 'title', 'subcommands')
const 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) {
// Create formatter without color to avoid storing ANSI codes in prog
// eslint-disable-next-line new-cap
const formatter = new this.formatter_class({ prog: this.prog })
formatter._set_color(false)
const positionals = this._get_positional_actions()
const groups = this._mutually_exclusive_groups
formatter.add_usage(undefined, positionals, groups, '')
kwargs.prog = formatter.format_help().trim()
}
// create the parsers action and add it to the positionals list
const parsers_class = this._pop_action_class(kwargs, 'parsers')
if (hasattr(kwargs, 'option_strings')) {
throw new TypeError("add_subparsers() got multiple values for argument 'option_strings'")
}
// eslint-disable-next-line new-cap
const action = new parsers_class(Object.assign({ option_strings: [] }, kwargs))
action._color = this.color
this._check_help(action)
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) {
const msg = sub('unrecognized arguments: %s', argv.join(' '))
if (this.exit_on_error) {
this.error(msg)
} else {
throw new ArgumentError(undefined, msg)
}
}
return args
}
parse_known_args (args = undefined, namespace = undefined) {
return this._parse_known_args2(args, namespace, false)
}
_parse_known_args2 (args, namespace, intermixed) {
if (args === undefined) {
args = get_argv().slice(1)
} else {
args = Array.from(args)
}
// default Namespace built from parser defaults
if (namespace === undefined) {
namespace = new Namespace()
}
// add any action defaults that aren't present
for (const 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 (const 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, intermixed)
} catch (err) {
if (err instanceof ArgumentError) {
this.error(err.message)
} else {
throw err
}
}
} else {
[namespace, args] = this._parse_known_args(args, namespace, intermixed)
}
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, intermixed) {
// 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
const action_conflicts = new Map()
for (const mutex_group of this._mutually_exclusive_groups) {
const group_actions = mutex_group._group_actions
for (const [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 '--'
const option_string_indices = {}
const arg_string_pattern_parts = []
const 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 {
const option_tuples = this._parse_optional(arg_string)
let pattern
if (option_tuples === undefined) {
pattern = 'A'
} else {
option_string_indices[i] = option_tuples
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
const seen_actions = new Set()
const seen_non_default_actions = new Set()
const warned = new Set()
let extras
const take_action = (action, argument_strings, option_string = undefined) => {
seen_actions.add(action)
const argument_values = this._get_values(action, argument_strings)
// error if this argument is not allowed with other previously
// seen arguments
if (action.option_strings.length || argument_strings.length) {
seen_non_default_actions.add(action)
for (const conflict_action of action_conflicts.get(action) || []) {
if (seen_non_default_actions.has(conflict_action)) {
const msg = 'not allowed with argument %s'
const 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
const consume_optional = start_index => {
// get the optional identified at this index
const option_tuples = option_string_indices[start_index]
if (option_tuples.length > 1) {
const options = option_tuples.map(([, option_string]) => option_string).join(', ')
const args = {option: arg_strings[start_index], matches: options}
const msg = 'ambiguous option: %(option)s could match %(matches)s'
throw new ArgumentError(undefined, sub(msg, args))
}
let [action, option_string, sep, explicit_arg] = option_tuples[0]
// identify additional optionals in the same arg string
// (e.g. -xyz is the same as -x -y -z if no args are required)
const action_tuples = []
let stop
for (;;) {
// if we found no optional action, skip it
if (action === undefined) {
extras.push(arg_strings[start_index])
extras_pattern.push('O')
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) {
const 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
const chars = this.prefix_chars
if (arg_count === 0 &&
!chars.includes(option_string[1]) &&
explicit_arg !== '') {
if (sep || chars.includes(explicit_arg[0])) {
const msg = 'ignored explicit argument %r'
throw new ArgumentError(action, sub(msg, explicit_arg))
}
action_tuples.push([action, [], option_string])
const char = option_string[0]
option_string = char + explicit_arg[0]
const optionals_map = this._option_string_actions
if (hasattr(optionals_map, option_string)) {
action = optionals_map[option_string]
explicit_arg = explicit_arg.slice(1)
if (!explicit_arg) {
sep = explicit_arg = undefined
} else if (explicit_arg[0] === '=') {
sep = '='
explicit_arg = explicit_arg.slice(1)
} else {
sep = ''
}
} else {
extras.push(char + explicit_arg)
extras_pattern.push('O')
stop = start_index + 1
break
}
// 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
const args = [explicit_arg]
action_tuples.push([action, args, option_string])
break
// error if a double-dash option did not use the
// explicit argument
} else {
const 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 {
const start = start_index + 1
const selected_patterns = arg_strings_pattern.slice(start)
const arg_count = this._match_argument(action, selected_patterns)
stop = start + arg_count
const 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 (const [action, args, option_string] of action_tuples) {
if (action.deprecated && !warned.has(option_string)) {
this._warning(sub("option '%(option)s' is deprecated", {
option: option_string
}))
warned.add(option_string)
}
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
const consume_positionals = start_index => {
// match as many Positionals as possible
const selected_pattern = arg_strings_pattern.slice(start_index)
const 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++) {
const action = positionals[i]
const arg_count = arg_counts[i]
const args = arg_strings.slice(start_index, start_index + arg_count)
// Strip out the first '--' if it is not in REMAINDER arg.
if (action.nargs === PARSER) {
if (arg_strings_pattern[start_index] === '-') {
assert(args[0] === '--')
_array_remove(args, '--')
}
} else if (action.nargs !== REMAINDER) {
if (arg_strings_pattern.slice(start_index, start_index + arg_count).includes('-')) {
_array_remove(args, '--')
}
}
start_index += arg_count
if (args.length && action.deprecated && !warned.has(action.dest)) {
this._warning(sub("argument '%(argument_name)s' is deprecated", {
argument_name: action.dest
}))
warned.add(action.dest)
}
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 extras_pattern = []
let start_index = 0
const 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 = start_index
while (next_option_string_index <= max_option_string_index) {
if (next_option_string_index in option_string_indices) {
break
}
next_option_string_index += 1
}
if (!intermixed && start_index !== next_option_string_index) {
const 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)) {
const strings = arg_strings.slice(start_index, next_option_string_index)
extras = extras.concat(strings)
extras_pattern = extras_pattern.concat(
arg_strings_pattern.slice(start_index, next_option_string_index).split(''))
start_index = next_option_string_index
}
// consume the next optional and any arguments for it
start_index = consume_optional(start_index)
}
if (!intermixed) {
// consume any positionals following the last Optional
const 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))
} else {
extras = extras.concat(arg_strings.slice(start_index))
extras_pattern = extras_pattern.concat(
arg_strings_pattern.slice(start_index).split(''))
extras_pattern = extras_pattern.join('')
assert(extras_pattern.length === extras.length)
// consume all positionals
arg_strings = extras.filter((string, i) => extras_pattern[i] !== 'O')
arg_strings_pattern = extras_pattern.replace(/O/g, '')
let stop_index = consume_positionals(0)
// leave unknown optionals and non-consumed positionals in extras
for (let i = 0; i < extras_pattern.length; i++) {
if (!stop_index) break
if (extras_pattern[i] !== 'O') {
stop_index -= 1
extras[i] = undefined
}
}
extras = extras.filter(string => string !== undefined)
}
// make sure all required actions were present and also convert
// action defaults which were not given as arguments
const required_actions = []
for (const 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) {
throw new ArgumentError(undefined,
sub('the following arguments are required: %s', required_actions.join(', ')))
}
// make sure all required groups had one option present
for (const group of this._mutually_exclusive_groups) {
if (group.required) {
let no_actions_used = true
for (const 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) {
const names = group._group_actions
.filter(action => action.help !== SUPPRESS)
.map(action => _get_action_name(action))
const msg = 'one of the arguments %s is required'
throw new ArgumentError(undefined, 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 (const 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 {
let args_file
try {
args_file = fs.readFileSync(arg_string.slice(1), 'utf8')
} catch (err) {
throw new ArgumentError(undefined, err.message)
}
let arg_strings = []
for (const arg_line of splitlines(args_file)) {
for (const 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)
}
}
// 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
const nargs_pattern = this._get_nargs_pattern(action)
const match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern))
// raise an exception if we weren't able to find a match
if (match === null) {
const 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
for (const i of range(actions.length, 0, -1)) {
const actions_slice = actions.slice(0, i)
const pattern = actions_slice.map(action => this._get_nargs_pattern(action)).join('')
const match = arg_strings_pattern.match(new RegExp('^' + pattern))
if (match !== null) {
const result = match.slice(1).map(string => string.length)
if (match[0].length < arg_strings_pattern.length &&
arg_strings_pattern[match[0].length] === 'O') {
while (result.length && !result[result.length - 1]) {
result.pop()
}
}
return result
}
}
return []
}
_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) {
const action = this._option_string_actions[arg_string]
return [[action, arg_string, undefined, 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('=')) {
const [option_string, explicit_arg] = _string_split(arg_string, '=', 1)
if (option_string in this._option_string_actions) {
const 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
const option_tuples = this._get_option_tuples(arg_string)
// if at least one action matched, return the options
if (option_tuples.length) {
return option_tuples
}
// 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, undefined]]
}
_get_option_tuples (option_string) {
const result = []
// option strings starting with two prefix characters are only
// split at the '='
const chars = this.prefix_chars
if (chars.includes(option_string[0]) && chars.includes(option_string[1])) {
if (this.allow_abbrev) {
let option_prefix, sep, explicit_arg
if (option_string.includes('=')) {
[option_prefix, explicit_arg] = _string_split(option_string, '=', 1)
sep = '='
} else {
option_prefix = option_string
sep = undefined
explicit_arg = undefined
}
for (const option_string of Object.keys(this._option_string_actions)) {
if (option_string.startsWith(option_prefix)) {
const action = this._option_string_actions[option_string]
const tup = [action, option_string, sep, 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, sep, explicit_arg
if (option_string.includes('=')) {
[option_prefix, explicit_arg] = _string_split(option_string, '=', 1)
sep = '='
} else {
option_prefix = option_string
sep = explicit_arg = undefined
}
const short_option_prefix = option_string.slice(0, 2)
const short_explicit_arg = option_string.slice(2)
for (const option_string of Object.keys(this._option_string_actions)) {
if (option_string === short_option_prefix) {
const action = this._option_string_actions[option_string]
const tup = [action, option_string, '', short_explicit_arg]
result.push(tup)
} else if (this.allow_abbrev && option_string.startsWith(option_prefix)) {
const action = this._option_string_actions[option_string]
const tup = [action, option_string, sep, explicit_arg]
result.push(tup)
}
}
// shouldn't ever get here
} else {
throw new ArgumentError(undefined, 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
const nargs = action.nargs
const option = action.option_strings.length
let nargs_pattern
// the default (None) is assumed to be a single argument
if (nargs === undefined) {
nargs_pattern = option ? '([A])' : '(-*A-*)'
// allow zero or one arguments
} else if (nargs === OPTIONAL) {
nargs_pattern = option ? '(A?)' : '(-*A?-*)'
// allow zero or more arguments
} else if (nargs === ZERO_OR_MORE) {
nargs_pattern = option ? '(A*)' : '(-*[A-]*)'
// allow one or more arguments
} else if (nargs === ONE_OR_MORE) {
nargs_pattern = option ? '(A+)' : '(-*A[A-]*)'
// allow any number of options or arguments
} else if (nargs === REMAINDER) {
nargs_pattern = option ? '([AO]*)' : '(.*)'
// allow one argument followed by any number of options or arguments
} else if (nargs === PARSER) {
nargs_pattern = option ? '(A[AO]*)' : '(-*A[-AO]*)'
// suppress action, like nargs=0
} else if (nargs === SUPPRESS) {
nargs_pattern = option ? '()' : '(-*)'
// all others should be integers
} else {
nargs_pattern = option
? sub('([AO]{%s})', nargs)
: sub('((?:-*A){%s}-*)', nargs)
}
// 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) {
const msg = sub('unrecognized arguments: %s', argv.join(' '))
if (this.exit_on_error) {
this.error(msg)
} else {
throw new ArgumentError(undefined, msg)
}
}
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.
const positionals = this._get_positional_actions()
const 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))
}
return this._parse_known_args2(args, namespace, true)
}
// ========================
// Value conversion methods
// ========================
_get_values (action, arg_strings) {
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 !== SUPPRESS) {
value = this._get_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 = []
}
// single argument or optional argument produces a single value
} else if (arg_strings.length === 1 && [undefined, OPTIONAL].includes(action.nargs)) {
const 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 (const v of value) {
this._check_value(action, v)
}
}
// return the converted value
return value
}
_get_value (action, arg_string) {
const 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))
}
// 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) {
const msg = err.message
throw new ArgumentError(action, msg)
// TypeErrors or ValueErrors also indicate errors
} else if (err instanceof TypeError) {
const name = getattr(action.type, 'name', repr(action.type))
const args = {type: name, value: arg_string}
const 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) return
const choices = _choices_to_array(action.choices)
if (choices.includes(value)) return
const args = {value: String(value),
choices: choices.map(choice => repr(String(choice))).join(', ')}
let msg = 'invalid choice: %(value)r (choose from %(choices)s)'
if (this.suggest_on_error && typeof value === 'string') {
if (choices.every(choice => typeof choice === 'string')) {
const { get_close_matches } = require('./difflib')
const suggestions = get_close_matches(value, choices, 1)
if (suggestions.length) {
args.closest = suggestions[0]
msg = 'invalid choice: %(value)r, maybe you meant %(closest)r? ' +
'(choose from %(choices)s)'
}
}
}
throw new ArgumentError(action, sub(msg, args))
}
// =======================
// Help-formatting methods
// =======================
format_usage () {
const formatter = this._get_formatter()
formatter.add_usage(this.usage, this._actions,
this._mutually_exclusive_groups)
return formatter.format_help()
}
format_help () {
const 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 (const 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
const formatter = new this.formatter_class({ prog: this.prog })
formatter._set_color(this.color)
return formatter
}
_get_validation_formatter () {
// Return cached formatter for read-only validation operations
// (_expand_help and _format_args). Avoids repeated slow _set_color calls.
if (this._cached_formatter === undefined) {
this._cached_formatter = this._get_formatter()
}
return this._cached_formatter
}
// =====================
// 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) {
file = file || process.stderr
if (file && typeof file.write === 'function') {
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.
*/
this.print_usage(process.stderr)
const args = {prog: this.prog, message}
this.exit(2, sub('%(prog)s: error: %(message)s\n', args))
}
_warning (message) {
const args = { prog: this.prog, message }
this._print_message(sub('%(prog)s: warning: %(message)s\n', args), process.stderr)
}
})
module.exports = {
ArgumentParser,
ArgumentError,
ArgumentTypeError,
BooleanOptionalAction,
FileType,
HelpFormatter,
ArgumentDefaultsHelpFormatter,
RawDescriptionHelpFormatter,
RawTextHelpFormatter,
MetavarTypeHelpFormatter,
Namespace,
Action,
ONE_OR_MORE,
OPTIONAL,
PARSER,
REMAINDER,
SUPPRESS,
ZERO_OR_MORE
}