dcp-client
Version:
Core libraries for accessing DCP network
95 lines (84 loc) • 3.63 kB
JavaScript
/**
* @file getopt.js - utility functions for parsing command-line options for DCP programs.
* Depends on posix-getopt npm library.
* @author Wes Garland, wes@distributive.network
* @date Nov 2025
*/
;
exports.parse = parse;
exports.uniq = uniq;
/**
* A posix getopt parser with a nice UX and API.
* - accepts optionsDefn string like posix-getopt module, but understands that the character * means
* long option only, and the -- option means to stop processing.
* - is a generator that yields for each option
* - option: character or long option when character is *.
* Magic option -- means that argvSegment is remainder of argv.
* - optarg: argument to option
* - optind: position of option character in argv
* - argvSegment: elements of argv used to make up option and optarg
*
* @note this generator intercepts process.stderr for the duration of iteration
* @note option characters above \u1000 are not supported
*
* @example parse(process.argv.slice(2), 'vh(help)*:(earnings-account)p:(port)')
* -v
* -h, --help
* --earnings-account=0xc0ffee
* -p123, --port=123
*
* @param {Array} argv array of options to parse
* @param {string} optionsDefn
* @throws {Error} when the options string is invalid
*/
function *parse(argv, optionsDefn)
{
const { BasicParser } = require('posix-getopt');
var longOptCount = 0;
optionsDefn = optionsDefn /* patchup long opt * to > \u1000 chars */
.match(/[A-Za-z*]:?(\([^)]*\))?/g)
.map(s => s[0] === '*' ? String.fromCharCode(++longOptCount + 0x1000) + s.slice(1) : s).join('');
const optionChars = optionsDefn.match(/[A-Za-z\u1000-\u1100/](:?\([^)]*\))?/g).map(s=>s[0]);
const uniqueOptionChars = uniq(optionChars);
if (optionChars.length !== uniqueOptionChars.length)
{
uniqueOptionChars.forEach(ch => {
if (optionChars.lastIndexOf(ch) !== optionChars.indexOf(ch))
throw new Error(`getopt - option character '${ch}' used more than once`);
});
throw new Error('unreachable code');
}
argv = process.argv.slice(0,2).concat(argv);
const parser = new BasicParser(optionsDefn, argv);
const psw = process.stderr.write;
process.stderr.write = function patchupGetoptErrorOutput(s, ...args) {
/* posix-getopt library has useless chr for long opts when stderr.write('option requires an argument -- ' + chr + '\n'); */
if (s.startsWith('option requires an argument -- '))
s = `option ${argv[parser.optind() - 1]} requires an argument\n`;
psw.call(process.stderr, s, ...args);
};
for (let opthnd, lastOpt=2; (opthnd = parser.getopt()); lastOpt = parser.optind())
{
let option;
/* To fake long-only-opts, we use short opts >= \u1000), and present only the long opt below */
if (opthnd.option < '\u1000')
option = opthnd.option;
else
option = (Object.entries(parser.gop_aliases).filter(longShort => longShort[1] === opthnd.option)[0] || ['?'])[0];
yield { option, optarg: opthnd.optarg, optind: parser.optind() - 2, argvSegment: argv.slice(lastOpt, parser.optind()) };
if (opthnd.error)
break;
}
if (argv.length !== parser.optind())
yield { option: '--', optind: parser.optind(), argvSegment: argv.slice(parser.optind()) };
process.stderr.write = psw; // eslint-disable-line require-atomic-updates
}
/**
* Return a new array composed only of unique elements of arr
* @param {Array} arr
* @returns {Array}
*/
function uniq(array)
{
return array.filter((val, idx, arr) => arr.indexOf(val) === idx);
}