2018-10-24 04:39:04 +02:00
|
|
|
import initState from './initState';
|
|
|
|
import IOption from './IOption';
|
|
|
|
import runPreParseHook from './runPreParseHook';
|
|
|
|
import verifyAndStoreOptionValue from './verifyAndStoreOptionValue';
|
2018-10-23 23:17:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 } } {
|
2018-10-24 04:39:04 +02:00
|
|
|
//Initialize state
|
|
|
|
const { outputArgs, shortToLongLookup, requiredOptions, failFunction } = initState(spec);
|
|
|
|
const args = runPreParseHook({ args: process.argv.slice(2), preParseHook, fail: failFunction });
|
2018-10-23 23:17:03 +02:00
|
|
|
|
|
|
|
//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.
|
2018-10-24 04:39:04 +02:00
|
|
|
let option = '';
|
2018-10-23 23:17:03 +02:00
|
|
|
for (const arg of args) {
|
2018-10-24 04:39:04 +02:00
|
|
|
if (option === '') {
|
2018-10-23 23:17:03 +02:00
|
|
|
/** We expect a name, must be one of:
|
|
|
|
* - `--name value`
|
|
|
|
* - `--name=value`
|
|
|
|
* - `-n value`
|
|
|
|
* - `-n=value`
|
|
|
|
*/
|
|
|
|
if (arg === '-' || arg === '--') {
|
|
|
|
failFunction(`Empty option "-" or "--" is not supported.`);
|
|
|
|
} else if (arg.startsWith('--')) { //long argument
|
2018-10-24 04:39:04 +02:00
|
|
|
option = arg.substr(2);
|
|
|
|
let value;
|
2018-10-23 23:17:03 +02:00
|
|
|
//If there's a =, immediately read the value
|
2018-10-24 04:39:04 +02:00
|
|
|
if (option.indexOf('=') !== -1) {
|
|
|
|
[option, value] = option.split('=', 2);
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
//Check that argument name is valid
|
2018-10-24 04:39:04 +02:00
|
|
|
if (spec[option] === undefined) {
|
|
|
|
failFunction(`Unknown option "--${option}".`);
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
//If value was provided, check that value is correct and remove name for next loop iteration
|
2018-10-24 04:39:04 +02:00
|
|
|
if (value !== undefined) {
|
|
|
|
verifyAndStoreOptionValue({option, value, verify: spec[option].verify, message: spec[option].message, fail: failFunction, outputArgs, requiredOptions});
|
|
|
|
option = '';
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
} else if (arg.startsWith('-')) { //short argument
|
2018-10-24 04:39:04 +02:00
|
|
|
option = arg.substr(1);
|
|
|
|
let value;
|
2018-10-23 23:17:03 +02:00
|
|
|
//If there's a =, immediately read the value
|
2018-10-24 04:39:04 +02:00
|
|
|
if (option.indexOf('=') !== -1) {
|
|
|
|
[option, value] = option.split('=', 2);
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
//Check that argument name is valid
|
2018-10-24 04:39:04 +02:00
|
|
|
if (shortToLongLookup[option] === undefined) {
|
|
|
|
failFunction(`Unknown short option "-${option}".`);
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
2018-10-24 04:39:04 +02:00
|
|
|
option = shortToLongLookup[option];
|
2018-10-23 23:17:03 +02:00
|
|
|
//If value was provided, check that value is correct and remove name for next loop iteration
|
2018-10-24 04:39:04 +02:00
|
|
|
if (value !== undefined) {
|
|
|
|
verifyAndStoreOptionValue({option, value, verify: spec[option].verify, message: spec[option].message, fail: failFunction, outputArgs, requiredOptions});
|
|
|
|
option = '';
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
failFunction(`Arguments must be preceded by an option but there was no option in front of "${arg}".`);
|
|
|
|
}
|
|
|
|
} else { //We expect a value, can be anything
|
2018-10-24 04:39:04 +02:00
|
|
|
verifyAndStoreOptionValue({option, value: arg, verify: spec[option].verify, message: spec[option].message, fail: failFunction, outputArgs, requiredOptions});
|
|
|
|
option = '';
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// argumentName must be cleared to '' after the value is read, so if it is not an empty string, the value was missing
|
2018-10-24 04:39:04 +02:00
|
|
|
if (option !== '') {
|
|
|
|
failFunction(`Option "${option}" was not followed by a value.`);
|
2018-10-23 23:17:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//check if any entry in requiredArguments was not set
|
|
|
|
for (const optName in requiredOptions) {
|
|
|
|
if (spec.hasOwnProperty(optName) && requiredOptions[optName] === false) {
|
|
|
|
failFunction(`Missing option "${optName}" even though it is required.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
fail: failFunction,
|
|
|
|
args: outputArgs,
|
|
|
|
};
|
|
|
|
}
|