diff --git a/index.js b/index.js index 19953b1..863a2fa 100644 --- a/index.js +++ b/index.js @@ -120,7 +120,8 @@ function validateAndFillDefaults(opts) { validateOrDefaultIfUnset(opts, 'lowerCaseFlags', 'boolean', true); validateOrDefaultIfUnset(opts, 'resolveFlagValueTypes', 'boolean', true); validateOrDefaultIfUnset(opts, 'allowNullDefaultFlagValues', 'boolean', true); - + validateOrDefaultIfUnset(opts, 'handleBackslashesForSpecialFlagChars', 'boolean', true); + validateOrDefaultIfUnset(opts, 'automaticBooleanFlagNegation', 'object', {}); namedValidateOrDefaultIfUnset('opts.automaticBooleanFlagNegation', opts.automaticBooleanFlagNegation, 'allowUnspacedNegatedFlags', 'boolean', true); @@ -525,8 +526,9 @@ function runFlagValueTypeResolving(opts, flagVal, flagConfig) { /** * @param {string} arg + * @param {DefaultParserOptsType} opts */ -function argMetaCreator(arg) { +function argMetaCreator(arg, opts) { const argsStr = new StringConsumer(arg); /** return meta flags */ @@ -560,10 +562,23 @@ function argMetaCreator(arg) { readingMinusSignCount = false; /** Flag has an assigned value in the arg*/ - if(char === "=" && isFlag && !isBackslashed && !hasPotentialFlagAssignedValue) { - hasPotentialFlagAssignedValue = true; - unescapedEqualSignPos = argsStr.getPointer()-1; - continue; + if(char === "=" && isFlag) { + if(!isBackslashed) { + if(!hasPotentialFlagAssignedValue) { + 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 - */ @@ -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 - if(char === '\\' && !isBackslashed) - isBackslashed = true; + // Weird and gross handling of backslashes, but explicit, 'defined' (in ./types.d.ts -> ParserOpts.handleBackslashesForSpecialFlagChars) and toggleable + if(char === '\\') { + 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) isBackslashed = false; } 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} shorthandString @@ -683,7 +709,7 @@ function parser(argv, opts) { let lookingForFlagVal = false; argv.forEach((arg) => { - const meta = argMetaCreator(arg); + const meta = argMetaCreator(arg, opts); if(meta.isFlag) { /** 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 */ @@ -729,7 +755,20 @@ function parser(argv, opts) { 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); /** @readonly */ let flagConfig = flag.flagConfig @@ -747,26 +786,18 @@ function parser(argv, opts) { if(flagConfig !== undefined && flagConfig.type !== undefined) flagType = flagConfig.type; - const castableType = getCastableType(arg); + const castableType = getCastableType(inputVal); if(['string', 'any', castableType].includes(flagType) || (castableType === 'number' && flagType === 'bigint')) - flag.value = arg; + flag.value = inputVal; else { if(opts.lowerCaseInputValues) - input.push(arg.toLowerCase()); - else - input.push(arg); + inputVal = inputVal.toLowerCase(); + input.push(inputVal); } lookingForFlagVal = false; return; } - else { - if(opts.lowerCaseInputValues) - input.push(arg.toLowerCase()); - else - input.push(arg); - return; - } }) const flagReturnObj = {}; diff --git a/types.d.ts b/types.d.ts index 330cfa0..c1bb860 100644 --- a/types.d.ts +++ b/types.d.ts @@ -59,10 +59,29 @@ export interface ParserOpts { behaviorOnInputError?: 'throw' | 'log' | 'log&exit' | 'ignore'; /** Wether to allow default flag values set to null being valid @default true */ 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 - /** 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 + /** + * 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 };