UNPKG

dcp-client

Version:

Core libraries for accessing DCP network

95 lines (84 loc) 3.63 kB
/** * @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 */ 'use strict'; 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); }