diff --git a/argparser.js b/argparser.js index 900f309..cab68c7 100644 --- a/argparser.js +++ b/argparser.js @@ -1,5 +1,3 @@ -module.exports = parse; - // https://unix.stackexchange.com/questions/364383/confusion-about-changing-meaning-of-arguments-and-options-is-there-an-official // https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/V1_chap12.html#tag_12_01 @@ -11,11 +9,89 @@ module.exports = parse; // 'operands' are (non-option) things that follow an option, but aren't option-arguments // eg 'filename.txt' in './example --verbose filename.txt' +const defaultErrorMessages = { + missingRequiredOption: (optionName) => `Missing required option '${optionName}'`, + missingRequiredArgument: (optionName) => `Missing argument for option ${optionName}`, + tooManyArguments: (optionName) => `Too many arguments for option ${optionName}`, +} + + +module.exports = class Opts { + constructor(argv) { + 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. + * Use this if you have short and long names for the same flag + * eg: '-d' and '--debug' + * + * @param {Array} namesWithDashes + * @returns {void} + **/ + synonymize(...namesWithDashes) { + const names = namesWithDashes.map(n => stripDashes(n)); + names.forEach((n) => { + const others = names.filter(x => x != n) + this.synonyms[n] = others; + }); + } + + /** + * Verify the presence of a required option + * @param {string} nameWithDashes + * @param {string} [errorMessage] + * @returns {Boolean} **True** indicates that the option is present + **/ + requireOption(nameWithDashes, errorMessage=null) { + const name = stripDashes(nameWithDashes); + console.log('req opt name:', name); + if (name in this.opts) return true; + if (name in this.synonyms) { + let syns = this.synonyms[name]; + console.log('syns', syns); + let hits = syns.filter(s => s in this.opts ).length; + if (hits > 0) { + 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 + **/ + requireOptionArgument(nameWithDashes, minRequired=1, maxRequired=1, tooFewMessage=null, tooManyMessage=null) { + let name = stripDashes(nameWithDashes); + if (name in this.opts) { + console.log('type', typeof this.opts[name] === 'boolean'); + console.log(this.opts[name]); + if (typeof this.opts[name] === 'boolean') { + throw new Error(tooFewMessage || defaultErrorMessages.missingRequiredArgument(nameWithDashes)); + } else if (this.opts[name].length < minRequired) { + throw new Error(tooFewMessage || defaultErrorMessages.missingRequiredArgument(nameWithDashes)); + } else if (this.opts[name].length > maxRequired) { + throw new Error(tooManyMessage || defaultErrorMessages.tooManyArguments(nameWithDashes)); + } + } + return true; + } +} /** * @returns [ string: (boolean | string[]) ] **/ -function parse(argv) { +function get(argv) { argv = process.argv.slice(2); // Label everything in argv as either @@ -78,4 +154,10 @@ function parse(argv) { } }); return out; +} + +function stripDashes(optionName) { + if (optionName.startsWith('--')) return optionName.substring(2); + if (optionName.startsWith('-')) return optionName.substring(1); + return optionName; } \ No newline at end of file diff --git a/test.js b/test.js index 4dfccd9..0aa7e38 100644 --- a/test.js +++ b/test.js @@ -1,3 +1,78 @@ -const args = require('./argparser.js')(process.argv); +const Opter = require('./argparser.js'); -console.log(args); \ No newline at end of file +// const args = argparser.parse(process.argv); +// console.log('args', args); +// console.log('args', args); +// requireOption(args, '--i'); +// argsrequire(args, '-i'); + +const opts = new Opter(process.argv) +opts.synonymize('-i', '--input'); +opts.requireOption('-i'); +opts.requireOptionArgument('-i'); +console.log(opts.opts); + +// 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} 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; +// } \ No newline at end of file