Added bigint flag type & negated flag bug fixes due to flag.requiredValue option, some refactors for run<Thing> & some other internal functions & fixed 'ignore' value for opts.behaviorOnInputError & fixed flag values on string typed flags when opts.resolveFlagValueTypes was turned on
This commit is contained in:
parent
aef5a62455
commit
ef73c447df
188
index.js
188
index.js
@ -31,6 +31,13 @@ const { ok } = require('node:assert');
|
|||||||
* @typedef {import('./types.d.ts').FlagAny} FlagAny
|
* @typedef {import('./types.d.ts').FlagAny} FlagAny
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
class InputError extends Error {
|
||||||
|
context = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnsetFlagValueType {}
|
||||||
|
const UNSET_FLAG_VALUE = new UnsetFlagValueType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {JSTypes} type
|
* @param {JSTypes} type
|
||||||
@ -90,10 +97,10 @@ function namedValidateOrDefaultIfUnset(objName, obj, keyName, keyType, defaultVa
|
|||||||
*/
|
*/
|
||||||
function validateAndFillDefaults(opts) {
|
function validateAndFillDefaults(opts) {
|
||||||
|
|
||||||
const allowedTypeofDefaultType = ["string", "number", "boolean"];
|
const allowedTypeofDefaultType = ["string", "number", "bigint", "boolean"];
|
||||||
const validFlagTypes = ['any', ...allowedTypeofDefaultType];
|
const validFlagTypes = ['any', ...allowedTypeofDefaultType];
|
||||||
|
|
||||||
const allowedBehaviorOnInputErrorTypes = ['throw', 'log', 'log&exit'];
|
const allowedBehaviorOnInputErrorTypes = ['throw', 'log', 'log&exit', 'ignore'];
|
||||||
|
|
||||||
validateOrDefaultIfUnset(opts, 'warningLogger', 'function', console.log);
|
validateOrDefaultIfUnset(opts, 'warningLogger', 'function', console.log);
|
||||||
validateOrDefaultIfUnset(opts, 'behaviorOnInputError', 'string', 'throw');
|
validateOrDefaultIfUnset(opts, 'behaviorOnInputError', 'string', 'throw');
|
||||||
@ -296,21 +303,20 @@ function getShorthandFlag(opts, flagKeyInput) {
|
|||||||
/**
|
/**
|
||||||
* @related runFlagValueTypeResolving
|
* @related runFlagValueTypeResolving
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @returns {"number"|"boolean"|"string"}
|
* @returns {"number"|"bigint"|"boolean"|"string"}
|
||||||
*/
|
*/
|
||||||
function getCastableType(input) {
|
function getCastableType(input) {
|
||||||
if(input.toLowerCase() === 'true' || input.toLowerCase() === 'false')
|
if(input.toLowerCase() === 'true' || input.toLowerCase() === 'false')
|
||||||
return 'boolean';
|
return 'boolean';
|
||||||
/// @ts-ignore
|
/// @ts-ignore
|
||||||
|
if(input.length >= 2 && input.endsWith('n') && !isNaN(input.slice(0, -1)))
|
||||||
|
return 'bigint';
|
||||||
|
/// @ts-ignore
|
||||||
if(!isNaN(input))
|
if(!isNaN(input))
|
||||||
return 'number';
|
return 'number';
|
||||||
return 'string';
|
return 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
class InputError extends Error {
|
|
||||||
context = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {{ error: InputError | Error, message: string, exitCode: number }} errorObj
|
* @param {{ error: InputError | Error, message: string, exitCode: number }} errorObj
|
||||||
@ -347,7 +353,9 @@ function isAssignedValueTypeAssignable(opts, meta, flag, argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const castableType = getCastableType(meta.flagAssignedValue);
|
const castableType = getCastableType(meta.flagAssignedValue);
|
||||||
if(flagConfig !== undefined && flagConfig.type !== 'any' && flagConfig.type !== castableType) {
|
if(flagConfig !== undefined && flagConfig.type !== 'any' && flagConfig.type !== 'string' && flagConfig.type !== castableType) {
|
||||||
|
if(flagConfig.type === 'bigint' && castableType === 'number')
|
||||||
|
return true;
|
||||||
const message = `Value "${meta.flagAssignedValue}" (type:"${castableType}") is not assignable to required type "${flagConfig.type}".`;
|
const message = `Value "${meta.flagAssignedValue}" (type:"${castableType}") is not assignable to required type "${flagConfig.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: flagConfig, flagType: flagConfig.type, flagMatch: meta.flagKey, value: meta.flagAssignedValue, assignableType: castableType, argv };
|
||||||
@ -403,60 +411,113 @@ function getAllShorthandFlags(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @modifies flag
|
||||||
|
* And uh, yeah bit of a heavy refactor given it's a biggggg chicken and egg situation, where I need the type of the flag value (assigned or given or default), but to get the
|
||||||
|
* proper flag config, I first need to run the negated boolean flag, so uh yeah, need to run allat twice :D
|
||||||
|
* ### To be run before any flag value assignments (flag.value always expected to be string)
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {string} flagKey
|
* @param {InternalFlagsFlagObj} flag
|
||||||
* @param {any} flagVal
|
|
||||||
*/
|
*/
|
||||||
function runFullFlagAutomaticBooleanNegation(opts, flagKey, flagVal) {
|
function runAutomaticBooleanFlagNegation(opts, flag) {
|
||||||
/** Make sure flag isn't set in our vals */
|
if(opts.automaticBooleanFlagNegation.enabled !== true)
|
||||||
if(!hasFullFlag(opts, flagKey) && opts.automaticBooleanFlagNegation.enabled && typeof flagVal === 'boolean' && flagKey.length > 2) {
|
return false;
|
||||||
if(flagKey.length > 3 && flagKey.toLowerCase().startsWith('no-')) {
|
if(flag.isShorthand && opts.automaticBooleanFlagNegation.shorthandNegation !== true)
|
||||||
flagKey = flagKey.slice(3);
|
return false;
|
||||||
flagVal = !flagVal;
|
|
||||||
}
|
|
||||||
else if(opts.automaticBooleanFlagNegation.allowUnspacedNegatedFlags && flagKey.toLowerCase().startsWith('no')) {
|
|
||||||
flagKey = flagKey.slice(2);
|
|
||||||
flagVal = !flagVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { flagKey, flagVal }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** @readonly */
|
||||||
* @param {DefaultParserOptsType} opts
|
let newKey = null;
|
||||||
* @param {string} flagKey
|
/** @readonly */
|
||||||
* @param {any} flagVal
|
let flagConfig = null;
|
||||||
*/
|
let negated = false;
|
||||||
function runShorthandFlagAutomaticBooleanNegation(opts, flagKey, flagVal) {
|
|
||||||
/** Make sure flag isn't set in our vals */
|
|
||||||
if(!hasFullFlag(opts, flagKey) && opts.automaticBooleanFlagNegation.enabled && opts.automaticBooleanFlagNegation.shorthandNegation && typeof flagVal === 'boolean' && flagKey.length >= 2) {
|
if(!flag.isShorthand) {
|
||||||
if(opts.automaticBooleanFlagNegation.allowUnspacedNegatedFlags && flagKey.toLowerCase().startsWith('n')) {
|
/** Make sure flag isn't set in our vals */
|
||||||
flagKey = flagKey.slice(1);
|
if(!hasFullFlag(opts, flag.key) && flag.key.length > 2) {
|
||||||
flagVal = !flagVal;
|
|
||||||
|
if(flag.key.length > 3 && flag.key.toLowerCase().startsWith('no-')) {
|
||||||
|
newKey = flag.key.slice(3);
|
||||||
|
negated = true;
|
||||||
|
flagConfig = getFullFlag(opts, newKey);
|
||||||
|
}
|
||||||
|
else if(opts.automaticBooleanFlagNegation.allowUnspacedNegatedFlags && flag.key.toLowerCase().startsWith('no')) {
|
||||||
|
newKey = flag.key.slice(2);
|
||||||
|
negated = true;
|
||||||
|
flagConfig = getFullFlag(opts, newKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/** Make sure flag isn't set in our vals */
|
||||||
|
if(!hasShorthandFlag(opts, flag.key) && flag.key.length >= 2) {
|
||||||
|
if(flag.key.toLowerCase().startsWith('n')) {
|
||||||
|
newKey = flag.key.slice(1);
|
||||||
|
negated = true;
|
||||||
|
flagConfig = getShorthandFlag(opts, newKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { flagKey, flagVal }
|
if(negated) {
|
||||||
|
/** @readonly */
|
||||||
|
let flagType = opts.defaultFlagType;
|
||||||
|
if(typeof flagConfig !== 'undefined')
|
||||||
|
flagType = flagConfig.type;
|
||||||
|
|
||||||
|
/** @type {ReturnType<typeof getCastableType>} */
|
||||||
|
let castableType = 'boolean';
|
||||||
|
if(flag.value !== UNSET_FLAG_VALUE)
|
||||||
|
/// @ts-ignore
|
||||||
|
castableType = getCastableType(flag.value);
|
||||||
|
|
||||||
|
if((flagType === 'boolean' || flagType === 'any') && castableType === 'boolean') {
|
||||||
|
flag.key = newKey;
|
||||||
|
flag.flagConfig = flagConfig;
|
||||||
|
flag.lookedupFlagConfig = 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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @related getCastableType
|
* @related getCastableType
|
||||||
|
* ### Warning: This does not check the flag type despite passing in a flagConfig!
|
||||||
* @param {DefaultParserOptsType} opts
|
* @param {DefaultParserOptsType} opts
|
||||||
* @param {string} flagKey
|
* @param {string} flagVal
|
||||||
* @param {any} flagVal
|
* @param {ReturnType<typeof getFlag>} flagConfig
|
||||||
*/
|
*/
|
||||||
function runFlagValueTypeResolving(opts, flagKey, flagVal) {
|
function runFlagValueTypeResolving(opts, flagVal, flagConfig) {
|
||||||
|
/** @type {any} */
|
||||||
|
let resolvedFlagVal = flagVal;
|
||||||
|
|
||||||
/** boolean checking */
|
/** boolean checking */
|
||||||
if(flagVal.toLowerCase() === 'true')
|
if(flagVal.toLowerCase() === 'true')
|
||||||
flagVal = true;
|
resolvedFlagVal = true;
|
||||||
else if(flagVal.toLowerCase() === 'false')
|
else if(flagVal.toLowerCase() === 'false')
|
||||||
flagVal = false;
|
resolvedFlagVal = false;
|
||||||
else if(opts.resolveFlagValueTypes) {
|
else if(opts.resolveFlagValueTypes) {
|
||||||
/** number checking */
|
/** bigint checking */
|
||||||
/// @ts-ignore
|
/// @ts-ignore
|
||||||
if(!isNaN(flagVal))
|
if(flagVal.length >= 2 && flagVal.endsWith('n') && !isNaN(flagVal.slice(0, -1)))
|
||||||
flagVal = Number(flagVal);
|
resolvedFlagVal = BigInt(flagVal.slice(0, -1));
|
||||||
|
/** number & bigint checking */
|
||||||
|
/// @ts-ignore
|
||||||
|
else if(!isNaN(flagVal)) {
|
||||||
|
if(typeof flagConfig === 'undefined' || flagConfig.type === 'number' || flagConfig.type === 'any')
|
||||||
|
resolvedFlagVal = Number(flagVal);
|
||||||
|
else if (typeof flagConfig !== 'undefined' && flagConfig.type === 'bigint')
|
||||||
|
resolvedFlagVal = BigInt(flagVal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { flagVal };
|
return resolvedFlagVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion SubworkFunctions
|
//#endregion SubworkFunctions
|
||||||
@ -586,9 +647,6 @@ function parseShorthandFlags(shorthandFlags, shorthandString) {
|
|||||||
//#region end SecondaryParsers
|
//#region end SecondaryParsers
|
||||||
//#endregion end SecondaryParsers
|
//#endregion end SecondaryParsers
|
||||||
|
|
||||||
class UnsetFlagValueType {}
|
|
||||||
const UNSET_FLAG_VALUE = new UnsetFlagValueType();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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, lookedupFlagConfig: boolean, flagConfig: ReturnType<typeof getFlag> }} InternalFlagsFlagObj
|
||||||
@ -689,9 +747,9 @@ function parser(argv, opts) {
|
|||||||
if(flagConfig !== undefined && flagConfig.type !== undefined)
|
if(flagConfig !== undefined && flagConfig.type !== undefined)
|
||||||
flagType = flagConfig.type;
|
flagType = flagConfig.type;
|
||||||
|
|
||||||
const { flagVal } = runFlagValueTypeResolving(opts, flag.key, arg);
|
const castableType = getCastableType(arg);
|
||||||
|
|
||||||
if(flagType === typeof flagVal || flagType === 'any')
|
if(['string', 'any', castableType].includes(flagType) || (castableType === 'number' && flagType === 'bigint'))
|
||||||
flag.value = arg;
|
flag.value = arg;
|
||||||
else
|
else
|
||||||
input.push(arg);
|
input.push(arg);
|
||||||
@ -707,6 +765,9 @@ function parser(argv, opts) {
|
|||||||
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);
|
||||||
|
|
||||||
/** Process some flag values */
|
/** Process some flag values */
|
||||||
let { key: flagKey, value: flagVal } = flag;
|
let { key: flagKey, value: flagVal } = flag;
|
||||||
|
|
||||||
@ -722,10 +783,9 @@ function parser(argv, opts) {
|
|||||||
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');
|
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(flagVal === UNSET_FLAG_VALUE) {
|
if(flagVal === UNSET_FLAG_VALUE) {
|
||||||
/** Setting defaults */
|
let flagType = opts.defaultFlagType
|
||||||
/** @type {number|string|boolean} */
|
|
||||||
flagVal = true;
|
|
||||||
if(flagConfig !== undefined) {
|
if(flagConfig !== undefined) {
|
||||||
|
flagType = flagConfig.type;
|
||||||
if(flagConfig.requiredValue === true) {
|
if(flagConfig.requiredValue === true) {
|
||||||
const message = `Flag "${flagConfig.name}" is missing a required value of type "${flagConfig.type}"!`;
|
const message = `Flag "${flagConfig.name}" is missing a required value of type "${flagConfig.type}"!`;
|
||||||
const error = new InputError(message);
|
const error = new InputError(message);
|
||||||
@ -740,15 +800,23 @@ function parser(argv, opts) {
|
|||||||
if(typeof flagConfig.default !== 'undefined' && flagConfig.type !== 'boolean')
|
if(typeof flagConfig.default !== 'undefined' && flagConfig.type !== 'boolean')
|
||||||
flagVal = flagConfig.default;
|
flagVal = flagConfig.default;
|
||||||
}
|
}
|
||||||
|
if(flagVal === UNSET_FLAG_VALUE) {
|
||||||
|
switch(flagType) {
|
||||||
|
case 'any':
|
||||||
|
case 'boolean':
|
||||||
|
flagVal = true;
|
||||||
|
break;
|
||||||
|
case "bigint":
|
||||||
|
case "number":
|
||||||
|
case "string":
|
||||||
|
flagVal = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof flagVal === 'string')
|
if(typeof flagVal === 'string')
|
||||||
({ flagVal } = runFlagValueTypeResolving(opts, flagKey, flagVal));
|
flagVal = runFlagValueTypeResolving(opts, flagVal, flagConfig);
|
||||||
/** Only on type flagVal */
|
|
||||||
if(flag.isShorthand)
|
|
||||||
({ flagKey, flagVal } = runShorthandFlagAutomaticBooleanNegation(opts, flagKey, flagVal));
|
|
||||||
else
|
|
||||||
({ flagKey, flagVal } = runFullFlagAutomaticBooleanNegation(opts, flagKey, flagVal));
|
|
||||||
|
|
||||||
|
|
||||||
flag.key = flagKey;
|
flag.key = flagKey;
|
||||||
@ -764,7 +832,7 @@ function parser(argv, opts) {
|
|||||||
flagReturnObj[realFlagKey] = flag.value;
|
flagReturnObj[realFlagKey] = flag.value;
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Setting defaults if missing */
|
/** 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;
|
||||||
|
|||||||
4
types.d.ts
vendored
4
types.d.ts
vendored
@ -9,7 +9,7 @@ interface FlagT<T, Z> {
|
|||||||
requiredValue?: boolean
|
requiredValue?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlagAny = FlagT<string, "string"> | FlagT<boolean, "boolean"> | FlagT<number, "number"> | FlagT<string | number | boolean, "any">
|
export type FlagAny = FlagT<string, "string"> | FlagT<boolean, "boolean"> | FlagT<number, "number"> | FlagT<bigint, "bigint"> | FlagT<string | number | bigint | boolean, "any">
|
||||||
|
|
||||||
export interface FlagsI { [key: readonly string]: FlagAny }
|
export interface FlagsI { [key: readonly string]: FlagAny }
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ export interface ParserOpts<Flags extends FlagsI> {
|
|||||||
/** Override to change how warnings are emitted @default console.warn */
|
/** Override to change how warnings are emitted @default console.warn */
|
||||||
warningLogger?: (log: string) => void;
|
warningLogger?: (log: string) => void;
|
||||||
/** Default type for either unknown flags or flags without an explicit type being set @default "any" */
|
/** Default type for either unknown flags or flags without an explicit type being set @default "any" */
|
||||||
defaultFlagType?: "string" | "boolean" | "number" | "any";
|
defaultFlagType?: "string" | "boolean" | "bigint" | "number" | "any";
|
||||||
/**
|
/**
|
||||||
* Behavior when input does not follow the provided flags constraints (eg: flag assigned value (--flag=value) not being the correct type).
|
* Behavior when input does not follow the provided flags constraints (eg: flag assigned value (--flag=value) not being the correct type).
|
||||||
*
|
*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user