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
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;
argv = argMate(['--foo', 'bar', '-i']);
argv = argMate(['--foo=', 'bar', '-i=123']);
argv = argMate(['--foo', 'bar2'], { foo: 'bar', i: 42 });
argv = argMate(process.argv.slice(2));
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);
const params = {
foo: 10,
bar: false
};
const config = {
allowUnknown: false
};
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
},
help: {
alias: ['h']
}
};
const config = {
allowUnknown: false,
error: msg => {
console.error('Error:', msg);
process.exit(1);
}
};
const argv = argMate(args, params, config);
if (argv.help) {
console.log(argInfo());
process.exit(0);
}
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);
const params = {
foo: 10,
bar: false
};
const config = {
allowUnknown: false
};
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
},
help: {
alias: ['h']
}
};
const config = {
allowUnknown: false,
error: msg => {
console.error('Error:', msg);
process.exit(1);
}
};
const argv = argMate(args, params, config);
if (argv.help) {
console.log(argInfo());
process.exit(0);
}
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 = {
foo: {
type: 'string',
default: 'val',
mandatory: true,
alias: [],
conflict: [],
valid: () => {}|[],
transform:
describe: 'Description here',
},
};
Config
const config = {
error: msg => {throw msg},
panic: msg => {throw msg},
allowUnknown: true,
allowNegatingFlags: true,
allowKeyNumValues: true
allowAssign: true
allowBoolString: true
strict: false
autoCamelKebabCase: true
outputAlias: false
outputInflate:false
intro: 'Intro Text',
outro: 'Outro 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',
outro: 'See you later!',
}
);
console.log(
argInfo({
width: 100,
format: 'cli',
voidIntro: false,
voidOutro: 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.
License