Breaking - Change: Rewrite with a new interface
This commit is contained in:
parent
b6005e11bb
commit
f43915c735
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
const opter = function (argv) {
|
|
||||||
return new Opter(argv);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const cfgExample = {
|
|
||||||
synonyms: [['-d', '--debug']],
|
|
||||||
requiredOptions: ['-i', '-o'],
|
|
||||||
requiredArguments: { '-i': {min: 1, max: 1, tooFew: '', tooMany: ''}, }
|
|
||||||
}
|
|
||||||
|
|
||||||
class Opter {
|
|
||||||
constructor(argv, cfg) {
|
|
||||||
// this.opts = ...
|
|
||||||
}
|
|
||||||
|
|
||||||
contains() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
opter.js
149
opter.js
|
|
@ -1,112 +1,83 @@
|
||||||
// https://unix.stackexchange.com/questions/364383/confusion-about-changing-meaning-of-arguments-and-options-is-there-an-official
|
/* For reference:
|
||||||
// https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/V1_chap12.html#tag_12_01
|
|
||||||
|
|
||||||
// 'options' are --foo, -f
|
- 'options' are flags preceded by one or two dashs:
|
||||||
//
|
eg. -f, --foo
|
||||||
// 'option-arguments' are (non-option) things that follow an option, and that the option expects
|
|
||||||
// eg. 'filename.txt' in './example --input filename.txt'
|
- 'option-arguments' are (non-option) strings that follow an option (and go with it)
|
||||||
//
|
eg. 'filename.txt' in './example --input filename.txt'
|
||||||
// 'operands' are (non-option) things that follow an option, but aren't option-arguments
|
|
||||||
// eg 'filename.txt' in './example --verbose filename.txt'
|
- 'operands' are (non-option) strings that follow an option, but aren't option-arguments
|
||||||
|
eg 'filename.txt' in './example --verbose filename.txt'
|
||||||
|
|
||||||
const defaultErrorMessages = {
|
see https://unix.stackexchange.com/questions/364383/confusion-about-changing-meaning-of-arguments-and-options-is-there-an-official
|
||||||
missingRequiredOption: (optionName) => `Missing required option '${optionName}'`,
|
and https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/V1_chap12.html#tag_12_01
|
||||||
missingRequiredArgument: (optionName) => `Missing argument for option ${optionName}`,
|
*/
|
||||||
tooManyArguments: (optionName) => `Too many arguments for option ${optionName}`,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO figure out how to avoid having to use '<options>.opts to access the output of this'
|
module.exports = class Opter {
|
||||||
|
synonyms = {};
|
||||||
module.exports = class Opts {
|
definedOptions = {};
|
||||||
constructor(argv) {
|
userOptions = {};
|
||||||
this.opts = get(argv);
|
|
||||||
this.synonyms = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO equip prototype with ability to print directly through `console.log(opts)`
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declare multiple option flags to be synonymous.
|
* @param {string} shortFlag
|
||||||
* Use this if you have short and long names for the same flag
|
* @param {string} longFlag
|
||||||
* eg: '-d' and '--debug'
|
* @param {boolean} [required]
|
||||||
*
|
* @param {boolean} [requiresArgument]
|
||||||
* @param {Array<string>} namesWithDashes
|
* @param {number} [maxArguments]
|
||||||
* @returns {void}
|
|
||||||
**/
|
**/
|
||||||
synonymize(...namesWithDashes) {
|
addOption(shortFlag, longFlag, required=false, requiresArgument=false, maxArguments=Infinity) {
|
||||||
const names = namesWithDashes.map(n => stripDashes(n));
|
let reqs = { required: required, requiresArgument: requiresArgument, maxArguments: maxArguments };
|
||||||
names.forEach((n) => {
|
const short = stripDashes(shortFlag);
|
||||||
const others = names.filter(x => x != n)
|
const long = stripDashes(longFlag);
|
||||||
this.synonyms[n] = others;
|
this.definedOptions[long] = reqs;
|
||||||
});
|
this.synonyms[long] = short;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify the presence of a required option
|
* @param {object} argv An argv object (`process.argv`)
|
||||||
* @param {string} nameWithDashes
|
* @returns {Object<string, boolean | Array<string>>}
|
||||||
* @param {string} [errorMessage]
|
|
||||||
* @returns {Boolean} **True** indicates that the option is present
|
|
||||||
**/
|
**/
|
||||||
requireOption(nameWithDashes, errorMessage=null) {
|
parse(argv) {
|
||||||
const name = stripDashes(nameWithDashes);
|
this.userOptions = argvToObject(argv);
|
||||||
if (name in this.opts) return true;
|
|
||||||
if (name in this.synonyms) {
|
|
||||||
const syns = this.synonyms[name];
|
|
||||||
const hits = syns.filter(s => s in this.opts ).length;
|
|
||||||
if (hits > 0) { return true; }
|
|
||||||
}
|
|
||||||
throw new Error(errorMessage || defaultErrorMessages.missingRequiredOption(nameWithDashes));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Object.keys(this.definedOptions).forEach(longName => {
|
||||||
* Verify the presence of arguments for an option that requires them,
|
let shortName = this.synonyms[longName];
|
||||||
* if that option is present
|
let reqs = this.definedOptions[longName];
|
||||||
* @param {string} nameWithDashes
|
let providedLong = typeof this.userOptions[longName] !== 'undefined';
|
||||||
* @param {number} [minRequired]
|
let providedShort = typeof this.userOptions[shortName] !== 'undefined';
|
||||||
* @param {number} [maxRequired]
|
let provided = providedLong || providedShort;
|
||||||
* @param {string} [tooFewMessage] Error message if there are too few arguments provided
|
|
||||||
* @param {string} [tooManyMessage] Error message if there are too many arguments provided
|
|
||||||
* @returns {Boolean} **True** indicates that the arguments are present, or that the option is *not* present
|
|
||||||
**/
|
|
||||||
requireOptionArgument(nameWithDashes, minRequired=1, maxRequired=1, tooFewMessage=null, tooManyMessage=null) {
|
|
||||||
let name = stripDashes(nameWithDashes);
|
|
||||||
|
|
||||||
const checkArgs = (name) => {
|
let userArgs = null;
|
||||||
if (typeof this.opts[name] === 'boolean') {
|
// arrays have type 'object':
|
||||||
throw new Error(tooFewMessage || defaultErrorMessages.missingRequiredArgument(nameWithDashes));
|
if (typeof this.userOptions[longName] === 'object') { userArgs = this.userOptions[longName]; }
|
||||||
} else if (this.opts[name].length < minRequired) {
|
if (typeof this.userOptions[shortName] === 'object') { userArgs = this.userOptions[shortName]; }
|
||||||
throw new Error(tooFewMessage || defaultErrorMessages.missingRequiredArgument(nameWithDashes));
|
|
||||||
} else if (this.opts[name].length > maxRequired) {
|
/* Check that required options are provided */
|
||||||
throw new Error(tooManyMessage || defaultErrorMessages.tooManyArguments(nameWithDashes));
|
if (reqs.required && !provided) {
|
||||||
|
throw new Error(`Missing required option '${longName}'`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (name in this.opts) {
|
/* Check the number of arguments */
|
||||||
checkArgs(name);
|
if (provided && reqs.requiresArgument) {
|
||||||
}
|
// Make sure required arguments are provided
|
||||||
if (name in this.synonyms) {
|
if (userArgs === null) {
|
||||||
const syn = this.synonyms[name].filter(s => s in this.opts )[0];
|
throw new Error(`Missing required argument for '${longName}'`);
|
||||||
checkArgs(syn);
|
}
|
||||||
}
|
// And make sure there aren't too many
|
||||||
return true;
|
if (userArgs.length > reqs.maxArguments) {
|
||||||
}
|
throw new Error(`Too many arguments for '${longName}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
contains(nameWithDashes) {
|
return this.userOptions;
|
||||||
const name = stripDashes(nameWithDashes);
|
|
||||||
if (name in this.opts) return true;
|
|
||||||
if (name in this.synonyms) {
|
|
||||||
const syns = this.synonyms[name];
|
|
||||||
const hits = syns.filter(s => s in this.opts).length;
|
|
||||||
if (hits > 0) { return true; }
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns [ string: (boolean | string[]) ]
|
* @returns {Object.<string, boolean | Array<string>>}
|
||||||
**/
|
**/
|
||||||
function get(argv) {
|
function argvToObject(argv) {
|
||||||
argv = process.argv.slice(2);
|
argv = process.argv.slice(2);
|
||||||
|
|
||||||
// Label everything in argv as either
|
// Label everything in argv as either
|
||||||
|
|
|
||||||
83
test.js
83
test.js
|
|
@ -1,80 +1,9 @@
|
||||||
const Opter = require('./opter.js');
|
const Opter = require('./opter.js');
|
||||||
|
|
||||||
// const args = argparser.parse(process.argv);
|
const opter = new Opter();
|
||||||
// console.log('args', args);
|
opter.addOption('-i', '--input', true);
|
||||||
// console.log('args', args);
|
opter.addOption('-d', '--debug');
|
||||||
// requireOption(args, '--i');
|
opter.addOption('-f', '--filename', false, true);
|
||||||
// argsrequire(args, '-i');
|
let opts = opter.parse(process.argv);
|
||||||
|
|
||||||
const opts = new Opter(process.argv)
|
console.log(opts);
|
||||||
opts.synonymize('-i', '--input');
|
|
||||||
opts.synonymize('-d', '--debug');
|
|
||||||
opts.requireOption('-i');
|
|
||||||
opts.requireOptionArgument('--input');
|
|
||||||
console.log('contains --debug', opts.contains('-d'));
|
|
||||||
console.log('contains --input', opts.contains('--input'));
|
|
||||||
|
|
||||||
// const _defaultErrorMessages = {
|
|
||||||
// missingRequiredOption: (optionName) => `Missing required option '${optionName}'`,
|
|
||||||
// missingRequiredArgument: (optionName) => `Missing argument for option ${optionName}`,
|
|
||||||
// tooManyArguments: (optionName) => `Too many arguments for option ${optionName}`,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Declare multiple option flags to be synonymous.
|
|
||||||
// * Use this if you have short and long names for the same flag
|
|
||||||
// * eg: '-d' and '--debug'
|
|
||||||
// *
|
|
||||||
// * @param {Array<string>} namesWithDashes
|
|
||||||
// * @returns {void}
|
|
||||||
// **/
|
|
||||||
// function synonymize(opts /* <-- TODO tmp */, ...namesWithDashes) {
|
|
||||||
// namesWithDashes.slice(1).forEach((n) => {
|
|
||||||
// const x = _stripDashes(n);
|
|
||||||
// this.opts[x] = opts[namesWithDashes[0]]; // TODO check 'this' works
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function _stripDashes(optionName) {
|
|
||||||
// if (optionName.startsWith('--')) return optionName.substring(2);
|
|
||||||
// if (optionName.startsWith('-')) return optionName.substring(1);
|
|
||||||
// return optionName;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Verify the presence of a required option
|
|
||||||
// * @param {string} nameWithDashes
|
|
||||||
// * @param {string} [errorMessage]
|
|
||||||
// * @returns {Boolean} **True** indicates that the option is present
|
|
||||||
// **/
|
|
||||||
// function requireOption(userProvidedOpts, nameWithDashes, errorMessage=null) {
|
|
||||||
// if (_stripDashes(nameWithDashes) in userProvidedOpts) return true;
|
|
||||||
// throw new Error(errorMessage || _defaultErrorMessages.missingRequiredOption(nameWithDashes));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Verify the presence of arguments for an option that requires them,
|
|
||||||
// * if that option is present
|
|
||||||
// * @param {string} nameWithDashes
|
|
||||||
// * @param {number} [minRequired]
|
|
||||||
// * @param {number} [maxRequired]
|
|
||||||
// * @param {string} [tooFewMessage] Error message if there are too few arguments provided
|
|
||||||
// * @param {string} [tooManyMessage] Error message if there are too many arguments provided
|
|
||||||
// * @returns {Boolean} **True** indicates that the arguments are present, or that the option is *not* present
|
|
||||||
// **/
|
|
||||||
// // TODO remove first param when making this a method on an Args object
|
|
||||||
// function argsrequire(userProvidedArgs, nameWithDashes, minRequired=1, maxRequired=1, tooFewMessage=null, tooManyMessage=null) {
|
|
||||||
// let name = _stripDashes(nameWithDashes);
|
|
||||||
// if (name in userProvidedArgs) {
|
|
||||||
// console.log('type', typeof userProvidedArgs[name] === 'boolean');
|
|
||||||
// console.log(userProvidedArgs[name]);
|
|
||||||
// if (typeof userProvidedArgs[name] === 'boolean') {
|
|
||||||
// throw new Error(tooFewMessage || _defaultErrorMessages.missingRequiredArgument(nameWithDashes));
|
|
||||||
// } else if (userProvidedArgs[name].length < minRequired) {
|
|
||||||
// throw new Error(tooFewMessage || _defaultErrorMessages.missingRequiredArgument(nameWithDashes));
|
|
||||||
// } else if (userProvidedArgs[name].length > maxRequired) {
|
|
||||||
// throw new Error(tooManyMessage || _defaultErrorMessages.tooManyArguments(nameWithDashes));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
Loading…
Reference in New Issue