@supernpm2024/labore-repudiandae-dolorem-in
Parse arguments into a tree structure.
Features
@supernpm2024/labore-repudiandae-dolorem-in is meant to be a minimal and less opinionated argument parser with the following goals and features:
- Preserve the structure of the provided arguments.
- No data types other than strings.
- Variadic arguments by default unless limits are configured.
- All arguments are treated as normal parameters unless configured to be an option or command.
- Aliases are not restricted to single characters and can be expanded to multiple options or commands with additional arguments.
- Recognize and split combined aliases (e.g. from
-abaa
to -a
, -ba
, -a
). - Recognize configured assignment (
=
) for options and commands (e.g. --flag=value
, command=value
). - No errors for unrecognized options or commands by default (except for misconfigured aliases and unrecognized aliases from a combined alias).
- Double-dash (
--
) is not treated as anything special but can be configured to be a non-strict subcommand.
Note that @supernpm2024/labore-repudiandae-dolorem-in only parses arguments based on the configuration it has been given. It is still up to the consumers/developers to interpret the parsed arguments and decide how to use these inputs to suit their application's needs.
If you're looking for something oddly specific and want more control when working with arguments, then @supernpm2024/labore-repudiandae-dolorem-in might be for you. Otherwise, you can check out the other great projects for parsing arguments like commander, yargs, minimist, and many more.
Install
npm install @supernpm2024/labore-repudiandae-dolorem-in
Usage
Import the module (ESM only):
import @supernpm2024/labore-repudiandae-dolorem-in from '@supernpm2024/labore-repudiandae-dolorem-in';
The @supernpm2024/labore-repudiandae-dolorem-in(args, options)
function accepts an array of strings and an options object. It returns a Node
object (root node).
const node = @supernpm2024/labore-repudiandae-dolorem-in(['--hello', 'world'], { min: 1 });
console.log(node.id, node.args);
null [ '--hello', 'world' ]
[!TIP]
See src/core/core.types.ts
for Node
, NodeData
, and Options
types.
The spec(options)
function can also be used to parse arguments while also being an options builder. See the Spec API section for more details.
[!IMPORTANT]
While the sections ahead use the core @supernpm2024/labore-repudiandae-dolorem-in()
function to show examples explaining the configuration setup, it is recommended to use the Spec API instead for the ease of building your parsing spec as it grows with more options and commands.
Also, for the sake of brevity, not all features/properties are included in this document and it is advised to view the referenced types to learn more.
Options and Commands
Configure options and commands by setting the args
object or function option.
While they may be configured similarly, options start with a hyphen (e.g. -a
, --add
) while commands do not (e.g. run
, start
).
When setting the args
object, the properties are used to match the arguments while the values are also options objects similar to the options from @supernpm2024/labore-repudiandae-dolorem-in(args, options)
.
Options and commands will capture arguments and stop when another option or command is matched or the configured limit is reached.
const node = @supernpm2024/labore-repudiandae-dolorem-in(['--add', 'foo', '--add', 'run', 'baz'], {
args: {
'--add': {},
run: { initial: ['bar'] }
}
});
for (const child of node.children) {
console.log(child.id, child.args);
}
--add [ 'foo' ]
--add []
run [ 'bar', 'baz' ]
You can also pass a function to the args
option. It has two parameters: the arg
string (comes from the args
array or from the split aliases of a combined alias) and the NodeData
object. It should return an options object, null
, or undefined
.
const args = ['--save', 'foo', '--create', '--add', 'bar'];
const node = @supernpm2024/labore-repudiandae-dolorem-in(args, {
args(arg, data) {
return arg.startsWith('--') ? {} : null;
}
});
for (const child of node.children) {
console.log(child.id, child.args);
}
--save [ 'foo' ]
--create []
--add [ 'bar' ]
[!WARNING]
Be aware that there may be cases where __proto__
and other hidden object properties are used as arguments. @supernpm2024/labore-repudiandae-dolorem-in does not block these possibly unsafe arguments, but it has some checks in place such as:
- The options object should be a valid object that does not equal the default object prototype (
options !== Object.prototype
). - Both options and
args
objects are reassigned to another object with a null
prototype before being used internally.
This may not apply to the args
function where you might use a predefined object that maps to options objects. Make sure to check for __proto__
and other related properties. Remove it by setting __proto__: null
and such, or use a Map
object instead.
const optionsMap = {
'--add': {},
'--save': {},
__proto__: {
validate(data) {
console.log('evil validate!', data.args);
return true;
}
}
};
Object.setPrototypeOf(optionsMap, null);
@supernpm2024/labore-repudiandae-dolorem-in(['__proto__', 'constructor'], { args: arg => optionsMap[arg] });
Strict Mode
Set the strict
boolean option to throw an error for unrecognized options that are not provided in the args
option.
By default, this is set to false
for the root node. If not explicitly provided, this option is inherited from the parent option or command.
try {
@supernpm2024/labore-repudiandae-dolorem-in(['run', 'build', '--if-preset'], {
strict: true,
args: {
run: { args: { '--if-present': {} } }
}
});
} catch (error) {
console.error(error + '');
}
ArgsTreeError: Command 'run' does not recognize the option: --if-preset
Assignment
Using =
, options or commands will treat the assigned value as their own argument regardless of any matches.
By default, this behavior is enabled for options but not for commands. To change this behavior, you can set the assign
boolean option.
const node = @supernpm2024/labore-repudiandae-dolorem-in(['--add=foo', 'copy=foo', '--save=bar', 'move=bar'], {
args: {
'--add': {},
'--save': { assign: false },
copy: {},
move: { assign: true }
}
});
for (const child of node.children) {
console.log(child.id, child.args);
}
--add [ 'foo', 'copy=foo', '--save=bar' ]
move [ 'bar' ]
Suboptions and Subcommands
Matching a suboption or subcommand means that its parent option or command will stop receiving arguments.
const node = @supernpm2024/labore-repudiandae-dolorem-in(['--add', 'run', '--add', 'build'], {
args: {
'--add': {},
run: {
args: {
}
}
}
});
const add = node.children[0];
const run = node.children[1];
console.log('root', node.args);
console.log(add.id, add.args);
console.log(run.id, run.args, run.parent === node);
root []
--add []
run [ '--add', 'build' ] true
In this example:
- The parent command of the
run
subcommand is the root node. - Once the
run
subcommand was matched, its options are used for parsing the proceeding arguments. - The second
--add
argument was not recognized as an option by run
(not in its args
option) and is treated as a normal argument.
Limits
Specify the required minimum and maximum number of arguments per option or command.
-
min
(type: number | null
) - Required number of arguments to read before the next parsed option or command.
An error is thrown if the option or command does not satisfy this condition.
-
max
(type: number | null
) - Maximum number of arguments to read before the next parsed option or command.
Arguments over the maximum limit are saved as arguments for the parent option or command instead.
Direct assignment will always read the assigned value as an argument for the option or command.
An error is thrown if the option or command does not satisfy this condition.
-
maxRead
(type: number | null
) - Similar to the max
option but does not throw an error.
If not provided, the value for max
is used instead.
This takes priority over the max
option when reading arguments, but the max
option is still used for validating the maximum number of arguments.
const args =
'--min foo1 bar1 ' +
'--max foo2 bar2 ' +
'--max-read foo3 bar3 ' +
'--max-read=foo4 bar4';
const node = @supernpm2024/labore-repudiandae-dolorem-in(args.split(' '), {
args: {
'--min': { min: 1 },
'--max': { max: 1 },
'--max-read': { maxRead: 0 }
}
});
console.log('root', node.args);
for (const child of node.children) {
console.log(child.id, child.args);
}
root [ 'bar2', 'foo3', 'bar3', 'bar4' ]
--min [ 'foo1', 'bar1' ]
--max [ 'foo2' ]
--max-read []
--max-read [ 'foo4' ]
Aliases
Configure aliases by setting the alias
object option.
It should include the list of aliases mapped to options or commands specified in the args
object option. An error is thrown if the option or command is not valid.
Only aliases that start with one hyphen (e.g. -a
, -bc
) can be combined together into one alias shorthand (e.g. -abc
).
Note that the assign
option applies to aliases of the options or commands and that limits also apply to their arguments.
const node = @supernpm2024/labore-repudiandae-dolorem-in(['-abacada=0', 'a=1', 'b=2'], {
alias: {
'-a': '--add',
'-ba': ['--add', 'bar', 'baz'],
'-ca': [['--add'], ['--save']],
'-da': [
['--add', 'multi', 'bar', 'baz'],
['--save', 'multi', 'foo', 'baz']
],
a: '--create',
b: 'start'
},
args: { '--add': {}, '--save': {}, '--create': {}, start: {} }
});
for (const child of node.children) {
console.log(child.id, child.alias, child.args);
}
--add -a []
--add -ba [ 'bar', 'baz' ]
--add -ca []
--save -ca []
--add -da [ 'multi', 'bar', 'baz' ]
--save -da [ 'multi', 'foo', 'baz', '0' ]
--create a [ '1', 'b=2' ]
Validation
Set the validate
function option to validate the arguments after they are saved for the option or command. It has a NodeData
object parameter and it should either return a boolean or throw an error manually. A validate error is thrown when false
is returned.
Note that a call to validate
means that the Node
has already parsed all of its arguments and that it has passed all validation checks beforehand. It is also safe to directly mutate the data.args
array in validate
, but you can always choose to transform and validate arguments after parsing.
const node = @supernpm2024/labore-repudiandae-dolorem-in(['--list', 'a,b,c'], {
args: {
'--list': {
min: 1,
max: 1,
validate(data) {
const args = data.args[0].split(',');
data.args.splice(0, data.args.length, ...args);
return true;
}
}
}
});
const list = node.children[0];
console.log(list.id, list.args);
--list [ 'a', 'b', 'c' ]
ArgsTreeError
For errors related to parsing and misconfiguration, an ArgsTreeError
is thrown. You can import this class to reference in catch blocks.
import @supernpm2024/labore-repudiandae-dolorem-in, { ArgsTreeError } from '@supernpm2024/labore-repudiandae-dolorem-in';
try {
@supernpm2024/labore-repudiandae-dolorem-in(['--add', 'foo'], {
args: { '--add': { min: 2 } }
});
} catch (error) {
if (error instanceof ArgsTreeError) {
console.error(JSON.stringify(error, undefined, 2));
}
}
{
"name": "ArgsTreeError",
"cause": "invalid-range",
"message": "Option '--add' expected at least 2 arguments, but got 1.",
"raw": "--add",
"alias": null,
"args": [
"foo"
],
"options": {
"min": 2
}
}
[!TIP]
See src/core/error.ts
for more details.
split
The split
function is used to split the combined value string based on the provided matches. It returns an object with the split values and the remaining values that were not split. Note that longer match strings take priority and are split first.
import { split } from '@supernpm2024/labore-repudiandae-dolorem-in';
console.log(split('foobar', ['foo', 'bar']));
console.log(split('foobarfoobaz', ['fo', 'foo', 'oba']));
console.log(split('foobarfoobaz', ['fo', 'oba', 'foo']));
{ values: [ 'foo', 'bar' ], remainder: [] }
{ values: [ 'foo', 'foo' ], remainder: [ 'bar', 'baz' ] }
{ values: [ 'fo', 'oba', 'fo', 'oba' ], remainder: [ 'r', 'z' ] }
stringify
The stringify
function returns a stringified Node
object. It also accepts an options object where you can specify what to show or hide from the generated tree string.
import @supernpm2024/labore-repudiandae-dolorem-in, { stringify } from '@supernpm2024/labore-repudiandae-dolorem-in';
const node = @supernpm2024/labore-repudiandae-dolorem-in(['start', 'foo', '-a', 'bar', '--save', 'baz'], {
args: {
start: {
alias: { '-a': '--add', '-s': '--save' },
args: { '--add': {}, '--save': {} }
}
}
});
const tree = stringify(node, {
args: true,
ancestors: true,
descendants: true
});
console.log(tree);
null (depth: 0)
├─┬ start (depth: 1)
│ ├─┬ :args (total: 1)
│ │ └── foo
│ ├─┬ --add (depth: 2, alias: -a)
│ │ ├─┬ :args (total: 1)
│ │ │ └── bar
│ │ └─┬ :ancestors (total: 2)
│ │ ├── null (depth: 0)
│ │ └── start (depth: 1)
│ ├─┬ --save (depth: 2)
│ │ ├─┬ :args (total: 1)
│ │ │ └── baz
│ │ └─┬ :ancestors (total: 2)
│ │ ├── null (depth: 0)
│ │ └── start (depth: 1)
│ ├─┬ :ancestors (total: 1)
│ │ └── null (depth: 0)
│ └─┬ :descendants (total: 2)
│ ├── --add (depth: 2, alias: -a)
│ └── --save (depth: 2)
└─┬ :descendants (total: 3)
├── start (depth: 1)
├── --add (depth: 2, alias: -a)
└── --save (depth: 2)
[!TIP]
See src/core/stringify.ts
for more details.
Spec API
The spec(options)
function returns a Spec
object (options builder). Calling parse(args)
uses the @supernpm2024/labore-repudiandae-dolorem-in
core function to parse the arguments and it also returns a Node
object (root node). To get the options object, use the options()
method.
import { spec } from '@supernpm2024/labore-repudiandae-dolorem-in';
const cmd = spec({ min: 1 });
cmd.option('--add').alias('-a').alias('--no-add', '0');
cmd.option('--save').alias('-s').alias('--no-save', ['0']);
const options = cmd.options();
console.log(options);
const node = cmd.parse(['foo', '--add', 'bar', '-s', 'baz']);
console.log('root', node.args);
for (const child of node.children) {
console.log(child.id, child.args);
}
[Object: null prototype] {
min: 1,
args: [Object: null prototype] {
'--add': [Object: null prototype] {},
'--save': [Object: null prototype] {}
},
alias: [Object: null prototype] {
'-a': '--add',
'--no-add': [ '--add', '0' ],
'-s': '--save',
'--no-save': [ '--save', '0' ]
}
}
root [ 'foo' ]
--add [ 'bar' ]
--save [ 'baz' ]
The options object passed to spec(options)
is similar to the options from @supernpm2024/labore-repudiandae-dolorem-in(args, options)
but the alias
and args
options are omitted.
[!TIP]
See src/core/spec.types.ts
for more details.
Spec Options and Commands
- Use the
option(arg, options)
and command(arg, options)
methods to add options and commands to the args
object option respectively. - Use the
args()
method to add an empty object to the args
option. - Use the
args(handler)
method to use a function for the args
option and use the handler
callback as a fallback.
const cmd = spec();
cmd.option('--add');
cmd.command('start');
cmd.args();
cmd.args((arg, data) => {
return arg === '--save' ? {} : null;
});
Spec Aliases
- Use the
aliases
method to assign aliases not bound to the current option or command. - Use the
alias
method to assign aliases to the current option or command. An error is thrown if the current option or command does not exist.
const cmd = spec();
cmd.aliases({ '-A': [['--add'], ['--save']] });
cmd.option('--add', { maxRead: 0 }).alias('-a').alias('--no-add', '0');
cmd.option('--save', { maxRead: 0 }).alias(['-s', '-sa'], ['1', '2', '3']);
Spec Suboptions and Subcommands
Use the spec
method that accepts a setup callback. This callback contains a Spec
object parameter (subspec) to modify the options of the current option or command. This can be called multiple times for the same option or command.
function commonSpec(spec) {
spec.option('--help', { maxRead: 0 }).alias('-h');
spec.command('--', { strict: false });
}
const cmd = spec({ strict: true })
.command('run-script')
.alias(['run', 'rum', 'urn'])
.spec(run => run.option('--workspace', { min: 1, max: 1 }).alias('-w'))
.spec(commonSpec);
commonSpec(cmd);
console.log('%o', cmd.options());
[Object: null prototype] {
strict: true,
args: [Object: null prototype] {
'run-script': [Object: null prototype] {
args: [Object: null prototype] {
'--workspace': [Object: null prototype] { min: 1, max: 1 },
'--help': [Object: null prototype] { maxRead: 0 },
'--': [Object: null prototype] {
strict: false,
args: [Object: null prototype] {}
}
},
alias: [Object: null prototype] { '-w': '--workspace', '-h': '--help' }
},
'--help': [Object: null prototype] { maxRead: 0 },
'--': [Object: null prototype] {
strict: false,
args: [Object: null prototype] {}
}
},
alias: [Object: null prototype] {
run: 'run-script',
rum: 'run-script',
urn: 'run-script',
'-h': '--help'
}
}
License
Licensed under the MIT License.