parseNamedArgs re-scanned argv with indexOf/includes once per flag — O(flags * argv) — on the command-dispatch hot path (24 call sites across gsd-tools + init/state/validate routers). Build a first-index Map of argv tokens in a single pass and use it for the flag lookups, dropping it to O(argv + flags). Semantics are identical: firstIndex.get(t)??-1 === indexOf(t), firstIndex.has(t) === includes(t); first-occurrence-wins and the value-token rejection are preserved. Adds the first behavior-lock tests for the module. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
1.9 KiB
JavaScript
63 lines
1.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Command Argument Projection Module
|
|
*
|
|
* Shared helpers for command-family adapters to project argv tokens into
|
|
* typed named values and multi-word segments.
|
|
*/
|
|
|
|
/**
|
|
* Extract named --flag <value> pairs from an args array.
|
|
* Returns an object mapping flag names to their values (null if absent).
|
|
* Flags listed in `booleanFlags` are treated as booleans.
|
|
*
|
|
* @param {string[]} args
|
|
* @param {string[]} [valueFlags]
|
|
* @param {string[]} [booleanFlags]
|
|
* @returns {Record<string, string|boolean|null>}
|
|
*/
|
|
function parseNamedArgs(args, valueFlags = [], booleanFlags = []) {
|
|
// Index each token's first position once (firstIndex.get(t) ?? -1 === args.indexOf(t),
|
|
// firstIndex.has(t) === args.includes(t)) so the flag loops below don't each re-scan
|
|
// argv — O(argv + flags) instead of O(flags * argv). Semantics are unchanged. (#312)
|
|
const firstIndex = new Map();
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (!firstIndex.has(args[i])) firstIndex.set(args[i], i);
|
|
}
|
|
const result = {};
|
|
for (const flag of valueFlags) {
|
|
const idx = firstIndex.has(`--${flag}`) ? firstIndex.get(`--${flag}`) : -1;
|
|
result[flag] = idx !== -1 && args[idx + 1] !== undefined && !args[idx + 1].startsWith('--')
|
|
? args[idx + 1]
|
|
: null;
|
|
}
|
|
for (const flag of booleanFlags) {
|
|
result[flag] = firstIndex.has(`--${flag}`);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Collect all tokens after --flag until the next --flag or end of args.
|
|
*
|
|
* @param {string[]} args
|
|
* @param {string} flag
|
|
* @returns {string|null}
|
|
*/
|
|
function parseMultiwordArg(args, flag) {
|
|
const idx = args.indexOf(`--${flag}`);
|
|
if (idx === -1) return null;
|
|
const tokens = [];
|
|
for (let i = idx + 1; i < args.length; i++) {
|
|
if (args[i].startsWith('--')) break;
|
|
tokens.push(args[i]);
|
|
}
|
|
return tokens.length > 0 ? tokens.join(' ') : null;
|
|
}
|
|
|
|
module.exports = {
|
|
parseNamedArgs,
|
|
parseMultiwordArg,
|
|
};
|