// Organization selector options (rename design r2, "Selector scopes and parser matrix"). // scripts/selector.sh is the shell half and keeps the same rules; packages/organization/tests/ // selector.test.mjs runs one table against both. // // --org and --business are one option family, and --org-file and --business-file are another. // A scope is one parser's own options: the parser that knows which tokens its other options // consume calls take() for a selector token it meets, so a value or positional text that happens // to read "--org" never reaches this file. Within one scope each spelling may appear once, and a // second spelling must carry exactly the same value. A missing value, an empty one, one that // starts with "-", an invalid one (by the parser's own predicate) and differing values refuse // through the parser's own refuse function, so each entry point keeps its exit code and wording. // The file family compares the strings given; its paths are checked later under existing rules. export const SELECTOR_FAMILIES = Object.freeze({ id: Object.freeze({ canonical: '--business', spellings: Object.freeze(['--business', '--org']) }), file: Object.freeze({ canonical: '--business-file', spellings: Object.freeze(['--business-file', '--org-file']) }), }); // Each refusal names one of these kinds, so a parser can map it to its own usage error. export const SELECTOR_REFUSALS = Object.freeze(['missing', 'flag', 'repeated', 'invalid', 'differ']); export function isSelector(token, family = 'id') { return SELECTOR_FAMILIES[family]?.spellings.includes(token) ?? false; } // refuse(kind, message) must throw; take() throws its own error if it returns. export function selectorScope({ family = 'id', valid = null, refuse }) { const spec = SELECTOR_FAMILIES[family]; if (!spec) throw new TypeError(`unknown selector family ${family}`); if (typeof refuse !== 'function') throw new TypeError('selectorScope needs a refuse function'); const seen = []; let value; const fail = (kind, message) => { refuse(kind, message); throw new Error(`selector refusal returned: ${message}`); }; return Object.freeze({ family, canonical: spec.canonical, owns: token => spec.spellings.includes(token), take(flag, given) { if (!spec.spellings.includes(flag)) throw new TypeError(`${flag} is not a ${family} selector`); if (seen.includes(flag)) fail('repeated', `${flag} given twice`); if (typeof given !== 'string' || given === '') fail('missing', `${flag} needs one value`); if (given.startsWith('-')) fail('flag', `${flag} needs one value, not ${given}`); if (valid && !valid(given)) fail('invalid', `${flag} ${given} is not valid`); if (seen.length && given !== value) fail('differ', `${seen[0]} ${value} and ${flag} ${given} conflict`); seen.push(flag); value = given; }, get value() { return value; }, get given() { return seen.length > 0; }, }); }