├── .gitignore ├── test ├── .eslintrc.yml └── .babelrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── workflows │ └── ci.yml ├── package.json ├── doc ├── port_difference.md └── migrate_v1_to_v2.md ├── lib ├── sub.js └── textwrap.js ├── README.md ├── .eslintrc.yml ├── CHANGELOG.md ├── LICENSE └── argparse.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: '@babel/eslint-parser' 2 | -------------------------------------------------------------------------------- /test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-syntax-class-properties"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: puzrin 2 | patreon: puzrin 3 | tidelift: "npm/argparse" 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Since this is port of python's argparse: 2 | 3 | 1. All "how to use" questions should be addressed to original version. 4 | 2. Prior to report bug, make sure python's argparse works as expected, and your 5 | problem is specific to this JS package. 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * 3' 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [ '12', '14' ] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - run: npm install 27 | 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "argparse", 3 | "description": "CLI arguments parser. Native port of python's argparse.", 4 | "version": "2.0.1", 5 | "keywords": [ 6 | "cli", 7 | "parser", 8 | "argparse", 9 | "option", 10 | "args" 11 | ], 12 | "main": "argparse.js", 13 | "files": [ 14 | "argparse.js", 15 | "lib/" 16 | ], 17 | "license": "Python-2.0", 18 | "repository": "nodeca/argparse", 19 | "scripts": { 20 | "lint": "eslint .", 21 | "test": "npm run lint && nyc mocha", 22 | "coverage": "npm run test && nyc report --reporter html" 23 | }, 24 | "devDependencies": { 25 | "@babel/eslint-parser": "^7.11.0", 26 | "@babel/plugin-syntax-class-properties": "^7.10.4", 27 | "eslint": "^7.5.0", 28 | "mocha": "^8.0.1", 29 | "nyc": "^15.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /doc/port_difference.md: -------------------------------------------------------------------------------- 1 | Differences with python version 2 | =============================== 3 | 4 | ## 1. Option object instead of keyword arguments 5 | 6 | Python: 7 | 8 | ```py 9 | # python allows keyword arguments 10 | parser = argparse.ArgumentParser(prog='PROG', usage='%(prog)s [options]') 11 | ``` 12 | 13 | Javascript: 14 | 15 | ```js 16 | // keyword arguments are passed as a single `options` object 17 | parser = argparse.ArgumentParser({ prog: 'PROG', usage: '%(prog)s [options]' }) 18 | ``` 19 | 20 | ## 2. Use strings 'int', 'float' or 'str' instead of built-in python types 21 | 22 | Python: 23 | 24 | ```py 25 | parser.add_argument('--foo', type=int) 26 | ``` 27 | 28 | Javascript: 29 | 30 | ```js 31 | parser.add_argument('--foo', { type: 'int' }) 32 | ``` 33 | 34 | ## 3. TypeError instead of ValueError 35 | 36 | Python raises TypeError or ValueError for various argument errors. Javascript raises TypeError in both cases. 37 | 38 | ## 4. FileType() returns a stream 39 | 40 | You should be closing it with `.close()` if available (which doesn't exist for stdin/stdout). 41 | 42 | ## 5. When class is called as a function, `.call` is executed 43 | 44 | Override `Action.call` instead of `Action.__call__` in inherited classes 45 | 46 | ## 6. Limited support for %-formats 47 | 48 | - `%s` is rendered as `String(arg)` 49 | - `%r` is rendered as `util.inspect(arg)` 50 | - `%d`, `%i` is rendered as `arg.toFixed(0)`, no precision digits or padding is supported 51 | - no other formats are implemented yet 52 | 53 | ## 7. No `gettext` support 54 | 55 | All error messages are hardcoded. 56 | -------------------------------------------------------------------------------- /lib/sub.js: -------------------------------------------------------------------------------- 1 | // Limited implementation of python % string operator, supports only %s and %r for now 2 | // (other formats are not used here, but may appear in custom templates) 3 | 4 | 'use strict' 5 | 6 | const { inspect } = require('util') 7 | 8 | 9 | module.exports = function sub(pattern, ...values) { 10 | let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g 11 | 12 | let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) { 13 | if (is_literal) return '%' 14 | 15 | let padded_count = 0 16 | if (is_padded) { 17 | if (values.length === 0) throw new TypeError('not enough arguments for format string') 18 | padded_count = values.shift() 19 | if (!Number.isInteger(padded_count)) throw new TypeError('* wants int') 20 | } 21 | 22 | let str 23 | if (name !== undefined) { 24 | let dict = values[0] 25 | if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping') 26 | if (!(name in dict)) throw new TypeError(`no such key: '${name}'`) 27 | str = dict[name] 28 | } else { 29 | if (values.length === 0) throw new TypeError('not enough arguments for format string') 30 | str = values.shift() 31 | } 32 | 33 | switch (format) { 34 | case 's': 35 | str = String(str) 36 | break 37 | case 'r': 38 | str = inspect(str) 39 | break 40 | case 'd': 41 | case 'i': 42 | if (typeof str !== 'number') { 43 | throw new TypeError(`%${format} format: a number is required, not ${typeof str}`) 44 | } 45 | str = String(str.toFixed(0)) 46 | break 47 | default: 48 | throw new TypeError(`unsupported format character '${format}'`) 49 | } 50 | 51 | if (padded_count > 0) { 52 | return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count) 53 | } else { 54 | return str 55 | } 56 | }) 57 | 58 | if (values.length) { 59 | if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) { 60 | // mapping 61 | } else { 62 | throw new TypeError('not all arguments converted during string formatting') 63 | } 64 | } 65 | 66 | return result 67 | } 68 | -------------------------------------------------------------------------------- /doc/migrate_v1_to_v2.md: -------------------------------------------------------------------------------- 1 | Migration from v1 to v2 2 | ======================= 3 | 4 | In short: 5 | 6 | - Fix all deprecation warnings. 7 | - If you extended argparse classes - propagate appropriate changes. 8 | 9 | 10 | ## 1. Change all options, method and action names from `camelCase` to `snake_case`. 11 | 12 | For example: 13 | 14 | - `argparse.ArgumentParser({ addHelp: false })` -> `argparse.ArgumentParser({ add_help: false })` 15 | - `parser.printHelp()` -> `parser.print_help()` 16 | - `parser.add_argument({ action: 'storeTrue' })` -> `parser.add_argument({ action: 'store_true' })` 17 | 18 | Old names still have aliases (with deprecation messages), and your code may work. 19 | But no guarantees, especially if you extend classes. 20 | 21 | 22 | ## 2. `defaultValue` => `default`, `constValue` => `const`. 23 | 24 | Old names still has aliases with deprecaion messages, to simplify migration. 25 | 26 | 27 | ## 3. In `add_argument`, argument names should be raw params (not array). 28 | 29 | ```js 30 | parser.add_argument('-h', '--help', { help: 'show this help message and exit' }) 31 | ``` 32 | 33 | Old signature is supported but shows deprecation message. 34 | 35 | 36 | ## 4. `debug` option of argparse.ArgumentParser is deprecated 37 | 38 | Override `.exit()` method instead. 39 | 40 | ```js 41 | const argparse = require('argparse') 42 | 43 | class MyArgumentParser extends argparse.ArgumentParser { 44 | exit() { console.log('no exiting today') } 45 | } 46 | 47 | parser = new MyArgumentParser() 48 | ``` 49 | 50 | ## 5. `version` option of argparse.ArgumentParser is deprecated 51 | 52 | Use `version` action instead: 53 | 54 | ```js 55 | parser.add_argument('-v', '--version', { action: 'version', version: '1.0.0' }) 56 | ``` 57 | 58 | ## 6. `string` type is renamed to `str` 59 | 60 | ```js 61 | parser.add_argument('--foo', { type: 'str' }) 62 | ``` 63 | 64 | Old signature is supported but shows deprecation message. 65 | 66 | ## 7. Only TypeErrors are intercepted from `type` functions 67 | 68 | If user input is invalid, throw TypeError instead of Error: 69 | 70 | ``` 71 | parser.add_argument('--digit', { 72 | type: function digit(v) { 73 | if (!/^\d$/.test(v)) throw TypeError('not a digit') 74 | return +v 75 | } 76 | }) 77 | ``` 78 | 79 | TypeErrors will get intercepted and turned into user-friendly error messages, 80 | but ordinary Errors will not. 81 | 82 | ## 8. constants are moved to top-level 83 | 84 | Constants `SUPPRESS`, `OPTIONAL`, `ZERO_OR_MORE`, `ONE_OR_MORE`, `PARSER`, 85 | and `REMAINDER` previously available as `argparse.Const.*` are renamed to `argparse.*`. 86 | 87 | Constant `_UNRECOGNIZED_ARGS_ATTR` is no longer exposed publicly. 88 | 89 | Constant `EOL` no longer exists (hardcoded as '\n') - replace with '\n' if you used it somewhere. 90 | 91 | ## 9. namespace methods `.isset`, `.set`, `.get`, `.unset` are removed 92 | 93 | Get values from `Namespace` as if it was a plain js object. 94 | 95 | ## 10. an absense of value is indicated by `undefined` instead of `null` 96 | 97 | - if you passed `null` to any of the functions, it will be treated as a value (not replaced by default) 98 | - `parse_args` will return `{ x: undefined }` instead of `{ x: null }` if optional arg isn't specified 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | argparse 2 | ======== 3 | 4 | [![CI](https://github.com/nodeca/argparse/workflows/CI/badge.svg?branch=master)](https://github.com/nodeca/argparse/actions) 5 | [![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse) 6 | 7 | CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)). 8 | 9 | **Difference with original.** 10 | 11 | - JS has no keyword arguments support. 12 | - Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`. 13 | - JS has no python's types `int`, `float`, ... 14 | - Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`. 15 | - `%r` format specifier uses `require('util').inspect()`. 16 | 17 | More details in [doc](./doc). 18 | 19 | 20 | Example 21 | ------- 22 | 23 | Following code is a JS program that takes a list of integers and produces either the sum or the max: 24 | 25 | ```js 26 | const { ArgumentParser } = require('argparse') 27 | 28 | const parser = new ArgumentParser({ description: 'Process some integers.' }) 29 | 30 | let sum = ints => ints.reduce((a, b) => a + b) 31 | let max = ints => ints.reduce((a, b) => a > b ? a : b) 32 | 33 | parser.add_argument('integers', { metavar: 'N', type: 'int', nargs: '+', 34 | help: 'an integer for the accumulator' }) 35 | parser.add_argument('--sum', { dest: 'accumulate', action: 'store_const', 36 | const: sum, default: max, 37 | help: 'sum the integers (default: find the max)' }); 38 | 39 | let args = parser.parse_args() 40 | console.log(args.accumulate(args.integers)) 41 | ``` 42 | 43 | Assuming the JS code above is saved into a file called prog.js, it can be run at the command line and provides useful help messages: 44 | 45 | ``` 46 | $ node prog.js -h 47 | usage: prog.js [-h] [--sum] N [N ...] 48 | 49 | Process some integers. 50 | 51 | positional arguments: 52 | N an integer for the accumulator 53 | 54 | optional arguments: 55 | -h, --help show this help message and exit 56 | --sum sum the integers (default: find the max) 57 | ``` 58 | 59 | When run with the appropriate arguments, it prints either the sum or the max of the command-line integers: 60 | 61 | ``` 62 | $ node prog.js 1 2 3 4 63 | 4 64 | $ node prog.js 1 2 3 4 --sum 65 | 10 66 | ``` 67 | 68 | If invalid arguments are passed in, it will issue an error: 69 | 70 | ``` 71 | $ node prog.js a b c 72 | usage: prog.js [-h] [--sum] N [N ...] 73 | prog.js: error: argument N: invalid 'int' value: 'a' 74 | ``` 75 | 76 | This is an example ported from Python. You can find detailed explanation [here](https://docs.python.org/3.9/library/argparse.html). 77 | 78 | 79 | API docs 80 | -------- 81 | 82 | Since this is a port with minimal divergence, there's no separate documentation. 83 | Use original one instead, with notes about difference. 84 | 85 | 1. [Original doc](https://docs.python.org/3.9/library/argparse.html). 86 | 2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html). 87 | 3. [Difference with python](./doc). 88 | 89 | 90 | argparse for enterprise 91 | ----------------------- 92 | 93 | Available as part of the Tidelift Subscription 94 | 95 | The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) 96 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | browser: false 4 | # Postponed: enable partially (less on server, more on client) 5 | es6: true 6 | 7 | parserOptions: 8 | ecmaVersion: '2020' 9 | 10 | rules: 11 | accessor-pairs: 2 12 | #array-bracket-spacing: [ 2, "always", { "singleValue": true, "objectsInArrays": true, "arraysInArrays": true } ] 13 | block-scoped-var: 2 14 | block-spacing: 2 15 | brace-style: [ 2, '1tbs', { "allowSingleLine": true } ] 16 | # Postponed 17 | #callback-return: 2 18 | comma-dangle: 2 19 | comma-spacing: 2 20 | comma-style: 2 21 | computed-property-spacing: [ 2, never ] 22 | # Postponed 23 | #consistent-return: 2 24 | consistent-this: [ 2, self ] 25 | # ? change to multi 26 | curly: [ 2, 'multi-line' ] 27 | # Postponed 28 | # dot-notation: [ 2, { allowKeywords: true } ] 29 | dot-location: [ 2, 'property' ] 30 | eol-last: 2 31 | eqeqeq: 2 32 | #func-style: [ 2, declaration ] 33 | # Postponed 34 | #global-require: 2 35 | guard-for-in: 2 36 | handle-callback-err: 2 37 | 38 | # Postponed 39 | indent: [ 2, 4, { 40 | SwitchCase: 1, 41 | MemberExpression: off, 42 | CallExpression: { arguments: off }, 43 | ObjectExpression: off, 44 | ArrayExpression: off 45 | } ] 46 | 47 | # key-spacing: [ 2, { "align": "value" } ] 48 | keyword-spacing: 2 49 | linebreak-style: 2 50 | #max-depth: [ 1, 3 ] 51 | #max-nested-callbacks: [ 1, 5 ] 52 | # string can exceed 80 chars, but should not overflow github website :) 53 | #max-len: [ 2, 120, 1000 ] 54 | new-cap: 2 55 | new-parens: 2 56 | # Postponed 57 | #newline-after-var: 2 58 | no-alert: 2 59 | no-array-constructor: 2 60 | no-bitwise: 2 61 | no-caller: 2 62 | #no-case-declarations: 2 63 | no-catch-shadow: 2 64 | no-cond-assign: 2 65 | no-console: 1 66 | no-constant-condition: 2 67 | #no-control-regex: 2 68 | no-debugger: 1 69 | no-delete-var: 2 70 | no-div-regex: 2 71 | no-dupe-args: 2 72 | no-dupe-keys: 2 73 | no-duplicate-case: 2 74 | #no-else-return: 2 75 | # Tend to drop 76 | # no-empty: 1 77 | no-empty-character-class: 2 78 | no-empty-pattern: 2 79 | no-eq-null: 2 80 | no-eval: 2 81 | no-ex-assign: 2 82 | no-extend-native: 2 83 | no-extra-bind: 2 84 | no-extra-boolean-cast: 2 85 | no-extra-semi: 2 86 | no-fallthrough: 2 87 | no-floating-decimal: 2 88 | no-func-assign: 2 89 | # Postponed 90 | #no-implicit-coercion: [2, { "boolean": true, "number": true, "string": true } ] 91 | no-implied-eval: 2 92 | no-inner-declarations: 2 93 | no-invalid-regexp: 2 94 | no-irregular-whitespace: 2 95 | no-iterator: 2 96 | no-label-var: 2 97 | no-labels: 2 98 | no-lone-blocks: 1 99 | #no-lonely-if: 2 100 | no-loop-func: 2 101 | no-mixed-requires: [ 1, { "grouping": true } ] 102 | no-mixed-spaces-and-tabs: 2 103 | # Postponed 104 | #no-native-reassign: 2 105 | no-negated-in-lhs: 2 106 | # Postponed 107 | #no-nested-ternary: 2 108 | no-new: 2 109 | no-new-func: 2 110 | no-new-object: 2 111 | no-new-require: 2 112 | no-new-wrappers: 2 113 | no-obj-calls: 2 114 | no-octal: 2 115 | no-octal-escape: 2 116 | no-path-concat: 2 117 | no-proto: 2 118 | no-redeclare: 2 119 | # Postponed 120 | #no-regex-spaces: 2 121 | no-return-assign: 2 122 | no-self-compare: 2 123 | no-sequences: 2 124 | # Postponed 125 | #no-shadow: 2 126 | no-shadow-restricted-names: 2 127 | no-sparse-arrays: 2 128 | # Postponed 129 | #no-sync: 2 130 | no-trailing-spaces: 2 131 | no-undef: 2 132 | #no-undef-init: 2 133 | #no-undefined: 2 134 | no-unexpected-multiline: 2 135 | no-unreachable: 2 136 | no-unused-expressions: 2 137 | no-unused-vars: 2 138 | #no-use-before-define: 2 139 | #no-void: 2 140 | no-with: 2 141 | #object-curly-spacing: [ 2, always, { "objectsInObjects": true, "arraysInObjects": true } ] 142 | operator-assignment: 1 143 | # Postponed 144 | #operator-linebreak: [ 2, after ] 145 | semi: [ 2, never ] 146 | semi-spacing: 2 147 | space-before-function-paren: [ 2, { "anonymous": "always", "named": "never" } ] 148 | space-in-parens: [ 2, never ] 149 | #space-infix-ops: 2 150 | space-unary-ops: 2 151 | # Postponed 152 | #spaced-comment: [ 1, always, { exceptions: [ '/', '=' ] } ] 153 | strict: [ 2, global ] 154 | quotes: [ 2, single, avoid-escape ] 155 | quote-props: [ 1, 'as-needed', { "keywords": false } ] 156 | radix: 2 157 | use-isnan: 2 158 | valid-typeof: 2 159 | yoda: [ 2, never, { "exceptRange": true } ] 160 | 161 | # 162 | # es6 163 | # 164 | arrow-body-style: [ 1, "as-needed" ] 165 | arrow-parens: [ 1, "as-needed" ] 166 | arrow-spacing: 2 167 | constructor-super: 2 168 | generator-star-spacing: [ 2, {"before": false, "after": true } ] 169 | no-class-assign: 2 170 | no-confusing-arrow: [ 1, { allowParens: true } ] 171 | no-const-assign: 2 172 | #no-constant-condition: 2 173 | no-dupe-class-members: 2 174 | no-this-before-super: 2 175 | # Postponed 176 | #no-var: 2 177 | #object-shorthand: 1 178 | # Postponed 179 | #prefer-arrow-callback: 1 180 | # Postponed 181 | #prefer-const: 1 182 | #prefer-reflect 183 | #prefer-spread 184 | # Postponed 185 | #prefer-template: 1 186 | require-yield: 1 187 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 9 | ## [2.0.1] - 2020-08-29 10 | ### Fixed 11 | - Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150. 12 | 13 | 14 | ## [2.0.0] - 2020-08-14 15 | ### Changed 16 | - Full rewrite. Now port from python 3.9.0 & more precise following. 17 | See [doc](./doc) for difference and migration info. 18 | - node.js 10+ required 19 | - Removed most of local docs in favour of original ones. 20 | 21 | 22 | ## [1.0.10] - 2018-02-15 23 | ### Fixed 24 | - Use .concat instead of + for arrays, #122. 25 | 26 | 27 | ## [1.0.9] - 2016-09-29 28 | ### Changed 29 | - Rerelease after 1.0.8 - deps cleanup. 30 | 31 | 32 | ## [1.0.8] - 2016-09-29 33 | ### Changed 34 | - Maintenance (deps bump, fix node 6.5+ tests, coverage report). 35 | 36 | 37 | ## [1.0.7] - 2016-03-17 38 | ### Changed 39 | - Teach `addArgument` to accept string arg names. #97, @tomxtobin. 40 | 41 | 42 | ## [1.0.6] - 2016-02-06 43 | ### Changed 44 | - Maintenance: moved to eslint & updated CS. 45 | 46 | 47 | ## [1.0.5] - 2016-02-05 48 | ### Changed 49 | - Removed lodash dependency to significantly reduce install size. 50 | Thanks to @mourner. 51 | 52 | 53 | ## [1.0.4] - 2016-01-17 54 | ### Changed 55 | - Maintenance: lodash update to 4.0.0. 56 | 57 | 58 | ## [1.0.3] - 2015-10-27 59 | ### Fixed 60 | - Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple. 61 | 62 | 63 | ## [1.0.2] - 2015-03-22 64 | ### Changed 65 | - Relaxed lodash version dependency. 66 | 67 | 68 | ## [1.0.1] - 2015-02-20 69 | ### Changed 70 | - Changed dependencies to be compatible with ancient nodejs. 71 | 72 | 73 | ## [1.0.0] - 2015-02-19 74 | ### Changed 75 | - Maintenance release. 76 | - Replaced `underscore` with `lodash`. 77 | - Bumped version to 1.0.0 to better reflect semver meaning. 78 | - HISTORY.md -> CHANGELOG.md 79 | 80 | 81 | ## [0.1.16] - 2013-12-01 82 | ### Changed 83 | - Maintenance release. Updated dependencies and docs. 84 | 85 | 86 | ## [0.1.15] - 2013-05-13 87 | ### Fixed 88 | - Fixed #55, @trebor89 89 | 90 | 91 | ## [0.1.14] - 2013-05-12 92 | ### Fixed 93 | - Fixed #62, @maxtaco 94 | 95 | 96 | ## [0.1.13] - 2013-04-08 97 | ### Changed 98 | - Added `.npmignore` to reduce package size 99 | 100 | 101 | ## [0.1.12] - 2013-02-10 102 | ### Fixed 103 | - Fixed conflictHandler (#46), @hpaulj 104 | 105 | 106 | ## [0.1.11] - 2013-02-07 107 | ### Added 108 | - Added 70+ tests (ported from python), @hpaulj 109 | - Added conflictHandler, @applepicke 110 | - Added fromfilePrefixChar, @hpaulj 111 | 112 | ### Fixed 113 | - Multiple bugfixes, @hpaulj 114 | 115 | 116 | ## [0.1.10] - 2012-12-30 117 | ### Added 118 | - Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion) 119 | support, thanks to @hpaulj 120 | 121 | ### Fixed 122 | - Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj 123 | 124 | 125 | ## [0.1.9] - 2012-12-27 126 | ### Fixed 127 | - Fixed option dest interferens with other options (issue #23), thanks to @hpaulj 128 | - Fixed default value behavior with `*` positionals, thanks to @hpaulj 129 | - Improve `getDefault()` behavior, thanks to @hpaulj 130 | - Improve negative argument parsing, thanks to @hpaulj 131 | 132 | 133 | ## [0.1.8] - 2012-12-01 134 | ### Fixed 135 | - Fixed parser parents (issue #19), thanks to @hpaulj 136 | - Fixed negative argument parse (issue #20), thanks to @hpaulj 137 | 138 | 139 | ## [0.1.7] - 2012-10-14 140 | ### Fixed 141 | - Fixed 'choices' argument parse (issue #16) 142 | - Fixed stderr output (issue #15) 143 | 144 | 145 | ## [0.1.6] - 2012-09-09 146 | ### Fixed 147 | - Fixed check for conflict of options (thanks to @tomxtobin) 148 | 149 | 150 | ## [0.1.5] - 2012-09-03 151 | ### Fixed 152 | - Fix parser #setDefaults method (thanks to @tomxtobin) 153 | 154 | 155 | ## [0.1.4] - 2012-07-30 156 | ### Fixed 157 | - Fixed pseudo-argument support (thanks to @CGamesPlay) 158 | - Fixed addHelp default (should be true), if not set (thanks to @benblank) 159 | 160 | 161 | ## [0.1.3] - 2012-06-27 162 | ### Fixed 163 | - Fixed formatter api name: Formatter -> HelpFormatter 164 | 165 | 166 | ## [0.1.2] - 2012-05-29 167 | ### Fixed 168 | - Removed excess whitespace in help 169 | - Fixed error reporting, when parcer with subcommands 170 | called with empty arguments 171 | 172 | ### Added 173 | - Added basic tests 174 | 175 | 176 | ## [0.1.1] - 2012-05-23 177 | ### Fixed 178 | - Fixed line wrapping in help formatter 179 | - Added better error reporting on invalid arguments 180 | 181 | 182 | ## [0.1.0] - 2012-05-16 183 | ### Added 184 | - First release. 185 | 186 | 187 | [2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1 188 | [2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0 189 | [1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10 190 | [1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9 191 | [1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8 192 | [1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7 193 | [1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6 194 | [1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5 195 | [1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4 196 | [1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3 197 | [1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2 198 | [1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1 199 | [1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0 200 | [0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16 201 | [0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15 202 | [0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14 203 | [0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13 204 | [0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12 205 | [0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11 206 | [0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10 207 | [0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9 208 | [0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8 209 | [0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7 210 | [0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6 211 | [0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5 212 | [0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4 213 | [0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3 214 | [0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2 215 | [0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1 216 | [0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0 217 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | A. HISTORY OF THE SOFTWARE 2 | ========================== 3 | 4 | Python was created in the early 1990s by Guido van Rossum at Stichting 5 | Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands 6 | as a successor of a language called ABC. Guido remains Python's 7 | principal author, although it includes many contributions from others. 8 | 9 | In 1995, Guido continued his work on Python at the Corporation for 10 | National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) 11 | in Reston, Virginia where he released several versions of the 12 | software. 13 | 14 | In May 2000, Guido and the Python core development team moved to 15 | BeOpen.com to form the BeOpen PythonLabs team. In October of the same 16 | year, the PythonLabs team moved to Digital Creations, which became 17 | Zope Corporation. In 2001, the Python Software Foundation (PSF, see 18 | https://www.python.org/psf/) was formed, a non-profit organization 19 | created specifically to own Python-related Intellectual Property. 20 | Zope Corporation was a sponsoring member of the PSF. 21 | 22 | All Python releases are Open Source (see http://www.opensource.org for 23 | the Open Source Definition). Historically, most, but not all, Python 24 | releases have also been GPL-compatible; the table below summarizes 25 | the various releases. 26 | 27 | Release Derived Year Owner GPL- 28 | from compatible? (1) 29 | 30 | 0.9.0 thru 1.2 1991-1995 CWI yes 31 | 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 32 | 1.6 1.5.2 2000 CNRI no 33 | 2.0 1.6 2000 BeOpen.com no 34 | 1.6.1 1.6 2001 CNRI yes (2) 35 | 2.1 2.0+1.6.1 2001 PSF no 36 | 2.0.1 2.0+1.6.1 2001 PSF yes 37 | 2.1.1 2.1+2.0.1 2001 PSF yes 38 | 2.1.2 2.1.1 2002 PSF yes 39 | 2.1.3 2.1.2 2002 PSF yes 40 | 2.2 and above 2.1.1 2001-now PSF yes 41 | 42 | Footnotes: 43 | 44 | (1) GPL-compatible doesn't mean that we're distributing Python under 45 | the GPL. All Python licenses, unlike the GPL, let you distribute 46 | a modified version without making your changes open source. The 47 | GPL-compatible licenses make it possible to combine Python with 48 | other software that is released under the GPL; the others don't. 49 | 50 | (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, 51 | because its license has a choice of law clause. According to 52 | CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 53 | is "not incompatible" with the GPL. 54 | 55 | Thanks to the many outside volunteers who have worked under Guido's 56 | direction to make these releases possible. 57 | 58 | 59 | B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON 60 | =============================================================== 61 | 62 | PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 63 | -------------------------------------------- 64 | 65 | 1. This LICENSE AGREEMENT is between the Python Software Foundation 66 | ("PSF"), and the Individual or Organization ("Licensee") accessing and 67 | otherwise using this software ("Python") in source or binary form and 68 | its associated documentation. 69 | 70 | 2. Subject to the terms and conditions of this License Agreement, PSF hereby 71 | grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, 72 | analyze, test, perform and/or display publicly, prepare derivative works, 73 | distribute, and otherwise use Python alone or in any derivative version, 74 | provided, however, that PSF's License Agreement and PSF's notice of copyright, 75 | i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 76 | 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; 77 | All Rights Reserved" are retained in Python alone or in any derivative version 78 | prepared by Licensee. 79 | 80 | 3. In the event Licensee prepares a derivative work that is based on 81 | or incorporates Python or any part thereof, and wants to make 82 | the derivative work available to others as provided herein, then 83 | Licensee hereby agrees to include in any such work a brief summary of 84 | the changes made to Python. 85 | 86 | 4. PSF is making Python available to Licensee on an "AS IS" 87 | basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 88 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND 89 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 90 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT 91 | INFRINGE ANY THIRD PARTY RIGHTS. 92 | 93 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 94 | FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 95 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, 96 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 97 | 98 | 6. This License Agreement will automatically terminate upon a material 99 | breach of its terms and conditions. 100 | 101 | 7. Nothing in this License Agreement shall be deemed to create any 102 | relationship of agency, partnership, or joint venture between PSF and 103 | Licensee. This License Agreement does not grant permission to use PSF 104 | trademarks or trade name in a trademark sense to endorse or promote 105 | products or services of Licensee, or any third party. 106 | 107 | 8. By copying, installing or otherwise using Python, Licensee 108 | agrees to be bound by the terms and conditions of this License 109 | Agreement. 110 | 111 | 112 | BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 113 | ------------------------------------------- 114 | 115 | BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 116 | 117 | 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an 118 | office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the 119 | Individual or Organization ("Licensee") accessing and otherwise using 120 | this software in source or binary form and its associated 121 | documentation ("the Software"). 122 | 123 | 2. Subject to the terms and conditions of this BeOpen Python License 124 | Agreement, BeOpen hereby grants Licensee a non-exclusive, 125 | royalty-free, world-wide license to reproduce, analyze, test, perform 126 | and/or display publicly, prepare derivative works, distribute, and 127 | otherwise use the Software alone or in any derivative version, 128 | provided, however, that the BeOpen Python License is retained in the 129 | Software, alone or in any derivative version prepared by Licensee. 130 | 131 | 3. BeOpen is making the Software available to Licensee on an "AS IS" 132 | basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 133 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND 134 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 135 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT 136 | INFRINGE ANY THIRD PARTY RIGHTS. 137 | 138 | 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE 139 | SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS 140 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY 141 | DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 142 | 143 | 5. This License Agreement will automatically terminate upon a material 144 | breach of its terms and conditions. 145 | 146 | 6. This License Agreement shall be governed by and interpreted in all 147 | respects by the law of the State of California, excluding conflict of 148 | law provisions. Nothing in this License Agreement shall be deemed to 149 | create any relationship of agency, partnership, or joint venture 150 | between BeOpen and Licensee. This License Agreement does not grant 151 | permission to use BeOpen trademarks or trade names in a trademark 152 | sense to endorse or promote products or services of Licensee, or any 153 | third party. As an exception, the "BeOpen Python" logos available at 154 | http://www.pythonlabs.com/logos.html may be used according to the 155 | permissions granted on that web page. 156 | 157 | 7. By copying, installing or otherwise using the software, Licensee 158 | agrees to be bound by the terms and conditions of this License 159 | Agreement. 160 | 161 | 162 | CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 163 | --------------------------------------- 164 | 165 | 1. This LICENSE AGREEMENT is between the Corporation for National 166 | Research Initiatives, having an office at 1895 Preston White Drive, 167 | Reston, VA 20191 ("CNRI"), and the Individual or Organization 168 | ("Licensee") accessing and otherwise using Python 1.6.1 software in 169 | source or binary form and its associated documentation. 170 | 171 | 2. Subject to the terms and conditions of this License Agreement, CNRI 172 | hereby grants Licensee a nonexclusive, royalty-free, world-wide 173 | license to reproduce, analyze, test, perform and/or display publicly, 174 | prepare derivative works, distribute, and otherwise use Python 1.6.1 175 | alone or in any derivative version, provided, however, that CNRI's 176 | License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 177 | 1995-2001 Corporation for National Research Initiatives; All Rights 178 | Reserved" are retained in Python 1.6.1 alone or in any derivative 179 | version prepared by Licensee. Alternately, in lieu of CNRI's License 180 | Agreement, Licensee may substitute the following text (omitting the 181 | quotes): "Python 1.6.1 is made available subject to the terms and 182 | conditions in CNRI's License Agreement. This Agreement together with 183 | Python 1.6.1 may be located on the Internet using the following 184 | unique, persistent identifier (known as a handle): 1895.22/1013. This 185 | Agreement may also be obtained from a proxy server on the Internet 186 | using the following URL: http://hdl.handle.net/1895.22/1013". 187 | 188 | 3. In the event Licensee prepares a derivative work that is based on 189 | or incorporates Python 1.6.1 or any part thereof, and wants to make 190 | the derivative work available to others as provided herein, then 191 | Licensee hereby agrees to include in any such work a brief summary of 192 | the changes made to Python 1.6.1. 193 | 194 | 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" 195 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 196 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND 197 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 198 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT 199 | INFRINGE ANY THIRD PARTY RIGHTS. 200 | 201 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 202 | 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 203 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, 204 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 205 | 206 | 6. This License Agreement will automatically terminate upon a material 207 | breach of its terms and conditions. 208 | 209 | 7. This License Agreement shall be governed by the federal 210 | intellectual property law of the United States, including without 211 | limitation the federal copyright law, and, to the extent such 212 | U.S. federal law does not apply, by the law of the Commonwealth of 213 | Virginia, excluding Virginia's conflict of law provisions. 214 | Notwithstanding the foregoing, with regard to derivative works based 215 | on Python 1.6.1 that incorporate non-separable material that was 216 | previously distributed under the GNU General Public License (GPL), the 217 | law of the Commonwealth of Virginia shall govern this License 218 | Agreement only as to issues arising under or with respect to 219 | Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this 220 | License Agreement shall be deemed to create any relationship of 221 | agency, partnership, or joint venture between CNRI and Licensee. This 222 | License Agreement does not grant permission to use CNRI trademarks or 223 | trade name in a trademark sense to endorse or promote products or 224 | services of Licensee, or any third party. 225 | 226 | 8. By clicking on the "ACCEPT" button where indicated, or by copying, 227 | installing or otherwise using Python 1.6.1, Licensee agrees to be 228 | bound by the terms and conditions of this License Agreement. 229 | 230 | ACCEPT 231 | 232 | 233 | CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 234 | -------------------------------------------------- 235 | 236 | Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, 237 | The Netherlands. All rights reserved. 238 | 239 | Permission to use, copy, modify, and distribute this software and its 240 | documentation for any purpose and without fee is hereby granted, 241 | provided that the above copyright notice appear in all copies and that 242 | both that copyright notice and this permission notice appear in 243 | supporting documentation, and that the name of Stichting Mathematisch 244 | Centrum or CWI not be used in advertising or publicity pertaining to 245 | distribution of the software without specific, written prior 246 | permission. 247 | 248 | STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO 249 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 250 | FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE 251 | FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 252 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 253 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 254 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 255 | -------------------------------------------------------------------------------- /lib/textwrap.js: -------------------------------------------------------------------------------- 1 | // Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions): 2 | // https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py 3 | 4 | 'use strict' 5 | 6 | /* 7 | * Text wrapping and filling. 8 | */ 9 | 10 | // Copyright (C) 1999-2001 Gregory P. Ward. 11 | // Copyright (C) 2002, 2003 Python Software Foundation. 12 | // Copyright (C) 2020 argparse.js authors 13 | // Originally written by Greg Ward 14 | 15 | // Hardcode the recognized whitespace characters to the US-ASCII 16 | // whitespace characters. The main reason for doing this is that 17 | // some Unicode spaces (like \u00a0) are non-breaking whitespaces. 18 | // 19 | // This less funky little regex just split on recognized spaces. E.g. 20 | // "Hello there -- you goof-ball, use the -b option!" 21 | // splits into 22 | // Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/ 23 | const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/ 24 | 25 | class TextWrapper { 26 | /* 27 | * Object for wrapping/filling text. The public interface consists of 28 | * the wrap() and fill() methods; the other methods are just there for 29 | * subclasses to override in order to tweak the default behaviour. 30 | * If you want to completely replace the main wrapping algorithm, 31 | * you'll probably have to override _wrap_chunks(). 32 | * 33 | * Several instance attributes control various aspects of wrapping: 34 | * width (default: 70) 35 | * the maximum width of wrapped lines (unless break_long_words 36 | * is false) 37 | * initial_indent (default: "") 38 | * string that will be prepended to the first line of wrapped 39 | * output. Counts towards the line's width. 40 | * subsequent_indent (default: "") 41 | * string that will be prepended to all lines save the first 42 | * of wrapped output; also counts towards each line's width. 43 | * expand_tabs (default: true) 44 | * Expand tabs in input text to spaces before further processing. 45 | * Each tab will become 0 .. 'tabsize' spaces, depending on its position 46 | * in its line. If false, each tab is treated as a single character. 47 | * tabsize (default: 8) 48 | * Expand tabs in input text to 0 .. 'tabsize' spaces, unless 49 | * 'expand_tabs' is false. 50 | * replace_whitespace (default: true) 51 | * Replace all whitespace characters in the input text by spaces 52 | * after tab expansion. Note that if expand_tabs is false and 53 | * replace_whitespace is true, every tab will be converted to a 54 | * single space! 55 | * fix_sentence_endings (default: false) 56 | * Ensure that sentence-ending punctuation is always followed 57 | * by two spaces. Off by default because the algorithm is 58 | * (unavoidably) imperfect. 59 | * break_long_words (default: true) 60 | * Break words longer than 'width'. If false, those words will not 61 | * be broken, and some lines might be longer than 'width'. 62 | * break_on_hyphens (default: true) 63 | * Allow breaking hyphenated words. If true, wrapping will occur 64 | * preferably on whitespaces and right after hyphens part of 65 | * compound words. 66 | * drop_whitespace (default: true) 67 | * Drop leading and trailing whitespace from lines. 68 | * max_lines (default: None) 69 | * Truncate wrapped lines. 70 | * placeholder (default: ' [...]') 71 | * Append to the last line of truncated text. 72 | */ 73 | 74 | constructor(options = {}) { 75 | let { 76 | width = 70, 77 | initial_indent = '', 78 | subsequent_indent = '', 79 | expand_tabs = true, 80 | replace_whitespace = true, 81 | fix_sentence_endings = false, 82 | break_long_words = true, 83 | drop_whitespace = true, 84 | break_on_hyphens = true, 85 | tabsize = 8, 86 | max_lines = undefined, 87 | placeholder=' [...]' 88 | } = options 89 | 90 | this.width = width 91 | this.initial_indent = initial_indent 92 | this.subsequent_indent = subsequent_indent 93 | this.expand_tabs = expand_tabs 94 | this.replace_whitespace = replace_whitespace 95 | this.fix_sentence_endings = fix_sentence_endings 96 | this.break_long_words = break_long_words 97 | this.drop_whitespace = drop_whitespace 98 | this.break_on_hyphens = break_on_hyphens 99 | this.tabsize = tabsize 100 | this.max_lines = max_lines 101 | this.placeholder = placeholder 102 | } 103 | 104 | 105 | // -- Private methods ----------------------------------------------- 106 | // (possibly useful for subclasses to override) 107 | 108 | _munge_whitespace(text) { 109 | /* 110 | * _munge_whitespace(text : string) -> string 111 | * 112 | * Munge whitespace in text: expand tabs and convert all other 113 | * whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz" 114 | * becomes " foo bar baz". 115 | */ 116 | if (this.expand_tabs) { 117 | text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js 118 | } 119 | if (this.replace_whitespace) { 120 | text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ') 121 | } 122 | return text 123 | } 124 | 125 | _split(text) { 126 | /* 127 | * _split(text : string) -> [string] 128 | * 129 | * Split the text to wrap into indivisible chunks. Chunks are 130 | * not quite the same as words; see _wrap_chunks() for full 131 | * details. As an example, the text 132 | * Look, goof-ball -- use the -b option! 133 | * breaks into the following chunks: 134 | * 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', 135 | * 'use', ' ', 'the', ' ', '-b', ' ', 'option!' 136 | * if break_on_hyphens is True, or in: 137 | * 'Look,', ' ', 'goof-ball', ' ', '--', ' ', 138 | * 'use', ' ', 'the', ' ', '-b', ' ', option!' 139 | * otherwise. 140 | */ 141 | let chunks = text.split(wordsep_simple_re) 142 | chunks = chunks.filter(Boolean) 143 | return chunks 144 | } 145 | 146 | _handle_long_word(reversed_chunks, cur_line, cur_len, width) { 147 | /* 148 | * _handle_long_word(chunks : [string], 149 | * cur_line : [string], 150 | * cur_len : int, width : int) 151 | * 152 | * Handle a chunk of text (most likely a word, not whitespace) that 153 | * is too long to fit in any line. 154 | */ 155 | // Figure out when indent is larger than the specified width, and make 156 | // sure at least one character is stripped off on every pass 157 | let space_left 158 | if (width < 1) { 159 | space_left = 1 160 | } else { 161 | space_left = width - cur_len 162 | } 163 | 164 | // If we're allowed to break long words, then do so: put as much 165 | // of the next chunk onto the current line as will fit. 166 | if (this.break_long_words) { 167 | cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left)) 168 | reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left) 169 | 170 | // Otherwise, we have to preserve the long word intact. Only add 171 | // it to the current line if there's nothing already there -- 172 | // that minimizes how much we violate the width constraint. 173 | } else if (!cur_line) { 174 | cur_line.push(...reversed_chunks.pop()) 175 | } 176 | 177 | // If we're not allowed to break long words, and there's already 178 | // text on the current line, do nothing. Next time through the 179 | // main loop of _wrap_chunks(), we'll wind up here again, but 180 | // cur_len will be zero, so the next line will be entirely 181 | // devoted to the long word that we can't handle right now. 182 | } 183 | 184 | _wrap_chunks(chunks) { 185 | /* 186 | * _wrap_chunks(chunks : [string]) -> [string] 187 | * 188 | * Wrap a sequence of text chunks and return a list of lines of 189 | * length 'self.width' or less. (If 'break_long_words' is false, 190 | * some lines may be longer than this.) Chunks correspond roughly 191 | * to words and the whitespace between them: each chunk is 192 | * indivisible (modulo 'break_long_words'), but a line break can 193 | * come between any two chunks. Chunks should not have internal 194 | * whitespace; ie. a chunk is either all whitespace or a "word". 195 | * Whitespace chunks will be removed from the beginning and end of 196 | * lines, but apart from that whitespace is preserved. 197 | */ 198 | let lines = [] 199 | let indent 200 | if (this.width <= 0) { 201 | throw Error(`invalid width ${this.width} (must be > 0)`) 202 | } 203 | if (this.max_lines !== undefined) { 204 | if (this.max_lines > 1) { 205 | indent = this.subsequent_indent 206 | } else { 207 | indent = this.initial_indent 208 | } 209 | if (indent.length + this.placeholder.trimStart().length > this.width) { 210 | throw Error('placeholder too large for max width') 211 | } 212 | } 213 | 214 | // Arrange in reverse order so items can be efficiently popped 215 | // from a stack of chucks. 216 | chunks = chunks.reverse() 217 | 218 | while (chunks.length > 0) { 219 | 220 | // Start the list of chunks that will make up the current line. 221 | // cur_len is just the length of all the chunks in cur_line. 222 | let cur_line = [] 223 | let cur_len = 0 224 | 225 | // Figure out which static string will prefix this line. 226 | let indent 227 | if (lines) { 228 | indent = this.subsequent_indent 229 | } else { 230 | indent = this.initial_indent 231 | } 232 | 233 | // Maximum width for this line. 234 | let width = this.width - indent.length 235 | 236 | // First chunk on line is whitespace -- drop it, unless this 237 | // is the very beginning of the text (ie. no lines started yet). 238 | if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) { 239 | chunks.pop() 240 | } 241 | 242 | while (chunks.length > 0) { 243 | let l = chunks[chunks.length - 1].length 244 | 245 | // Can at least squeeze this chunk onto the current line. 246 | if (cur_len + l <= width) { 247 | cur_line.push(chunks.pop()) 248 | cur_len += l 249 | 250 | // Nope, this line is full. 251 | } else { 252 | break 253 | } 254 | } 255 | 256 | // The current line is full, and the next chunk is too big to 257 | // fit on *any* line (not just this one). 258 | if (chunks.length && chunks[chunks.length - 1].length > width) { 259 | this._handle_long_word(chunks, cur_line, cur_len, width) 260 | cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0) 261 | } 262 | 263 | // If the last chunk on this line is all whitespace, drop it. 264 | if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') { 265 | cur_len -= cur_line[cur_line.length - 1].length 266 | cur_line.pop() 267 | } 268 | 269 | if (cur_line) { 270 | if (this.max_lines === undefined || 271 | lines.length + 1 < this.max_lines || 272 | (chunks.length === 0 || 273 | this.drop_whitespace && 274 | chunks.length === 1 && 275 | !chunks[0].trim()) && cur_len <= width) { 276 | // Convert current line back to a string and store it in 277 | // list of all lines (return value). 278 | lines.push(indent + cur_line.join('')) 279 | } else { 280 | let had_break = false 281 | while (cur_line) { 282 | if (cur_line[cur_line.length - 1].trim() && 283 | cur_len + this.placeholder.length <= width) { 284 | cur_line.push(this.placeholder) 285 | lines.push(indent + cur_line.join('')) 286 | had_break = true 287 | break 288 | } 289 | cur_len -= cur_line[-1].length 290 | cur_line.pop() 291 | } 292 | if (!had_break) { 293 | if (lines) { 294 | let prev_line = lines[lines.length - 1].trimEnd() 295 | if (prev_line.length + this.placeholder.length <= 296 | this.width) { 297 | lines[lines.length - 1] = prev_line + this.placeholder 298 | break 299 | } 300 | } 301 | lines.push(indent + this.placeholder.lstrip()) 302 | } 303 | break 304 | } 305 | } 306 | } 307 | 308 | return lines 309 | } 310 | 311 | _split_chunks(text) { 312 | text = this._munge_whitespace(text) 313 | return this._split(text) 314 | } 315 | 316 | // -- Public interface ---------------------------------------------- 317 | 318 | wrap(text) { 319 | /* 320 | * wrap(text : string) -> [string] 321 | * 322 | * Reformat the single paragraph in 'text' so it fits in lines of 323 | * no more than 'self.width' columns, and return a list of wrapped 324 | * lines. Tabs in 'text' are expanded with string.expandtabs(), 325 | * and all other whitespace characters (including newline) are 326 | * converted to space. 327 | */ 328 | let chunks = this._split_chunks(text) 329 | // not implemented in js 330 | //if (this.fix_sentence_endings) { 331 | // this._fix_sentence_endings(chunks) 332 | //} 333 | return this._wrap_chunks(chunks) 334 | } 335 | 336 | fill(text) { 337 | /* 338 | * fill(text : string) -> string 339 | * 340 | * Reformat the single paragraph in 'text' to fit in lines of no 341 | * more than 'self.width' columns, and return a new string 342 | * containing the entire wrapped paragraph. 343 | */ 344 | return this.wrap(text).join('\n') 345 | } 346 | } 347 | 348 | 349 | // -- Convenience interface --------------------------------------------- 350 | 351 | function wrap(text, options = {}) { 352 | /* 353 | * Wrap a single paragraph of text, returning a list of wrapped lines. 354 | * 355 | * Reformat the single paragraph in 'text' so it fits in lines of no 356 | * more than 'width' columns, and return a list of wrapped lines. By 357 | * default, tabs in 'text' are expanded with string.expandtabs(), and 358 | * all other whitespace characters (including newline) are converted to 359 | * space. See TextWrapper class for available keyword args to customize 360 | * wrapping behaviour. 361 | */ 362 | let { width = 70, ...kwargs } = options 363 | let w = new TextWrapper(Object.assign({ width }, kwargs)) 364 | return w.wrap(text) 365 | } 366 | 367 | function fill(text, options = {}) { 368 | /* 369 | * Fill a single paragraph of text, returning a new string. 370 | * 371 | * Reformat the single paragraph in 'text' to fit in lines of no more 372 | * than 'width' columns, and return a new string containing the entire 373 | * wrapped paragraph. As with wrap(), tabs are expanded and other 374 | * whitespace characters converted to space. See TextWrapper class for 375 | * available keyword args to customize wrapping behaviour. 376 | */ 377 | let { width = 70, ...kwargs } = options 378 | let w = new TextWrapper(Object.assign({ width }, kwargs)) 379 | return w.fill(text) 380 | } 381 | 382 | // -- Loosely related functionality ------------------------------------- 383 | 384 | let _whitespace_only_re = /^[ \t]+$/mg 385 | let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg 386 | 387 | function dedent(text) { 388 | /* 389 | * Remove any common leading whitespace from every line in `text`. 390 | * 391 | * This can be used to make triple-quoted strings line up with the left 392 | * edge of the display, while still presenting them in the source code 393 | * in indented form. 394 | * 395 | * Note that tabs and spaces are both treated as whitespace, but they 396 | * are not equal: the lines " hello" and "\\thello" are 397 | * considered to have no common leading whitespace. 398 | * 399 | * Entirely blank lines are normalized to a newline character. 400 | */ 401 | // Look for the longest leading string of spaces and tabs common to 402 | // all lines. 403 | let margin = undefined 404 | text = text.replace(_whitespace_only_re, '') 405 | let indents = text.match(_leading_whitespace_re) || [] 406 | for (let indent of indents) { 407 | indent = indent.slice(0, -1) 408 | 409 | if (margin === undefined) { 410 | margin = indent 411 | 412 | // Current line more deeply indented than previous winner: 413 | // no change (previous winner is still on top). 414 | } else if (indent.startsWith(margin)) { 415 | // pass 416 | 417 | // Current line consistent with and no deeper than previous winner: 418 | // it's the new winner. 419 | } else if (margin.startsWith(indent)) { 420 | margin = indent 421 | 422 | // Find the largest common whitespace between current line and previous 423 | // winner. 424 | } else { 425 | for (let i = 0; i < margin.length && i < indent.length; i++) { 426 | if (margin[i] !== indent[i]) { 427 | margin = margin.slice(0, i) 428 | break 429 | } 430 | } 431 | } 432 | } 433 | 434 | if (margin) { 435 | text = text.replace(new RegExp('^' + margin, 'mg'), '') 436 | } 437 | return text 438 | } 439 | 440 | module.exports = { wrap, fill, dedent } 441 | -------------------------------------------------------------------------------- /argparse.js: -------------------------------------------------------------------------------- 1 | // Port of python's argparse module, version 3.9.0: 2 | // https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py 3 | 4 | 'use strict' 5 | 6 | // Copyright (C) 2010-2020 Python Software Foundation. 7 | // Copyright (C) 2020 argparse.js authors 8 | 9 | /* 10 | * Command-line parsing library 11 | * 12 | * This module is an optparse-inspired command-line parsing library that: 13 | * 14 | * - handles both optional and positional arguments 15 | * - produces highly informative usage messages 16 | * - supports parsers that dispatch to sub-parsers 17 | * 18 | * The following is a simple usage example that sums integers from the 19 | * command-line and writes the result to a file:: 20 | * 21 | * parser = argparse.ArgumentParser( 22 | * description='sum the integers at the command line') 23 | * parser.add_argument( 24 | * 'integers', metavar='int', nargs='+', type=int, 25 | * help='an integer to be summed') 26 | * parser.add_argument( 27 | * '--log', default=sys.stdout, type=argparse.FileType('w'), 28 | * help='the file where the sum should be written') 29 | * args = parser.parse_args() 30 | * args.log.write('%s' % sum(args.integers)) 31 | * args.log.close() 32 | * 33 | * The module contains the following public classes: 34 | * 35 | * - ArgumentParser -- The main entry point for command-line parsing. As the 36 | * example above shows, the add_argument() method is used to populate 37 | * the parser with actions for optional and positional arguments. Then 38 | * the parse_args() method is invoked to convert the args at the 39 | * command-line into an object with attributes. 40 | * 41 | * - ArgumentError -- The exception raised by ArgumentParser objects when 42 | * there are errors with the parser's actions. Errors raised while 43 | * parsing the command-line are caught by ArgumentParser and emitted 44 | * as command-line messages. 45 | * 46 | * - FileType -- A factory for defining types of files to be created. As the 47 | * example above shows, instances of FileType are typically passed as 48 | * the type= argument of add_argument() calls. 49 | * 50 | * - Action -- The base class for parser actions. Typically actions are 51 | * selected by passing strings like 'store_true' or 'append_const' to 52 | * the action= argument of add_argument(). However, for greater 53 | * customization of ArgumentParser actions, subclasses of Action may 54 | * be defined and passed as the action= argument. 55 | * 56 | * - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, 57 | * ArgumentDefaultsHelpFormatter -- Formatter classes which 58 | * may be passed as the formatter_class= argument to the 59 | * ArgumentParser constructor. HelpFormatter is the default, 60 | * RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser 61 | * not to change the formatting for help text, and 62 | * ArgumentDefaultsHelpFormatter adds information about argument defaults 63 | * to the help. 64 | * 65 | * All other classes in this module are considered implementation details. 66 | * (Also note that HelpFormatter and RawDescriptionHelpFormatter are only 67 | * considered public as object names -- the API of the formatter objects is 68 | * still considered an implementation detail.) 69 | */ 70 | 71 | const SUPPRESS = '==SUPPRESS==' 72 | 73 | const OPTIONAL = '?' 74 | const ZERO_OR_MORE = '*' 75 | const ONE_OR_MORE = '+' 76 | const PARSER = 'A...' 77 | const REMAINDER = '...' 78 | const _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' 79 | 80 | 81 | // ================================== 82 | // Utility functions used for porting 83 | // ================================== 84 | const assert = require('assert') 85 | const util = require('util') 86 | const fs = require('fs') 87 | const sub = require('./lib/sub') 88 | const path = require('path') 89 | const repr = util.inspect 90 | 91 | function get_argv() { 92 | // omit first argument (which is assumed to be interpreter - `node`, `coffee`, `ts-node`, etc.) 93 | return process.argv.slice(1) 94 | } 95 | 96 | function get_terminal_size() { 97 | return { 98 | columns: +process.env.COLUMNS || process.stdout.columns || 80 99 | } 100 | } 101 | 102 | function hasattr(object, name) { 103 | return Object.prototype.hasOwnProperty.call(object, name) 104 | } 105 | 106 | function getattr(object, name, value) { 107 | return hasattr(object, name) ? object[name] : value 108 | } 109 | 110 | function setattr(object, name, value) { 111 | object[name] = value 112 | } 113 | 114 | function setdefault(object, name, value) { 115 | if (!hasattr(object, name)) object[name] = value 116 | return object[name] 117 | } 118 | 119 | function delattr(object, name) { 120 | delete object[name] 121 | } 122 | 123 | function range(from, to, step=1) { 124 | // range(10) is equivalent to range(0, 10) 125 | if (arguments.length === 1) [ to, from ] = [ from, 0 ] 126 | if (typeof from !== 'number' || typeof to !== 'number' || typeof step !== 'number') { 127 | throw new TypeError('argument cannot be interpreted as an integer') 128 | } 129 | if (step === 0) throw new TypeError('range() arg 3 must not be zero') 130 | 131 | let result = [] 132 | if (step > 0) { 133 | for (let i = from; i < to; i += step) result.push(i) 134 | } else { 135 | for (let i = from; i > to; i += step) result.push(i) 136 | } 137 | return result 138 | } 139 | 140 | function splitlines(str, keepends = false) { 141 | let result 142 | if (!keepends) { 143 | result = str.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/) 144 | } else { 145 | result = [] 146 | let parts = str.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/) 147 | for (let i = 0; i < parts.length; i += 2) { 148 | result.push(parts[i] + (i + 1 < parts.length ? parts[i + 1] : '')) 149 | } 150 | } 151 | if (!result[result.length - 1]) result.pop() 152 | return result 153 | } 154 | 155 | function _string_lstrip(string, prefix_chars) { 156 | let idx = 0 157 | while (idx < string.length && prefix_chars.includes(string[idx])) idx++ 158 | return idx ? string.slice(idx) : string 159 | } 160 | 161 | function _string_split(string, sep, maxsplit) { 162 | let result = string.split(sep) 163 | if (result.length > maxsplit) { 164 | result = result.slice(0, maxsplit).concat([ result.slice(maxsplit).join(sep) ]) 165 | } 166 | return result 167 | } 168 | 169 | function _array_equal(array1, array2) { 170 | if (array1.length !== array2.length) return false 171 | for (let i = 0; i < array1.length; i++) { 172 | if (array1[i] !== array2[i]) return false 173 | } 174 | return true 175 | } 176 | 177 | function _array_remove(array, item) { 178 | let idx = array.indexOf(item) 179 | if (idx === -1) throw new TypeError(sub('%r not in list', item)) 180 | array.splice(idx, 1) 181 | } 182 | 183 | // normalize choices to array; 184 | // this isn't required in python because `in` and `map` operators work with anything, 185 | // but in js dealing with multiple types here is too clunky 186 | function _choices_to_array(choices) { 187 | if (choices === undefined) { 188 | return [] 189 | } else if (Array.isArray(choices)) { 190 | return choices 191 | } else if (choices !== null && typeof choices[Symbol.iterator] === 'function') { 192 | return Array.from(choices) 193 | } else if (typeof choices === 'object' && choices !== null) { 194 | return Object.keys(choices) 195 | } else { 196 | throw new Error(sub('invalid choices value: %r', choices)) 197 | } 198 | } 199 | 200 | // decorator that allows a class to be called without new 201 | function _callable(cls) { 202 | let result = { // object is needed for inferred class name 203 | [cls.name]: function (...args) { 204 | let this_class = new.target === result || !new.target 205 | return Reflect.construct(cls, args, this_class ? cls : new.target) 206 | } 207 | } 208 | result[cls.name].prototype = cls.prototype 209 | // fix default tag for toString, e.g. [object Action] instead of [object Object] 210 | cls.prototype[Symbol.toStringTag] = cls.name 211 | return result[cls.name] 212 | } 213 | 214 | function _alias(object, from, to) { 215 | try { 216 | let name = object.constructor.name 217 | Object.defineProperty(object, from, { 218 | value: util.deprecate(object[to], sub('%s.%s() is renamed to %s.%s()', 219 | name, from, name, to)), 220 | enumerable: false 221 | }) 222 | } catch {} 223 | } 224 | 225 | // decorator that allows snake_case class methods to be called with camelCase and vice versa 226 | function _camelcase_alias(_class) { 227 | for (let name of Object.getOwnPropertyNames(_class.prototype)) { 228 | let camelcase = name.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) 229 | if (camelcase !== name) _alias(_class.prototype, camelcase, name) 230 | } 231 | return _class 232 | } 233 | 234 | function _to_legacy_name(key) { 235 | key = key.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) 236 | if (key === 'default') key = 'defaultValue' 237 | if (key === 'const') key = 'constant' 238 | return key 239 | } 240 | 241 | function _to_new_name(key) { 242 | if (key === 'defaultValue') key = 'default' 243 | if (key === 'constant') key = 'const' 244 | key = key.replace(/[A-Z]/g, c => '_' + c.toLowerCase()) 245 | return key 246 | } 247 | 248 | // parse options 249 | let no_default = Symbol('no_default_value') 250 | function _parse_opts(args, descriptor) { 251 | function get_name() { 252 | let stack = new Error().stack.split('\n') 253 | .map(x => x.match(/^ at (.*) \(.*\)$/)) 254 | .filter(Boolean) 255 | .map(m => m[1]) 256 | .map(fn => fn.match(/[^ .]*$/)[0]) 257 | 258 | if (stack.length && stack[0] === get_name.name) stack.shift() 259 | if (stack.length && stack[0] === _parse_opts.name) stack.shift() 260 | return stack.length ? stack[0] : '' 261 | } 262 | 263 | args = Array.from(args) 264 | let kwargs = {} 265 | let result = [] 266 | let last_opt = args.length && args[args.length - 1] 267 | 268 | if (typeof last_opt === 'object' && last_opt !== null && !Array.isArray(last_opt) && 269 | (!last_opt.constructor || last_opt.constructor.name === 'Object')) { 270 | kwargs = Object.assign({}, args.pop()) 271 | } 272 | 273 | // LEGACY (v1 compatibility): camelcase 274 | let renames = [] 275 | for (let key of Object.keys(descriptor)) { 276 | let old_name = _to_legacy_name(key) 277 | if (old_name !== key && (old_name in kwargs)) { 278 | if (key in kwargs) { 279 | // default and defaultValue specified at the same time, happens often in old tests 280 | //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) 281 | } else { 282 | kwargs[key] = kwargs[old_name] 283 | } 284 | renames.push([ old_name, key ]) 285 | delete kwargs[old_name] 286 | } 287 | } 288 | if (renames.length) { 289 | let name = get_name() 290 | deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', 291 | name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) 292 | } 293 | // end 294 | 295 | let missing_positionals = [] 296 | let positional_count = args.length 297 | 298 | for (let [ key, def ] of Object.entries(descriptor)) { 299 | if (key[0] === '*') { 300 | if (key.length > 0 && key[1] === '*') { 301 | // LEGACY (v1 compatibility): camelcase 302 | let renames = [] 303 | for (let key of Object.keys(kwargs)) { 304 | let new_name = _to_new_name(key) 305 | if (new_name !== key && (key in kwargs)) { 306 | if (new_name in kwargs) { 307 | // default and defaultValue specified at the same time, happens often in old tests 308 | //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), new_name)) 309 | } else { 310 | kwargs[new_name] = kwargs[key] 311 | } 312 | renames.push([ key, new_name ]) 313 | delete kwargs[key] 314 | } 315 | } 316 | if (renames.length) { 317 | let name = get_name() 318 | deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', 319 | name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) 320 | } 321 | // end 322 | result.push(kwargs) 323 | kwargs = {} 324 | } else { 325 | result.push(args) 326 | args = [] 327 | } 328 | } else if (key in kwargs && args.length > 0) { 329 | throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) 330 | } else if (key in kwargs) { 331 | result.push(kwargs[key]) 332 | delete kwargs[key] 333 | } else if (args.length > 0) { 334 | result.push(args.shift()) 335 | } else if (def !== no_default) { 336 | result.push(def) 337 | } else { 338 | missing_positionals.push(key) 339 | } 340 | } 341 | 342 | if (Object.keys(kwargs).length) { 343 | throw new TypeError(sub('%s() got an unexpected keyword argument %r', 344 | get_name(), Object.keys(kwargs)[0])) 345 | } 346 | 347 | if (args.length) { 348 | let from = Object.entries(descriptor).filter(([ k, v ]) => k[0] !== '*' && v !== no_default).length 349 | let to = Object.entries(descriptor).filter(([ k ]) => k[0] !== '*').length 350 | throw new TypeError(sub('%s() takes %s positional argument%s but %s %s given', 351 | get_name(), 352 | from === to ? sub('from %s to %s', from, to) : to, 353 | from === to && to === 1 ? '' : 's', 354 | positional_count, 355 | positional_count === 1 ? 'was' : 'were')) 356 | } 357 | 358 | if (missing_positionals.length) { 359 | let strs = missing_positionals.map(repr) 360 | if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1] 361 | let str_joined = strs.join(strs.length === 2 ? '' : ', ') 362 | throw new TypeError(sub('%s() missing %i required positional argument%s: %s', 363 | get_name(), strs.length, strs.length === 1 ? '' : 's', str_joined)) 364 | } 365 | 366 | return result 367 | } 368 | 369 | let _deprecations = {} 370 | function deprecate(id, string) { 371 | _deprecations[id] = _deprecations[id] || util.deprecate(() => {}, string) 372 | _deprecations[id]() 373 | } 374 | 375 | 376 | // ============================= 377 | // Utility functions and classes 378 | // ============================= 379 | function _AttributeHolder(cls = Object) { 380 | /* 381 | * Abstract base class that provides __repr__. 382 | * 383 | * The __repr__ method returns a string in the format:: 384 | * ClassName(attr=name, attr=name, ...) 385 | * The attributes are determined either by a class-level attribute, 386 | * '_kwarg_names', or by inspecting the instance __dict__. 387 | */ 388 | 389 | return class _AttributeHolder extends cls { 390 | [util.inspect.custom]() { 391 | let type_name = this.constructor.name 392 | let arg_strings = [] 393 | let star_args = {} 394 | for (let arg of this._get_args()) { 395 | arg_strings.push(repr(arg)) 396 | } 397 | for (let [ name, value ] of this._get_kwargs()) { 398 | if (/^[a-z_][a-z0-9_$]*$/i.test(name)) { 399 | arg_strings.push(sub('%s=%r', name, value)) 400 | } else { 401 | star_args[name] = value 402 | } 403 | } 404 | if (Object.keys(star_args).length) { 405 | arg_strings.push(sub('**%s', repr(star_args))) 406 | } 407 | return sub('%s(%s)', type_name, arg_strings.join(', ')) 408 | } 409 | 410 | toString() { 411 | return this[util.inspect.custom]() 412 | } 413 | 414 | _get_kwargs() { 415 | return Object.entries(this) 416 | } 417 | 418 | _get_args() { 419 | return [] 420 | } 421 | } 422 | } 423 | 424 | 425 | function _copy_items(items) { 426 | if (items === undefined) { 427 | return [] 428 | } 429 | return items.slice(0) 430 | } 431 | 432 | 433 | // =============== 434 | // Formatting Help 435 | // =============== 436 | const HelpFormatter = _camelcase_alias(_callable(class HelpFormatter { 437 | /* 438 | * Formatter for generating usage messages and argument help strings. 439 | * 440 | * Only the name of this class is considered a public API. All the methods 441 | * provided by the class are considered an implementation detail. 442 | */ 443 | 444 | constructor() { 445 | let [ 446 | prog, 447 | indent_increment, 448 | max_help_position, 449 | width 450 | ] = _parse_opts(arguments, { 451 | prog: no_default, 452 | indent_increment: 2, 453 | max_help_position: 24, 454 | width: undefined 455 | }) 456 | 457 | // default setting for width 458 | if (width === undefined) { 459 | width = get_terminal_size().columns 460 | width -= 2 461 | } 462 | 463 | this._prog = prog 464 | this._indent_increment = indent_increment 465 | this._max_help_position = Math.min(max_help_position, 466 | Math.max(width - 20, indent_increment * 2)) 467 | this._width = width 468 | 469 | this._current_indent = 0 470 | this._level = 0 471 | this._action_max_length = 0 472 | 473 | this._root_section = this._Section(this, undefined) 474 | this._current_section = this._root_section 475 | 476 | this._whitespace_matcher = /[ \t\n\r\f\v]+/g // equivalent to python /\s+/ with ASCII flag 477 | this._long_break_matcher = /\n\n\n+/g 478 | } 479 | 480 | // =============================== 481 | // Section and indentation methods 482 | // =============================== 483 | _indent() { 484 | this._current_indent += this._indent_increment 485 | this._level += 1 486 | } 487 | 488 | _dedent() { 489 | this._current_indent -= this._indent_increment 490 | assert(this._current_indent >= 0, 'Indent decreased below 0.') 491 | this._level -= 1 492 | } 493 | 494 | _add_item(func, args) { 495 | this._current_section.items.push([ func, args ]) 496 | } 497 | 498 | // ======================== 499 | // Message building methods 500 | // ======================== 501 | start_section(heading) { 502 | this._indent() 503 | let section = this._Section(this, this._current_section, heading) 504 | this._add_item(section.format_help.bind(section), []) 505 | this._current_section = section 506 | } 507 | 508 | end_section() { 509 | this._current_section = this._current_section.parent 510 | this._dedent() 511 | } 512 | 513 | add_text(text) { 514 | if (text !== SUPPRESS && text !== undefined) { 515 | this._add_item(this._format_text.bind(this), [text]) 516 | } 517 | } 518 | 519 | add_usage(usage, actions, groups, prefix = undefined) { 520 | if (usage !== SUPPRESS) { 521 | let args = [ usage, actions, groups, prefix ] 522 | this._add_item(this._format_usage.bind(this), args) 523 | } 524 | } 525 | 526 | add_argument(action) { 527 | if (action.help !== SUPPRESS) { 528 | 529 | // find all invocations 530 | let invocations = [this._format_action_invocation(action)] 531 | for (let subaction of this._iter_indented_subactions(action)) { 532 | invocations.push(this._format_action_invocation(subaction)) 533 | } 534 | 535 | // update the maximum item length 536 | let invocation_length = Math.max(...invocations.map(invocation => invocation.length)) 537 | let action_length = invocation_length + this._current_indent 538 | this._action_max_length = Math.max(this._action_max_length, 539 | action_length) 540 | 541 | // add the item to the list 542 | this._add_item(this._format_action.bind(this), [action]) 543 | } 544 | } 545 | 546 | add_arguments(actions) { 547 | for (let action of actions) { 548 | this.add_argument(action) 549 | } 550 | } 551 | 552 | // ======================= 553 | // Help-formatting methods 554 | // ======================= 555 | format_help() { 556 | let help = this._root_section.format_help() 557 | if (help) { 558 | help = help.replace(this._long_break_matcher, '\n\n') 559 | help = help.replace(/^\n+|\n+$/g, '') + '\n' 560 | } 561 | return help 562 | } 563 | 564 | _join_parts(part_strings) { 565 | return part_strings.filter(part => part && part !== SUPPRESS).join('') 566 | } 567 | 568 | _format_usage(usage, actions, groups, prefix) { 569 | if (prefix === undefined) { 570 | prefix = 'usage: ' 571 | } 572 | 573 | // if usage is specified, use that 574 | if (usage !== undefined) { 575 | usage = sub(usage, { prog: this._prog }) 576 | 577 | // if no optionals or positionals are available, usage is just prog 578 | } else if (usage === undefined && !actions.length) { 579 | usage = sub('%(prog)s', { prog: this._prog }) 580 | 581 | // if optionals and positionals are available, calculate usage 582 | } else if (usage === undefined) { 583 | let prog = sub('%(prog)s', { prog: this._prog }) 584 | 585 | // split optionals from positionals 586 | let optionals = [] 587 | let positionals = [] 588 | for (let action of actions) { 589 | if (action.option_strings.length) { 590 | optionals.push(action) 591 | } else { 592 | positionals.push(action) 593 | } 594 | } 595 | 596 | // build full usage string 597 | let action_usage = this._format_actions_usage([].concat(optionals).concat(positionals), groups) 598 | usage = [ prog, action_usage ].map(String).join(' ') 599 | 600 | // wrap the usage parts if it's too long 601 | let text_width = this._width - this._current_indent 602 | if (prefix.length + usage.length > text_width) { 603 | 604 | // break usage into wrappable parts 605 | let part_regexp = /\(.*?\)+(?=\s|$)|\[.*?\]+(?=\s|$)|\S+/g 606 | let opt_usage = this._format_actions_usage(optionals, groups) 607 | let pos_usage = this._format_actions_usage(positionals, groups) 608 | let opt_parts = opt_usage.match(part_regexp) || [] 609 | let pos_parts = pos_usage.match(part_regexp) || [] 610 | assert(opt_parts.join(' ') === opt_usage) 611 | assert(pos_parts.join(' ') === pos_usage) 612 | 613 | // helper for wrapping lines 614 | let get_lines = (parts, indent, prefix = undefined) => { 615 | let lines = [] 616 | let line = [] 617 | let line_len 618 | if (prefix !== undefined) { 619 | line_len = prefix.length - 1 620 | } else { 621 | line_len = indent.length - 1 622 | } 623 | for (let part of parts) { 624 | if (line_len + 1 + part.length > text_width && line) { 625 | lines.push(indent + line.join(' ')) 626 | line = [] 627 | line_len = indent.length - 1 628 | } 629 | line.push(part) 630 | line_len += part.length + 1 631 | } 632 | if (line.length) { 633 | lines.push(indent + line.join(' ')) 634 | } 635 | if (prefix !== undefined) { 636 | lines[0] = lines[0].slice(indent.length) 637 | } 638 | return lines 639 | } 640 | 641 | let lines 642 | 643 | // if prog is short, follow it with optionals or positionals 644 | if (prefix.length + prog.length <= 0.75 * text_width) { 645 | let indent = ' '.repeat(prefix.length + prog.length + 1) 646 | if (opt_parts.length) { 647 | lines = get_lines([prog].concat(opt_parts), indent, prefix) 648 | lines = lines.concat(get_lines(pos_parts, indent)) 649 | } else if (pos_parts.length) { 650 | lines = get_lines([prog].concat(pos_parts), indent, prefix) 651 | } else { 652 | lines = [prog] 653 | } 654 | 655 | // if prog is long, put it on its own line 656 | } else { 657 | let indent = ' '.repeat(prefix.length) 658 | let parts = [].concat(opt_parts).concat(pos_parts) 659 | lines = get_lines(parts, indent) 660 | if (lines.length > 1) { 661 | lines = [] 662 | lines = lines.concat(get_lines(opt_parts, indent)) 663 | lines = lines.concat(get_lines(pos_parts, indent)) 664 | } 665 | lines = [prog].concat(lines) 666 | } 667 | 668 | // join lines into usage 669 | usage = lines.join('\n') 670 | } 671 | } 672 | 673 | // prefix with 'usage:' 674 | return sub('%s%s\n\n', prefix, usage) 675 | } 676 | 677 | _format_actions_usage(actions, groups) { 678 | // find group indices and identify actions in groups 679 | let group_actions = new Set() 680 | let inserts = {} 681 | for (let group of groups) { 682 | let start = actions.indexOf(group._group_actions[0]) 683 | if (start === -1) { 684 | continue 685 | } else { 686 | let end = start + group._group_actions.length 687 | if (_array_equal(actions.slice(start, end), group._group_actions)) { 688 | for (let action of group._group_actions) { 689 | group_actions.add(action) 690 | } 691 | if (!group.required) { 692 | if (start in inserts) { 693 | inserts[start] += ' [' 694 | } else { 695 | inserts[start] = '[' 696 | } 697 | if (end in inserts) { 698 | inserts[end] += ']' 699 | } else { 700 | inserts[end] = ']' 701 | } 702 | } else { 703 | if (start in inserts) { 704 | inserts[start] += ' (' 705 | } else { 706 | inserts[start] = '(' 707 | } 708 | if (end in inserts) { 709 | inserts[end] += ')' 710 | } else { 711 | inserts[end] = ')' 712 | } 713 | } 714 | for (let i of range(start + 1, end)) { 715 | inserts[i] = '|' 716 | } 717 | } 718 | } 719 | } 720 | 721 | // collect all actions format strings 722 | let parts = [] 723 | for (let [ i, action ] of Object.entries(actions)) { 724 | 725 | // suppressed arguments are marked with None 726 | // remove | separators for suppressed arguments 727 | if (action.help === SUPPRESS) { 728 | parts.push(undefined) 729 | if (inserts[+i] === '|') { 730 | delete inserts[+i] 731 | } else if (inserts[+i + 1] === '|') { 732 | delete inserts[+i + 1] 733 | } 734 | 735 | // produce all arg strings 736 | } else if (!action.option_strings.length) { 737 | let default_value = this._get_default_metavar_for_positional(action) 738 | let part = this._format_args(action, default_value) 739 | 740 | // if it's in a group, strip the outer [] 741 | if (group_actions.has(action)) { 742 | if (part[0] === '[' && part[part.length - 1] === ']') { 743 | part = part.slice(1, -1) 744 | } 745 | } 746 | 747 | // add the action string to the list 748 | parts.push(part) 749 | 750 | // produce the first way to invoke the option in brackets 751 | } else { 752 | let option_string = action.option_strings[0] 753 | let part 754 | 755 | // if the Optional doesn't take a value, format is: 756 | // -s or --long 757 | if (action.nargs === 0) { 758 | part = action.format_usage() 759 | 760 | // if the Optional takes a value, format is: 761 | // -s ARGS or --long ARGS 762 | } else { 763 | let default_value = this._get_default_metavar_for_optional(action) 764 | let args_string = this._format_args(action, default_value) 765 | part = sub('%s %s', option_string, args_string) 766 | } 767 | 768 | // make it look optional if it's not required or in a group 769 | if (!action.required && !group_actions.has(action)) { 770 | part = sub('[%s]', part) 771 | } 772 | 773 | // add the action string to the list 774 | parts.push(part) 775 | } 776 | } 777 | 778 | // insert things at the necessary indices 779 | for (let i of Object.keys(inserts).map(Number).sort((a, b) => b - a)) { 780 | parts.splice(+i, 0, inserts[+i]) 781 | } 782 | 783 | // join all the action items with spaces 784 | let text = parts.filter(Boolean).join(' ') 785 | 786 | // clean up separators for mutually exclusive groups 787 | text = text.replace(/([\[(]) /g, '$1') 788 | text = text.replace(/ ([\])])/g, '$1') 789 | text = text.replace(/[\[(] *[\])]/g, '') 790 | text = text.replace(/\(([^|]*)\)/g, '$1', text) 791 | text = text.trim() 792 | 793 | // return the text 794 | return text 795 | } 796 | 797 | _format_text(text) { 798 | if (text.includes('%(prog)')) { 799 | text = sub(text, { prog: this._prog }) 800 | } 801 | let text_width = Math.max(this._width - this._current_indent, 11) 802 | let indent = ' '.repeat(this._current_indent) 803 | return this._fill_text(text, text_width, indent) + '\n\n' 804 | } 805 | 806 | _format_action(action) { 807 | // determine the required width and the entry label 808 | let help_position = Math.min(this._action_max_length + 2, 809 | this._max_help_position) 810 | let help_width = Math.max(this._width - help_position, 11) 811 | let action_width = help_position - this._current_indent - 2 812 | let action_header = this._format_action_invocation(action) 813 | let indent_first 814 | 815 | // no help; start on same line and add a final newline 816 | if (!action.help) { 817 | let tup = [ this._current_indent, '', action_header ] 818 | action_header = sub('%*s%s\n', ...tup) 819 | 820 | // short action name; start on the same line and pad two spaces 821 | } else if (action_header.length <= action_width) { 822 | let tup = [ this._current_indent, '', action_width, action_header ] 823 | action_header = sub('%*s%-*s ', ...tup) 824 | indent_first = 0 825 | 826 | // long action name; start on the next line 827 | } else { 828 | let tup = [ this._current_indent, '', action_header ] 829 | action_header = sub('%*s%s\n', ...tup) 830 | indent_first = help_position 831 | } 832 | 833 | // collect the pieces of the action help 834 | let parts = [action_header] 835 | 836 | // if there was help for the action, add lines of help text 837 | if (action.help) { 838 | let help_text = this._expand_help(action) 839 | let help_lines = this._split_lines(help_text, help_width) 840 | parts.push(sub('%*s%s\n', indent_first, '', help_lines[0])) 841 | for (let line of help_lines.slice(1)) { 842 | parts.push(sub('%*s%s\n', help_position, '', line)) 843 | } 844 | 845 | // or add a newline if the description doesn't end with one 846 | } else if (!action_header.endsWith('\n')) { 847 | parts.push('\n') 848 | } 849 | 850 | // if there are any sub-actions, add their help as well 851 | for (let subaction of this._iter_indented_subactions(action)) { 852 | parts.push(this._format_action(subaction)) 853 | } 854 | 855 | // return a single string 856 | return this._join_parts(parts) 857 | } 858 | 859 | _format_action_invocation(action) { 860 | if (!action.option_strings.length) { 861 | let default_value = this._get_default_metavar_for_positional(action) 862 | let metavar = this._metavar_formatter(action, default_value)(1)[0] 863 | return metavar 864 | 865 | } else { 866 | let parts = [] 867 | 868 | // if the Optional doesn't take a value, format is: 869 | // -s, --long 870 | if (action.nargs === 0) { 871 | parts = parts.concat(action.option_strings) 872 | 873 | // if the Optional takes a value, format is: 874 | // -s ARGS, --long ARGS 875 | } else { 876 | let default_value = this._get_default_metavar_for_optional(action) 877 | let args_string = this._format_args(action, default_value) 878 | for (let option_string of action.option_strings) { 879 | parts.push(sub('%s %s', option_string, args_string)) 880 | } 881 | } 882 | 883 | return parts.join(', ') 884 | } 885 | } 886 | 887 | _metavar_formatter(action, default_metavar) { 888 | let result 889 | if (action.metavar !== undefined) { 890 | result = action.metavar 891 | } else if (action.choices !== undefined) { 892 | let choice_strs = _choices_to_array(action.choices).map(String) 893 | result = sub('{%s}', choice_strs.join(',')) 894 | } else { 895 | result = default_metavar 896 | } 897 | 898 | function format(tuple_size) { 899 | if (Array.isArray(result)) { 900 | return result 901 | } else { 902 | return Array(tuple_size).fill(result) 903 | } 904 | } 905 | return format 906 | } 907 | 908 | _format_args(action, default_metavar) { 909 | let get_metavar = this._metavar_formatter(action, default_metavar) 910 | let result 911 | if (action.nargs === undefined) { 912 | result = sub('%s', ...get_metavar(1)) 913 | } else if (action.nargs === OPTIONAL) { 914 | result = sub('[%s]', ...get_metavar(1)) 915 | } else if (action.nargs === ZERO_OR_MORE) { 916 | let metavar = get_metavar(1) 917 | if (metavar.length === 2) { 918 | result = sub('[%s [%s ...]]', ...metavar) 919 | } else { 920 | result = sub('[%s ...]', ...metavar) 921 | } 922 | } else if (action.nargs === ONE_OR_MORE) { 923 | result = sub('%s [%s ...]', ...get_metavar(2)) 924 | } else if (action.nargs === REMAINDER) { 925 | result = '...' 926 | } else if (action.nargs === PARSER) { 927 | result = sub('%s ...', ...get_metavar(1)) 928 | } else if (action.nargs === SUPPRESS) { 929 | result = '' 930 | } else { 931 | let formats 932 | try { 933 | formats = range(action.nargs).map(() => '%s') 934 | } catch (err) { 935 | throw new TypeError('invalid nargs value') 936 | } 937 | result = sub(formats.join(' '), ...get_metavar(action.nargs)) 938 | } 939 | return result 940 | } 941 | 942 | _expand_help(action) { 943 | let params = Object.assign({ prog: this._prog }, action) 944 | for (let name of Object.keys(params)) { 945 | if (params[name] === SUPPRESS) { 946 | delete params[name] 947 | } 948 | } 949 | for (let name of Object.keys(params)) { 950 | if (params[name] && params[name].name) { 951 | params[name] = params[name].name 952 | } 953 | } 954 | if (params.choices !== undefined) { 955 | let choices_str = _choices_to_array(params.choices).map(String).join(', ') 956 | params.choices = choices_str 957 | } 958 | // LEGACY (v1 compatibility): camelcase 959 | for (let key of Object.keys(params)) { 960 | let old_name = _to_legacy_name(key) 961 | if (old_name !== key) { 962 | params[old_name] = params[key] 963 | } 964 | } 965 | // end 966 | return sub(this._get_help_string(action), params) 967 | } 968 | 969 | * _iter_indented_subactions(action) { 970 | if (typeof action._get_subactions === 'function') { 971 | this._indent() 972 | yield* action._get_subactions() 973 | this._dedent() 974 | } 975 | } 976 | 977 | _split_lines(text, width) { 978 | text = text.replace(this._whitespace_matcher, ' ').trim() 979 | // The textwrap module is used only for formatting help. 980 | // Delay its import for speeding up the common usage of argparse. 981 | let textwrap = require('./lib/textwrap') 982 | return textwrap.wrap(text, { width }) 983 | } 984 | 985 | _fill_text(text, width, indent) { 986 | text = text.replace(this._whitespace_matcher, ' ').trim() 987 | let textwrap = require('./lib/textwrap') 988 | return textwrap.fill(text, { width, 989 | initial_indent: indent, 990 | subsequent_indent: indent }) 991 | } 992 | 993 | _get_help_string(action) { 994 | return action.help 995 | } 996 | 997 | _get_default_metavar_for_optional(action) { 998 | return action.dest.toUpperCase() 999 | } 1000 | 1001 | _get_default_metavar_for_positional(action) { 1002 | return action.dest 1003 | } 1004 | })) 1005 | 1006 | HelpFormatter.prototype._Section = _callable(class _Section { 1007 | 1008 | constructor(formatter, parent, heading = undefined) { 1009 | this.formatter = formatter 1010 | this.parent = parent 1011 | this.heading = heading 1012 | this.items = [] 1013 | } 1014 | 1015 | format_help() { 1016 | // format the indented section 1017 | if (this.parent !== undefined) { 1018 | this.formatter._indent() 1019 | } 1020 | let item_help = this.formatter._join_parts(this.items.map(([ func, args ]) => func.apply(null, args))) 1021 | if (this.parent !== undefined) { 1022 | this.formatter._dedent() 1023 | } 1024 | 1025 | // return nothing if the section was empty 1026 | if (!item_help) { 1027 | return '' 1028 | } 1029 | 1030 | // add the heading if the section was non-empty 1031 | let heading 1032 | if (this.heading !== SUPPRESS && this.heading !== undefined) { 1033 | let current_indent = this.formatter._current_indent 1034 | heading = sub('%*s%s:\n', current_indent, '', this.heading) 1035 | } else { 1036 | heading = '' 1037 | } 1038 | 1039 | // join the section-initial newline, the heading and the help 1040 | return this.formatter._join_parts(['\n', heading, item_help, '\n']) 1041 | } 1042 | }) 1043 | 1044 | 1045 | const RawDescriptionHelpFormatter = _camelcase_alias(_callable(class RawDescriptionHelpFormatter extends HelpFormatter { 1046 | /* 1047 | * Help message formatter which retains any formatting in descriptions. 1048 | * 1049 | * Only the name of this class is considered a public API. All the methods 1050 | * provided by the class are considered an implementation detail. 1051 | */ 1052 | 1053 | _fill_text(text, width, indent) { 1054 | return splitlines(text, true).map(line => indent + line).join('') 1055 | } 1056 | })) 1057 | 1058 | 1059 | const RawTextHelpFormatter = _camelcase_alias(_callable(class RawTextHelpFormatter extends RawDescriptionHelpFormatter { 1060 | /* 1061 | * Help message formatter which retains formatting of all help text. 1062 | * 1063 | * Only the name of this class is considered a public API. All the methods 1064 | * provided by the class are considered an implementation detail. 1065 | */ 1066 | 1067 | _split_lines(text/*, width*/) { 1068 | return splitlines(text) 1069 | } 1070 | })) 1071 | 1072 | 1073 | const ArgumentDefaultsHelpFormatter = _camelcase_alias(_callable(class ArgumentDefaultsHelpFormatter extends HelpFormatter { 1074 | /* 1075 | * Help message formatter which adds default values to argument help. 1076 | * 1077 | * Only the name of this class is considered a public API. All the methods 1078 | * provided by the class are considered an implementation detail. 1079 | */ 1080 | 1081 | _get_help_string(action) { 1082 | let help = action.help 1083 | // LEGACY (v1 compatibility): additional check for defaultValue needed 1084 | if (!action.help.includes('%(default)') && !action.help.includes('%(defaultValue)')) { 1085 | if (action.default !== SUPPRESS) { 1086 | let defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] 1087 | if (action.option_strings.length || defaulting_nargs.includes(action.nargs)) { 1088 | help += ' (default: %(default)s)' 1089 | } 1090 | } 1091 | } 1092 | return help 1093 | } 1094 | })) 1095 | 1096 | 1097 | const MetavarTypeHelpFormatter = _camelcase_alias(_callable(class MetavarTypeHelpFormatter extends HelpFormatter { 1098 | /* 1099 | * Help message formatter which uses the argument 'type' as the default 1100 | * metavar value (instead of the argument 'dest') 1101 | * 1102 | * Only the name of this class is considered a public API. All the methods 1103 | * provided by the class are considered an implementation detail. 1104 | */ 1105 | 1106 | _get_default_metavar_for_optional(action) { 1107 | return typeof action.type === 'function' ? action.type.name : action.type 1108 | } 1109 | 1110 | _get_default_metavar_for_positional(action) { 1111 | return typeof action.type === 'function' ? action.type.name : action.type 1112 | } 1113 | })) 1114 | 1115 | 1116 | // ===================== 1117 | // Options and Arguments 1118 | // ===================== 1119 | function _get_action_name(argument) { 1120 | if (argument === undefined) { 1121 | return undefined 1122 | } else if (argument.option_strings.length) { 1123 | return argument.option_strings.join('/') 1124 | } else if (![ undefined, SUPPRESS ].includes(argument.metavar)) { 1125 | return argument.metavar 1126 | } else if (![ undefined, SUPPRESS ].includes(argument.dest)) { 1127 | return argument.dest 1128 | } else { 1129 | return undefined 1130 | } 1131 | } 1132 | 1133 | 1134 | const ArgumentError = _callable(class ArgumentError extends Error { 1135 | /* 1136 | * An error from creating or using an argument (optional or positional). 1137 | * 1138 | * The string value of this exception is the message, augmented with 1139 | * information about the argument that caused it. 1140 | */ 1141 | 1142 | constructor(argument, message) { 1143 | super() 1144 | this.name = 'ArgumentError' 1145 | this._argument_name = _get_action_name(argument) 1146 | this._message = message 1147 | this.message = this.str() 1148 | } 1149 | 1150 | str() { 1151 | let format 1152 | if (this._argument_name === undefined) { 1153 | format = '%(message)s' 1154 | } else { 1155 | format = 'argument %(argument_name)s: %(message)s' 1156 | } 1157 | return sub(format, { message: this._message, 1158 | argument_name: this._argument_name }) 1159 | } 1160 | }) 1161 | 1162 | 1163 | const ArgumentTypeError = _callable(class ArgumentTypeError extends Error { 1164 | /* 1165 | * An error from trying to convert a command line string to a type. 1166 | */ 1167 | 1168 | constructor(message) { 1169 | super(message) 1170 | this.name = 'ArgumentTypeError' 1171 | } 1172 | }) 1173 | 1174 | 1175 | // ============== 1176 | // Action classes 1177 | // ============== 1178 | const Action = _camelcase_alias(_callable(class Action extends _AttributeHolder(Function) { 1179 | /* 1180 | * Information about how to convert command line strings to Python objects. 1181 | * 1182 | * Action objects are used by an ArgumentParser to represent the information 1183 | * needed to parse a single argument from one or more strings from the 1184 | * command line. The keyword arguments to the Action constructor are also 1185 | * all attributes of Action instances. 1186 | * 1187 | * Keyword Arguments: 1188 | * 1189 | * - option_strings -- A list of command-line option strings which 1190 | * should be associated with this action. 1191 | * 1192 | * - dest -- The name of the attribute to hold the created object(s) 1193 | * 1194 | * - nargs -- The number of command-line arguments that should be 1195 | * consumed. By default, one argument will be consumed and a single 1196 | * value will be produced. Other values include: 1197 | * - N (an integer) consumes N arguments (and produces a list) 1198 | * - '?' consumes zero or one arguments 1199 | * - '*' consumes zero or more arguments (and produces a list) 1200 | * - '+' consumes one or more arguments (and produces a list) 1201 | * Note that the difference between the default and nargs=1 is that 1202 | * with the default, a single value will be produced, while with 1203 | * nargs=1, a list containing a single value will be produced. 1204 | * 1205 | * - const -- The value to be produced if the option is specified and the 1206 | * option uses an action that takes no values. 1207 | * 1208 | * - default -- The value to be produced if the option is not specified. 1209 | * 1210 | * - type -- A callable that accepts a single string argument, and 1211 | * returns the converted value. The standard Python types str, int, 1212 | * float, and complex are useful examples of such callables. If None, 1213 | * str is used. 1214 | * 1215 | * - choices -- A container of values that should be allowed. If not None, 1216 | * after a command-line argument has been converted to the appropriate 1217 | * type, an exception will be raised if it is not a member of this 1218 | * collection. 1219 | * 1220 | * - required -- True if the action must always be specified at the 1221 | * command line. This is only meaningful for optional command-line 1222 | * arguments. 1223 | * 1224 | * - help -- The help string describing the argument. 1225 | * 1226 | * - metavar -- The name to be used for the option's argument with the 1227 | * help string. If None, the 'dest' value will be used as the name. 1228 | */ 1229 | 1230 | constructor() { 1231 | let [ 1232 | option_strings, 1233 | dest, 1234 | nargs, 1235 | const_value, 1236 | default_value, 1237 | type, 1238 | choices, 1239 | required, 1240 | help, 1241 | metavar 1242 | ] = _parse_opts(arguments, { 1243 | option_strings: no_default, 1244 | dest: no_default, 1245 | nargs: undefined, 1246 | const: undefined, 1247 | default: undefined, 1248 | type: undefined, 1249 | choices: undefined, 1250 | required: false, 1251 | help: undefined, 1252 | metavar: undefined 1253 | }) 1254 | 1255 | // when this class is called as a function, redirect it to .call() method of itself 1256 | super('return arguments.callee.call.apply(arguments.callee, arguments)') 1257 | 1258 | this.option_strings = option_strings 1259 | this.dest = dest 1260 | this.nargs = nargs 1261 | this.const = const_value 1262 | this.default = default_value 1263 | this.type = type 1264 | this.choices = choices 1265 | this.required = required 1266 | this.help = help 1267 | this.metavar = metavar 1268 | } 1269 | 1270 | _get_kwargs() { 1271 | let names = [ 1272 | 'option_strings', 1273 | 'dest', 1274 | 'nargs', 1275 | 'const', 1276 | 'default', 1277 | 'type', 1278 | 'choices', 1279 | 'help', 1280 | 'metavar' 1281 | ] 1282 | return names.map(name => [ name, getattr(this, name) ]) 1283 | } 1284 | 1285 | format_usage() { 1286 | return this.option_strings[0] 1287 | } 1288 | 1289 | call(/*parser, namespace, values, option_string = undefined*/) { 1290 | throw new Error('.call() not defined') 1291 | } 1292 | })) 1293 | 1294 | 1295 | const BooleanOptionalAction = _camelcase_alias(_callable(class BooleanOptionalAction extends Action { 1296 | 1297 | constructor() { 1298 | let [ 1299 | option_strings, 1300 | dest, 1301 | default_value, 1302 | type, 1303 | choices, 1304 | required, 1305 | help, 1306 | metavar 1307 | ] = _parse_opts(arguments, { 1308 | option_strings: no_default, 1309 | dest: no_default, 1310 | default: undefined, 1311 | type: undefined, 1312 | choices: undefined, 1313 | required: false, 1314 | help: undefined, 1315 | metavar: undefined 1316 | }) 1317 | 1318 | let _option_strings = [] 1319 | for (let option_string of option_strings) { 1320 | _option_strings.push(option_string) 1321 | 1322 | if (option_string.startsWith('--')) { 1323 | option_string = '--no-' + option_string.slice(2) 1324 | _option_strings.push(option_string) 1325 | } 1326 | } 1327 | 1328 | if (help !== undefined && default_value !== undefined) { 1329 | help += ` (default: ${default_value})` 1330 | } 1331 | 1332 | super({ 1333 | option_strings: _option_strings, 1334 | dest, 1335 | nargs: 0, 1336 | default: default_value, 1337 | type, 1338 | choices, 1339 | required, 1340 | help, 1341 | metavar 1342 | }) 1343 | } 1344 | 1345 | call(parser, namespace, values, option_string = undefined) { 1346 | if (this.option_strings.includes(option_string)) { 1347 | setattr(namespace, this.dest, !option_string.startsWith('--no-')) 1348 | } 1349 | } 1350 | 1351 | format_usage() { 1352 | return this.option_strings.join(' | ') 1353 | } 1354 | })) 1355 | 1356 | 1357 | const _StoreAction = _callable(class _StoreAction extends Action { 1358 | 1359 | constructor() { 1360 | let [ 1361 | option_strings, 1362 | dest, 1363 | nargs, 1364 | const_value, 1365 | default_value, 1366 | type, 1367 | choices, 1368 | required, 1369 | help, 1370 | metavar 1371 | ] = _parse_opts(arguments, { 1372 | option_strings: no_default, 1373 | dest: no_default, 1374 | nargs: undefined, 1375 | const: undefined, 1376 | default: undefined, 1377 | type: undefined, 1378 | choices: undefined, 1379 | required: false, 1380 | help: undefined, 1381 | metavar: undefined 1382 | }) 1383 | 1384 | if (nargs === 0) { 1385 | throw new TypeError('nargs for store actions must be != 0; if you ' + 1386 | 'have nothing to store, actions such as store ' + 1387 | 'true or store const may be more appropriate') 1388 | } 1389 | if (const_value !== undefined && nargs !== OPTIONAL) { 1390 | throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) 1391 | } 1392 | super({ 1393 | option_strings, 1394 | dest, 1395 | nargs, 1396 | const: const_value, 1397 | default: default_value, 1398 | type, 1399 | choices, 1400 | required, 1401 | help, 1402 | metavar 1403 | }) 1404 | } 1405 | 1406 | call(parser, namespace, values/*, option_string = undefined*/) { 1407 | setattr(namespace, this.dest, values) 1408 | } 1409 | }) 1410 | 1411 | 1412 | const _StoreConstAction = _callable(class _StoreConstAction extends Action { 1413 | 1414 | constructor() { 1415 | let [ 1416 | option_strings, 1417 | dest, 1418 | const_value, 1419 | default_value, 1420 | required, 1421 | help 1422 | //, metavar 1423 | ] = _parse_opts(arguments, { 1424 | option_strings: no_default, 1425 | dest: no_default, 1426 | const: no_default, 1427 | default: undefined, 1428 | required: false, 1429 | help: undefined, 1430 | metavar: undefined 1431 | }) 1432 | 1433 | super({ 1434 | option_strings, 1435 | dest, 1436 | nargs: 0, 1437 | const: const_value, 1438 | default: default_value, 1439 | required, 1440 | help 1441 | }) 1442 | } 1443 | 1444 | call(parser, namespace/*, values, option_string = undefined*/) { 1445 | setattr(namespace, this.dest, this.const) 1446 | } 1447 | }) 1448 | 1449 | 1450 | const _StoreTrueAction = _callable(class _StoreTrueAction extends _StoreConstAction { 1451 | 1452 | constructor() { 1453 | let [ 1454 | option_strings, 1455 | dest, 1456 | default_value, 1457 | required, 1458 | help 1459 | ] = _parse_opts(arguments, { 1460 | option_strings: no_default, 1461 | dest: no_default, 1462 | default: false, 1463 | required: false, 1464 | help: undefined 1465 | }) 1466 | 1467 | super({ 1468 | option_strings, 1469 | dest, 1470 | const: true, 1471 | default: default_value, 1472 | required, 1473 | help 1474 | }) 1475 | } 1476 | }) 1477 | 1478 | 1479 | const _StoreFalseAction = _callable(class _StoreFalseAction extends _StoreConstAction { 1480 | 1481 | constructor() { 1482 | let [ 1483 | option_strings, 1484 | dest, 1485 | default_value, 1486 | required, 1487 | help 1488 | ] = _parse_opts(arguments, { 1489 | option_strings: no_default, 1490 | dest: no_default, 1491 | default: true, 1492 | required: false, 1493 | help: undefined 1494 | }) 1495 | 1496 | super({ 1497 | option_strings, 1498 | dest, 1499 | const: false, 1500 | default: default_value, 1501 | required, 1502 | help 1503 | }) 1504 | } 1505 | }) 1506 | 1507 | 1508 | const _AppendAction = _callable(class _AppendAction extends Action { 1509 | 1510 | constructor() { 1511 | let [ 1512 | option_strings, 1513 | dest, 1514 | nargs, 1515 | const_value, 1516 | default_value, 1517 | type, 1518 | choices, 1519 | required, 1520 | help, 1521 | metavar 1522 | ] = _parse_opts(arguments, { 1523 | option_strings: no_default, 1524 | dest: no_default, 1525 | nargs: undefined, 1526 | const: undefined, 1527 | default: undefined, 1528 | type: undefined, 1529 | choices: undefined, 1530 | required: false, 1531 | help: undefined, 1532 | metavar: undefined 1533 | }) 1534 | 1535 | if (nargs === 0) { 1536 | throw new TypeError('nargs for append actions must be != 0; if arg ' + 1537 | 'strings are not supplying the value to append, ' + 1538 | 'the append const action may be more appropriate') 1539 | } 1540 | if (const_value !== undefined && nargs !== OPTIONAL) { 1541 | throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) 1542 | } 1543 | super({ 1544 | option_strings, 1545 | dest, 1546 | nargs, 1547 | const: const_value, 1548 | default: default_value, 1549 | type, 1550 | choices, 1551 | required, 1552 | help, 1553 | metavar 1554 | }) 1555 | } 1556 | 1557 | call(parser, namespace, values/*, option_string = undefined*/) { 1558 | let items = getattr(namespace, this.dest, undefined) 1559 | items = _copy_items(items) 1560 | items.push(values) 1561 | setattr(namespace, this.dest, items) 1562 | } 1563 | }) 1564 | 1565 | 1566 | const _AppendConstAction = _callable(class _AppendConstAction extends Action { 1567 | 1568 | constructor() { 1569 | let [ 1570 | option_strings, 1571 | dest, 1572 | const_value, 1573 | default_value, 1574 | required, 1575 | help, 1576 | metavar 1577 | ] = _parse_opts(arguments, { 1578 | option_strings: no_default, 1579 | dest: no_default, 1580 | const: no_default, 1581 | default: undefined, 1582 | required: false, 1583 | help: undefined, 1584 | metavar: undefined 1585 | }) 1586 | 1587 | super({ 1588 | option_strings, 1589 | dest, 1590 | nargs: 0, 1591 | const: const_value, 1592 | default: default_value, 1593 | required, 1594 | help, 1595 | metavar 1596 | }) 1597 | } 1598 | 1599 | call(parser, namespace/*, values, option_string = undefined*/) { 1600 | let items = getattr(namespace, this.dest, undefined) 1601 | items = _copy_items(items) 1602 | items.push(this.const) 1603 | setattr(namespace, this.dest, items) 1604 | } 1605 | }) 1606 | 1607 | 1608 | const _CountAction = _callable(class _CountAction extends Action { 1609 | 1610 | constructor() { 1611 | let [ 1612 | option_strings, 1613 | dest, 1614 | default_value, 1615 | required, 1616 | help 1617 | ] = _parse_opts(arguments, { 1618 | option_strings: no_default, 1619 | dest: no_default, 1620 | default: undefined, 1621 | required: false, 1622 | help: undefined 1623 | }) 1624 | 1625 | super({ 1626 | option_strings, 1627 | dest, 1628 | nargs: 0, 1629 | default: default_value, 1630 | required, 1631 | help 1632 | }) 1633 | } 1634 | 1635 | call(parser, namespace/*, values, option_string = undefined*/) { 1636 | let count = getattr(namespace, this.dest, undefined) 1637 | if (count === undefined) { 1638 | count = 0 1639 | } 1640 | setattr(namespace, this.dest, count + 1) 1641 | } 1642 | }) 1643 | 1644 | 1645 | const _HelpAction = _callable(class _HelpAction extends Action { 1646 | 1647 | constructor() { 1648 | let [ 1649 | option_strings, 1650 | dest, 1651 | default_value, 1652 | help 1653 | ] = _parse_opts(arguments, { 1654 | option_strings: no_default, 1655 | dest: SUPPRESS, 1656 | default: SUPPRESS, 1657 | help: undefined 1658 | }) 1659 | 1660 | super({ 1661 | option_strings, 1662 | dest, 1663 | default: default_value, 1664 | nargs: 0, 1665 | help 1666 | }) 1667 | } 1668 | 1669 | call(parser/*, namespace, values, option_string = undefined*/) { 1670 | parser.print_help() 1671 | parser.exit() 1672 | } 1673 | }) 1674 | 1675 | 1676 | const _VersionAction = _callable(class _VersionAction extends Action { 1677 | 1678 | constructor() { 1679 | let [ 1680 | option_strings, 1681 | version, 1682 | dest, 1683 | default_value, 1684 | help 1685 | ] = _parse_opts(arguments, { 1686 | option_strings: no_default, 1687 | version: undefined, 1688 | dest: SUPPRESS, 1689 | default: SUPPRESS, 1690 | help: "show program's version number and exit" 1691 | }) 1692 | 1693 | super({ 1694 | option_strings, 1695 | dest, 1696 | default: default_value, 1697 | nargs: 0, 1698 | help 1699 | }) 1700 | this.version = version 1701 | } 1702 | 1703 | call(parser/*, namespace, values, option_string = undefined*/) { 1704 | let version = this.version 1705 | if (version === undefined) { 1706 | version = parser.version 1707 | } 1708 | let formatter = parser._get_formatter() 1709 | formatter.add_text(version) 1710 | parser._print_message(formatter.format_help(), process.stdout) 1711 | parser.exit() 1712 | } 1713 | }) 1714 | 1715 | 1716 | const _SubParsersAction = _camelcase_alias(_callable(class _SubParsersAction extends Action { 1717 | 1718 | constructor() { 1719 | let [ 1720 | option_strings, 1721 | prog, 1722 | parser_class, 1723 | dest, 1724 | required, 1725 | help, 1726 | metavar 1727 | ] = _parse_opts(arguments, { 1728 | option_strings: no_default, 1729 | prog: no_default, 1730 | parser_class: no_default, 1731 | dest: SUPPRESS, 1732 | required: false, 1733 | help: undefined, 1734 | metavar: undefined 1735 | }) 1736 | 1737 | let name_parser_map = {} 1738 | 1739 | super({ 1740 | option_strings, 1741 | dest, 1742 | nargs: PARSER, 1743 | choices: name_parser_map, 1744 | required, 1745 | help, 1746 | metavar 1747 | }) 1748 | 1749 | this._prog_prefix = prog 1750 | this._parser_class = parser_class 1751 | this._name_parser_map = name_parser_map 1752 | this._choices_actions = [] 1753 | } 1754 | 1755 | add_parser() { 1756 | let [ 1757 | name, 1758 | kwargs 1759 | ] = _parse_opts(arguments, { 1760 | name: no_default, 1761 | '**kwargs': no_default 1762 | }) 1763 | 1764 | // set prog from the existing prefix 1765 | if (kwargs.prog === undefined) { 1766 | kwargs.prog = sub('%s %s', this._prog_prefix, name) 1767 | } 1768 | 1769 | let aliases = getattr(kwargs, 'aliases', []) 1770 | delete kwargs.aliases 1771 | 1772 | // create a pseudo-action to hold the choice help 1773 | if ('help' in kwargs) { 1774 | let help = kwargs.help 1775 | delete kwargs.help 1776 | let choice_action = this._ChoicesPseudoAction(name, aliases, help) 1777 | this._choices_actions.push(choice_action) 1778 | } 1779 | 1780 | // create the parser and add it to the map 1781 | let parser = new this._parser_class(kwargs) 1782 | this._name_parser_map[name] = parser 1783 | 1784 | // make parser available under aliases also 1785 | for (let alias of aliases) { 1786 | this._name_parser_map[alias] = parser 1787 | } 1788 | 1789 | return parser 1790 | } 1791 | 1792 | _get_subactions() { 1793 | return this._choices_actions 1794 | } 1795 | 1796 | call(parser, namespace, values/*, option_string = undefined*/) { 1797 | let parser_name = values[0] 1798 | let arg_strings = values.slice(1) 1799 | 1800 | // set the parser name if requested 1801 | if (this.dest !== SUPPRESS) { 1802 | setattr(namespace, this.dest, parser_name) 1803 | } 1804 | 1805 | // select the parser 1806 | if (hasattr(this._name_parser_map, parser_name)) { 1807 | parser = this._name_parser_map[parser_name] 1808 | } else { 1809 | let args = {parser_name, 1810 | choices: this._name_parser_map.join(', ')} 1811 | let msg = sub('unknown parser %(parser_name)r (choices: %(choices)s)', args) 1812 | throw new ArgumentError(this, msg) 1813 | } 1814 | 1815 | // parse all the remaining options into the namespace 1816 | // store any unrecognized options on the object, so that the top 1817 | // level parser can decide what to do with them 1818 | 1819 | // In case this subparser defines new defaults, we parse them 1820 | // in a new namespace object and then update the original 1821 | // namespace for the relevant parts. 1822 | let subnamespace 1823 | [ subnamespace, arg_strings ] = parser.parse_known_args(arg_strings, undefined) 1824 | for (let [ key, value ] of Object.entries(subnamespace)) { 1825 | setattr(namespace, key, value) 1826 | } 1827 | 1828 | if (arg_strings.length) { 1829 | setdefault(namespace, _UNRECOGNIZED_ARGS_ATTR, []) 1830 | getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).push(...arg_strings) 1831 | } 1832 | } 1833 | })) 1834 | 1835 | 1836 | _SubParsersAction.prototype._ChoicesPseudoAction = _callable(class _ChoicesPseudoAction extends Action { 1837 | constructor(name, aliases, help) { 1838 | let metavar = name, dest = name 1839 | if (aliases.length) { 1840 | metavar += sub(' (%s)', aliases.join(', ')) 1841 | } 1842 | super({ option_strings: [], dest, help, metavar }) 1843 | } 1844 | }) 1845 | 1846 | 1847 | const _ExtendAction = _callable(class _ExtendAction extends _AppendAction { 1848 | call(parser, namespace, values/*, option_string = undefined*/) { 1849 | let items = getattr(namespace, this.dest, undefined) 1850 | items = _copy_items(items) 1851 | items = items.concat(values) 1852 | setattr(namespace, this.dest, items) 1853 | } 1854 | }) 1855 | 1856 | 1857 | // ============== 1858 | // Type classes 1859 | // ============== 1860 | const FileType = _callable(class FileType extends Function { 1861 | /* 1862 | * Factory for creating file object types 1863 | * 1864 | * Instances of FileType are typically passed as type= arguments to the 1865 | * ArgumentParser add_argument() method. 1866 | * 1867 | * Keyword Arguments: 1868 | * - mode -- A string indicating how the file is to be opened. Accepts the 1869 | * same values as the builtin open() function. 1870 | * - bufsize -- The file's desired buffer size. Accepts the same values as 1871 | * the builtin open() function. 1872 | * - encoding -- The file's encoding. Accepts the same values as the 1873 | * builtin open() function. 1874 | * - errors -- A string indicating how encoding and decoding errors are to 1875 | * be handled. Accepts the same value as the builtin open() function. 1876 | */ 1877 | 1878 | constructor() { 1879 | let [ 1880 | flags, 1881 | encoding, 1882 | mode, 1883 | autoClose, 1884 | emitClose, 1885 | start, 1886 | end, 1887 | highWaterMark, 1888 | fs 1889 | ] = _parse_opts(arguments, { 1890 | flags: 'r', 1891 | encoding: undefined, 1892 | mode: undefined, // 0o666 1893 | autoClose: undefined, // true 1894 | emitClose: undefined, // false 1895 | start: undefined, // 0 1896 | end: undefined, // Infinity 1897 | highWaterMark: undefined, // 64 * 1024 1898 | fs: undefined 1899 | }) 1900 | 1901 | // when this class is called as a function, redirect it to .call() method of itself 1902 | super('return arguments.callee.call.apply(arguments.callee, arguments)') 1903 | 1904 | Object.defineProperty(this, 'name', { 1905 | get() { 1906 | return sub('FileType(%r)', flags) 1907 | } 1908 | }) 1909 | this._flags = flags 1910 | this._options = {} 1911 | if (encoding !== undefined) this._options.encoding = encoding 1912 | if (mode !== undefined) this._options.mode = mode 1913 | if (autoClose !== undefined) this._options.autoClose = autoClose 1914 | if (emitClose !== undefined) this._options.emitClose = emitClose 1915 | if (start !== undefined) this._options.start = start 1916 | if (end !== undefined) this._options.end = end 1917 | if (highWaterMark !== undefined) this._options.highWaterMark = highWaterMark 1918 | if (fs !== undefined) this._options.fs = fs 1919 | } 1920 | 1921 | call(string) { 1922 | // the special argument "-" means sys.std{in,out} 1923 | if (string === '-') { 1924 | if (this._flags.includes('r')) { 1925 | return process.stdin 1926 | } else if (this._flags.includes('w')) { 1927 | return process.stdout 1928 | } else { 1929 | let msg = sub('argument "-" with mode %r', this._flags) 1930 | throw new TypeError(msg) 1931 | } 1932 | } 1933 | 1934 | // all other arguments are used as file names 1935 | let fd 1936 | try { 1937 | fd = fs.openSync(string, this._flags, this._options.mode) 1938 | } catch (e) { 1939 | let args = { filename: string, error: e.message } 1940 | let message = "can't open '%(filename)s': %(error)s" 1941 | throw new ArgumentTypeError(sub(message, args)) 1942 | } 1943 | 1944 | let options = Object.assign({ fd, flags: this._flags }, this._options) 1945 | if (this._flags.includes('r')) { 1946 | return fs.createReadStream(undefined, options) 1947 | } else if (this._flags.includes('w')) { 1948 | return fs.createWriteStream(undefined, options) 1949 | } else { 1950 | let msg = sub('argument "%s" with mode %r', string, this._flags) 1951 | throw new TypeError(msg) 1952 | } 1953 | } 1954 | 1955 | [util.inspect.custom]() { 1956 | let args = [ this._flags ] 1957 | let kwargs = Object.entries(this._options).map(([ k, v ]) => { 1958 | if (k === 'mode') v = { value: v, [util.inspect.custom]() { return '0o' + this.value.toString(8) } } 1959 | return [ k, v ] 1960 | }) 1961 | let args_str = [] 1962 | .concat(args.filter(arg => arg !== -1).map(repr)) 1963 | .concat(kwargs.filter(([/*kw*/, arg]) => arg !== undefined) 1964 | .map(([kw, arg]) => sub('%s=%r', kw, arg))) 1965 | .join(', ') 1966 | return sub('%s(%s)', this.constructor.name, args_str) 1967 | } 1968 | 1969 | toString() { 1970 | return this[util.inspect.custom]() 1971 | } 1972 | }) 1973 | 1974 | // =========================== 1975 | // Optional and Positional Parsing 1976 | // =========================== 1977 | const Namespace = _callable(class Namespace extends _AttributeHolder() { 1978 | /* 1979 | * Simple object for storing attributes. 1980 | * 1981 | * Implements equality by attribute names and values, and provides a simple 1982 | * string representation. 1983 | */ 1984 | 1985 | constructor(options = {}) { 1986 | super() 1987 | Object.assign(this, options) 1988 | } 1989 | }) 1990 | 1991 | // unset string tag to mimic plain object 1992 | Namespace.prototype[Symbol.toStringTag] = undefined 1993 | 1994 | 1995 | const _ActionsContainer = _camelcase_alias(_callable(class _ActionsContainer { 1996 | 1997 | constructor() { 1998 | let [ 1999 | description, 2000 | prefix_chars, 2001 | argument_default, 2002 | conflict_handler 2003 | ] = _parse_opts(arguments, { 2004 | description: no_default, 2005 | prefix_chars: no_default, 2006 | argument_default: no_default, 2007 | conflict_handler: no_default 2008 | }) 2009 | 2010 | this.description = description 2011 | this.argument_default = argument_default 2012 | this.prefix_chars = prefix_chars 2013 | this.conflict_handler = conflict_handler 2014 | 2015 | // set up registries 2016 | this._registries = {} 2017 | 2018 | // register actions 2019 | this.register('action', undefined, _StoreAction) 2020 | this.register('action', 'store', _StoreAction) 2021 | this.register('action', 'store_const', _StoreConstAction) 2022 | this.register('action', 'store_true', _StoreTrueAction) 2023 | this.register('action', 'store_false', _StoreFalseAction) 2024 | this.register('action', 'append', _AppendAction) 2025 | this.register('action', 'append_const', _AppendConstAction) 2026 | this.register('action', 'count', _CountAction) 2027 | this.register('action', 'help', _HelpAction) 2028 | this.register('action', 'version', _VersionAction) 2029 | this.register('action', 'parsers', _SubParsersAction) 2030 | this.register('action', 'extend', _ExtendAction) 2031 | // LEGACY (v1 compatibility): camelcase variants 2032 | ;[ 'storeConst', 'storeTrue', 'storeFalse', 'appendConst' ].forEach(old_name => { 2033 | let new_name = _to_new_name(old_name) 2034 | this.register('action', old_name, util.deprecate(this._registry_get('action', new_name), 2035 | sub('{action: "%s"} is renamed to {action: "%s"}', old_name, new_name))) 2036 | }) 2037 | // end 2038 | 2039 | // raise an exception if the conflict handler is invalid 2040 | this._get_handler() 2041 | 2042 | // action storage 2043 | this._actions = [] 2044 | this._option_string_actions = {} 2045 | 2046 | // groups 2047 | this._action_groups = [] 2048 | this._mutually_exclusive_groups = [] 2049 | 2050 | // defaults storage 2051 | this._defaults = {} 2052 | 2053 | // determines whether an "option" looks like a negative number 2054 | this._negative_number_matcher = /^-\d+$|^-\d*\.\d+$/ 2055 | 2056 | // whether or not there are any optionals that look like negative 2057 | // numbers -- uses a list so it can be shared and edited 2058 | this._has_negative_number_optionals = [] 2059 | } 2060 | 2061 | // ==================== 2062 | // Registration methods 2063 | // ==================== 2064 | register(registry_name, value, object) { 2065 | let registry = setdefault(this._registries, registry_name, {}) 2066 | registry[value] = object 2067 | } 2068 | 2069 | _registry_get(registry_name, value, default_value = undefined) { 2070 | return getattr(this._registries[registry_name], value, default_value) 2071 | } 2072 | 2073 | // ================================== 2074 | // Namespace default accessor methods 2075 | // ================================== 2076 | set_defaults(kwargs) { 2077 | Object.assign(this._defaults, kwargs) 2078 | 2079 | // if these defaults match any existing arguments, replace 2080 | // the previous default on the object with the new one 2081 | for (let action of this._actions) { 2082 | if (action.dest in kwargs) { 2083 | action.default = kwargs[action.dest] 2084 | } 2085 | } 2086 | } 2087 | 2088 | get_default(dest) { 2089 | for (let action of this._actions) { 2090 | if (action.dest === dest && action.default !== undefined) { 2091 | return action.default 2092 | } 2093 | } 2094 | return this._defaults[dest] 2095 | } 2096 | 2097 | 2098 | // ======================= 2099 | // Adding argument actions 2100 | // ======================= 2101 | add_argument() { 2102 | /* 2103 | * add_argument(dest, ..., name=value, ...) 2104 | * add_argument(option_string, option_string, ..., name=value, ...) 2105 | */ 2106 | let [ 2107 | args, 2108 | kwargs 2109 | ] = _parse_opts(arguments, { 2110 | '*args': no_default, 2111 | '**kwargs': no_default 2112 | }) 2113 | // LEGACY (v1 compatibility), old-style add_argument([ args ], { options }) 2114 | if (args.length === 1 && Array.isArray(args[0])) { 2115 | args = args[0] 2116 | deprecate('argument-array', 2117 | sub('use add_argument(%(args)s, {...}) instead of add_argument([ %(args)s ], { ... })', { 2118 | args: args.map(repr).join(', ') 2119 | })) 2120 | } 2121 | // end 2122 | 2123 | // if no positional args are supplied or only one is supplied and 2124 | // it doesn't look like an option string, parse a positional 2125 | // argument 2126 | let chars = this.prefix_chars 2127 | if (!args.length || args.length === 1 && !chars.includes(args[0][0])) { 2128 | if (args.length && 'dest' in kwargs) { 2129 | throw new TypeError('dest supplied twice for positional argument') 2130 | } 2131 | kwargs = this._get_positional_kwargs(...args, kwargs) 2132 | 2133 | // otherwise, we're adding an optional argument 2134 | } else { 2135 | kwargs = this._get_optional_kwargs(...args, kwargs) 2136 | } 2137 | 2138 | // if no default was supplied, use the parser-level default 2139 | if (!('default' in kwargs)) { 2140 | let dest = kwargs.dest 2141 | if (dest in this._defaults) { 2142 | kwargs.default = this._defaults[dest] 2143 | } else if (this.argument_default !== undefined) { 2144 | kwargs.default = this.argument_default 2145 | } 2146 | } 2147 | 2148 | // create the action object, and add it to the parser 2149 | let action_class = this._pop_action_class(kwargs) 2150 | if (typeof action_class !== 'function') { 2151 | throw new TypeError(sub('unknown action "%s"', action_class)) 2152 | } 2153 | // eslint-disable-next-line new-cap 2154 | let action = new action_class(kwargs) 2155 | 2156 | // raise an error if the action type is not callable 2157 | let type_func = this._registry_get('type', action.type, action.type) 2158 | if (typeof type_func !== 'function') { 2159 | throw new TypeError(sub('%r is not callable', type_func)) 2160 | } 2161 | 2162 | if (type_func === FileType) { 2163 | throw new TypeError(sub('%r is a FileType class object, instance of it' + 2164 | ' must be passed', type_func)) 2165 | } 2166 | 2167 | // raise an error if the metavar does not match the type 2168 | if ('_get_formatter' in this) { 2169 | try { 2170 | this._get_formatter()._format_args(action, undefined) 2171 | } catch (err) { 2172 | // check for 'invalid nargs value' is an artifact of TypeError and ValueError in js being the same 2173 | if (err instanceof TypeError && err.message !== 'invalid nargs value') { 2174 | throw new TypeError('length of metavar tuple does not match nargs') 2175 | } else { 2176 | throw err 2177 | } 2178 | } 2179 | } 2180 | 2181 | return this._add_action(action) 2182 | } 2183 | 2184 | add_argument_group() { 2185 | let group = _ArgumentGroup(this, ...arguments) 2186 | this._action_groups.push(group) 2187 | return group 2188 | } 2189 | 2190 | add_mutually_exclusive_group() { 2191 | // eslint-disable-next-line no-use-before-define 2192 | let group = _MutuallyExclusiveGroup(this, ...arguments) 2193 | this._mutually_exclusive_groups.push(group) 2194 | return group 2195 | } 2196 | 2197 | _add_action(action) { 2198 | // resolve any conflicts 2199 | this._check_conflict(action) 2200 | 2201 | // add to actions list 2202 | this._actions.push(action) 2203 | action.container = this 2204 | 2205 | // index the action by any option strings it has 2206 | for (let option_string of action.option_strings) { 2207 | this._option_string_actions[option_string] = action 2208 | } 2209 | 2210 | // set the flag if any option strings look like negative numbers 2211 | for (let option_string of action.option_strings) { 2212 | if (this._negative_number_matcher.test(option_string)) { 2213 | if (!this._has_negative_number_optionals.length) { 2214 | this._has_negative_number_optionals.push(true) 2215 | } 2216 | } 2217 | } 2218 | 2219 | // return the created action 2220 | return action 2221 | } 2222 | 2223 | _remove_action(action) { 2224 | _array_remove(this._actions, action) 2225 | } 2226 | 2227 | _add_container_actions(container) { 2228 | // collect groups by titles 2229 | let title_group_map = {} 2230 | for (let group of this._action_groups) { 2231 | if (group.title in title_group_map) { 2232 | let msg = 'cannot merge actions - two groups are named %r' 2233 | throw new TypeError(sub(msg, group.title)) 2234 | } 2235 | title_group_map[group.title] = group 2236 | } 2237 | 2238 | // map each action to its group 2239 | let group_map = new Map() 2240 | for (let group of container._action_groups) { 2241 | 2242 | // if a group with the title exists, use that, otherwise 2243 | // create a new group matching the container's group 2244 | if (!(group.title in title_group_map)) { 2245 | title_group_map[group.title] = this.add_argument_group({ 2246 | title: group.title, 2247 | description: group.description, 2248 | conflict_handler: group.conflict_handler 2249 | }) 2250 | } 2251 | 2252 | // map the actions to their new group 2253 | for (let action of group._group_actions) { 2254 | group_map.set(action, title_group_map[group.title]) 2255 | } 2256 | } 2257 | 2258 | // add container's mutually exclusive groups 2259 | // NOTE: if add_mutually_exclusive_group ever gains title= and 2260 | // description= then this code will need to be expanded as above 2261 | for (let group of container._mutually_exclusive_groups) { 2262 | let mutex_group = this.add_mutually_exclusive_group({ 2263 | required: group.required 2264 | }) 2265 | 2266 | // map the actions to their new mutex group 2267 | for (let action of group._group_actions) { 2268 | group_map.set(action, mutex_group) 2269 | } 2270 | } 2271 | 2272 | // add all actions to this container or their group 2273 | for (let action of container._actions) { 2274 | group_map.get(action)._add_action(action) 2275 | } 2276 | } 2277 | 2278 | _get_positional_kwargs() { 2279 | let [ 2280 | dest, 2281 | kwargs 2282 | ] = _parse_opts(arguments, { 2283 | dest: no_default, 2284 | '**kwargs': no_default 2285 | }) 2286 | 2287 | // make sure required is not specified 2288 | if ('required' in kwargs) { 2289 | let msg = "'required' is an invalid argument for positionals" 2290 | throw new TypeError(msg) 2291 | } 2292 | 2293 | // mark positional arguments as required if at least one is 2294 | // always required 2295 | if (![OPTIONAL, ZERO_OR_MORE].includes(kwargs.nargs)) { 2296 | kwargs.required = true 2297 | } 2298 | if (kwargs.nargs === ZERO_OR_MORE && !('default' in kwargs)) { 2299 | kwargs.required = true 2300 | } 2301 | 2302 | // return the keyword arguments with no option strings 2303 | return Object.assign(kwargs, { dest, option_strings: [] }) 2304 | } 2305 | 2306 | _get_optional_kwargs() { 2307 | let [ 2308 | args, 2309 | kwargs 2310 | ] = _parse_opts(arguments, { 2311 | '*args': no_default, 2312 | '**kwargs': no_default 2313 | }) 2314 | 2315 | // determine short and long option strings 2316 | let option_strings = [] 2317 | let long_option_strings = [] 2318 | let option_string 2319 | for (option_string of args) { 2320 | // error on strings that don't start with an appropriate prefix 2321 | if (!this.prefix_chars.includes(option_string[0])) { 2322 | let args = {option: option_string, 2323 | prefix_chars: this.prefix_chars} 2324 | let msg = 'invalid option string %(option)r: ' + 2325 | 'must start with a character %(prefix_chars)r' 2326 | throw new TypeError(sub(msg, args)) 2327 | } 2328 | 2329 | // strings starting with two prefix characters are long options 2330 | option_strings.push(option_string) 2331 | if (option_string.length > 1 && this.prefix_chars.includes(option_string[1])) { 2332 | long_option_strings.push(option_string) 2333 | } 2334 | } 2335 | 2336 | // infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' 2337 | let dest = kwargs.dest 2338 | delete kwargs.dest 2339 | if (dest === undefined) { 2340 | let dest_option_string 2341 | if (long_option_strings.length) { 2342 | dest_option_string = long_option_strings[0] 2343 | } else { 2344 | dest_option_string = option_strings[0] 2345 | } 2346 | dest = _string_lstrip(dest_option_string, this.prefix_chars) 2347 | if (!dest) { 2348 | let msg = 'dest= is required for options like %r' 2349 | throw new TypeError(sub(msg, option_string)) 2350 | } 2351 | dest = dest.replace(/-/g, '_') 2352 | } 2353 | 2354 | // return the updated keyword arguments 2355 | return Object.assign(kwargs, { dest, option_strings }) 2356 | } 2357 | 2358 | _pop_action_class(kwargs, default_value = undefined) { 2359 | let action = getattr(kwargs, 'action', default_value) 2360 | delete kwargs.action 2361 | return this._registry_get('action', action, action) 2362 | } 2363 | 2364 | _get_handler() { 2365 | // determine function from conflict handler string 2366 | let handler_func_name = sub('_handle_conflict_%s', this.conflict_handler) 2367 | if (typeof this[handler_func_name] === 'function') { 2368 | return this[handler_func_name] 2369 | } else { 2370 | let msg = 'invalid conflict_resolution value: %r' 2371 | throw new TypeError(sub(msg, this.conflict_handler)) 2372 | } 2373 | } 2374 | 2375 | _check_conflict(action) { 2376 | 2377 | // find all options that conflict with this option 2378 | let confl_optionals = [] 2379 | for (let option_string of action.option_strings) { 2380 | if (hasattr(this._option_string_actions, option_string)) { 2381 | let confl_optional = this._option_string_actions[option_string] 2382 | confl_optionals.push([ option_string, confl_optional ]) 2383 | } 2384 | } 2385 | 2386 | // resolve any conflicts 2387 | if (confl_optionals.length) { 2388 | let conflict_handler = this._get_handler() 2389 | conflict_handler.call(this, action, confl_optionals) 2390 | } 2391 | } 2392 | 2393 | _handle_conflict_error(action, conflicting_actions) { 2394 | let message = conflicting_actions.length === 1 ? 2395 | 'conflicting option string: %s' : 2396 | 'conflicting option strings: %s' 2397 | let conflict_string = conflicting_actions.map(([ option_string/*, action*/ ]) => option_string).join(', ') 2398 | throw new ArgumentError(action, sub(message, conflict_string)) 2399 | } 2400 | 2401 | _handle_conflict_resolve(action, conflicting_actions) { 2402 | 2403 | // remove all conflicting options 2404 | for (let [ option_string, action ] of conflicting_actions) { 2405 | 2406 | // remove the conflicting option 2407 | _array_remove(action.option_strings, option_string) 2408 | delete this._option_string_actions[option_string] 2409 | 2410 | // if the option now has no option string, remove it from the 2411 | // container holding it 2412 | if (!action.option_strings.length) { 2413 | action.container._remove_action(action) 2414 | } 2415 | } 2416 | } 2417 | })) 2418 | 2419 | 2420 | const _ArgumentGroup = _callable(class _ArgumentGroup extends _ActionsContainer { 2421 | 2422 | constructor() { 2423 | let [ 2424 | container, 2425 | title, 2426 | description, 2427 | kwargs 2428 | ] = _parse_opts(arguments, { 2429 | container: no_default, 2430 | title: undefined, 2431 | description: undefined, 2432 | '**kwargs': no_default 2433 | }) 2434 | 2435 | // add any missing keyword arguments by checking the container 2436 | setdefault(kwargs, 'conflict_handler', container.conflict_handler) 2437 | setdefault(kwargs, 'prefix_chars', container.prefix_chars) 2438 | setdefault(kwargs, 'argument_default', container.argument_default) 2439 | super(Object.assign({ description }, kwargs)) 2440 | 2441 | // group attributes 2442 | this.title = title 2443 | this._group_actions = [] 2444 | 2445 | // share most attributes with the container 2446 | this._registries = container._registries 2447 | this._actions = container._actions 2448 | this._option_string_actions = container._option_string_actions 2449 | this._defaults = container._defaults 2450 | this._has_negative_number_optionals = 2451 | container._has_negative_number_optionals 2452 | this._mutually_exclusive_groups = container._mutually_exclusive_groups 2453 | } 2454 | 2455 | _add_action(action) { 2456 | action = super._add_action(action) 2457 | this._group_actions.push(action) 2458 | return action 2459 | } 2460 | 2461 | _remove_action(action) { 2462 | super._remove_action(action) 2463 | _array_remove(this._group_actions, action) 2464 | } 2465 | }) 2466 | 2467 | 2468 | const _MutuallyExclusiveGroup = _callable(class _MutuallyExclusiveGroup extends _ArgumentGroup { 2469 | 2470 | constructor() { 2471 | let [ 2472 | container, 2473 | required 2474 | ] = _parse_opts(arguments, { 2475 | container: no_default, 2476 | required: false 2477 | }) 2478 | 2479 | super(container) 2480 | this.required = required 2481 | this._container = container 2482 | } 2483 | 2484 | _add_action(action) { 2485 | if (action.required) { 2486 | let msg = 'mutually exclusive arguments must be optional' 2487 | throw new TypeError(msg) 2488 | } 2489 | action = this._container._add_action(action) 2490 | this._group_actions.push(action) 2491 | return action 2492 | } 2493 | 2494 | _remove_action(action) { 2495 | this._container._remove_action(action) 2496 | _array_remove(this._group_actions, action) 2497 | } 2498 | }) 2499 | 2500 | 2501 | const ArgumentParser = _camelcase_alias(_callable(class ArgumentParser extends _AttributeHolder(_ActionsContainer) { 2502 | /* 2503 | * Object for parsing command line strings into Python objects. 2504 | * 2505 | * Keyword Arguments: 2506 | * - prog -- The name of the program (default: sys.argv[0]) 2507 | * - usage -- A usage message (default: auto-generated from arguments) 2508 | * - description -- A description of what the program does 2509 | * - epilog -- Text following the argument descriptions 2510 | * - parents -- Parsers whose arguments should be copied into this one 2511 | * - formatter_class -- HelpFormatter class for printing help messages 2512 | * - prefix_chars -- Characters that prefix optional arguments 2513 | * - fromfile_prefix_chars -- Characters that prefix files containing 2514 | * additional arguments 2515 | * - argument_default -- The default value for all arguments 2516 | * - conflict_handler -- String indicating how to handle conflicts 2517 | * - add_help -- Add a -h/-help option 2518 | * - allow_abbrev -- Allow long options to be abbreviated unambiguously 2519 | * - exit_on_error -- Determines whether or not ArgumentParser exits with 2520 | * error info when an error occurs 2521 | */ 2522 | 2523 | constructor() { 2524 | let [ 2525 | prog, 2526 | usage, 2527 | description, 2528 | epilog, 2529 | parents, 2530 | formatter_class, 2531 | prefix_chars, 2532 | fromfile_prefix_chars, 2533 | argument_default, 2534 | conflict_handler, 2535 | add_help, 2536 | allow_abbrev, 2537 | exit_on_error, 2538 | debug, // LEGACY (v1 compatibility), debug mode 2539 | version // LEGACY (v1 compatibility), version 2540 | ] = _parse_opts(arguments, { 2541 | prog: undefined, 2542 | usage: undefined, 2543 | description: undefined, 2544 | epilog: undefined, 2545 | parents: [], 2546 | formatter_class: HelpFormatter, 2547 | prefix_chars: '-', 2548 | fromfile_prefix_chars: undefined, 2549 | argument_default: undefined, 2550 | conflict_handler: 'error', 2551 | add_help: true, 2552 | allow_abbrev: true, 2553 | exit_on_error: true, 2554 | debug: undefined, // LEGACY (v1 compatibility), debug mode 2555 | version: undefined // LEGACY (v1 compatibility), version 2556 | }) 2557 | 2558 | // LEGACY (v1 compatibility) 2559 | if (debug !== undefined) { 2560 | deprecate('debug', 2561 | 'The "debug" argument to ArgumentParser is deprecated. Please ' + 2562 | 'override ArgumentParser.exit function instead.' 2563 | ) 2564 | } 2565 | 2566 | if (version !== undefined) { 2567 | deprecate('version', 2568 | 'The "version" argument to ArgumentParser is deprecated. Please use ' + 2569 | "add_argument(..., { action: 'version', version: 'N', ... }) instead." 2570 | ) 2571 | } 2572 | // end 2573 | 2574 | super({ 2575 | description, 2576 | prefix_chars, 2577 | argument_default, 2578 | conflict_handler 2579 | }) 2580 | 2581 | // default setting for prog 2582 | if (prog === undefined) { 2583 | prog = path.basename(get_argv()[0] || '') 2584 | } 2585 | 2586 | this.prog = prog 2587 | this.usage = usage 2588 | this.epilog = epilog 2589 | this.formatter_class = formatter_class 2590 | this.fromfile_prefix_chars = fromfile_prefix_chars 2591 | this.add_help = add_help 2592 | this.allow_abbrev = allow_abbrev 2593 | this.exit_on_error = exit_on_error 2594 | // LEGACY (v1 compatibility), debug mode 2595 | this.debug = debug 2596 | // end 2597 | 2598 | this._positionals = this.add_argument_group('positional arguments') 2599 | this._optionals = this.add_argument_group('optional arguments') 2600 | this._subparsers = undefined 2601 | 2602 | // register types 2603 | function identity(string) { 2604 | return string 2605 | } 2606 | this.register('type', undefined, identity) 2607 | this.register('type', null, identity) 2608 | this.register('type', 'auto', identity) 2609 | this.register('type', 'int', function (x) { 2610 | let result = Number(x) 2611 | if (!Number.isInteger(result)) { 2612 | throw new TypeError(sub('could not convert string to int: %r', x)) 2613 | } 2614 | return result 2615 | }) 2616 | this.register('type', 'float', function (x) { 2617 | let result = Number(x) 2618 | if (isNaN(result)) { 2619 | throw new TypeError(sub('could not convert string to float: %r', x)) 2620 | } 2621 | return result 2622 | }) 2623 | this.register('type', 'str', String) 2624 | // LEGACY (v1 compatibility): custom types 2625 | this.register('type', 'string', 2626 | util.deprecate(String, 'use {type:"str"} or {type:String} instead of {type:"string"}')) 2627 | // end 2628 | 2629 | // add help argument if necessary 2630 | // (using explicit default to override global argument_default) 2631 | let default_prefix = prefix_chars.includes('-') ? '-' : prefix_chars[0] 2632 | if (this.add_help) { 2633 | this.add_argument( 2634 | default_prefix + 'h', 2635 | default_prefix.repeat(2) + 'help', 2636 | { 2637 | action: 'help', 2638 | default: SUPPRESS, 2639 | help: 'show this help message and exit' 2640 | } 2641 | ) 2642 | } 2643 | // LEGACY (v1 compatibility), version 2644 | if (version) { 2645 | this.add_argument( 2646 | default_prefix + 'v', 2647 | default_prefix.repeat(2) + 'version', 2648 | { 2649 | action: 'version', 2650 | default: SUPPRESS, 2651 | version: this.version, 2652 | help: "show program's version number and exit" 2653 | } 2654 | ) 2655 | } 2656 | // end 2657 | 2658 | // add parent arguments and defaults 2659 | for (let parent of parents) { 2660 | this._add_container_actions(parent) 2661 | Object.assign(this._defaults, parent._defaults) 2662 | } 2663 | } 2664 | 2665 | // ======================= 2666 | // Pretty __repr__ methods 2667 | // ======================= 2668 | _get_kwargs() { 2669 | let names = [ 2670 | 'prog', 2671 | 'usage', 2672 | 'description', 2673 | 'formatter_class', 2674 | 'conflict_handler', 2675 | 'add_help' 2676 | ] 2677 | return names.map(name => [ name, getattr(this, name) ]) 2678 | } 2679 | 2680 | // ================================== 2681 | // Optional/Positional adding methods 2682 | // ================================== 2683 | add_subparsers() { 2684 | let [ 2685 | kwargs 2686 | ] = _parse_opts(arguments, { 2687 | '**kwargs': no_default 2688 | }) 2689 | 2690 | if (this._subparsers !== undefined) { 2691 | this.error('cannot have multiple subparser arguments') 2692 | } 2693 | 2694 | // add the parser class to the arguments if it's not present 2695 | setdefault(kwargs, 'parser_class', this.constructor) 2696 | 2697 | if ('title' in kwargs || 'description' in kwargs) { 2698 | let title = getattr(kwargs, 'title', 'subcommands') 2699 | let description = getattr(kwargs, 'description', undefined) 2700 | delete kwargs.title 2701 | delete kwargs.description 2702 | this._subparsers = this.add_argument_group(title, description) 2703 | } else { 2704 | this._subparsers = this._positionals 2705 | } 2706 | 2707 | // prog defaults to the usage message of this parser, skipping 2708 | // optional arguments and with no "usage:" prefix 2709 | if (kwargs.prog === undefined) { 2710 | let formatter = this._get_formatter() 2711 | let positionals = this._get_positional_actions() 2712 | let groups = this._mutually_exclusive_groups 2713 | formatter.add_usage(this.usage, positionals, groups, '') 2714 | kwargs.prog = formatter.format_help().trim() 2715 | } 2716 | 2717 | // create the parsers action and add it to the positionals list 2718 | let parsers_class = this._pop_action_class(kwargs, 'parsers') 2719 | // eslint-disable-next-line new-cap 2720 | let action = new parsers_class(Object.assign({ option_strings: [] }, kwargs)) 2721 | this._subparsers._add_action(action) 2722 | 2723 | // return the created parsers action 2724 | return action 2725 | } 2726 | 2727 | _add_action(action) { 2728 | if (action.option_strings.length) { 2729 | this._optionals._add_action(action) 2730 | } else { 2731 | this._positionals._add_action(action) 2732 | } 2733 | return action 2734 | } 2735 | 2736 | _get_optional_actions() { 2737 | return this._actions.filter(action => action.option_strings.length) 2738 | } 2739 | 2740 | _get_positional_actions() { 2741 | return this._actions.filter(action => !action.option_strings.length) 2742 | } 2743 | 2744 | // ===================================== 2745 | // Command line argument parsing methods 2746 | // ===================================== 2747 | parse_args(args = undefined, namespace = undefined) { 2748 | let argv 2749 | [ args, argv ] = this.parse_known_args(args, namespace) 2750 | if (argv && argv.length > 0) { 2751 | let msg = 'unrecognized arguments: %s' 2752 | this.error(sub(msg, argv.join(' '))) 2753 | } 2754 | return args 2755 | } 2756 | 2757 | parse_known_args(args = undefined, namespace = undefined) { 2758 | if (args === undefined) { 2759 | args = get_argv().slice(1) 2760 | } 2761 | 2762 | // default Namespace built from parser defaults 2763 | if (namespace === undefined) { 2764 | namespace = new Namespace() 2765 | } 2766 | 2767 | // add any action defaults that aren't present 2768 | for (let action of this._actions) { 2769 | if (action.dest !== SUPPRESS) { 2770 | if (!hasattr(namespace, action.dest)) { 2771 | if (action.default !== SUPPRESS) { 2772 | setattr(namespace, action.dest, action.default) 2773 | } 2774 | } 2775 | } 2776 | } 2777 | 2778 | // add any parser defaults that aren't present 2779 | for (let dest of Object.keys(this._defaults)) { 2780 | if (!hasattr(namespace, dest)) { 2781 | setattr(namespace, dest, this._defaults[dest]) 2782 | } 2783 | } 2784 | 2785 | // parse the arguments and exit if there are any errors 2786 | if (this.exit_on_error) { 2787 | try { 2788 | [ namespace, args ] = this._parse_known_args(args, namespace) 2789 | } catch (err) { 2790 | if (err instanceof ArgumentError) { 2791 | this.error(err.message) 2792 | } else { 2793 | throw err 2794 | } 2795 | } 2796 | } else { 2797 | [ namespace, args ] = this._parse_known_args(args, namespace) 2798 | } 2799 | 2800 | if (hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) { 2801 | args = args.concat(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) 2802 | delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) 2803 | } 2804 | 2805 | return [ namespace, args ] 2806 | } 2807 | 2808 | _parse_known_args(arg_strings, namespace) { 2809 | // replace arg strings that are file references 2810 | if (this.fromfile_prefix_chars !== undefined) { 2811 | arg_strings = this._read_args_from_files(arg_strings) 2812 | } 2813 | 2814 | // map all mutually exclusive arguments to the other arguments 2815 | // they can't occur with 2816 | let action_conflicts = new Map() 2817 | for (let mutex_group of this._mutually_exclusive_groups) { 2818 | let group_actions = mutex_group._group_actions 2819 | for (let [ i, mutex_action ] of Object.entries(mutex_group._group_actions)) { 2820 | let conflicts = action_conflicts.get(mutex_action) || [] 2821 | conflicts = conflicts.concat(group_actions.slice(0, +i)) 2822 | conflicts = conflicts.concat(group_actions.slice(+i + 1)) 2823 | action_conflicts.set(mutex_action, conflicts) 2824 | } 2825 | } 2826 | 2827 | // find all option indices, and determine the arg_string_pattern 2828 | // which has an 'O' if there is an option at an index, 2829 | // an 'A' if there is an argument, or a '-' if there is a '--' 2830 | let option_string_indices = {} 2831 | let arg_string_pattern_parts = [] 2832 | let arg_strings_iter = Object.entries(arg_strings)[Symbol.iterator]() 2833 | for (let [ i, arg_string ] of arg_strings_iter) { 2834 | 2835 | // all args after -- are non-options 2836 | if (arg_string === '--') { 2837 | arg_string_pattern_parts.push('-') 2838 | for ([ i, arg_string ] of arg_strings_iter) { 2839 | arg_string_pattern_parts.push('A') 2840 | } 2841 | 2842 | // otherwise, add the arg to the arg strings 2843 | // and note the index if it was an option 2844 | } else { 2845 | let option_tuple = this._parse_optional(arg_string) 2846 | let pattern 2847 | if (option_tuple === undefined) { 2848 | pattern = 'A' 2849 | } else { 2850 | option_string_indices[i] = option_tuple 2851 | pattern = 'O' 2852 | } 2853 | arg_string_pattern_parts.push(pattern) 2854 | } 2855 | } 2856 | 2857 | // join the pieces together to form the pattern 2858 | let arg_strings_pattern = arg_string_pattern_parts.join('') 2859 | 2860 | // converts arg strings to the appropriate and then takes the action 2861 | let seen_actions = new Set() 2862 | let seen_non_default_actions = new Set() 2863 | let extras 2864 | 2865 | let take_action = (action, argument_strings, option_string = undefined) => { 2866 | seen_actions.add(action) 2867 | let argument_values = this._get_values(action, argument_strings) 2868 | 2869 | // error if this argument is not allowed with other previously 2870 | // seen arguments, assuming that actions that use the default 2871 | // value don't really count as "present" 2872 | if (argument_values !== action.default) { 2873 | seen_non_default_actions.add(action) 2874 | for (let conflict_action of action_conflicts.get(action) || []) { 2875 | if (seen_non_default_actions.has(conflict_action)) { 2876 | let msg = 'not allowed with argument %s' 2877 | let action_name = _get_action_name(conflict_action) 2878 | throw new ArgumentError(action, sub(msg, action_name)) 2879 | } 2880 | } 2881 | } 2882 | 2883 | // take the action if we didn't receive a SUPPRESS value 2884 | // (e.g. from a default) 2885 | if (argument_values !== SUPPRESS) { 2886 | action(this, namespace, argument_values, option_string) 2887 | } 2888 | } 2889 | 2890 | // function to convert arg_strings into an optional action 2891 | let consume_optional = start_index => { 2892 | 2893 | // get the optional identified at this index 2894 | let option_tuple = option_string_indices[start_index] 2895 | let [ action, option_string, explicit_arg ] = option_tuple 2896 | 2897 | // identify additional optionals in the same arg string 2898 | // (e.g. -xyz is the same as -x -y -z if no args are required) 2899 | let action_tuples = [] 2900 | let stop 2901 | for (;;) { 2902 | 2903 | // if we found no optional action, skip it 2904 | if (action === undefined) { 2905 | extras.push(arg_strings[start_index]) 2906 | return start_index + 1 2907 | } 2908 | 2909 | // if there is an explicit argument, try to match the 2910 | // optional's string arguments to only this 2911 | if (explicit_arg !== undefined) { 2912 | let arg_count = this._match_argument(action, 'A') 2913 | 2914 | // if the action is a single-dash option and takes no 2915 | // arguments, try to parse more single-dash options out 2916 | // of the tail of the option string 2917 | let chars = this.prefix_chars 2918 | if (arg_count === 0 && !chars.includes(option_string[1])) { 2919 | action_tuples.push([ action, [], option_string ]) 2920 | let char = option_string[0] 2921 | option_string = char + explicit_arg[0] 2922 | let new_explicit_arg = explicit_arg.slice(1) || undefined 2923 | let optionals_map = this._option_string_actions 2924 | if (hasattr(optionals_map, option_string)) { 2925 | action = optionals_map[option_string] 2926 | explicit_arg = new_explicit_arg 2927 | } else { 2928 | let msg = 'ignored explicit argument %r' 2929 | throw new ArgumentError(action, sub(msg, explicit_arg)) 2930 | } 2931 | 2932 | // if the action expect exactly one argument, we've 2933 | // successfully matched the option; exit the loop 2934 | } else if (arg_count === 1) { 2935 | stop = start_index + 1 2936 | let args = [ explicit_arg ] 2937 | action_tuples.push([ action, args, option_string ]) 2938 | break 2939 | 2940 | // error if a double-dash option did not use the 2941 | // explicit argument 2942 | } else { 2943 | let msg = 'ignored explicit argument %r' 2944 | throw new ArgumentError(action, sub(msg, explicit_arg)) 2945 | } 2946 | 2947 | // if there is no explicit argument, try to match the 2948 | // optional's string arguments with the following strings 2949 | // if successful, exit the loop 2950 | } else { 2951 | let start = start_index + 1 2952 | let selected_patterns = arg_strings_pattern.slice(start) 2953 | let arg_count = this._match_argument(action, selected_patterns) 2954 | stop = start + arg_count 2955 | let args = arg_strings.slice(start, stop) 2956 | action_tuples.push([ action, args, option_string ]) 2957 | break 2958 | } 2959 | } 2960 | 2961 | // add the Optional to the list and return the index at which 2962 | // the Optional's string args stopped 2963 | assert(action_tuples.length) 2964 | for (let [ action, args, option_string ] of action_tuples) { 2965 | take_action(action, args, option_string) 2966 | } 2967 | return stop 2968 | } 2969 | 2970 | // the list of Positionals left to be parsed; this is modified 2971 | // by consume_positionals() 2972 | let positionals = this._get_positional_actions() 2973 | 2974 | // function to convert arg_strings into positional actions 2975 | let consume_positionals = start_index => { 2976 | // match as many Positionals as possible 2977 | let selected_pattern = arg_strings_pattern.slice(start_index) 2978 | let arg_counts = this._match_arguments_partial(positionals, selected_pattern) 2979 | 2980 | // slice off the appropriate arg strings for each Positional 2981 | // and add the Positional and its args to the list 2982 | for (let i = 0; i < positionals.length && i < arg_counts.length; i++) { 2983 | let action = positionals[i] 2984 | let arg_count = arg_counts[i] 2985 | let args = arg_strings.slice(start_index, start_index + arg_count) 2986 | start_index += arg_count 2987 | take_action(action, args) 2988 | } 2989 | 2990 | // slice off the Positionals that we just parsed and return the 2991 | // index at which the Positionals' string args stopped 2992 | positionals = positionals.slice(arg_counts.length) 2993 | return start_index 2994 | } 2995 | 2996 | // consume Positionals and Optionals alternately, until we have 2997 | // passed the last option string 2998 | extras = [] 2999 | let start_index = 0 3000 | let max_option_string_index = Math.max(-1, ...Object.keys(option_string_indices).map(Number)) 3001 | while (start_index <= max_option_string_index) { 3002 | 3003 | // consume any Positionals preceding the next option 3004 | let next_option_string_index = Math.min( 3005 | // eslint-disable-next-line no-loop-func 3006 | ...Object.keys(option_string_indices).map(Number).filter(index => index >= start_index) 3007 | ) 3008 | if (start_index !== next_option_string_index) { 3009 | let positionals_end_index = consume_positionals(start_index) 3010 | 3011 | // only try to parse the next optional if we didn't consume 3012 | // the option string during the positionals parsing 3013 | if (positionals_end_index > start_index) { 3014 | start_index = positionals_end_index 3015 | continue 3016 | } else { 3017 | start_index = positionals_end_index 3018 | } 3019 | } 3020 | 3021 | // if we consumed all the positionals we could and we're not 3022 | // at the index of an option string, there were extra arguments 3023 | if (!(start_index in option_string_indices)) { 3024 | let strings = arg_strings.slice(start_index, next_option_string_index) 3025 | extras = extras.concat(strings) 3026 | start_index = next_option_string_index 3027 | } 3028 | 3029 | // consume the next optional and any arguments for it 3030 | start_index = consume_optional(start_index) 3031 | } 3032 | 3033 | // consume any positionals following the last Optional 3034 | let stop_index = consume_positionals(start_index) 3035 | 3036 | // if we didn't consume all the argument strings, there were extras 3037 | extras = extras.concat(arg_strings.slice(stop_index)) 3038 | 3039 | // make sure all required actions were present and also convert 3040 | // action defaults which were not given as arguments 3041 | let required_actions = [] 3042 | for (let action of this._actions) { 3043 | if (!seen_actions.has(action)) { 3044 | if (action.required) { 3045 | required_actions.push(_get_action_name(action)) 3046 | } else { 3047 | // Convert action default now instead of doing it before 3048 | // parsing arguments to avoid calling convert functions 3049 | // twice (which may fail) if the argument was given, but 3050 | // only if it was defined already in the namespace 3051 | if (action.default !== undefined && 3052 | typeof action.default === 'string' && 3053 | hasattr(namespace, action.dest) && 3054 | action.default === getattr(namespace, action.dest)) { 3055 | setattr(namespace, action.dest, 3056 | this._get_value(action, action.default)) 3057 | } 3058 | } 3059 | } 3060 | } 3061 | 3062 | if (required_actions.length) { 3063 | this.error(sub('the following arguments are required: %s', 3064 | required_actions.join(', '))) 3065 | } 3066 | 3067 | // make sure all required groups had one option present 3068 | for (let group of this._mutually_exclusive_groups) { 3069 | if (group.required) { 3070 | let no_actions_used = true 3071 | for (let action of group._group_actions) { 3072 | if (seen_non_default_actions.has(action)) { 3073 | no_actions_used = false 3074 | break 3075 | } 3076 | } 3077 | 3078 | // if no actions were used, report the error 3079 | if (no_actions_used) { 3080 | let names = group._group_actions 3081 | .filter(action => action.help !== SUPPRESS) 3082 | .map(action => _get_action_name(action)) 3083 | let msg = 'one of the arguments %s is required' 3084 | this.error(sub(msg, names.join(' '))) 3085 | } 3086 | } 3087 | } 3088 | 3089 | // return the updated namespace and the extra arguments 3090 | return [ namespace, extras ] 3091 | } 3092 | 3093 | _read_args_from_files(arg_strings) { 3094 | // expand arguments referencing files 3095 | let new_arg_strings = [] 3096 | for (let arg_string of arg_strings) { 3097 | 3098 | // for regular arguments, just add them back into the list 3099 | if (!arg_string || !this.fromfile_prefix_chars.includes(arg_string[0])) { 3100 | new_arg_strings.push(arg_string) 3101 | 3102 | // replace arguments referencing files with the file content 3103 | } else { 3104 | try { 3105 | let args_file = fs.readFileSync(arg_string.slice(1), 'utf8') 3106 | let arg_strings = [] 3107 | for (let arg_line of splitlines(args_file)) { 3108 | for (let arg of this.convert_arg_line_to_args(arg_line)) { 3109 | arg_strings.push(arg) 3110 | } 3111 | } 3112 | arg_strings = this._read_args_from_files(arg_strings) 3113 | new_arg_strings = new_arg_strings.concat(arg_strings) 3114 | } catch (err) { 3115 | this.error(err.message) 3116 | } 3117 | } 3118 | } 3119 | 3120 | // return the modified argument list 3121 | return new_arg_strings 3122 | } 3123 | 3124 | convert_arg_line_to_args(arg_line) { 3125 | return [arg_line] 3126 | } 3127 | 3128 | _match_argument(action, arg_strings_pattern) { 3129 | // match the pattern for this action to the arg strings 3130 | let nargs_pattern = this._get_nargs_pattern(action) 3131 | let match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern)) 3132 | 3133 | // raise an exception if we weren't able to find a match 3134 | if (match === null) { 3135 | let nargs_errors = { 3136 | undefined: 'expected one argument', 3137 | [OPTIONAL]: 'expected at most one argument', 3138 | [ONE_OR_MORE]: 'expected at least one argument' 3139 | } 3140 | let msg = nargs_errors[action.nargs] 3141 | if (msg === undefined) { 3142 | msg = sub(action.nargs === 1 ? 'expected %s argument' : 'expected %s arguments', action.nargs) 3143 | } 3144 | throw new ArgumentError(action, msg) 3145 | } 3146 | 3147 | // return the number of arguments matched 3148 | return match[1].length 3149 | } 3150 | 3151 | _match_arguments_partial(actions, arg_strings_pattern) { 3152 | // progressively shorten the actions list by slicing off the 3153 | // final actions until we find a match 3154 | let result = [] 3155 | for (let i of range(actions.length, 0, -1)) { 3156 | let actions_slice = actions.slice(0, i) 3157 | let pattern = actions_slice.map(action => this._get_nargs_pattern(action)).join('') 3158 | let match = arg_strings_pattern.match(new RegExp('^' + pattern)) 3159 | if (match !== null) { 3160 | result = result.concat(match.slice(1).map(string => string.length)) 3161 | break 3162 | } 3163 | } 3164 | 3165 | // return the list of arg string counts 3166 | return result 3167 | } 3168 | 3169 | _parse_optional(arg_string) { 3170 | // if it's an empty string, it was meant to be a positional 3171 | if (!arg_string) { 3172 | return undefined 3173 | } 3174 | 3175 | // if it doesn't start with a prefix, it was meant to be positional 3176 | if (!this.prefix_chars.includes(arg_string[0])) { 3177 | return undefined 3178 | } 3179 | 3180 | // if the option string is present in the parser, return the action 3181 | if (arg_string in this._option_string_actions) { 3182 | let action = this._option_string_actions[arg_string] 3183 | return [ action, arg_string, undefined ] 3184 | } 3185 | 3186 | // if it's just a single character, it was meant to be positional 3187 | if (arg_string.length === 1) { 3188 | return undefined 3189 | } 3190 | 3191 | // if the option string before the "=" is present, return the action 3192 | if (arg_string.includes('=')) { 3193 | let [ option_string, explicit_arg ] = _string_split(arg_string, '=', 1) 3194 | if (option_string in this._option_string_actions) { 3195 | let action = this._option_string_actions[option_string] 3196 | return [ action, option_string, explicit_arg ] 3197 | } 3198 | } 3199 | 3200 | // search through all possible prefixes of the option string 3201 | // and all actions in the parser for possible interpretations 3202 | let option_tuples = this._get_option_tuples(arg_string) 3203 | 3204 | // if multiple actions match, the option string was ambiguous 3205 | if (option_tuples.length > 1) { 3206 | let options = option_tuples.map(([ /*action*/, option_string/*, explicit_arg*/ ]) => option_string).join(', ') 3207 | let args = {option: arg_string, matches: options} 3208 | let msg = 'ambiguous option: %(option)s could match %(matches)s' 3209 | this.error(sub(msg, args)) 3210 | 3211 | // if exactly one action matched, this segmentation is good, 3212 | // so return the parsed action 3213 | } else if (option_tuples.length === 1) { 3214 | let [ option_tuple ] = option_tuples 3215 | return option_tuple 3216 | } 3217 | 3218 | // if it was not found as an option, but it looks like a negative 3219 | // number, it was meant to be positional 3220 | // unless there are negative-number-like options 3221 | if (this._negative_number_matcher.test(arg_string)) { 3222 | if (!this._has_negative_number_optionals.length) { 3223 | return undefined 3224 | } 3225 | } 3226 | 3227 | // if it contains a space, it was meant to be a positional 3228 | if (arg_string.includes(' ')) { 3229 | return undefined 3230 | } 3231 | 3232 | // it was meant to be an optional but there is no such option 3233 | // in this parser (though it might be a valid option in a subparser) 3234 | return [ undefined, arg_string, undefined ] 3235 | } 3236 | 3237 | _get_option_tuples(option_string) { 3238 | let result = [] 3239 | 3240 | // option strings starting with two prefix characters are only 3241 | // split at the '=' 3242 | let chars = this.prefix_chars 3243 | if (chars.includes(option_string[0]) && chars.includes(option_string[1])) { 3244 | if (this.allow_abbrev) { 3245 | let option_prefix, explicit_arg 3246 | if (option_string.includes('=')) { 3247 | [ option_prefix, explicit_arg ] = _string_split(option_string, '=', 1) 3248 | } else { 3249 | option_prefix = option_string 3250 | explicit_arg = undefined 3251 | } 3252 | for (let option_string of Object.keys(this._option_string_actions)) { 3253 | if (option_string.startsWith(option_prefix)) { 3254 | let action = this._option_string_actions[option_string] 3255 | let tup = [ action, option_string, explicit_arg ] 3256 | result.push(tup) 3257 | } 3258 | } 3259 | } 3260 | 3261 | // single character options can be concatenated with their arguments 3262 | // but multiple character options always have to have their argument 3263 | // separate 3264 | } else if (chars.includes(option_string[0]) && !chars.includes(option_string[1])) { 3265 | let option_prefix = option_string 3266 | let explicit_arg = undefined 3267 | let short_option_prefix = option_string.slice(0, 2) 3268 | let short_explicit_arg = option_string.slice(2) 3269 | 3270 | for (let option_string of Object.keys(this._option_string_actions)) { 3271 | if (option_string === short_option_prefix) { 3272 | let action = this._option_string_actions[option_string] 3273 | let tup = [ action, option_string, short_explicit_arg ] 3274 | result.push(tup) 3275 | } else if (option_string.startsWith(option_prefix)) { 3276 | let action = this._option_string_actions[option_string] 3277 | let tup = [ action, option_string, explicit_arg ] 3278 | result.push(tup) 3279 | } 3280 | } 3281 | 3282 | // shouldn't ever get here 3283 | } else { 3284 | this.error(sub('unexpected option string: %s', option_string)) 3285 | } 3286 | 3287 | // return the collected option tuples 3288 | return result 3289 | } 3290 | 3291 | _get_nargs_pattern(action) { 3292 | // in all examples below, we have to allow for '--' args 3293 | // which are represented as '-' in the pattern 3294 | let nargs = action.nargs 3295 | let nargs_pattern 3296 | 3297 | // the default (None) is assumed to be a single argument 3298 | if (nargs === undefined) { 3299 | nargs_pattern = '(-*A-*)' 3300 | 3301 | // allow zero or one arguments 3302 | } else if (nargs === OPTIONAL) { 3303 | nargs_pattern = '(-*A?-*)' 3304 | 3305 | // allow zero or more arguments 3306 | } else if (nargs === ZERO_OR_MORE) { 3307 | nargs_pattern = '(-*[A-]*)' 3308 | 3309 | // allow one or more arguments 3310 | } else if (nargs === ONE_OR_MORE) { 3311 | nargs_pattern = '(-*A[A-]*)' 3312 | 3313 | // allow any number of options or arguments 3314 | } else if (nargs === REMAINDER) { 3315 | nargs_pattern = '([-AO]*)' 3316 | 3317 | // allow one argument followed by any number of options or arguments 3318 | } else if (nargs === PARSER) { 3319 | nargs_pattern = '(-*A[-AO]*)' 3320 | 3321 | // suppress action, like nargs=0 3322 | } else if (nargs === SUPPRESS) { 3323 | nargs_pattern = '(-*-*)' 3324 | 3325 | // all others should be integers 3326 | } else { 3327 | nargs_pattern = sub('(-*%s-*)', 'A'.repeat(nargs).split('').join('-*')) 3328 | } 3329 | 3330 | // if this is an optional action, -- is not allowed 3331 | if (action.option_strings.length) { 3332 | nargs_pattern = nargs_pattern.replace(/-\*/g, '') 3333 | nargs_pattern = nargs_pattern.replace(/-/g, '') 3334 | } 3335 | 3336 | // return the pattern 3337 | return nargs_pattern 3338 | } 3339 | 3340 | // ======================== 3341 | // Alt command line argument parsing, allowing free intermix 3342 | // ======================== 3343 | 3344 | parse_intermixed_args(args = undefined, namespace = undefined) { 3345 | let argv 3346 | [ args, argv ] = this.parse_known_intermixed_args(args, namespace) 3347 | if (argv.length) { 3348 | let msg = 'unrecognized arguments: %s' 3349 | this.error(sub(msg, argv.join(' '))) 3350 | } 3351 | return args 3352 | } 3353 | 3354 | parse_known_intermixed_args(args = undefined, namespace = undefined) { 3355 | // returns a namespace and list of extras 3356 | // 3357 | // positional can be freely intermixed with optionals. optionals are 3358 | // first parsed with all positional arguments deactivated. The 'extras' 3359 | // are then parsed. If the parser definition is incompatible with the 3360 | // intermixed assumptions (e.g. use of REMAINDER, subparsers) a 3361 | // TypeError is raised. 3362 | // 3363 | // positionals are 'deactivated' by setting nargs and default to 3364 | // SUPPRESS. This blocks the addition of that positional to the 3365 | // namespace 3366 | 3367 | let extras 3368 | let positionals = this._get_positional_actions() 3369 | let a = positionals.filter(action => [ PARSER, REMAINDER ].includes(action.nargs)) 3370 | if (a.length) { 3371 | throw new TypeError(sub('parse_intermixed_args: positional arg' + 3372 | ' with nargs=%s', a[0].nargs)) 3373 | } 3374 | 3375 | for (let group of this._mutually_exclusive_groups) { 3376 | for (let action of group._group_actions) { 3377 | if (positionals.includes(action)) { 3378 | throw new TypeError('parse_intermixed_args: positional in' + 3379 | ' mutuallyExclusiveGroup') 3380 | } 3381 | } 3382 | } 3383 | 3384 | let save_usage 3385 | try { 3386 | save_usage = this.usage 3387 | let remaining_args 3388 | try { 3389 | if (this.usage === undefined) { 3390 | // capture the full usage for use in error messages 3391 | this.usage = this.format_usage().slice(7) 3392 | } 3393 | for (let action of positionals) { 3394 | // deactivate positionals 3395 | action.save_nargs = action.nargs 3396 | // action.nargs = 0 3397 | action.nargs = SUPPRESS 3398 | action.save_default = action.default 3399 | action.default = SUPPRESS 3400 | } 3401 | [ namespace, remaining_args ] = this.parse_known_args(args, 3402 | namespace) 3403 | for (let action of positionals) { 3404 | // remove the empty positional values from namespace 3405 | let attr = getattr(namespace, action.dest) 3406 | if (Array.isArray(attr) && attr.length === 0) { 3407 | // eslint-disable-next-line no-console 3408 | console.warn(sub('Do not expect %s in %s', action.dest, namespace)) 3409 | delattr(namespace, action.dest) 3410 | } 3411 | } 3412 | } finally { 3413 | // restore nargs and usage before exiting 3414 | for (let action of positionals) { 3415 | action.nargs = action.save_nargs 3416 | action.default = action.save_default 3417 | } 3418 | } 3419 | let optionals = this._get_optional_actions() 3420 | try { 3421 | // parse positionals. optionals aren't normally required, but 3422 | // they could be, so make sure they aren't. 3423 | for (let action of optionals) { 3424 | action.save_required = action.required 3425 | action.required = false 3426 | } 3427 | for (let group of this._mutually_exclusive_groups) { 3428 | group.save_required = group.required 3429 | group.required = false 3430 | } 3431 | [ namespace, extras ] = this.parse_known_args(remaining_args, 3432 | namespace) 3433 | } finally { 3434 | // restore parser values before exiting 3435 | for (let action of optionals) { 3436 | action.required = action.save_required 3437 | } 3438 | for (let group of this._mutually_exclusive_groups) { 3439 | group.required = group.save_required 3440 | } 3441 | } 3442 | } finally { 3443 | this.usage = save_usage 3444 | } 3445 | return [ namespace, extras ] 3446 | } 3447 | 3448 | // ======================== 3449 | // Value conversion methods 3450 | // ======================== 3451 | _get_values(action, arg_strings) { 3452 | // for everything but PARSER, REMAINDER args, strip out first '--' 3453 | if (![PARSER, REMAINDER].includes(action.nargs)) { 3454 | try { 3455 | _array_remove(arg_strings, '--') 3456 | } catch (err) {} 3457 | } 3458 | 3459 | let value 3460 | // optional argument produces a default when not present 3461 | if (!arg_strings.length && action.nargs === OPTIONAL) { 3462 | if (action.option_strings.length) { 3463 | value = action.const 3464 | } else { 3465 | value = action.default 3466 | } 3467 | if (typeof value === 'string') { 3468 | value = this._get_value(action, value) 3469 | this._check_value(action, value) 3470 | } 3471 | 3472 | // when nargs='*' on a positional, if there were no command-line 3473 | // args, use the default if it is anything other than None 3474 | } else if (!arg_strings.length && action.nargs === ZERO_OR_MORE && 3475 | !action.option_strings.length) { 3476 | if (action.default !== undefined) { 3477 | value = action.default 3478 | } else { 3479 | value = arg_strings 3480 | } 3481 | this._check_value(action, value) 3482 | 3483 | // single argument or optional argument produces a single value 3484 | } else if (arg_strings.length === 1 && [undefined, OPTIONAL].includes(action.nargs)) { 3485 | let arg_string = arg_strings[0] 3486 | value = this._get_value(action, arg_string) 3487 | this._check_value(action, value) 3488 | 3489 | // REMAINDER arguments convert all values, checking none 3490 | } else if (action.nargs === REMAINDER) { 3491 | value = arg_strings.map(v => this._get_value(action, v)) 3492 | 3493 | // PARSER arguments convert all values, but check only the first 3494 | } else if (action.nargs === PARSER) { 3495 | value = arg_strings.map(v => this._get_value(action, v)) 3496 | this._check_value(action, value[0]) 3497 | 3498 | // SUPPRESS argument does not put anything in the namespace 3499 | } else if (action.nargs === SUPPRESS) { 3500 | value = SUPPRESS 3501 | 3502 | // all other types of nargs produce a list 3503 | } else { 3504 | value = arg_strings.map(v => this._get_value(action, v)) 3505 | for (let v of value) { 3506 | this._check_value(action, v) 3507 | } 3508 | } 3509 | 3510 | // return the converted value 3511 | return value 3512 | } 3513 | 3514 | _get_value(action, arg_string) { 3515 | let type_func = this._registry_get('type', action.type, action.type) 3516 | if (typeof type_func !== 'function') { 3517 | let msg = '%r is not callable' 3518 | throw new ArgumentError(action, sub(msg, type_func)) 3519 | } 3520 | 3521 | // convert the value to the appropriate type 3522 | let result 3523 | try { 3524 | try { 3525 | result = type_func(arg_string) 3526 | } catch (err) { 3527 | // Dear TC39, why would you ever consider making es6 classes not callable? 3528 | // We had one universal interface, [[Call]], which worked for anything 3529 | // (with familiar this-instanceof guard for classes). Now we have two. 3530 | if (err instanceof TypeError && 3531 | /Class constructor .* cannot be invoked without 'new'/.test(err.message)) { 3532 | // eslint-disable-next-line new-cap 3533 | result = new type_func(arg_string) 3534 | } else { 3535 | throw err 3536 | } 3537 | } 3538 | 3539 | } catch (err) { 3540 | // ArgumentTypeErrors indicate errors 3541 | if (err instanceof ArgumentTypeError) { 3542 | //let name = getattr(action.type, 'name', repr(action.type)) 3543 | let msg = err.message 3544 | throw new ArgumentError(action, msg) 3545 | 3546 | // TypeErrors or ValueErrors also indicate errors 3547 | } else if (err instanceof TypeError) { 3548 | let name = getattr(action.type, 'name', repr(action.type)) 3549 | let args = {type: name, value: arg_string} 3550 | let msg = 'invalid %(type)s value: %(value)r' 3551 | throw new ArgumentError(action, sub(msg, args)) 3552 | } else { 3553 | throw err 3554 | } 3555 | } 3556 | 3557 | // return the converted value 3558 | return result 3559 | } 3560 | 3561 | _check_value(action, value) { 3562 | // converted value must be one of the choices (if specified) 3563 | if (action.choices !== undefined && !_choices_to_array(action.choices).includes(value)) { 3564 | let args = {value, 3565 | choices: _choices_to_array(action.choices).map(repr).join(', ')} 3566 | let msg = 'invalid choice: %(value)r (choose from %(choices)s)' 3567 | throw new ArgumentError(action, sub(msg, args)) 3568 | } 3569 | } 3570 | 3571 | // ======================= 3572 | // Help-formatting methods 3573 | // ======================= 3574 | format_usage() { 3575 | let formatter = this._get_formatter() 3576 | formatter.add_usage(this.usage, this._actions, 3577 | this._mutually_exclusive_groups) 3578 | return formatter.format_help() 3579 | } 3580 | 3581 | format_help() { 3582 | let formatter = this._get_formatter() 3583 | 3584 | // usage 3585 | formatter.add_usage(this.usage, this._actions, 3586 | this._mutually_exclusive_groups) 3587 | 3588 | // description 3589 | formatter.add_text(this.description) 3590 | 3591 | // positionals, optionals and user-defined groups 3592 | for (let action_group of this._action_groups) { 3593 | formatter.start_section(action_group.title) 3594 | formatter.add_text(action_group.description) 3595 | formatter.add_arguments(action_group._group_actions) 3596 | formatter.end_section() 3597 | } 3598 | 3599 | // epilog 3600 | formatter.add_text(this.epilog) 3601 | 3602 | // determine help from format above 3603 | return formatter.format_help() 3604 | } 3605 | 3606 | _get_formatter() { 3607 | // eslint-disable-next-line new-cap 3608 | return new this.formatter_class({ prog: this.prog }) 3609 | } 3610 | 3611 | // ===================== 3612 | // Help-printing methods 3613 | // ===================== 3614 | print_usage(file = undefined) { 3615 | if (file === undefined) file = process.stdout 3616 | this._print_message(this.format_usage(), file) 3617 | } 3618 | 3619 | print_help(file = undefined) { 3620 | if (file === undefined) file = process.stdout 3621 | this._print_message(this.format_help(), file) 3622 | } 3623 | 3624 | _print_message(message, file = undefined) { 3625 | if (message) { 3626 | if (file === undefined) file = process.stderr 3627 | file.write(message) 3628 | } 3629 | } 3630 | 3631 | // =============== 3632 | // Exiting methods 3633 | // =============== 3634 | exit(status = 0, message = undefined) { 3635 | if (message) { 3636 | this._print_message(message, process.stderr) 3637 | } 3638 | process.exit(status) 3639 | } 3640 | 3641 | error(message) { 3642 | /* 3643 | * error(message: string) 3644 | * 3645 | * Prints a usage message incorporating the message to stderr and 3646 | * exits. 3647 | * 3648 | * If you override this in a subclass, it should not return -- it 3649 | * should either exit or raise an exception. 3650 | */ 3651 | 3652 | // LEGACY (v1 compatibility), debug mode 3653 | if (this.debug === true) throw new Error(message) 3654 | // end 3655 | this.print_usage(process.stderr) 3656 | let args = {prog: this.prog, message: message} 3657 | this.exit(2, sub('%(prog)s: error: %(message)s\n', args)) 3658 | } 3659 | })) 3660 | 3661 | 3662 | module.exports = { 3663 | ArgumentParser, 3664 | ArgumentError, 3665 | ArgumentTypeError, 3666 | BooleanOptionalAction, 3667 | FileType, 3668 | HelpFormatter, 3669 | ArgumentDefaultsHelpFormatter, 3670 | RawDescriptionHelpFormatter, 3671 | RawTextHelpFormatter, 3672 | MetavarTypeHelpFormatter, 3673 | Namespace, 3674 | Action, 3675 | ONE_OR_MORE, 3676 | OPTIONAL, 3677 | PARSER, 3678 | REMAINDER, 3679 | SUPPRESS, 3680 | ZERO_OR_MORE 3681 | } 3682 | 3683 | // LEGACY (v1 compatibility), Const alias 3684 | Object.defineProperty(module.exports, 'Const', { 3685 | get() { 3686 | let result = {} 3687 | Object.entries({ ONE_OR_MORE, OPTIONAL, PARSER, REMAINDER, SUPPRESS, ZERO_OR_MORE }).forEach(([ n, v ]) => { 3688 | Object.defineProperty(result, n, { 3689 | get() { 3690 | deprecate(n, sub('use argparse.%s instead of argparse.Const.%s', n, n)) 3691 | return v 3692 | } 3693 | }) 3694 | }) 3695 | Object.entries({ _UNRECOGNIZED_ARGS_ATTR }).forEach(([ n, v ]) => { 3696 | Object.defineProperty(result, n, { 3697 | get() { 3698 | deprecate(n, sub('argparse.Const.%s is an internal symbol and will no longer be available', n)) 3699 | return v 3700 | } 3701 | }) 3702 | }) 3703 | return result 3704 | }, 3705 | enumerable: false 3706 | }) 3707 | // end 3708 | --------------------------------------------------------------------------------