🚚 Migrate parts to argument-parser repo

This commit is contained in:
C-3PO 2018-10-23 23:26:22 +02:00
parent 2cfe532e6a
commit 48d9a4803d
Signed by: c3po
GPG key ID: 62993C4BB4D86F24
6 changed files with 14 additions and 202 deletions

4
package-lock.json generated
View file

@ -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",

View file

@ -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": {

View file

@ -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} <value>${(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,
};
}

View file

@ -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.` },

View file

@ -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

View file

@ -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