From 48d9a4803dc6750f0ccba53215bc42dcd247fd2d Mon Sep 17 00:00:00 2001 From: C-3PO Date: Tue, 23 Oct 2018 23:26:22 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=9A=20Migrate=20parts=20to=20argument-?= =?UTF-8?q?parser=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 4 + package.json | 6 +- src/funcs/parseArguments.ts | 192 ------------------------------------ src/getManifest.ts | 2 +- src/getPatchZip.ts | 6 +- src/getSolidpkg.ts | 6 +- 6 files changed, 14 insertions(+), 202 deletions(-) delete mode 100644 src/funcs/parseArguments.ts diff --git a/package-lock.json b/package-lock.json index 200c40d..9ae61d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,10 @@ "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", "dev": true }, + "argument-parser": { + "version": "git+https://git.jedipedia.net/swtor/argument-parser.git#7b757e53771dfea2326b2c8d6901e39c66e23230", + "from": "git+https://git.jedipedia.net/swtor/argument-parser.git" + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", diff --git a/package.json b/package.json index 65dd395..fc4fe30 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,16 @@ "homepage": "https://git.jedipedia.net/swtor/ssn-tools", "repository": { "type": "git", - "url": "git@jedipedia.net:swtor/ssn-tools.git" + "url": "https://git.jedipedia.net/swtor/ssn-tools.git" }, - "main": "index.js", "scripts": { - "preinstall": "rm -rf node_modules && rm -f package-lock.json", + "preinstall": "rm -rf node_modules package-lock.json", "start": "rm -rf dist && tsc && chmod +x dist/*.js", "test": "echo \"Error: no test specified\" && exit 1", "update": "rm -rf node_modules package-lock.json && npm install" }, "dependencies": { + "argument-parser": "git+https://git.jedipedia.net/swtor/argument-parser.git", "ssn": "git+https://git.jedipedia.net/swtor/ssn.git" }, "devDependencies": { diff --git a/src/funcs/parseArguments.ts b/src/funcs/parseArguments.ts deleted file mode 100644 index cd9c44c..0000000 --- a/src/funcs/parseArguments.ts +++ /dev/null @@ -1,192 +0,0 @@ -import failWithError from './failWithError'; - -/** - * A command line option - */ -interface IOption { - /** The default value to use, if this argument is missing. If set to `undefined`, this argument is considered to be required and the process will terminate whenever it is missing. */ - default?: string; - /** The short version option as an alias for the long option, must be a single character. Can be set to an empty string or `undefined` if there should be no short option. */ - short?: string; - /** A human readable description of this option, for display in the usage message. Not displayed if `undefined` or an empty string. */ - description?: string; - /** A function that verifies if the given value is a valid value for this option. If set to `undefined`, any value is accepted for this option. */ - verify?: (value: string) => boolean; - /** A function returning a custom error message that is shown if the verify function returns false. If the function is `undefined`, or it returns `undefined` or an empty string, a default error message is shown instead. */ - message?: (value: string) => string; -} - -/** - * 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 } } { - //The parsed arguments that are returned by this function - const outputArgs: { [key: string]: string } = {}; - - //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 === '') { - failWithError('', `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]+)*$/)) { - failWithError('', `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) { - failWithError('', `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; - } - } - } - - //--------------------------------------------------------------- - - let argumentOption = ''; - - const usage = `./${process.argv[1].substr(process.argv[1].lastIndexOf('/') + 1)} [options]\nOPTIONS\n${ - Object.entries(spec).map(([key, {default: defaultValue, short, description}]) => ( - ` ${(defaultValue === undefined) ? '' : '['}${(short !== undefined && short !== '') ? `-${short}, ` : ''}--${key} ${(defaultValue === undefined) ? '' : `], defaults to "${defaultValue}"`}${(description !== undefined && description !== '') ? `\n ${description}` : ''}` - )).join('\n') - }`; - - const failFunction = failWithError.bind(null, usage); - - //The original arguments from the command line - let args = process.argv.slice(2); - - //If defined, run the pre-parse hook to modify the arguments - if (preParseHook !== undefined) { - try { - const modifiedArgs = preParseHook(args, failFunction); - if (modifiedArgs !== undefined) { - args = modifiedArgs; - } - } catch (error) { - failFunction(`Could not run pre-parse hook, encountered error: ${String(error)}.`); - } - } - - function parseValue(argumentValue: string) { - //Verify value for correctness - const verifyFunc = spec[argumentOption].verify; - const messageFunc = spec[argumentOption].message; - try { - if (verifyFunc !== undefined && !verifyFunc(argumentValue)) { - if (messageFunc !== undefined) { - try { - const customMessage = messageFunc(argumentValue); - if (customMessage !== undefined && customMessage !== '') { - failFunction(customMessage); - } - } catch (error) { - failFunction(`Invalid value set for option "${argumentOption}": "${argumentValue}". Could not show custom error message: ${String(error)}`); - } - } - failFunction(`Invalid value set for option "${argumentOption}": "${argumentValue}".`); - } - } catch (error) { - failFunction(`Invalid value set for option "${argumentOption}", the validation of "${argumentValue}" failed: ${String(error)}`); - } - - //Remember the value, and if it is a required argument, mark that we have encountered it - outputArgs[argumentOption] = argumentValue; - if (requiredOptions[argumentOption] === false) { - requiredOptions[argumentOption] = true; - } - argumentOption = ''; - } - - //--------------------------------------------------------------- - - //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. - for (const arg of args) { - if (argumentOption === '') { - /** We expect a name, must be one of: - * - `--name value` - * - `--name=value` - * - `-n value` - * - `-n=value` - */ - if (arg === '-' || arg === '--') { - //TODO: what about the -- option? skip parsing of all following options? - failFunction(`Empty option "-" or "--" is not supported.`); - } else if (arg.startsWith('--')) { //long argument - argumentOption = arg.substr(2); - let argumentValue; - //If there's a =, immediately read the value - if (argumentOption.indexOf('=') !== -1) { - [argumentOption, argumentValue] = argumentOption.split('=', 2); - } - //Check that argument name is valid - if (spec[argumentOption] === undefined) { - failFunction(`Unknown option "--${argumentOption}".`); - } - //If value was provided, check that value is correct and remove name for next loop iteration - if (argumentValue !== undefined) { - parseValue(argumentValue); - argumentOption = ''; - } - } else if (arg.startsWith('-')) { //short argument - argumentOption = arg.substr(1); - let argumentValue; - //If there's a =, immediately read the value - if (argumentOption.indexOf('=') !== -1) { - [argumentOption, argumentValue] = argumentOption.split('=', 2); - } - //Check that argument name is valid - if (shortToLongLookup[argumentOption] === undefined) { - failFunction(`Unknown short option "-${argumentOption}".`); - } - argumentOption = shortToLongLookup[argumentOption]; - //If value was provided, check that value is correct and remove name for next loop iteration - if (argumentValue !== undefined) { - parseValue(argumentValue); - argumentOption = ''; - } - } 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 - parseValue(arg); - } - - } - - // argumentName must be cleared to '' after the value is read, so if it is not an empty string, the value was missing - if (argumentOption !== '') { - failFunction(`Option "${argumentOption}" 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) { - failFunction(`Missing option "${optName}" even though it is required.`); - } - } - - return { - fail: failFunction, - args: outputArgs, - }; -} diff --git a/src/getManifest.ts b/src/getManifest.ts index 7dcd926..7cc7e33 100644 --- a/src/getManifest.ts +++ b/src/getManifest.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node +import parseArguments from 'argument-parser'; import { getManifest, IManifest, Product, verifyProductName } from 'ssn'; -import parseArguments from './funcs/parseArguments'; const { args, fail } = parseArguments({ product: { short: 'p', description: 'Name of the product we want to get the manifest on', verify: verifyProductName, message: (value) => `"${value}" is not a valid product name.` }, diff --git a/src/getPatchZip.ts b/src/getPatchZip.ts index fdca03f..65f2b78 100644 --- a/src/getPatchZip.ts +++ b/src/getPatchZip.ts @@ -1,12 +1,12 @@ #!/usr/bin/env node +import parseArguments from 'argument-parser'; import { getPatchZip, ISsnFileEntry, Product, verifyProductName } from 'ssn'; -import parseArguments from './funcs/parseArguments'; const { args, fail } = parseArguments({ product: { short: 'p', description: 'Name of the product we want to get the patch info on', verify: verifyProductName, message: (value) => `"${value}" is not a valid product name.` }, - from: { short: 'f', description: 'Number of the from release, must be an integer in the range [-1, 999]', verify: (str) => str.match(/^(-1|0|[1-9][0-9]{0,2})$/) !== null }, - to: { short: 't', description: 'Number of the to release, must be an integer in the range [0, 999]', verify: (str) => str.match(/^(0|[1-9][0-9]{0,2})$/) !== null }, + from: { short: 'f', description: 'Number of the from release, must be an integer in the range [-1, 999]', verify: (str) => (str.match(/^(-1|0|[1-9][0-9]{0,2})$/) !== null) }, + to: { short: 't', description: 'Number of the to release, must be an integer in the range [0, 999]', verify: (str) => (str.match(/^(0|[1-9][0-9]{0,2})$/) !== null) }, }, (origArgs) => ((origArgs.length === 3) ? ['--product', origArgs[0], '--from', origArgs[1], '--to', origArgs[2]] : undefined)); //Get the .zip file from this patch and write output to stdout diff --git a/src/getSolidpkg.ts b/src/getSolidpkg.ts index 8c5da55..8173703 100644 --- a/src/getSolidpkg.ts +++ b/src/getSolidpkg.ts @@ -1,12 +1,12 @@ #!/usr/bin/env node +import parseArguments from 'argument-parser'; import { getSolidpkg, ISolidSimple, Product, verifyProductName } from 'ssn'; -import parseArguments from './funcs/parseArguments'; const { args, fail } = parseArguments({ product: { short: 'p', description: 'Name of the product we want to get the solidpkg on', verify: verifyProductName, message: (value) => `"${value}" is not a valid product name.` }, - from: { short: 'f', description: 'Number of the from release, must be an integer in the range [-1, 999]', verify: (str) => str.match(/^(-1|0|[1-9][0-9]{0,2})$/) !== null }, - to: { short: 't', description: 'Number of the to release, must be an integer in the range [0, 999]', verify: (str) => str.match(/^(0|[1-9][0-9]{0,2})$/) !== null }, + from: { short: 'f', description: 'Number of the from release, must be an integer in the range [-1, 999]', verify: (str) => (str.match(/^(-1|0|[1-9][0-9]{0,2})$/) !== null) }, + to: { short: 't', description: 'Number of the to release, must be an integer in the range [0, 999]', verify: (str) => (str.match(/^(0|[1-9][0-9]{0,2})$/) !== null) }, }, (origArgs) => ((origArgs.length === 3) ? ['--product', origArgs[0], '--from', origArgs[1], '--to', origArgs[2]] : undefined)); //Get solidpkg and write output to stdout