Added opts.handleBackslashesForSpecialFlagChars and fixed/clarified the code for backslashes & clarified opts.lowerCaseInputValues & opts.lowerCaseFlagValues descriptions

This commit is contained in:
Oxtaly 2025-05-11 21:14:20 +02:00
parent 49d74d9de5
commit 815bb52510
2 changed files with 76 additions and 26 deletions

View File

@ -120,6 +120,7 @@ function validateAndFillDefaults(opts) {
validateOrDefaultIfUnset(opts, 'lowerCaseFlags', 'boolean', true); validateOrDefaultIfUnset(opts, 'lowerCaseFlags', 'boolean', true);
validateOrDefaultIfUnset(opts, 'resolveFlagValueTypes', 'boolean', true); validateOrDefaultIfUnset(opts, 'resolveFlagValueTypes', 'boolean', true);
validateOrDefaultIfUnset(opts, 'allowNullDefaultFlagValues', 'boolean', true); validateOrDefaultIfUnset(opts, 'allowNullDefaultFlagValues', 'boolean', true);
validateOrDefaultIfUnset(opts, 'handleBackslashesForSpecialFlagChars', 'boolean', true);
validateOrDefaultIfUnset(opts, 'automaticBooleanFlagNegation', 'object', {}); validateOrDefaultIfUnset(opts, 'automaticBooleanFlagNegation', 'object', {});
@ -525,8 +526,9 @@ function runFlagValueTypeResolving(opts, flagVal, flagConfig) {
/** /**
* @param {string} arg * @param {string} arg
* @param {DefaultParserOptsType} opts
*/ */
function argMetaCreator(arg) { function argMetaCreator(arg, opts) {
const argsStr = new StringConsumer(arg); const argsStr = new StringConsumer(arg);
/** return meta flags */ /** return meta flags */
@ -560,10 +562,23 @@ function argMetaCreator(arg) {
readingMinusSignCount = false; readingMinusSignCount = false;
/** Flag has an assigned value in the arg*/ /** Flag has an assigned value in the arg*/
if(char === "=" && isFlag && !isBackslashed && !hasPotentialFlagAssignedValue) { if(char === "=" && isFlag) {
hasPotentialFlagAssignedValue = true; if(!isBackslashed) {
unescapedEqualSignPos = argsStr.getPointer()-1; if(!hasPotentialFlagAssignedValue) {
continue; hasPotentialFlagAssignedValue = true;
unescapedEqualSignPos = argsStr.getPointer()-1;
continue;
}
} else {
if(hasFlagAssignedValue) {
/** Consume the backslash */
flagAssignedValue = flagAssignedValue.slice(0, -1)
} else {
/** Consume the backslash */
flagKey = flagKey.slice(0, -1)
}
isBackslashed = false;
}
} }
/** Making sure there is indeed something after the initial - */ /** Making sure there is indeed something after the initial - */
@ -589,16 +604,27 @@ function argMetaCreator(arg) {
} }
} }
// Idk how to feel about the partial backslashes handling here, most other flag parsers don't handle it but it seems mostly logical to me // Weird and gross handling of backslashes, but explicit, 'defined' (in ./types.d.ts -> ParserOpts.handleBackslashesForSpecialFlagChars) and toggleable
if(char === '\\' && !isBackslashed) if(char === '\\') {
isBackslashed = true; if(opts.handleBackslashesForSpecialFlagChars) {
if(!isBackslashed) {
if(isFlag && argsStr.hasNext() && argsStr.peek() === '-') {
/** Consume the backslash */
flagKey = flagKey.slice(0, -1)
}
else
isBackslashed = true;
}
}
}
else if(isBackslashed) else if(isBackslashed)
isBackslashed = false; isBackslashed = false;
} }
return { isFlag, minusSignCount, flagKey, hasFlagAssignedValue, flagAssignedValue, unescapedEqualSignPos }; return { isFlag, minusSignCount, flagKey, hasFlagAssignedValue, flagAssignedValue, unescapedEqualSignPos };
} }
// TODO: Integrate this function in the main argMetaCreator function, redundant while loop; // // * Impossibel to easily integrate due to backslash lookahead handling for the flag key
// // ^ ~~TODO~~: ~Integrate this function in the main argMetaCreator function, redundant while loop;~~
/** /**
* @param {string[]} shorthandFlags * @param {string[]} shorthandFlags
* @param {string} shorthandString * @param {string} shorthandString
@ -683,7 +709,7 @@ function parser(argv, opts) {
let lookingForFlagVal = false; let lookingForFlagVal = false;
argv.forEach((arg) => { argv.forEach((arg) => {
const meta = argMetaCreator(arg); const meta = argMetaCreator(arg, opts);
if(meta.isFlag) { if(meta.isFlag) {
/** If allow singular dashlong flags, just fall through to the full flag handling */ /** If allow singular dashlong flags, just fall through to the full flag handling */
/** Redundant hasFullFlag given later might getFullFlag but yk easier implementation like this */ /** Redundant hasFullFlag given later might getFullFlag but yk easier implementation like this */
@ -729,7 +755,20 @@ function parser(argv, opts) {
return; return;
} }
else if (lookingForFlagVal) { else {
let inputVal = arg;
// Hardcoded edge case, not a fan, but if it has more than one backslash, it was never gonna be a flag anyways
if(opts.handleBackslashesForSpecialFlagChars && inputVal.startsWith('\\-'))
inputVal = inputVal.slice(1);
if(!lookingForFlagVal) {
if(opts.lowerCaseInputValues)
inputVal = inputVal.toLowerCase();
input.push(inputVal);
return
}
const flag = flags.at(-1); const flag = flags.at(-1);
/** @readonly */ /** @readonly */
let flagConfig = flag.flagConfig let flagConfig = flag.flagConfig
@ -747,26 +786,18 @@ function parser(argv, opts) {
if(flagConfig !== undefined && flagConfig.type !== undefined) if(flagConfig !== undefined && flagConfig.type !== undefined)
flagType = flagConfig.type; flagType = flagConfig.type;
const castableType = getCastableType(arg); const castableType = getCastableType(inputVal);
if(['string', 'any', castableType].includes(flagType) || (castableType === 'number' && flagType === 'bigint')) if(['string', 'any', castableType].includes(flagType) || (castableType === 'number' && flagType === 'bigint'))
flag.value = arg; flag.value = inputVal;
else { else {
if(opts.lowerCaseInputValues) if(opts.lowerCaseInputValues)
input.push(arg.toLowerCase()); inputVal = inputVal.toLowerCase();
else input.push(inputVal);
input.push(arg);
} }
lookingForFlagVal = false; lookingForFlagVal = false;
return; return;
} }
else {
if(opts.lowerCaseInputValues)
input.push(arg.toLowerCase());
else
input.push(arg);
return;
}
}) })
const flagReturnObj = {}; const flagReturnObj = {};

