argmate
v0.8.2
Published
Fast CLI parameter parsing. Zero dependencies. Good DX. Pre-compile for turbo speed.
Downloads
48
Maintainers
Readme
ArgMate
Your go-to mate for CLI parameter parsing. Friendly, faster than a cut snake, and with added sprinkles of convenience to make your development experience a breeze. Only 6KB and zero dependencies - cheers!
While developing tools like AlaSQL and RexReplace, I've often been torn between two types of CLI parsers. On one hand, there are feature-rich options like yargs and commander. Despite their heavy startup time, these parsers provide useful features like easy defaults, smooth validation, and well-structured CLI help text output. On the other hand, simpler alternatives like nopt and mri excel in performance but lack in development experience. After uncovering yet another performance hit from using a heavyweight parser, I decided to solve this issue once and for all.
Benchmark:
argMate 9,089,813 ops/sec ±2.15% (98 runs sampled) 1x
nopt 2,070,397 ops/sec ±1.21% (94 runs sampled) 4x
mri 1,832,768 ops/sec ±0.13% (99 runs sampled) 5x
minimist 706,265 ops/sec ±1.05% (94 runs sampled) 13x
yargs-parser 67,417 ops/sec ±0.39% (97 runs sampled) 135x
Meet ArgMate, a CLI parameter parser that's not just fast - it's 4-5 times faster than other parsers focused on speed, while still being feature-rich. But how?!? A computer processes instructions at a set pace. To get results faster, the only option is to do fewer things. By minimising how many times variables are touched and keeping those operations close together, the implementation enables efficient caching of data, resulting in fewer CPU cycles to get stuff done.
Installation
yarn add argmate
# or
npm install argmate
Usage
argMate(arguments, [parameterDetails [, config ]]);
Examples
Getting started
ArgMate follows traditional CLI notations - similar to yargs and mri. Here are some simple examples:
import argMate from 'argmate';
let argv;
// By default, parameters are treated as boolean flags
// Non-parameters are stored in the `_` property of the output
argv = argMate(['--foo', 'bar', '-i']);
// {_: ['bar'], foo: true, i: true}
// Use the `=` notation for assignment, with or without seperation to the value
// Type is inferred from the value (string or number)
argv = argMate(['--foo=', 'bar', '-i=123']);
// {_: [], foo: 'bar', i: 123}
// Setting a default value makes the parser treat it as a parameter that must be assigned
// The type is guessed based on the default value
argv = argMate(['--foo', 'bar2'], { foo: 'bar', i: 42 });
// {_: [], foo: 'bar2', i: 42}
// Example running params from CLI `node index.js --foo=bar -X .md`
argv = argMate(process.argv.slice(2));
// { _: ['.md'], foo: "bar", X: true }
Default values and limiting input to known parameters
You can provide default values and enforce that no unknown parameters are allowed:
import argMate from 'argmate';
const args = process.argv.slice(2);
// Define parameter types and default values
const params = {
foo: 10, // --foo is expected to be an integer, default: 10
bar: false // --bar is expected to be a boolean, default: false
};
const config = {
allowUnknown: false // Only allow specified parameters (--foo and --bar)
};
const argv = argMate(args, params, config);
Same example but a bit shorter
import argMate from 'argmate';
const argv = argMate(process.argv.slice(2),
{
foo: 10,
bar: false
}, {
allowUnknown: false
});
Real world example
Here's a more comprehensive example demonstrating additional features:
import argMate, { argInfo } from 'argmate';
const args = process.argv.slice(2);
const params = {
start: {
default: 0,
alias: ['s']
},
steps: {
type: 'number',
mandatory: true,
alias: ['l', 'loops'],
valid: v => v > 0 // Validate the input
},
help: {
alias: ['h']
}
};
const config = {
allowUnknown: false,
error: msg => {
console.error('Error:', msg);
process.exit(1);
}
};
const argv = argMate(args, params, config);
// Display help and exit if the help flag is set
if (argv.help) {
console.log(argInfo());
process.exit(0);
}
// Use the parsed arguments
for (let i = argv.start; i < argv.start + argv.steps; i++) {
console.log(i);
}
Enforcing parameter types and limiting allowed values
You can provide default values and enforce that no other parameters are allowed:
import argMate from 'argmate';
const args = process.argv.slice(2);
// Define parameter types and default values
const params = {
foo: 10, // --foo is expected to be an integer, default: 10
bar: false // --bar is expected to be a boolean, default: false
};
const config = {
allowUnknown: false // Only allow specified parameters (--foo and --bar)
};
const argv = argMate(args, params, config);
Same example but a bit shorter
import argMate from 'argmate';
const argv = ArgMate(process.argv.slice(2),
{
foo: 10,
bar: false
}, {
allowUnknown: false
});
Real world example
Here's a more comprehensive example demonstrating additional features:
import argMate, { argInfo } from 'argmate';
const args = process.argv.slice(2);
const params = {
start: {
default: 0,
alias: ['s']
},
steps: {
type: 'number',
mandatory: true,
alias: ['l', 'loops'],
valid: v => v > 0 // Validate the input
},
help: {
alias: ['h']
}
};
const config = {
allowUnknown: false,
error: msg => {
console.error('Error:', msg);
process.exit(1);
}
};
const argv = argMate(args, params, config);
// Display help and exit if the help flag is set
if (argv.help) {
console.log(argInfo());
process.exit(0);
}
// Use the parsed arguments
for (let i = argv.start; i < argv.start + argv.steps; i++) {
console.log(i);
}
Configuration
Params
the second parameter for argMate is a a configuration object defining the parameters you expect and their propeties
const params = {
// The object returned from argMate will only have propety names provided in this object (foo in this example) But see outputAlias config below
foo: {
type: 'string', // boolean | string | number | float | int | hex | array | string[] | number[] | float[] | int[] | hex[]. Optional. Defaults to boolean.
default: 'val', // The default value for the parameter. If the type is not specified, the type will be determined from this field. Optional.
mandatory: true, // Calls config.error if the value is not provided. No effect if used in combination with "default".
alias: [], // Other values to be treated as this parameter. Also accepts a string with a single value.
// If you camelCase the property name, it will treat kebab-case of the word as an alias (so fooBar will automaticly have foo-bar as alias). Can also be a comma seperated string
conflict: [], // Other keys to be treated as conflicting. Also accepts a single string. Can also be a comma seperated string.
valid: () => {}|[], // Function to check if the value is valid (will call config.error if not valid). Can also be an array of valid values (case sensitive). If you want case insensitive make a function with a regex valid:v=>/foo|bar/i.test(v) will accept both Foo and BAR.
transform: // function that will transform the value. Example: trim values by using transform:v=>v.trim();
describe: 'Description here', // A description of the parameter. Will be used for the help text (see below).
},
};
Config
// THe default values of all possible porperties of the config object
const config = {
error: msg => {throw msg}, // This function will be called when there is a problem with input data (foreample if you try to assign a value to a parameter you have defined as boolean). Defaults to throwing the error messages.
panic: msg => {throw msg}, // This function will be called when there is a problem with the configuration of the parameters. YOu should only encounter these during development. Defaults to throwing the error messages.
allowUnknown: true, // Allows you to provie parameters not defined in the config objecet
allowNegatingFlags: true, // Will let you prepend boolean parameters with "no-" provide the value as false. If so, --no-foo will result in {'_':[], 'foo': false}.
allowKeyNumValues: true // Allows you to use ultra short notations like '-r255' to set -r = 255
allowAssign: true // Allow the use of = after a parameter to indicate that the next value should to be assigned as a value. Works both for the value as part of the same parameter (-p=2) or the value in the next argument (-p= 2)
allowBoolString: true // Let you assign boolean parameters from strings like true|yes|on|false|no|off
strict: false // Will set all allow* propeties to false. Individual paramters can overwrite this by also being provided.
autoCamelKebabCase: true // Let you treat input like 'foo-bar' as 'fooBar'
outputAlias: false // If set to true the returned data object will contain one property per parameter plus one for each alias. (Normally --foo with -f as alias will only come as {foo:...}. If this option is set to true it will output {foo:..., f: ...})
outputInflate:false // Will expand keys with dots in them into nested objects (--a.b=22 will result in {a:{b:22}})
intro: 'Intro Text', // Text that goes above the information about each parameter in the help text.
outro: 'Outro Text', // Text that goes below the information about each parameter in the help text.
};
Help Text
You can call argInfo()
after invoking argMate()
to get a CLI-friendly description.
import argMate, {argInfo} from 'argmate';
const argv = argMate(
process.argv.slice(2),
{
foo: {type: 'string'},
foo2: {type: 'string'},
},
{
intro: 'Introduction here', // Text to add above the information about each parameter in the help text.
outro: 'See you later!', // Text to add below the information about each parameter in the help text.
}
);
console.log(
argInfo({
width: 100, // Max character limit of the width of the output.
format: 'cli', // cli | markdown. Default CLI.
voidIntro: false, // Avoid including the intro. Default false.
voidOutro: false, // Avoid including the outro. Default false .
})
);
Notes
Demonstrate how to use macros to pregenerate engineConfig to make things even faster. manual or via https://bun.sh/docs/bundler/macros - https://bun.sh/docs/bundler/macros#export-condition-macro
If you provide array kind of types (like string[]) you can trust the value is alwas an array. If no values provided the array is emptly.
If you dont specify, you get some help, but not consistency. If you specify you know exactly what you get.
Defaults to consider unknown params as flags. IF you want unknown things to be assigned you add a = behind the flag.
- undefined parameters will default to being a boolean. If you want to assign values you need to A) define a type (or a default value) in the config obj, or B) add "=" to the parameter in the inputs
If you provide the same alias to two parameters, the alias will stay with the first parameter you define.
for defined params you need to provide int, number or float as type for it to be a number in the resulting data object expect( argMate(['--host', 'localhost', '--port', '555'], { host: '', port: 0, }) ).toEqual({ host: 'localhost', port: 555, _: [], });
but if you have not defined the param and provide is as assigned then numbers will be identified and provided as value
expect(argMate(['--host=', 'localhost', '--port=', '555'], {})).toEqual({ host: 'localhost', port: 555, _: [], }); expect(argMate(['--host=localhost', '--port=55.5'], {})).toEqual({ host: 'localhost', port: 55.5, _: [], });
lite quirks
- Lite will not convert your 0x prepended hexvalues to int
ideas
- ? Flag to outoconvert _ values to int when convertable? (like deno)
- We do not support autoconverting magic strings like "true" and "false"
- maybe we should have an option to convert magic strings...
- ? input type json thet is parsed and added as data?
- ? Commaseperated list to array?
Please note that argMate is an OPEN open source software project. This means that individuals making significant and valuable contributions are given commit access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.