diff --git a/src/index.ts b/src/index.ts index f5e8caf..64040b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import initState from './initState'; -import IOption from './IOption'; +import IOption from './interfaces/IOption'; import parseArgument from './parseArgument'; import runPreParseHook from './runPreParseHook'; @@ -28,11 +28,9 @@ export default function parseArguments( } //check if any entry in requiredArguments was not set - for (const optName in requiredOptions) { - if (spec.hasOwnProperty(optName) && requiredOptions[optName] === false) { - throw new Error(`Missing option "${optName}" even though it is required.`); - } - } + Object.entries(requiredOptions).filter(([, wasFound]) => wasFound === false).forEach(([option]) => { + throw new Error(`Missing option "${option}" even though it is required.`); + }); } catch (error) { fail((error as Error).message); } diff --git a/src/initState.ts b/src/initState.ts index c77bf8a..4355bf4 100644 --- a/src/initState.ts +++ b/src/initState.ts @@ -1,40 +1,19 @@ import failWithError from './failWithError'; -import IOption from './IOption'; - -const failFunction: (error?: string) => never = failWithError.bind(null, ''); +import IOption from './interfaces/IOption'; +import IState from './interfaces/IState'; +import parseSpecEntry from './parseSpecEntry'; export default function initState(spec: { [key: string]: IOption }) { //The parsed arguments that are returned by this function - const outputArgs: { [key: string]: string } = {}; + const outputArgs: IState['outputArgs'] = {}; //Create a lookup table to find a long option when given the short option, and to check if all required options are set - const shortToLongLookup: { [key: string]: string } = {}; - const requiredOptions: { [key: string]: boolean } = {}; - for (const longOption in spec) { - if (spec.hasOwnProperty(longOption)) { - if (longOption === '') { - return failFunction(`The long option may not be an empty string. This is a bug in the source code of the script that you tried to call.`); - } - if (!longOption.match(/^[a-z0-9]+(-[a-z0-9]+)*$/)) { - return failFunction(`The long option "${longOption}" must only contain alpha-numeric characters, optionally separated by hyphens. This is a bug in the source code of the script that you tried to call.`); - } - - const shortOption = spec[longOption].short; - if (shortOption !== undefined && shortOption !== '') { - if (shortOption.length > 1) { - return failFunction(`Short options must only be one character long but the short option "${shortOption}" for "${longOption}" was ${shortOption.length} characters long. This is a bug in the source code of the script that you tried to call.`); - } - shortToLongLookup[shortOption] = longOption; - } - - //If a default value is given, use the default value (it can be overridden later), otherwise mark argument as being required - const defaultValue = spec[longOption].default; - if (defaultValue !== undefined) { - outputArgs[longOption] = defaultValue; - } else { - requiredOptions[longOption] = false; - } - } + const shortToLongLookup: IState['shortToLongLookup'] = {}; + const requiredOptions: IState['requiredOptions'] = {}; + try { + Object.entries(spec).forEach(parseSpecEntry.bind(null, { outputArgs, shortToLongLookup, requiredOptions })); + } catch (error) { + failWithError('', (error as Error).message); } const usage = `./${process.argv[1].substr(process.argv[1].lastIndexOf('/') + 1)} [options]\nOPTIONS\n${ diff --git a/src/IOption.ts b/src/interfaces/IOption.ts similarity index 100% rename from src/IOption.ts rename to src/interfaces/IOption.ts diff --git a/src/interfaces/IState.ts b/src/interfaces/IState.ts new file mode 100644 index 0000000..1100357 --- /dev/null +++ b/src/interfaces/IState.ts @@ -0,0 +1,8 @@ +export default interface IState { + /** Stores the parsed arguments that are returned by the argument parser */ + outputArgs: { [key: string]: string }; + /** Lookup table from the short option names to their corresponding long option nmae */ + shortToLongLookup: { [key: string]: string }; + /** For each required option, store if the parser already encountered it. At the end of parsing, all entries must be set to true or the parser fails. */ + requiredOptions: { [key: string]: boolean }; +} diff --git a/src/parseArgument.ts b/src/parseArgument.ts index 4c07c0b..8365726 100644 --- a/src/parseArgument.ts +++ b/src/parseArgument.ts @@ -1,12 +1,13 @@ -import IOption from './IOption'; +import IOption from './interfaces/IOption'; +import IState from './interfaces/IState'; import verifyAndStoreOptionValue from './verifyAndStoreOptionValue'; export default function parseArgument( { spec, outputArgs, requiredOptions, shortToLongLookup }: { spec: { [key: string]: IOption }, - outputArgs: { [key: string]: string }, - requiredOptions: { [key: string]: boolean }, - shortToLongLookup: { [key: string]: string }, + outputArgs: IState['outputArgs'], + requiredOptions: IState['requiredOptions'], + shortToLongLookup: IState['shortToLongLookup'], }, previousOption: string, arg: string, diff --git a/src/parseSpecEntry.ts b/src/parseSpecEntry.ts new file mode 100644 index 0000000..2806925 --- /dev/null +++ b/src/parseSpecEntry.ts @@ -0,0 +1,25 @@ +import IOption from './interfaces/IOption'; +import IState from './interfaces/IState'; + +export default function parseSpecEntry({ outputArgs, requiredOptions, shortToLongLookup }: IState, [longOption, { short: shortOption, default: defaultValue }]: [string, IOption]) { + if (longOption === '') { + throw new Error(`The long option may not be an empty string. This is a bug in the source code of the script that you tried to call.`); + } + if (!longOption.match(/^[a-z0-9]+(-[a-z0-9]+)*$/)) { + throw new Error(`The long option "${longOption}" must only contain alpha-numeric characters, optionally separated by hyphens. This is a bug in the source code of the script that you tried to call.`); + } + + if (shortOption !== undefined && shortOption !== '') { + if (shortOption.length > 1) { + throw new Error(`Short options must only be one character long but the short option "${shortOption}" for "${longOption}" was ${shortOption.length} characters long. This is a bug in the source code of the script that you tried to call.`); + } + shortToLongLookup[shortOption] = longOption; + } + + //If a default value is given, use the default value (it can be overridden later), otherwise mark argument as being required + if (defaultValue !== undefined) { + outputArgs[longOption] = defaultValue; + } else { + requiredOptions[longOption] = false; + } +} diff --git a/src/verifyAndStoreOptionValue.ts b/src/verifyAndStoreOptionValue.ts index 3b64317..d009275 100644 --- a/src/verifyAndStoreOptionValue.ts +++ b/src/verifyAndStoreOptionValue.ts @@ -1,4 +1,5 @@ -import IOption from './IOption'; +import IOption from './interfaces/IOption'; +import IState from './interfaces/IState'; export default function verifyAndStoreOptionValue({ option, @@ -12,8 +13,8 @@ export default function verifyAndStoreOptionValue({ value: string, verify: IOption['verify'], message: IOption['message'], - outputArgs: { [key: string]: string }, - requiredOptions: { [key: string]: boolean }, + outputArgs: IState['outputArgs'], + requiredOptions: IState['requiredOptions'], }) { //Verify value for correctness try {