import initState from './initState'; import IOption from './IOption'; import runPreParseHook from './runPreParseHook'; import verifyAndStoreOptionValue from './verifyAndStoreOptionValue'; /** * Checks the command line arguments against the given specification, and returns the arguments as a JavaScript object, as well as a failure function that prints the usage instructions before terminating. * If an error is found, outputs an error message and terminates with a non-zero exit code. * @param spec A specification of what arguments are expected. * @param preParseHook Optionally, a function to modify the command line arguments before parsing them. */ export default function parseArguments( spec: { [key: string]: IOption }, preParseHook?: (args: string[], fail: (message: string) => void) => (string[] | undefined), ): { fail: (errorMessage: string) => void, args: { [key: string]: string } } { //Initialize state const { outputArgs, shortToLongLookup, requiredOptions, fail } = initState(spec); const args = runPreParseHook({ args: process.argv.slice(2), preParseHook, fail }); //Iterate through all command line arguments. When we have read both name and value, verify the argument for correctness. //Show error if a name has no value afterwards, or a value has no name in front of it. let option = ''; for (const arg of args) { if (option === '') { /** We expect a name, must be one of: * - `--name value` * - `--name=value` * - `-n value` * - `-n=value` */ if (arg === '-' || arg === '--') { fail(`Empty option "-" or "--" is not supported.`); } else if (arg.startsWith('--')) { //long argument option = arg.substr(2); let value; //If there's a =, immediately read the value if (option.indexOf('=') !== -1) { [option, value] = option.split('=', 2); } //Check that argument name is valid if (spec[option] === undefined) { fail(`Unknown option "--${option}".`); } //If value was provided, check that value is correct and remove name for next loop iteration if (value !== undefined) { verifyAndStoreOptionValue({option, value, verify: spec[option].verify, message: spec[option].message, fail, outputArgs, requiredOptions}); option = ''; } } else if (arg.startsWith('-')) { //short argument option = arg.substr(1); let value; //If there's a =, immediately read the value if (option.indexOf('=') !== -1) { [option, value] = option.split('=', 2); } //Check that argument name is valid if (shortToLongLookup[option] === undefined) { fail(`Unknown short option "-${option}".`); } option = shortToLongLookup[option]; //If value was provided, check that value is correct and remove name for next loop iteration if (value !== undefined) { verifyAndStoreOptionValue({option, value, verify: spec[option].verify, message: spec[option].message, fail, outputArgs, requiredOptions}); option = ''; } } else { fail(`Arguments must be preceded by an option but there was no option in front of "${arg}".`); } } else { //We expect a value, can be anything verifyAndStoreOptionValue({option, value: arg, verify: spec[option].verify, message: spec[option].message, fail, outputArgs, requiredOptions}); option = ''; } } // argumentName must be cleared to '' after the value is read, so if it is not an empty string, the value was missing if (option !== '') { fail(`Option "${option}" was not followed by a value.`); } //check if any entry in requiredArguments was not set for (const optName in requiredOptions) { if (spec.hasOwnProperty(optName) && requiredOptions[optName] === false) { fail(`Missing option "${optName}" even though it is required.`); } } return { fail, args: outputArgs, }; }