args-tokens
Version:
parseArgs tokens compatibility and more high-performance parser
199 lines (198 loc) • 5.01 kB
JavaScript
//#region src/parser.ts
const HYPHEN_CHAR = "-";
const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0);
const EQUAL_CHAR = "=";
const EQUAL_CODE = EQUAL_CHAR.codePointAt(0);
const TERMINATOR = "--";
const SHORT_OPTION_PREFIX = HYPHEN_CHAR;
const LONG_OPTION_PREFIX = "--";
/**
* Parse command line arguments.
*
* @param args - command line arguments
* @param options - parse options, about details see {@link ParserOptions}
* @returns Argument tokens.
*
* @example
* ```js
* import { parseArgs } from 'args-tokens' // for Node.js and Bun
* // import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno
*
* const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz'])
* // do something with using tokens
* // ...
* console.log('tokens:', tokens)
* ```
*/
function parseArgs(args, options = {}) {
const { allowCompatible = false } = options;
const tokens = [];
const remainings = [...args];
let index = -1;
let groupCount = 0;
let hasShortValueSeparator = false;
while (remainings.length > 0) {
const arg = remainings.shift();
if (arg == void 0) break;
const nextArg = remainings[0];
if (groupCount > 0) groupCount--;
else index++;
if (arg === TERMINATOR) {
tokens.push({
kind: "option-terminator",
index
});
const mapped = remainings.map((arg$1) => {
return {
kind: "positional",
index: ++index,
value: arg$1
};
});
tokens.push(...mapped);
break;
}
if (isShortOption(arg)) {
const shortOption = arg.charAt(1);
let value;
let inlineValue;
if (groupCount) {
tokens.push({
kind: "option",
name: shortOption,
rawName: arg,
index,
value,
inlineValue
});
if (groupCount === 1 && hasOptionValue(nextArg)) {
value = remainings.shift();
if (hasShortValueSeparator) {
inlineValue = true;
hasShortValueSeparator = false;
}
tokens.push({
kind: "option",
index,
value,
inlineValue
});
}
} else tokens.push({
kind: "option",
name: shortOption,
rawName: arg,
index,
value,
inlineValue
});
if (value != null) ++index;
continue;
}
if (isShortOptionGroup(arg)) {
const expanded = [];
let shortValue = "";
for (let i = 1; i < arg.length; i++) {
const shortableOption = arg.charAt(i);
if (hasShortValueSeparator) shortValue += shortableOption;
else if (!allowCompatible && shortableOption.codePointAt(0) === EQUAL_CODE) hasShortValueSeparator = true;
else expanded.push(`${SHORT_OPTION_PREFIX}${shortableOption}`);
}
if (shortValue) expanded.push(shortValue);
remainings.unshift(...expanded);
groupCount = expanded.length;
continue;
}
if (isLongOption(arg)) {
const longOption = arg.slice(2);
tokens.push({
kind: "option",
name: longOption,
rawName: arg,
index,
value: void 0,
inlineValue: void 0
});
continue;
}
if (isLongOptionAndValue(arg)) {
const equalIndex = arg.indexOf(EQUAL_CHAR);
const longOption = arg.slice(2, equalIndex);
const value = arg.slice(equalIndex + 1);
tokens.push({
kind: "option",
name: longOption,
rawName: `${LONG_OPTION_PREFIX}${longOption}`,
index,
value,
inlineValue: true
});
continue;
}
tokens.push({
kind: "positional",
index,
value: arg
});
}
return tokens;
}
/**
* Check if `arg` is a short option (e.g. `-f`).
*
* @param arg - An argument to check
* @returns Whether `arg` is a short option.
*/
function isShortOption(arg) {
return arg.length === 2 && arg.codePointAt(0) === HYPHEN_CODE && arg.codePointAt(1) !== HYPHEN_CODE;
}
/**
* Check if `arg` is a short option group (e.g. `-abc`).
*
* @param arg - An argument to check
* @returns Whether `arg` is a short option group.
*/
function isShortOptionGroup(arg) {
if (arg.length <= 2) return false;
if (arg.codePointAt(0) !== HYPHEN_CODE) return false;
if (arg.codePointAt(1) === HYPHEN_CODE) return false;
return true;
}
/**
* Check if `arg` is a long option (e.g. `--foo`).
*
* @param arg - An argument to check
* @returns Whether `arg` is a long option.
*/
function isLongOption(arg) {
return hasLongOptionPrefix(arg) && !arg.includes(EQUAL_CHAR, 3);
}
/**
* Check if `arg` is a long option with value (e.g. `--foo=bar`).
*
* @param arg - An argument to check
* @returns Whether `arg` is a long option.
*/
function isLongOptionAndValue(arg) {
return hasLongOptionPrefix(arg) && arg.includes(EQUAL_CHAR, 3);
}
/**
* Check if `arg` is a long option prefix (e.g. `--`).
*
* @param arg - An argument to check
* @returns Whether `arg` is a long option prefix.
*/
function hasLongOptionPrefix(arg) {
return arg.length > 2 && ~arg.indexOf(LONG_OPTION_PREFIX);
}
/**
* Check if a `value` is an option value.
*
* @param value - A value to check
* @returns Whether a `value` is an option value.
*/
function hasOptionValue(value) {
return !(value == null) && value.codePointAt(0) !== HYPHEN_CODE;
}
//#endregion
export { hasLongOptionPrefix, isShortOption, parseArgs };