From 81b106088d8607f9928a5b15025c82c0f93ce8e0 Mon Sep 17 00:00:00 2001 From: n loewen Date: Sat, 26 Aug 2023 20:52:27 -0400 Subject: [PATCH] First commit: WIP - Start to create a parser for command-line 'argv' arguments --- .gitignore | 2 + arg-parser.js | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 .gitignore create mode 100755 arg-parser.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2608ec2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.vscode \ No newline at end of file diff --git a/arg-parser.js b/arg-parser.js new file mode 100755 index 0000000..5a04321 --- /dev/null +++ b/arg-parser.js @@ -0,0 +1,138 @@ +#!/usr/bin/env node + +const test = require('node:test'); + +// WIP + +// Example +/* +let cpuArguments = { + help: '', // TODO + args: [ + { flags: { short: 'f' }, + name: 'Filename', + requiresValue: true, }, + { name: 'Clock speed', + flags: { short: 'c', long: 'clock' }, + requiresValue: true, + optional: true }, + { name: 'Pretty-print display', + flags: { short: 'p', long: 'pretty' }, + optional: true, }, + { name: 'Single-step', + flags: { short: 's', long: 'step' }, + optional: true, }, + { 'name': 'Debug', + flags: { short: 'd', long: 'debug' }, + optional: true, + } + ] +}; +*/ + + +// Constructors + +function Arguments(argumentsObject=[], helpText='') { + this.help = helpText; + this.args = argumentsObject; + Arguments.prototype.add = (...a) => a.forEach((x) => argumentsObject.push(x)); +}; + +function Arg({ + name = 'Flag', + shortFlag = null, + longFlag = null, + requiresValue=false, + optional=false +}) { + this.name = name; + this.flags = { short: shortFlag, long: longFlag }; + this.requiresValue = requiresValue; + this.optional = optional; +}; + +// Test constructors + +let cpuArguments = new Arguments(); +cpuArguments.add( + new Arg({name: 'Filename', shortFlag: 'f', requiresValue: true}), + new Arg({name: 'Clock speed', shortFlag: 'c', longFlag: 'clock', requiresValue: true, optional: true}), + new Arg({name: 'Debug', shortFlag: 'd', longFlag: 'debug', optional: true}), + new Arg({name: 'Pretty-print display', shortFlag: 'p', longFlag: 'pretty', optional: true}), + new Arg({name: 'Single-step', shortFlag: 's', longFlag: 'step', optional: true})); + +///console.log(cpuArguments); +///console.log('flags', cpuArguments.args[0].flags); + + +// MODULE: + +// TODO: +// - building a structured output +// - testing +// - exporting as object + +const argsRequiringValues = cpuArguments.args.filter((a) => a.requiresValue); +const requiredArgs = cpuArguments.args.filter((a) => !a.optional); + +let argv = process.argv.slice(2); + +// Split up grouped short flags +let argvSplit = [].concat(...argv.map((x) => { + if (x.startsWith('-') && !x.startsWith('--')) { // short flags + let arr = x.substring(1).split(''); + arr = arr.map((y) => '-' + y); + return arr; + } + return x; +})); + + +// Check if all the required flags have been included +requiredArgs.forEach((a) => { + if (!argvSplit.includes('-'+a.flags.short) && !argvSplit.includes('-'+a.flags.long)) { + let long = (a.flags.long) ? `--${a.flags.long}, ` : ''; + throw new Error(`${a.name} flag (-${a.flags.short}${long}) is required`); + } +}); + +// Validate... +argvSplit.forEach((currentArg, index) => { + console.log('checking', currentArg); + // Check for attempts to use --flags that don't exist + if (currentArg.startsWith('--')) { + console.log('--'); + let valid = cpuArguments.args.filter((a) => { return A.flags.long === currentArg.substring(2)}).length > 0; + console.log('valid', valid); + if (!valid) { throw new Error(`There is no ${currentArg} option`); } + } else if (currentArg.startsWith('-')) { + console.log(currentArg.substring(1)); + let valid = cpuArguments.args.filter((a) => { console.log(a); return a.flags.short === currentArg.substring(1)}).length > 0; + if (!valid) { throw new Error(`There is no ${currentArg} option`); } + } + ///console.log(' it\'s a valid argument'); + + ///console.log(' argsrequiringvalues', argsRequiringValues); + ///console.log(' currentarg', currentArg); + + // Check if it requires a value... + // and if it does, do a cursory check to see if one is likely to be there + let shouldCheckValue = argsRequiringValues.filter((a) => { + /// console.log('a', a); + let noDash = currentArg.substring(1); + return (a.flags.short === noDash || a.flags.long === noDash); + }).length > 0; + /// console.log(' requires a value?', shouldCheckValue); + /// console.log(' split', argvSplit); + + if (shouldCheckValue) { + ///console.log(currentArg); + let errorMessage = `Value required for ${currentArg}`; + // If there's nothing after this flag, error; + // Or if the next item is a flag... error: + /// console.log('idx', index, argvSplit[index]); + if (typeof argvSplit[index + 1] === 'undefined') { throw new Error(errorMessage); } + else if (argvSplit[index + 1].startsWith('-')) { throw new Error(errorMessage); }; + } +}); \ No newline at end of file