23
types.d.ts vendored
View File

@ -59,10 +59,29 @@ export interface ParserOpts<Flags extends FlagsI> {
behaviorOnInputError?: 'throw' | 'log' | 'log&exit' | 'ignore'; behaviorOnInputError?: 'throw' | 'log' | 'log&exit' | 'ignore';
/** Wether to allow default flag values set to null being valid @default true */ /** Wether to allow default flag values set to null being valid @default true */
allowNullDefaultFlagValues?: boolean; allowNullDefaultFlagValues?: boolean;
/** If enabled, makes all input values lowercase @default false */ /**
* If enabled, makes all input values lowercase after flag processing. It does not apply to 'values' that get naturally assigned to flags
* (eg: `['--flag', 'VALUE']` -> `{ 'flag': 'VALUE' }`)
* @default false
*/
lowerCaseInputValues?: boolean lowerCaseInputValues?: boolean
/** If enabled, makes all string flag values lowercase after flag processing @default false */ /**
* If enabled, makes all string flag values lowercase. It applies to any string flag values, either naturally assigned (eg: `['--flag', 'VALUE']` -> `{ flags: { 'flag': 'value' } }`)
* or directly assigned (eg: `['--flag=VALUE']` -> `{ flags: { 'flag': 'value' } }`)
* @default false
*/
lowerCaseFlagValues?: boolean lowerCaseFlagValues?: boolean
/**
* If disabled, does not acknowledge or consume backslashes, making flag assignments (=) and designated flag characters (--) inescapable within flags
*
*
* #### It needs to be noted that backslashes are only ever handled/consumed in flags AND if they impact special flag characters (ie: minus signs (-) and equal signs (=));
* The only exception is a backslash escaping a flag character (-) at the very beginning of a value (ie: '\--not_a_flag'), which will be consumed with this option
* enabled but not counted as a flag (ie: `['\--not_a_flag']` -> `{ input: ['--not_a_flag'], flags: {} }`), and not consumed but also not counted as a flag with it
* disabled (ie: `['\--not_a_flag']` -> `{ input: ['\--not_a_flag'], flags: {} }`).
* @default true
*/
handleBackslashesForSpecialFlagChars?: boolean
} }
function __type__getType(e: any) { return typeof e }; function __type__getType(e: any) { return typeof e };