Additions & fixes, mainly filtering options & internal refactors
Additions:
Filtering!:
opts.shouldStopParsingFunc(): more information in commit's types.d.ts;
opts.shouldStopParsingStr: more information in commit's types.d.ts;
opts.parseFilterFunc(): more information in commit's types.d.ts;
(tweaked (lookingForFlagVal -> naturalFlagAssignment) to include the flag's type to add onto new argMeta for filtering functions);
isTypeAssignable()
assureValidFlagConfigState(): Now guarantees that flag.config is assigned right after runAutomaticBooleanFlagNegation() in the flag branch of arg handling, skipping the need for other flagConfig lookups (as it would be required in one step or another regardless);
runFlagAssignedValueAssignment(): extracted main code of meta.hasAssignedValue branch into this function (also adjusted to account for flag.isNegated);
internalFlag.isNegated: set by runAutomaticBooleanFlagNegation(), for refactored boolean flag negation handling;
internalFlag.type: extracted flagType assignments into guaranteed
Removals:
internalFlag.lookedupFlagConfig: flag.config is now assured with assureValidFlagConfigState() after runAutomaticBooleanFlagNegation(), and is instead tested against flag.config === null within assureValidFlagConfigState() to avoid repeating getting flag;
Fixes:
expectType(): Removed unused (and broken) null handling from;
validateOrDefaultIfUnset(): Fixed passing through to expectType() when setting the default, breaking if the default type isn't the 'required' type (ie: null value defaults);
opts.lowerCaseFlags now correctly applies to shorthand flags;
flags with an explicit string type now correctly do not apply opts.resolveFlagValueTypes even if enabled;
defined boolean type flags that undergo automatic boolean negation (ie: --no-flag) now correctly resolve if there is an input value after it (bug with a defined flag: `['--no-flag', 'hi'] -> { 'no-flag': 'hi' }`);
Fixed some spelling issues (eg: 2x otps -> opts);
Other:
Copied StringConsumer from OxLib into index.js
Moved stage of runAutomaticBooleanFlagNegation() from beginning of flag post arg handling -> pre flag assignments in arg handling, with a small refactor;
-> Addition of internalFlag.isNegated
Cleaned up and clarified the purpose of isAssignedValueTypeAssignable() -> assignedValueUncastableTypeError() to externalize the check thanks to new isTypeAssignable();
Cleaned up and clarified the purpose of unknownFlag() -> unknownFlagError() to externalize the check;
Main arg handling 'loop' is not a for loop instead of a foreach to allow for a break statement for the new opts.shouldStopParsing* options;
-Note: Other small unmentioned things, and big commit, probably forgot some note-worthy things, mb;
This commit is contained in:
parent
4876804e6c
commit
5533706b30
527
index.js
527
index.js
@ -1,5 +1,85 @@
|
|||||||
|
|
||||||
const { StringConsumer } = require('../OxLib/utils/utils')
|
|
||||||
|
/** 'bundling' my own dependencies */
|
||||||
|
class StringConsumer {
|
||||||
|
/** @readonly */
|
||||||
|
static VERSION = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
*/
|
||||||
|
constructor(str) {
|
||||||
|
/** @private */
|
||||||
|
this.base = str;
|
||||||
|
/** @private */
|
||||||
|
this.i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the next character in line
|
||||||
|
* If getPointer() == getFullStr().length, hasNext() = false
|
||||||
|
*/
|
||||||
|
getPointer() {
|
||||||
|
return this.i;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullStr() {
|
||||||
|
return this.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next characters while consuming them
|
||||||
|
* @param {number} [num]
|
||||||
|
*/
|
||||||
|
next(num) {
|
||||||
|
if(num < 1)
|
||||||
|
throw new RangeError(`Num ${num} < 1! Num must be between 1 and fullStr length.`)
|
||||||
|
if(typeof num !== 'number')
|
||||||
|
num = 1;
|
||||||
|
if(this.i == this.base.length)
|
||||||
|
return null;
|
||||||
|
let retStr = this.base.slice(this.i, this.i + num);
|
||||||
|
this.i += num;
|
||||||
|
if(this.i >= this.base.length)
|
||||||
|
this.i = this.base.length;
|
||||||
|
return retStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next characters without consuming them
|
||||||
|
* @param {number} [num]
|
||||||
|
*/
|
||||||
|
peek(num) {
|
||||||
|
if(num < 1)
|
||||||
|
throw new RangeError(`Num ${num} < 1! Num must be between 1 and fullStr length.`)
|
||||||
|
if(typeof num !== 'number')
|
||||||
|
num = 1;
|
||||||
|
if(this.i == this.base.length)
|
||||||
|
return null;
|
||||||
|
let retStr = this.base.slice(this.i, this.i + num);
|
||||||
|
return retStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
return this.next(this.base.length-this.i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} [num]
|
||||||
|
*/
|
||||||
|
hasNext(num) {
|
||||||
|
if(num < 1)
|
||||||
|
throw new RangeError(`Num ${num} < 1! Num must be between 1 and fullStr length.`)
|
||||||
|
if(typeof num !== 'number')
|
||||||
|
num = 1;
|
||||||
|
return this.i + num <= this.base.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { ok } = require('node:assert');
|
const { ok } = require('node:assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +111,10 @@ const { ok } = require('node:assert');
|
|||||||
* @typedef {import('./types.d.ts').FlagAny} FlagAny
|
* @typedef {import('./types.d.ts').FlagAny} FlagAny
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./types.d.ts').argMeta} argMeta
|
||||||
|
*/
|
||||||
|
|
||||||
class InputError extends Error {
|
class InputError extends Error {
|
||||||
context = null;
|
context = null;
|
||||||
};
|
};
|
||||||
@ -43,22 +127,19 @@ const UNSET_FLAG_VALUE = new UnsetFlagValueType();
|
|||||||
* @param {JSTypes} type
|
* @param {JSTypes} type
|
||||||
* @param {any} value
|
* @param {any} value
|
||||||
* @param {boolean} [acceptsUndefined=false]
|
* @param {boolean} [acceptsUndefined=false]
|
||||||
* @param {boolean} [acceptsNull=false]
|
|
||||||
*/
|
*/
|
||||||
function expectType(key, type, value, acceptsUndefined, acceptsNull) {
|
function expectType(key, type, value, acceptsUndefined) {
|
||||||
if(typeof acceptsNull !== 'boolean')
|
|
||||||
acceptsNull = false;
|
|
||||||
if(typeof acceptsUndefined !== 'boolean')
|
if(typeof acceptsUndefined !== 'boolean')
|
||||||
acceptsUndefined = false;
|
acceptsUndefined = false;
|
||||||
if(!acceptsUndefined && typeof value === 'undefined')
|
|
||||||
throw new TypeError(`Required type ${type} for ${key} is undefined!`);
|
|
||||||
if(typeof value === 'undefined') {
|
if(typeof value === 'undefined') {
|
||||||
if(!acceptsUndefined)
|
if(acceptsUndefined)
|
||||||
throw new TypeError(`Type ${type} for ${key} does not accept null values!`);
|
return
|
||||||
else
|
else
|
||||||
return;
|
throw new TypeError(`Required type ${type} for ${key} is undefined!`);
|
||||||
}
|
}
|
||||||
if(typeof value !== type)
|
|
||||||
|
if(typeof value !== type)
|
||||||
throw new TypeError(`Expected ${key} to be ${type}, received ${typeof value}!`);
|
throw new TypeError(`Expected ${key} to be ${type}, received ${typeof value}!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +153,10 @@ if(typeof value !== type)
|
|||||||
*/
|
*/
|
||||||
function validateOrDefaultIfUnset(obj, keyName, keyType, defaultVal, objName) {
|
function validateOrDefaultIfUnset(obj, keyName, keyType, defaultVal, objName) {
|
||||||
if(typeof objName === 'undefined')
|
if(typeof objName === 'undefined')
|
||||||
objName = 'otps';
|
objName = 'opts';
|
||||||
if(typeof obj[keyName] === 'undefined')
|
if(typeof obj[keyName] === 'undefined')
|
||||||
obj[keyName] = defaultVal;
|
return obj[keyName] = defaultVal;
|
||||||
expectType(`${objName}.${String(keyName)}`, keyType, obj[keyName], true, false)
|
return expectType(`${objName}.${String(keyName)}`, keyType, obj[keyName], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +168,7 @@ function validateOrDefaultIfUnset(obj, keyName, keyType, defaultVal, objName) {
|
|||||||
* @param {any} defaultVal
|
* @param {any} defaultVal
|
||||||
*/
|
*/
|
||||||
function namedValidateOrDefaultIfUnset(objName, obj, keyName, keyType, defaultVal) {
|
function namedValidateOrDefaultIfUnset(objName, obj, keyName, keyType, defaultVal) {
|
||||||
validateOrDefaultIfUnset(obj, keyName, keyType, defaultVal, objName);
|
return validateOrDefaultIfUnset(obj, keyName, keyType, defaultVal, objName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,16 +186,29 @@ function validateAndFillDefaults(opts) {
|
|||||||
validateOrDefaultIfUnset(opts, 'warningLogger', 'function', console.log);
|
validateOrDefaultIfUnset(opts, 'warningLogger', 'function', console.log);
|
||||||
validateOrDefaultIfUnset(opts, 'behaviorOnInputError', 'string', 'throw');
|
validateOrDefaultIfUnset(opts, 'behaviorOnInputError', 'string', 'throw');
|
||||||
if(!allowedBehaviorOnInputErrorTypes.includes(opts.behaviorOnInputError))
|
if(!allowedBehaviorOnInputErrorTypes.includes(opts.behaviorOnInputError))
|
||||||
throw new TypeError(`Expected opts.behaviorOnInputError === ${allowedBehaviorOnInputErrorTypes.map((type) => `"${type}"`).join(" | ")}, received "${opts.behaviorOnInputError}"!`);
|
throw new TypeError(`Expected opts.behaviorOnInputError === ${allowedBehaviorOnInputErrorTypes.map((type) => `"${type}"`).join(" | ")} | undefined, received "${opts.behaviorOnInputError}"!`);
|
||||||
validateOrDefaultIfUnset(opts, 'defaultFlagType', 'string', 'any');
|
validateOrDefaultIfUnset(opts, 'defaultFlagType', 'string', 'any');
|
||||||
if(!validFlagTypes.includes(opts.defaultFlagType))
|
if(!validFlagTypes.includes(opts.defaultFlagType))
|
||||||
throw new TypeError(`Expected opts.defaultFlagType === ${validFlagTypes.map((type) => `"${type}"`).join(" | ")}, received "${opts.defaultFlagType}"!`);
|
throw new TypeError(`Expected opts.defaultFlagType === ${validFlagTypes.map((type) => `"${type}"`).join(" | ")} | undefined, received "${opts.defaultFlagType}"!`);
|
||||||
|
|
||||||
/** Category: @default false */
|
/** Category: @default false */
|
||||||
validateOrDefaultIfUnset(opts, 'allowSingularDashLongFlags', 'boolean', false);
|
validateOrDefaultIfUnset(opts, 'allowSingularDashLongFlags', 'boolean', false);
|
||||||
validateOrDefaultIfUnset(opts, 'lowerCaseFlagValues', 'boolean', false);
|
validateOrDefaultIfUnset(opts, 'lowerCaseFlagValues', 'boolean', false);
|
||||||
validateOrDefaultIfUnset(opts, 'lowerCaseInputValues', 'boolean', false);
|
validateOrDefaultIfUnset(opts, 'lowerCaseInputValues', 'boolean', false);
|
||||||
|
|
||||||
|
if(opts.shouldStopParsingFunc !== null)
|
||||||
|
validateOrDefaultIfUnset(opts, 'shouldStopParsingFunc', 'function', null);
|
||||||
|
if(opts.shouldStopParsingStr !== null)
|
||||||
|
validateOrDefaultIfUnset(opts, 'shouldStopParsingStr', 'string', null);
|
||||||
|
|
||||||
|
if(opts.shouldStopParsingStr !== null && opts.shouldStopParsingFunc !== null) {
|
||||||
|
opts.warningLogger(`warn: opts.shouldStopParsingStr and opts.shouldStopParsingFunc are both present, opts.shouldStopParsingStr will be ignored!`);
|
||||||
|
opts.shouldStopParsingStr = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(opts.parseFilterFunc !== null)
|
||||||
|
validateOrDefaultIfUnset(opts, 'parseFilterFunc', 'function', null);
|
||||||
|
|
||||||
/** Category: @default true */
|
/** Category: @default true */
|
||||||
validateOrDefaultIfUnset(opts, 'allowUnknownFlags', 'boolean', true);
|
validateOrDefaultIfUnset(opts, 'allowUnknownFlags', 'boolean', true);
|
||||||
validateOrDefaultIfUnset(opts, 'lowerCaseFlags', 'boolean', true);
|
validateOrDefaultIfUnset(opts, 'lowerCaseFlags', 'boolean', true);
|
||||||
@ -131,10 +225,9 @@ function validateAndFillDefaults(opts) {
|
|||||||
|
|
||||||
validateOrDefaultIfUnset(opts, 'flags', "object", {});
|
validateOrDefaultIfUnset(opts, 'flags', "object", {});
|
||||||
|
|
||||||
|
|
||||||
Object.entries(opts.flags).forEach(/** @param {[string, FlagAny]} value */ ([flagName, flagData]) => {
|
Object.entries(opts.flags).forEach(/** @param {[string, FlagAny]} value */ ([flagName, flagData]) => {
|
||||||
// namedValidateOrDefaultIfUnset('flag', flagData, 'default')
|
// namedValidateOrDefaultIfUnset('flag', flagData, 'default')
|
||||||
expectType(`flag["${flagName}"]`, 'object', flagData, false, false);
|
expectType(`flag["${flagName}"]`, 'object', flagData, false);
|
||||||
|
|
||||||
if(typeof flagData.type === 'undefined') {
|
if(typeof flagData.type === 'undefined') {
|
||||||
opts.warningLogger(`warn: flag["${flagName}"].type is undefined, defaulting to '${opts.defaultFlagType}'`);
|
opts.warningLogger(`warn: flag["${flagName}"].type is undefined, defaulting to '${opts.defaultFlagType}'`);
|
||||||
@ -169,7 +262,7 @@ function validateAndFillDefaults(opts) {
|
|||||||
if(typeof flagData.aliases !== 'undefined' && (!Array.isArray(flagData.aliases) || flagData.aliases.some((alias) => typeof alias !== 'string')))
|
if(typeof flagData.aliases !== 'undefined' && (!Array.isArray(flagData.aliases) || flagData.aliases.some((alias) => typeof alias !== 'string')))
|
||||||
throw new TypeError(`flag["${flagName}"].aliases must be type string[]!`);
|
throw new TypeError(`flag["${flagName}"].aliases must be type string[]!`);
|
||||||
|
|
||||||
//#region I hate that, setting useless properties that echo a value that I should supposedly already have, but it would require a non trival refactoring in a lot of places so this is easier
|
//#region I hate that, setting useless properties that echo a value that I should supposedly already have, but it would require a non trivial refactoring in a lot of places so this is easier
|
||||||
//its also wrongly typed because obviously flag.name doesn't actually exist outside of me parsing the options, so it 's type is actually in `ReturnType<typeof getFlag()>`
|
//its also wrongly typed because obviously flag.name doesn't actually exist outside of me parsing the options, so it 's type is actually in `ReturnType<typeof getFlag()>`
|
||||||
//#endregion
|
//#endregion
|
||||||
///@ts-expect-error
|
///@ts-expect-error
|
||||||
@ -300,12 +393,25 @@ function getShorthandFlag(opts, flagKeyInput) {
|
|||||||
return getFlag(opts, flagKeyInput, true);
|
return getFlag(opts, flagKeyInput, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DefaultParserOptsType} opts
|
||||||
|
* @param {InternalFlagsFlagObj} flag
|
||||||
|
*/
|
||||||
|
function assureValidFlagConfigState(opts, flag) {
|
||||||
|
if(flag.config === null) {
|
||||||
|
if(flag.isShorthand)
|
||||||
|
flag.config = getShorthandFlag(opts, flag.key);
|
||||||
|
else
|
||||||
|
flag.config = getFullFlag(opts, flag.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion findFlagFunctions
|
//#endregion findFlagFunctions
|
||||||
//#region end findFlagFunctions
|
//#region end findFlagFunctions
|
||||||
//#endregion end findFlagFunctions
|
//#endregion end findFlagFunctions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @related runFlagValueTypeResolving
|
* @related runFlagValueTypeResolving,isTypeAssignable
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @returns {"number"|"bigint"|"boolean"|"string"}
|
* @returns {"number"|"bigint"|"boolean"|"string"}
|
||||||
*/
|
*/
|
||||||
@ -321,6 +427,21 @@ function getCastableType(input) {
|
|||||||
return 'string';
|
return 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @related runFlagValueTypeResolving,getCastableType
|
||||||
|
* @param {FlagAny['type']} baseType
|
||||||
|
* @param {FlagAny['type']} comparedType
|
||||||
|
*/
|
||||||
|
function isTypeAssignable(baseType, comparedType) {
|
||||||
|
if(baseType === comparedType)
|
||||||
|
return true;
|
||||||
|
if(baseType === 'any' || baseType === 'string')
|
||||||
|
return true;
|
||||||
|
if(baseType === 'bigint' && comparedType === 'number')
|
||||||
|
return true;
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {{ error: InputError | Error, message: string, exitCode: number }} errorObj
|
* @param {{ error: InputError | Error, message: string, exitCode: number }} errorObj
|
||||||
@ -337,62 +458,46 @@ function giveError(opts, errorObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws if otps.behaviorOnInputError == 'throw' and flag value isn't assignable to it's required type
|
* @throws if opts.behaviorOnInputError == 'throw' && flag value isn't assignable to it's required type
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {ReturnType<typeof argMetaCreator>} meta
|
* @param {ReturnType<typeof parseArg>} meta
|
||||||
* @param {InternalFlagsFlagObj} flag
|
* @param {InternalFlagsFlagObj} flag
|
||||||
* @param {string[]} argv - For the error context
|
* @param {string[]} argv - For the error context
|
||||||
* @returns {boolean} - true = the assigned value is assignable to flag
|
|
||||||
*/
|
*/
|
||||||
function isAssignedValueTypeAssignable(opts, meta, flag, argv) {
|
function assignedValueUncastableTypeError(opts, meta, flag, argv) {
|
||||||
/** @readonly */
|
|
||||||
let flagConfig = flag.flagConfig
|
|
||||||
if(!flag.lookedupFlagConfig) {
|
|
||||||
if(flag.isShorthand)
|
|
||||||
flagConfig = getShorthandFlag(opts, flag.key);
|
|
||||||
else
|
|
||||||
flagConfig = getFullFlag(opts, flag.key);
|
|
||||||
flag.flagConfig = flagConfig;
|
|
||||||
flag.lookedupFlagConfig = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const castableType = getCastableType(meta.flagAssignedValue);
|
const castableType = getCastableType(meta.flagAssignedValue);
|
||||||
if(flagConfig !== undefined && flagConfig.type !== 'any' && flagConfig.type !== 'string' && flagConfig.type !== castableType) {
|
let valDisplay = `"${meta.flagAssignedValue}"`
|
||||||
if(flagConfig.type === 'bigint' && castableType === 'number')
|
if(typeof meta.flagAssignedValue !== 'string')
|
||||||
return true;
|
valDisplay = meta.flagAssignedValue;
|
||||||
const message = `Value "${meta.flagAssignedValue}" (type:"${castableType}") is not assignable to required type "${flagConfig.type}".`;
|
const message = `Value ${valDisplay} (castable:"${castableType}") is not assignable to required type "${flag.type}".`;
|
||||||
const error = new InputError(message);
|
const error = new InputError(message);
|
||||||
error.context = { flag: flagConfig, flagType: flagConfig.type, flagMatch: meta.flagKey, value: meta.flagAssignedValue, assignableType: castableType, argv };
|
error.context = { flag: flag.config, flagType: flag.type, flagMatch: meta.flagKey, value: meta.flagAssignedValue, assignableType: castableType, argv };
|
||||||
giveError(opts, {
|
giveError(opts, {
|
||||||
error,
|
error,
|
||||||
message: message + `\n - Flag: "${flagConfig.name}", match: "${meta.flagKey}", value: "${meta.flagAssignedValue}", assignableType: "${castableType}", requiredType: "${flagConfig.type}"`,
|
message: message + `\n - Flag: "${flag.config.name}", match: "${meta.flagKey}", value: ${valDisplay}, assignableType: "${castableType}", requiredType: "${flag.type}"`,
|
||||||
exitCode: 1
|
exitCode: 1
|
||||||
});
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws if behaviorOnInputError == 'throw' and opts.allowUnknownFlags == false and flag isn't found in the flag config
|
* @throws if behaviorOnInputError == 'throw'
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {string} flagKey
|
* @param {string} flagKey
|
||||||
* @param {any} flagVal
|
* @param {any} flagVal
|
||||||
* @param {string[]} argv - For the errror context
|
* @param {string[]} argv - For the error context
|
||||||
*/
|
*/
|
||||||
function unknownFlag(opts, flagKey, flagVal, argv) {
|
function unknownFlagError(opts, flagKey, flagVal, argv) {
|
||||||
if(!opts.allowUnknownFlags) {
|
|
||||||
const message = `Received unknown flag "${flagKey}", but opts.allowUnknownFlags is disabled!`;
|
const message = `Received unknown flag "${flagKey}", but opts.allowUnknownFlags is disabled!`;
|
||||||
const error = new InputError(message);
|
const error = new InputError(message);
|
||||||
error.context = { flagKey, flagVal, argv };
|
error.context = { flagKey, flagVal, argv };
|
||||||
|
let valDisplay = `"${flagVal}"`
|
||||||
|
if(typeof flagVal !== 'string')
|
||||||
|
valDisplay = flagVal
|
||||||
giveError(opts, {
|
giveError(opts, {
|
||||||
error,
|
error,
|
||||||
message: message + `\n - Flag: <not found>, match: "${flagKey}", value: "${flagVal}"`,
|
message: message + `\n - Flag: <not found>, match: "${flagKey}", value: ${valDisplay}`,
|
||||||
exitCode: 2
|
exitCode: 2
|
||||||
});
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region SubworkFunctions
|
//#region SubworkFunctions
|
||||||
@ -405,9 +510,9 @@ function getAllShorthandFlags(opts) {
|
|||||||
const allShorthandFlags = []
|
const allShorthandFlags = []
|
||||||
Object.values(opts.flags).forEach((flag) => {
|
Object.values(opts.flags).forEach((flag) => {
|
||||||
allShorthandFlags.push(...flag.shorthands);
|
allShorthandFlags.push(...flag.shorthands);
|
||||||
/** FLAG: ${opts.automaticBooleanFlagNegation.shorthandNegation} */
|
|
||||||
if(opts.automaticBooleanFlagNegation.shorthandNegation && flag.type === 'boolean') {
|
if(opts.automaticBooleanFlagNegation.shorthandNegation && flag.type === 'boolean') {
|
||||||
/// @ts-expect-error
|
// For typescript
|
||||||
|
ok(Array.isArray(flag.shorthands), 'Array.isArray(flag.shorthands): Should have been parsed by validateAndFillDefaults and made into an array!');
|
||||||
allShorthandFlags.push(...flag.shorthands.map((shorthand) => "n" + shorthand));
|
allShorthandFlags.push(...flag.shorthands.map((shorthand) => "n" + shorthand));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -415,8 +520,8 @@ function getAllShorthandFlags(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @modifies flag
|
* Can (only if negated flag) @modify flag.key && flag.config
|
||||||
* ### To be run before any flag value assignments (flag.value always expected to be string)
|
* ## To be ran before any flag value assignments: flag.value always expected to be string
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {InternalFlagsFlagObj} flag
|
* @param {InternalFlagsFlagObj} flag
|
||||||
*/
|
*/
|
||||||
@ -436,7 +541,6 @@ function runAutomaticBooleanFlagNegation(opts, flag) {
|
|||||||
if(!flag.isShorthand) {
|
if(!flag.isShorthand) {
|
||||||
/** Make sure flag isn't set in our vals */
|
/** Make sure flag isn't set in our vals */
|
||||||
if(!hasFullFlag(opts, flag.key) && flag.key.length > 2) {
|
if(!hasFullFlag(opts, flag.key) && flag.key.length > 2) {
|
||||||
|
|
||||||
if(flag.key.length > 3 && flag.key.toLowerCase().startsWith('no-')) {
|
if(flag.key.length > 3 && flag.key.toLowerCase().startsWith('no-')) {
|
||||||
newKey = flag.key.slice(3);
|
newKey = flag.key.slice(3);
|
||||||
negated = true;
|
negated = true;
|
||||||
@ -458,31 +562,21 @@ function runAutomaticBooleanFlagNegation(opts, flag) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(negated) {
|
if(negated) {
|
||||||
/** @readonly */
|
/** Cannot use flag.type as it's not yet initialized, and useless to set it as it will always be done right after this function call */
|
||||||
let flagType = opts.defaultFlagType;
|
const flagType = flagConfig?.type || opts.defaultFlagType;
|
||||||
if(typeof flagConfig !== 'undefined')
|
|
||||||
flagType = flagConfig.type;
|
|
||||||
|
|
||||||
/** @type {ReturnType<typeof getCastableType>} */
|
/** @type {ReturnType<typeof getCastableType>} */
|
||||||
let castableType = 'boolean';
|
let castableType = 'boolean';
|
||||||
if(flag.value !== UNSET_FLAG_VALUE)
|
if(flag.value !== UNSET_FLAG_VALUE)
|
||||||
/// @ts-ignore
|
/// @ts-expect-error - flag.value is always string at this moment in time
|
||||||
castableType = getCastableType(flag.value);
|
castableType = getCastableType(flag.value);
|
||||||
|
|
||||||
if((flagType === 'boolean' || flagType === 'any') && castableType === 'boolean') {
|
if(castableType === 'boolean' && isTypeAssignable(flagType, castableType)) {
|
||||||
flag.key = newKey;
|
flag.key = newKey;
|
||||||
flag.flagConfig = flagConfig;
|
flag.config = flagConfig;
|
||||||
flag.lookedupFlagConfig = true;
|
flag.isNegated = true;
|
||||||
if(flag.value === UNSET_FLAG_VALUE)
|
|
||||||
flag.value = false;
|
|
||||||
else {
|
|
||||||
/** @type {string} */
|
|
||||||
/// @ts-ignore
|
|
||||||
let flagVal = flag.value;
|
|
||||||
let boolVal = runFlagValueTypeResolving(opts, flagVal, undefined);
|
|
||||||
flag.value = !boolVal;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,16 +584,18 @@ function runAutomaticBooleanFlagNegation(opts, flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @related getCastableType
|
* @related getCastableType,isTypeAssignable
|
||||||
* ### Warning: This does not check the flag type despite passing in a flagConfig!
|
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {string} flagVal
|
* @param {string} flagVal
|
||||||
* @param {ReturnType<typeof getFlag>} flagConfig
|
* @param {FlagAny['type']} [expectedType]
|
||||||
*/
|
*/
|
||||||
function runFlagValueTypeResolving(opts, flagVal, flagConfig) {
|
function runFlagValueTypeResolving(opts, flagVal, expectedType) {
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
let resolvedFlagVal = flagVal;
|
let resolvedFlagVal = flagVal;
|
||||||
|
|
||||||
|
if(expectedType !== undefined && expectedType === 'string')
|
||||||
|
return flagVal;
|
||||||
|
|
||||||
/** boolean checking */
|
/** boolean checking */
|
||||||
if(flagVal.toLowerCase() === 'true')
|
if(flagVal.toLowerCase() === 'true')
|
||||||
resolvedFlagVal = true;
|
resolvedFlagVal = true;
|
||||||
@ -507,21 +603,44 @@ function runFlagValueTypeResolving(opts, flagVal, flagConfig) {
|
|||||||
resolvedFlagVal = false;
|
resolvedFlagVal = false;
|
||||||
else if(opts.resolveFlagValueTypes) {
|
else if(opts.resolveFlagValueTypes) {
|
||||||
/** bigint checking */
|
/** bigint checking */
|
||||||
/// @ts-ignore
|
/// @ts-expect-error - isNaN on string
|
||||||
if(flagVal.length >= 2 && flagVal.endsWith('n') && !isNaN(flagVal.slice(0, -1)))
|
if(flagVal.length >= 2 && flagVal.endsWith('n') && !isNaN(flagVal.slice(0, -1)))
|
||||||
resolvedFlagVal = BigInt(flagVal.slice(0, -1));
|
resolvedFlagVal = BigInt(flagVal.slice(0, -1));
|
||||||
/** number & bigint checking */
|
/** number & bigint checking */
|
||||||
/// @ts-ignore
|
/// @ts-expect-error - isNaN on string
|
||||||
else if(!isNaN(flagVal)) {
|
else if(!isNaN(flagVal)) {
|
||||||
if(typeof flagConfig === 'undefined' || flagConfig.type === 'number' || flagConfig.type === 'any')
|
if(expectedType === undefined || expectedType === 'number' || expectedType === 'any')
|
||||||
resolvedFlagVal = Number(flagVal);
|
resolvedFlagVal = Number(flagVal);
|
||||||
else if (typeof flagConfig !== 'undefined' && flagConfig.type === 'bigint')
|
else if (expectedType !== undefined && expectedType === 'bigint')
|
||||||
resolvedFlagVal = BigInt(flagVal);
|
resolvedFlagVal = BigInt(flagVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolvedFlagVal;
|
return resolvedFlagVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @related getCastableType,isTypeAssignable
|
||||||
|
* @param {DefaultParserOptsType} opts
|
||||||
|
* @param {ReturnType<typeof parseArg>} meta
|
||||||
|
* @param {InternalFlagsFlagObj} flag
|
||||||
|
* @param {string[]} argv - For the error context
|
||||||
|
*/
|
||||||
|
function runFlagAssignedValueAssignment(opts, meta, flag, argv) {
|
||||||
|
if(flag.config !== undefined && !isTypeAssignable(flag.type, getCastableType(meta.flagAssignedValue))) {
|
||||||
|
assignedValueUncastableTypeError(opts, meta, flag, argv);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flagVal = runFlagValueTypeResolving(opts, meta.flagAssignedValue, flag.type);
|
||||||
|
|
||||||
|
if(flag.isNegated && typeof flagVal === 'boolean' && isTypeAssignable(flag.type, 'boolean')) {
|
||||||
|
flag.value = !flagVal
|
||||||
|
} else {
|
||||||
|
flag.value = flagVal;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion SubworkFunctions
|
//#endregion SubworkFunctions
|
||||||
//#region SecondaryParsers
|
//#region SecondaryParsers
|
||||||
|
|
||||||
@ -529,7 +648,7 @@ function runFlagValueTypeResolving(opts, flagVal, flagConfig) {
|
|||||||
* @param {string} arg
|
* @param {string} arg
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
*/
|
*/
|
||||||
function argMetaCreator(arg, opts) {
|
function parseArg(arg, opts) {
|
||||||
const argsStr = new StringConsumer(arg);
|
const argsStr = new StringConsumer(arg);
|
||||||
|
|
||||||
/** return meta flags */
|
/** return meta flags */
|
||||||
@ -624,8 +743,6 @@ function argMetaCreator(arg, opts) {
|
|||||||
return { isFlag, minusSignCount, flagKey, hasFlagAssignedValue, flagAssignedValue, unescapedEqualSignPos };
|
return { isFlag, minusSignCount, flagKey, hasFlagAssignedValue, flagAssignedValue, unescapedEqualSignPos };
|
||||||
}
|
}
|
||||||
|
|
||||||
// // * 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
|
||||||
@ -676,7 +793,7 @@ function parseShorthandFlags(shorthandFlags, shorthandString) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
* @typedef {{ key: string, value: typeof UNSET_FLAG_VALUE | number | boolean | string, isShorthand: boolean, lookedupFlagConfig: boolean, flagConfig: ReturnType<typeof getFlag> }} InternalFlagsFlagObj
|
* @typedef {{ key: string, value: typeof UNSET_FLAG_VALUE | number | boolean | string, isShorthand: boolean, config: ReturnType<typeof getFlag>, isNegated: boolean, type: FlagAny['type'] }} InternalFlagsFlagObj
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Support multiple flag as a option = overload input
|
// TODO: Support multiple flag as a option = overload input
|
||||||
@ -691,13 +808,13 @@ function parseShorthandFlags(shorthandFlags, shorthandString) {
|
|||||||
* @template {{ [K in keyof Flags]: Flags[K]['type'] extends 'boolean' ? boolean : Flags[K]['type'] extends 'number' ? number : Flags[K]['type'] extends 'string' ? string : string | boolean | number }} FlagsReturn
|
* @template {{ [K in keyof Flags]: Flags[K]['type'] extends 'boolean' ? boolean : Flags[K]['type'] extends 'number' ? number : Flags[K]['type'] extends 'string' ? string : string | boolean | number }} FlagsReturn
|
||||||
* @param {string[]} argv
|
* @param {string[]} argv
|
||||||
* @param {ParserOpts<Flags>} opts
|
* @param {ParserOpts<Flags>} opts
|
||||||
* @returns {{ input: string[], flags: FlagsReturn } }}
|
* @returns {{ input: string[], flags: FlagsReturn, unparsed: string[] } }}
|
||||||
*/
|
*/
|
||||||
function parser(argv, opts) {
|
function parser(argv, opts) {
|
||||||
if(!Array.isArray(argv) || argv.some((e) => typeof e !== 'string'))
|
if(!Array.isArray(argv) || argv.some((e) => typeof e !== 'string'))
|
||||||
throw new TypeError(`Argv must be type string[]!`);
|
throw new TypeError(`Argv must be type string[]!`);
|
||||||
|
|
||||||
expectType('opts', 'object', opts, false, false);
|
expectType('opts', 'object', opts, false);
|
||||||
/** @throwable Will throw if anything is wrong */
|
/** @throwable Will throw if anything is wrong */
|
||||||
validateAndFillDefaults(opts);
|
validateAndFillDefaults(opts);
|
||||||
|
|
||||||
@ -715,30 +832,102 @@ function parser(argv, opts) {
|
|||||||
const flags = [];
|
const flags = [];
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
const input = [];
|
const input = [];
|
||||||
|
/** @type {string[]} */
|
||||||
|
const unparsed = [];
|
||||||
|
|
||||||
|
/** @readonly Internal value for keeping track previous flag being available for a natural flag value assignment (ie: ['--flag', 'hi'] -> { "flag": 'hi' }) */
|
||||||
|
let naturalFlagAssignment = {
|
||||||
|
isLookingForValue: false,
|
||||||
|
/** @type {FlagAny['type']} */
|
||||||
|
requiredType: null
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < argv.length; i++) {
|
||||||
|
const arg = argv[i];
|
||||||
|
|
||||||
|
const meta = parseArg(arg, opts);
|
||||||
|
|
||||||
|
/** @type {argMeta} */
|
||||||
|
const argMeta = {
|
||||||
|
...meta,
|
||||||
|
isShorthand: meta.minusSignCount === 1,
|
||||||
|
isInput: !meta.isFlag,
|
||||||
|
isFlagNaturalValue: false
|
||||||
|
}
|
||||||
|
if(!meta.isFlag && naturalFlagAssignment.isLookingForValue && isTypeAssignable(naturalFlagAssignment.requiredType, getCastableType(arg))) {
|
||||||
|
argMeta.isFlagNaturalValue = true;
|
||||||
|
argMeta.isInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Parsing filters
|
||||||
|
if(typeof opts.shouldStopParsingFunc === 'function') {
|
||||||
|
let shouldStop = false;
|
||||||
|
try {
|
||||||
|
shouldStop = opts.shouldStopParsingFunc(arg, i, argv, argMeta, input);
|
||||||
|
} catch (stopParsingFuncError) {
|
||||||
|
const error = new EvalError(`Function opts.shouldStopParsingFunc threw an error!`);
|
||||||
|
error.cause = stopParsingFuncError;
|
||||||
|
/// @ts-ignore
|
||||||
|
error.context = { arg, index: i, argMeta, stopParsingFunc: opts.shouldStopParsingFunc, argv };
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if(shouldStop) {
|
||||||
|
unparsed.push(...argv.slice(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(typeof opts.shouldStopParsingStr === 'string' && opts.shouldStopParsingStr === arg) {
|
||||||
|
unparsed.push(...argv.slice(i))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(typeof opts.parseFilterFunc === 'function') {
|
||||||
|
let filtered = false;
|
||||||
|
try {
|
||||||
|
filtered = opts.parseFilterFunc(arg, i, argv, argMeta, input);
|
||||||
|
} catch (parseFilterError) {
|
||||||
|
const error = new EvalError(`Function opts.parseFilterFunc threw an error!`);
|
||||||
|
error.cause = parseFilterError;
|
||||||
|
/// @ts-ignore
|
||||||
|
error.context = { arg, index: i, argMeta, parseFilterFunc: opts.parseFilterFunc, argv };
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if(filtered) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion Parsing filters
|
||||||
|
|
||||||
let lookingForFlagVal = false;
|
|
||||||
argv.forEach((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 */
|
||||||
if(meta.minusSignCount === 1 && (!opts.allowSingularDashLongFlags || !hasFullFlag(opts, meta.flagKey))) {
|
if(argMeta.isShorthand && !(opts.allowSingularDashLongFlags && hasFullFlag(opts, meta.flagKey))) {
|
||||||
if(opts.lowerCaseFlags)
|
|
||||||
arg = arg.toLowerCase();
|
|
||||||
|
|
||||||
parseShorthandFlags(allShorthandFlags, meta.flagKey)
|
parseShorthandFlags(allShorthandFlags, meta.flagKey)
|
||||||
.forEach((arg) => flags.push({ key: arg, value: UNSET_FLAG_VALUE, isShorthand: true, lookedupFlagConfig: false, flagConfig: null }));
|
.forEach((shorthandKey) => {
|
||||||
|
/** @readonly */
|
||||||
|
let flagKey = shorthandKey;
|
||||||
|
if(opts.lowerCaseFlags)
|
||||||
|
flagKey = flagKey.toLowerCase();
|
||||||
|
|
||||||
|
/** @type {InternalFlagsFlagObj} */
|
||||||
|
const flag = { key: flagKey, value: UNSET_FLAG_VALUE, isShorthand: true, config: null, isNegated: false, type: opts.defaultFlagType };
|
||||||
|
runAutomaticBooleanFlagNegation(opts, flag);
|
||||||
|
assureValidFlagConfigState(opts, flag);
|
||||||
|
if(flag.config !== undefined)
|
||||||
|
flag.type = flag.config.type;
|
||||||
|
|
||||||
|
flags.push(flag);
|
||||||
|
});
|
||||||
|
|
||||||
|
const flag = flags.at(-1);
|
||||||
|
|
||||||
if(meta.hasFlagAssignedValue) {
|
if(meta.hasFlagAssignedValue) {
|
||||||
const flag = flags.at(-1);
|
naturalFlagAssignment = { isLookingForValue: false, requiredType: null };
|
||||||
/** @throwable Can throw depending on opts.behaviorOnInputError */
|
runFlagAssignedValueAssignment(opts, meta, flag, argv);
|
||||||
if(isAssignedValueTypeAssignable(opts, meta, flag, argv))
|
continue;
|
||||||
flag.value = meta.flagAssignedValue;
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
lookingForFlagVal = true;
|
naturalFlagAssignment = { isLookingForValue: true, requiredType: flag.type };
|
||||||
}
|
continue;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @readonly I like dark blue, plus if is better than a ? assignment from a const */
|
/** @readonly I like dark blue, plus if is better than a ? assignment from a const */
|
||||||
@ -746,22 +935,25 @@ function parser(argv, opts) {
|
|||||||
if(opts.lowerCaseFlags)
|
if(opts.lowerCaseFlags)
|
||||||
flagKey = flagKey.toLowerCase();
|
flagKey = flagKey.toLowerCase();
|
||||||
|
|
||||||
const flagConfig = getFullFlag(opts, flagKey);
|
/** @type {InternalFlagsFlagObj} */
|
||||||
|
const flag = { key: flagKey, value: UNSET_FLAG_VALUE, isShorthand: false, config: null, isNegated: false, type: opts.defaultFlagType };
|
||||||
|
|
||||||
|
runAutomaticBooleanFlagNegation(opts, flag);
|
||||||
|
assureValidFlagConfigState(opts, flag);
|
||||||
|
if(flag.config !== undefined)
|
||||||
|
flag.type = flag.config.type;
|
||||||
|
|
||||||
if(meta.hasFlagAssignedValue) {
|
if(meta.hasFlagAssignedValue) {
|
||||||
/** @type {InternalFlagsFlagObj} */
|
naturalFlagAssignment = { isLookingForValue: false, requiredType: null };
|
||||||
const flag = { key: flagKey, value: meta.flagAssignedValue, isShorthand: false, lookedupFlagConfig: true, flagConfig: flagConfig };
|
const succeeded = runFlagAssignedValueAssignment(opts, meta, flag, argv);
|
||||||
if(!isAssignedValueTypeAssignable(opts, meta, flag, argv))
|
if(succeeded)
|
||||||
return;
|
|
||||||
|
|
||||||
flags.push(flag);
|
flags.push(flag);
|
||||||
lookingForFlagVal = false;
|
continue;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flags.push({ key: flagKey, value: UNSET_FLAG_VALUE, isShorthand: false, lookedupFlagConfig: true, flagConfig });
|
flags.push(flag);
|
||||||
lookingForFlagVal = true;
|
naturalFlagAssignment = { isLookingForValue: true, requiredType: flag.type };
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -771,84 +963,61 @@ function parser(argv, opts) {
|
|||||||
if(opts.handleBackslashesForSpecialFlagChars && inputVal.startsWith('\\-'))
|
if(opts.handleBackslashesForSpecialFlagChars && inputVal.startsWith('\\-'))
|
||||||
inputVal = inputVal.slice(1);
|
inputVal = inputVal.slice(1);
|
||||||
|
|
||||||
if(!lookingForFlagVal) {
|
if(!naturalFlagAssignment.isLookingForValue) {
|
||||||
if(opts.lowerCaseInputValues)
|
if(opts.lowerCaseInputValues)
|
||||||
inputVal = inputVal.toLowerCase();
|
inputVal = inputVal.toLowerCase();
|
||||||
input.push(inputVal);
|
input.push(inputVal);
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const flag = flags.at(-1);
|
const flag = flags.at(-1);
|
||||||
/** @readonly */
|
|
||||||
let flagConfig = flag.flagConfig
|
if(isTypeAssignable(flag.type, getCastableType(inputVal))) {
|
||||||
if(!flag.lookedupFlagConfig) {
|
const flagVal = runFlagValueTypeResolving(opts, inputVal, flag.type);
|
||||||
if(flag.isShorthand)
|
|
||||||
flagConfig = getShorthandFlag(opts, flag.key);
|
if(flag.isNegated && typeof flagVal === 'boolean' && isTypeAssignable(flag.type, 'boolean')) {
|
||||||
else
|
flag.value = !flagVal
|
||||||
flagConfig = getFullFlag(opts, flag.key);
|
} else {
|
||||||
flag.flagConfig = flagConfig;
|
flag.value = flagVal;
|
||||||
flag.lookedupFlagConfig = true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let flagType = opts.defaultFlagType;
|
|
||||||
if(flagConfig !== undefined && flagConfig.type !== undefined)
|
|
||||||
flagType = flagConfig.type;
|
|
||||||
|
|
||||||
const castableType = getCastableType(inputVal);
|
|
||||||
|
|
||||||
if(['string', 'any', castableType].includes(flagType) || (castableType === 'number' && flagType === 'bigint'))
|
|
||||||
flag.value = inputVal;
|
|
||||||
else {
|
else {
|
||||||
if(opts.lowerCaseInputValues)
|
if(opts.lowerCaseInputValues)
|
||||||
inputVal = inputVal.toLowerCase();
|
inputVal = inputVal.toLowerCase();
|
||||||
input.push(inputVal);
|
input.push(inputVal);
|
||||||
}
|
}
|
||||||
lookingForFlagVal = false;
|
naturalFlagAssignment = { isLookingForValue: false, requiredType: null };
|
||||||
return;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const flagReturnObj = {};
|
const flagReturnObj = {};
|
||||||
/** Some (not all, some is run in previous loop) Flag processing (like checking types, which we can't run in the argv.forEach) and return object building */
|
/** Some (not all, some is run in previous loop) Flag processing (like checking types, which we can't run in the argv.forEach) and return object building */
|
||||||
flags.forEach((flag) => {
|
flags.forEach((flag) => {
|
||||||
/** Only on type flagVal */
|
|
||||||
runAutomaticBooleanFlagNegation(opts, flag);
|
|
||||||
|
|
||||||
/** Building return OBJ */
|
|
||||||
/** @readonly */
|
|
||||||
let flagConfig = flag.flagConfig
|
|
||||||
if(!flag.lookedupFlagConfig) {
|
|
||||||
if(flag.isShorthand)
|
|
||||||
flagConfig = getShorthandFlag(opts, flag.key);
|
|
||||||
else
|
|
||||||
flagConfig = getFullFlag(opts, flag.key);
|
|
||||||
}
|
|
||||||
ok(flagConfig !== null, 'flagConfig should always be undefined if not found, and would only be in the wrong state of null if not reassigned at a later point (which should be impossible');
|
|
||||||
|
|
||||||
if(flag.value === UNSET_FLAG_VALUE) {
|
if(flag.value === UNSET_FLAG_VALUE) {
|
||||||
let flagType = opts.defaultFlagType
|
if(flag.config !== undefined) {
|
||||||
if(flagConfig !== undefined) {
|
if(flag.config.requiredValue === true) {
|
||||||
flagType = flagConfig.type;
|
const message = `Flag "${flag.config.name}" is missing a required value of type "${flag.config.type}"!`;
|
||||||
if(flagConfig.requiredValue === true) {
|
|
||||||
const message = `Flag "${flagConfig.name}" is missing a required value of type "${flagConfig.type}"!`;
|
|
||||||
const error = new InputError(message);
|
const error = new InputError(message);
|
||||||
error.context = { flag: flagConfig, flagType: flagConfig.type, flagMatch: flag.key, value: UNSET_FLAG_VALUE, argv };
|
error.context = { flag: flag.config, flagType: flag.config.type, flagMatch: flag.key, value: UNSET_FLAG_VALUE, argv };
|
||||||
return giveError(opts, {
|
return giveError(opts, {
|
||||||
error,
|
error,
|
||||||
message: message + `\n - Flag: "${flagConfig.name}", match: "${flag.key}", value: UNSET_FLAG_VALUE, type: "${flagConfig.type}"`,
|
message: message + `\n - Flag: "${flag.config.name}", match: "${flag.key}", value: UNSET_FLAG_VALUE, type: "${flag.config.type}"`,
|
||||||
exitCode: 1
|
exitCode: 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/** Set default unless bool type */
|
/** Set default except booleans (since this function only runs if the flag is present, and a boolean flag being present is by itself a true value (unless negated)) */
|
||||||
if(typeof flagConfig.default !== 'undefined' && flagConfig.type !== 'boolean')
|
if(typeof flag.config.default !== 'undefined' && flag.config.type !== 'boolean')
|
||||||
flag.value = flagConfig.default;
|
flag.value = flag.config.default;
|
||||||
}
|
}
|
||||||
if(flag.value === UNSET_FLAG_VALUE) {
|
if(flag.value === UNSET_FLAG_VALUE) {
|
||||||
switch(flagType) {
|
switch(flag.type) {
|
||||||
case 'any':
|
case 'any':
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
|
/** ProCoder: flag.value = !flag.isNegated */
|
||||||
flag.value = true;
|
flag.value = true;
|
||||||
|
if(flag.isNegated)
|
||||||
|
flag.value = false;
|
||||||
break;
|
break;
|
||||||
case "bigint":
|
case "bigint":
|
||||||
case "number":
|
case "number":
|
||||||
@ -857,35 +1026,33 @@ function parser(argv, opts) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if(typeof flag.value === 'string')
|
if(typeof flag.value === 'string')
|
||||||
flag.value = runFlagValueTypeResolving(opts, flag.value, flagConfig);
|
flag.value = runFlagValueTypeResolving(opts, flag.value, flag.type);
|
||||||
if(typeof flag.value === 'string' && opts.lowerCaseFlagValues)
|
if(typeof flag.value === 'string' && opts.lowerCaseFlagValues)
|
||||||
flag.value = flag.value.toLowerCase()
|
flag.value = flag.value.toLowerCase()
|
||||||
|
|
||||||
let realFlagKey = flag.key;
|
|
||||||
if(flagConfig !== undefined) {
|
|
||||||
realFlagKey = flagConfig.name;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
unknownFlag(opts, flag.key, flag.value, argv);
|
|
||||||
|
|
||||||
flagReturnObj[realFlagKey] = flag.value;
|
if(flag.config === undefined && !opts.allowUnknownFlags)
|
||||||
|
return unknownFlagError(opts, flag.key, flag.value, argv);
|
||||||
|
|
||||||
|
const flagName = flag.config?.name || flag.key;
|
||||||
|
|
||||||
|
flagReturnObj[flagName] = flag.value;
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Setting missing flags */
|
/** Setting missing flags */
|
||||||
Object.entries(opts.flags).forEach(([flagName, flagConfig]) => {
|
Object.entries(opts.flags).forEach(([flagName, flagConfig]) => {
|
||||||
if(typeof flagReturnObj[flagName] !== 'undefined')
|
if(typeof flagReturnObj[flagName] !== 'undefined')
|
||||||
return;
|
return;
|
||||||
/** Not a needed check as you can tell, but present to explicitely highlight that behavior in the code */
|
/** Not a needed check as you can tell, but present to explicitly highlight that behavior in the code */
|
||||||
if(typeof flagConfig.default === 'undefined')
|
if(typeof flagConfig.default === 'undefined')
|
||||||
return flagReturnObj[flagName] = undefined;
|
return flagReturnObj[flagName] = undefined;
|
||||||
flagReturnObj[flagName] = flagConfig.default;
|
flagReturnObj[flagName] = flagConfig.default;
|
||||||
});
|
});
|
||||||
|
|
||||||
/// @ts-ignore
|
/// @ts-ignore - falsly wrong flags type
|
||||||
return { flags: flagReturnObj, input };
|
return { flags: flagReturnObj, input, unparsed };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { parser };
|
module.exports = { parser, InputError };
|
||||||
56
types.d.ts
vendored
56
types.d.ts
vendored
@ -84,6 +84,62 @@ export interface ParserOpts<Flags extends FlagsI> {
|
|||||||
handleBackslashesForSpecialFlagChars?: boolean
|
handleBackslashesForSpecialFlagChars?: boolean
|
||||||
/** If enabled, automatically adds shorthands as valid flag aliases (eg: -f &-> --f) @default true */
|
/** If enabled, automatically adds shorthands as valid flag aliases (eg: -f &-> --f) @default true */
|
||||||
addFlagShorthandsAsAliases?: boolean
|
addFlagShorthandsAsAliases?: boolean
|
||||||
|
/**
|
||||||
|
* If a function is provided, stops parsing when that function returns a truthy value.
|
||||||
|
*
|
||||||
|
* Adds any following args (including the matching arg) into the returned unparsed array.
|
||||||
|
*
|
||||||
|
* **Overrides shouldStopParsingStr**
|
||||||
|
* @param arg - to-be parsed arg
|
||||||
|
* @param index - index of the arg within the argv array
|
||||||
|
* @param argv - argv array
|
||||||
|
* @param argMeta - meta object containing informations about the arg (*properties subject to change)
|
||||||
|
* @param input - input array, containing the currently parsed input values
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
shouldStopParsingFunc?(arg: string, index: number, argv: string[], argMeta: argMeta, input: string[]): boolean
|
||||||
|
/**
|
||||||
|
* If a string is provided, stops parsing if the to-be parsed arg matches the string provided exactly.
|
||||||
|
* The to-be parsed arg is not yet lowercased by any lowerCase* options, it is therefore recommended to use shouldStopParsingFunc instead if that is a requirement.
|
||||||
|
*
|
||||||
|
* Adds any following args (including the matching arg) into the returned unparsed array.
|
||||||
|
*
|
||||||
|
* **Overridden by shouldStopParsingFunc**
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
shouldStopParsingStr?: null | string
|
||||||
|
/**
|
||||||
|
* If a function is provided, will only parse the provided arg if the provided function returns a truthy value.
|
||||||
|
*
|
||||||
|
* Runs after shouldStopParsingFunc
|
||||||
|
*
|
||||||
|
* Does not add filtered out args into the returned unparsed array.
|
||||||
|
*
|
||||||
|
* @param arg - to-be parsed arg
|
||||||
|
* @param index - index of the arg within the argv array
|
||||||
|
* @param argv - argv array
|
||||||
|
* @param argMeta - meta object containing informations about the arg (*properties subject to change)
|
||||||
|
* @param input - input array, containing the currently parsed input values
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
parseFilterFunc?(arg: string, index: number, argv: string[], argMeta: argMeta, input: string[]): boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type argMeta = {
|
||||||
|
/** Is set to true if the arg will be parsed as a flag */
|
||||||
|
isFlag: boolean;
|
||||||
|
/** Is isFlag is true, the flag will be parsed as a shorthand */
|
||||||
|
isShorthand: boolean;
|
||||||
|
/** Is isFlag is true, is set to the flag key without any leading flag characters (eg: `"--flag" -> "flag"`) */
|
||||||
|
flagKey: string;
|
||||||
|
/** Is true if isFlag is true and the flag has an assigned value (ie: `arg === "--flag=value"`) */
|
||||||
|
hasFlagAssignedValue: boolean;
|
||||||
|
/** Is hasFlagAssignedValue is true, is set to the flag's assigned value (eg: `"--flag=value" -> "value"`) */
|
||||||
|
flagAssignedValue: string;
|
||||||
|
/** Is set to true if the arg will be treated as an input string, ie: if isFlag and isFlagNaturalValue are false */
|
||||||
|
isInput: boolean;
|
||||||
|
/** Is set to true if this arg would be treated as the value to the most recently parsed flag (ie: `["--flag", "value"][1].argMeta.isFlagNaturalValue === true` ) */
|
||||||
|
isFlagNaturalValue: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function __type__getType(e: any) { return typeof e };
|
function __type__getType(e: any) { return typeof e };
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user