@loomcli/core 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/NOTICE +34 -0
  2. package/dist/application.d.ts +11 -6
  3. package/dist/application.js +57 -19
  4. package/dist/capture.d.ts +65 -0
  5. package/dist/capture.js +99 -0
  6. package/dist/chain.d.ts +28 -17
  7. package/dist/chain.js +11 -10
  8. package/dist/command-rules.d.ts +1 -1
  9. package/dist/command-rules.js +2 -2
  10. package/dist/command.d.ts +31 -47
  11. package/dist/command.js +204 -209
  12. package/dist/errors.d.ts +22 -21
  13. package/dist/errors.js +38 -18
  14. package/dist/facts.d.ts +5 -0
  15. package/dist/facts.js +8 -1
  16. package/dist/globals.d.ts +48 -22
  17. package/dist/globals.js +72 -29
  18. package/dist/glyphs.generated.js +1 -1
  19. package/dist/index.d.ts +5 -3
  20. package/dist/index.js +3 -1
  21. package/dist/input-rules.d.ts +20 -2
  22. package/dist/input-rules.js +47 -5
  23. package/dist/inspect.d.ts +33 -17
  24. package/dist/inspect.js +74 -87
  25. package/dist/locate.js +52 -60
  26. package/dist/options.d.ts +101 -83
  27. package/dist/options.js +311 -267
  28. package/dist/parse.d.ts +162 -0
  29. package/dist/parse.js +601 -0
  30. package/dist/plain.d.ts +52 -2
  31. package/dist/plain.js +228 -2
  32. package/dist/plugin-rules.d.ts +8 -4
  33. package/dist/plugin-rules.js +13 -9
  34. package/dist/plugin-settings.d.ts +18 -0
  35. package/dist/plugin-settings.js +38 -0
  36. package/dist/plugin.d.ts +32 -27
  37. package/dist/plugin.js +107 -89
  38. package/dist/sources.d.ts +18 -9
  39. package/dist/sources.js +50 -20
  40. package/dist/style-layout.js +2 -2
  41. package/dist/style-width.d.ts +13 -0
  42. package/dist/style-width.js +170 -0
  43. package/dist/types.d.ts +70 -30
  44. package/dist/unicode.generated.d.ts +27 -0
  45. package/dist/unicode.generated.js +1036 -0
  46. package/dist/validation.d.ts +108 -34
  47. package/dist/validation.js +282 -125
  48. package/dist/view.d.ts +16 -11
  49. package/dist/view.js +13 -4
  50. package/licenses/unicode-LICENSE.txt +41 -0
  51. package/licenses/uucode-LICENSE.md +35 -0
  52. package/package.json +9 -5
package/dist/errors.d.ts CHANGED
@@ -10,20 +10,6 @@ import type { InputIdentity } from './types.js';
10
10
  * keeps the raw value.
11
11
  */
12
12
  export declare function quoted(value: unknown): string;
13
- /**
14
- * The two short-group faults. A value option that is not last in its group names that option's
15
- * spelling; a group that mixes scopes names only the two letters that disagree, because the rest
16
- * of the group may hold an inline value. `token` keeps the whole group as the reported fact.
17
- */
18
- type ShortGroupFault = {
19
- reason: 'value-position';
20
- token: string;
21
- } | {
22
- reason: 'mixed-scope';
23
- token: string;
24
- global: string;
25
- other: string;
26
- };
27
13
  /**
28
14
  * The one message a distributed build shows an operator for a defect or a declaration fault: the
29
15
  * application name and a fixed phrase, with no reason, class name, code, or path.
@@ -123,24 +109,39 @@ export declare class UnknownOptionError extends UsageError {
123
109
  readonly spelling: string;
124
110
  constructor(spelling: string);
125
111
  }
112
+ /**
113
+ * Where a missing value should have been: the words ran out, or the next word was an option word or
114
+ * the bare `--`, which is never a separate value, so the sentence names the attached form too.
115
+ */
116
+ type MissingValueForm = 'attached' | 'separate';
126
117
  export declare class MissingValueError extends UsageError {
127
118
  readonly spelling: string;
128
- constructor(spelling: string);
119
+ constructor(spelling: string, form?: MissingValueForm);
129
120
  }
130
- /** A Boolean spelling takes no value, so the token carried one the declaration cannot accept. */
121
+ /**
122
+ * A Boolean or counted spelling takes no value, so the token carried one the declaration cannot
123
+ * accept. A counted option's sentence tells the operator to repeat the spelling instead, and `kind`
124
+ * chooses the sentence alone.
125
+ */
131
126
  export declare class UnexpectedValueError extends UsageError {
132
127
  readonly spelling: string;
133
128
  readonly value: string;
134
- constructor(spelling: string, value: string);
129
+ constructor(spelling: string, value: string, kind?: 'boolean' | 'count');
135
130
  }
136
131
  export declare class RepeatedOptionError extends UsageError {
137
132
  readonly spelling: string;
138
133
  constructor(spelling: string);
139
134
  }
140
- export declare class ShortGroupError extends UsageError {
141
- readonly token: string;
142
- readonly reason: 'value-position' | 'mixed-scope';
143
- constructor(fault: ShortGroupFault);
135
+ /**
136
+ * An option word the Command routing reached does not declare, while a visible Command below it
137
+ * does, such as a Command's own option typed before its name, or a parent's own option the routed
138
+ * Command declares with another value class. `commands` holds each such Command's path from the
139
+ * root, in authoring order.
140
+ */
141
+ export declare class MisplacedOptionError extends UsageError {
142
+ readonly spelling: string;
143
+ readonly commands: readonly (readonly string[])[];
144
+ constructor(spelling: string, commands: readonly (readonly string[])[]);
144
145
  }
145
146
  /**
146
147
  * Exit 1: the declaration is wrong, so the author reads its Developer Diagnostic. A rule and the
package/dist/errors.js CHANGED
@@ -50,10 +50,16 @@ function nonCallableMessage(command, candidates) {
50
50
  ? `A command is required.${offering(candidates, 'command')}`
51
51
  : `${routedSentence(command)} requires a subcommand.${offering(candidates, 'subcommand')}`;
52
52
  }
53
- function shortGroupMessage(fault) {
54
- return fault.reason === 'value-position'
55
- ? `Value option ${quoted(fault.token)} must be last in its short group. Supply its value in the next token.`
56
- : `A short group mixes the global option ${quoted(`-${fault.global}`)} with ${quoted(`-${fault.other}`)}, which is not a global option. Supply global options as separate tokens, and local options after their command name.`;
53
+ /**
54
+ * The sentence for an option word the routed Command's table does not hold, while visible Commands
55
+ * below it declare it. Each Command reads as its path from the root.
56
+ */
57
+ function misplacedMessage(spelling, commands) {
58
+ const names = commands.map((path) => path.join(' '));
59
+ const [only] = names;
60
+ return names.length === 1 && only !== undefined
61
+ ? `Option ${quoted(spelling)} belongs to command ${quoted(only)}. Supply it after ${quoted(only)}.`
62
+ : `Option ${quoted(spelling)} belongs to commands ${names.join(', ')}. Supply it after the command name.`;
57
63
  }
58
64
  /**
59
65
  * The sentence for a class whose declared code no failure may exit with. The class is named by its
@@ -248,18 +254,26 @@ export class UnknownOptionError extends UsageError {
248
254
  }
249
255
  export class MissingValueError extends UsageError {
250
256
  spelling;
251
- constructor(spelling) {
252
- super(`Option ${quoted(spelling)} requires a value. Supply a value after ${quoted(spelling)}.`);
257
+ constructor(spelling, form = 'separate') {
258
+ super(form === 'attached'
259
+ ? `Option ${quoted(spelling)} requires a value. Supply a value after ${quoted(spelling)}, or attach one that starts with a hyphen as ${quoted(`${spelling}=<value>`)}.`
260
+ : `Option ${quoted(spelling)} requires a value. Supply a value after ${quoted(spelling)}.`);
253
261
  this.name = 'MissingValueError';
254
262
  this.spelling = spelling;
255
263
  }
256
264
  }
257
- /** A Boolean spelling takes no value, so the token carried one the declaration cannot accept. */
265
+ /**
266
+ * A Boolean or counted spelling takes no value, so the token carried one the declaration cannot
267
+ * accept. A counted option's sentence tells the operator to repeat the spelling instead, and `kind`
268
+ * chooses the sentence alone.
269
+ */
258
270
  export class UnexpectedValueError extends UsageError {
259
271
  spelling;
260
272
  value;
261
- constructor(spelling, value) {
262
- super(`Boolean option ${quoted(spelling)} does not accept a value. Supply the flag alone.`);
273
+ constructor(spelling, value, kind = 'boolean') {
274
+ super(kind === 'count'
275
+ ? `Counted option ${quoted(spelling)} does not accept a value. Repeat ${quoted(spelling)} to raise its count.`
276
+ : `Boolean option ${quoted(spelling)} does not accept a value. Supply the flag alone.`);
263
277
  this.name = 'UnexpectedValueError';
264
278
  this.spelling = spelling;
265
279
  this.value = value;
@@ -273,14 +287,20 @@ export class RepeatedOptionError extends UsageError {
273
287
  this.spelling = spelling;
274
288
  }
275
289
  }
276
- export class ShortGroupError extends UsageError {
277
- token;
278
- reason;
279
- constructor(fault) {
280
- super(shortGroupMessage(fault));
281
- this.name = 'ShortGroupError';
282
- this.reason = fault.reason;
283
- this.token = fault.token;
290
+ /**
291
+ * An option word the Command routing reached does not declare, while a visible Command below it
292
+ * does, such as a Command's own option typed before its name, or a parent's own option the routed
293
+ * Command declares with another value class. `commands` holds each such Command's path from the
294
+ * root, in authoring order.
295
+ */
296
+ export class MisplacedOptionError extends UsageError {
297
+ spelling;
298
+ commands;
299
+ constructor(spelling, commands) {
300
+ super(misplacedMessage(spelling, commands));
301
+ this.commands = commands;
302
+ this.name = 'MisplacedOptionError';
303
+ this.spelling = spelling;
284
304
  }
285
305
  }
286
306
  /** The platform's error options one value carries, read without trusting its shape. */
@@ -498,7 +518,7 @@ for (const Class of [
498
518
  MissingValueError,
499
519
  UnexpectedValueError,
500
520
  RepeatedOptionError,
501
- ShortGroupError,
521
+ MisplacedOptionError,
502
522
  DeclarationError,
503
523
  FatalError,
504
524
  InternalError,
package/dist/facts.d.ts CHANGED
@@ -9,6 +9,11 @@ export interface FactSite {
9
9
  readonly declaration: Omit<Finding, 'mark' | 'note'>;
10
10
  readonly at: string;
11
11
  }
12
+ /**
13
+ * The note a finding carries for what a plugin declared on the author's behalf, such as an input
14
+ * its hook declares or the settings its factory takes, so the diagnostic names the declarer.
15
+ */
16
+ export declare function declarerNote(identity: string): string;
12
17
  /** The finding for the call one site holds, marking one part of it, with a note when given. */
13
18
  export declare function siteFinding(site: FactSite, mark: string, note?: string): Finding;
14
19
  /**
package/dist/facts.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { misplacedListingFact, notOneLine } from './command-rules.js';
2
- import { DeclarationError } from './errors.js';
2
+ import { DeclarationError, quoted } from './errors.js';
3
3
  import { flagNotBoolean } from './input-rules.js';
4
4
  /**
5
5
  * One character outside Unicode `White_Space`, so a fact holds prose and not only spacing.
@@ -12,6 +12,13 @@ const prose = /\P{White_Space}/u;
12
12
  * The seven are LF, VT, FF, CR, NEL, LS, and PS, each of them `White_Space` too.
13
13
  */
14
14
  const lineTerminator = /[\n\v\f\r\u0085\u2028\u2029]/u;
15
+ /**
16
+ * The note a finding carries for what a plugin declared on the author's behalf, such as an input
17
+ * its hook declares or the settings its factory takes, so the diagnostic names the declarer.
18
+ */
19
+ export function declarerNote(identity) {
20
+ return `declared by plugin ${quoted(identity)}`;
21
+ }
15
22
  /** The finding for the call one site holds, marking one part of it, with a note when given. */
16
23
  export function siteFinding(site, mark, note) {
17
24
  const finding = { ...site.declaration, mark };
package/dist/globals.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { BoundOption } from './bindings.js';
2
2
  import type { DescriptorRegistry } from './extension.js';
3
- import type { InputSite } from './facts.js';
3
+ import type { FactSite, InputSite } from './facts.js';
4
4
  import { compileOptions } from './options.js';
5
- import type { CompileScope } from './options.js';
5
+ import type { CompileScope, SpellingTable } from './options.js';
6
6
  import type { BuiltPlugin } from './plugin.js';
7
- import type { OptionConfig, OptionValue } from './types.js';
7
+ import type { OptionConfig } from './types.js';
8
8
  import type { InputDeclaration, OptionInput, ValidatedInputs } from './validation.js';
9
9
  /**
10
10
  * Who declared one option that shares the globals table, or the Command that declares a local
@@ -27,10 +27,10 @@ interface TableEntry {
27
27
  site: InputSite;
28
28
  }
29
29
  /**
30
- * The globals table the pre-scan reads, with every rule that pairs two of its options settled: one
31
- * spelling map, the scope that owns each key and where it was declared, and the variable each
32
- * option binds. It holds the application's global options and every installed plugin's options,
33
- * because the pre-scan reads one table.
30
+ * The globals table, with every rule that pairs two of its options settled: one spelling map, the
31
+ * scope that owns each key and where it was declared, and the variable each option binds. It holds
32
+ * the application's global options and every installed plugin's options, because both are global
33
+ * options that routing reads and every Command's table holds.
34
34
  */
35
35
  interface GlobalTable {
36
36
  names: ReadonlyMap<string, TableEntry>;
@@ -39,26 +39,33 @@ interface GlobalTable {
39
39
  variables: ReadonlyMap<string, BoundOption>;
40
40
  }
41
41
  /**
42
- * The compiled table one graph build shares. `inputs` holds the application's declarations alone,
43
- * because a plugin option is never validated and never reaches an action.
42
+ * The compiled table one graph build shares. `inputs` holds every global option, the application's
43
+ * in authoring order and then each installed plugin's in installation order, which is the order
44
+ * the input-source stage fills them, validation checks them, and `inspect()` lists them. `sites`
45
+ * holds the call that declared each of them, which a fault about one rebuilds. `bind` reads every
46
+ * global option's validated value, keyed by declared name, as every action receives it. `table`
47
+ * holds the global options alone as parser entries, which routing reads at a Command without its
48
+ * own options to offer and every Command's table joins.
44
49
  */
45
50
  interface BuiltGlobals extends GlobalTable {
46
51
  bind: (values: ValidatedInputs) => unknown;
47
52
  inputs: readonly OptionInput[];
48
53
  plugins: readonly BuiltPlugin[];
54
+ sites: ReadonlyMap<InputDeclaration, InputSite>;
55
+ table: SpellingTable;
49
56
  }
50
57
  /** The validated extension record of each declaration, keyed by the declaration itself. */
51
58
  type InputRecords = ReadonlyMap<InputDeclaration, Readonly<Record<string, unknown>>>;
52
59
  /**
53
- * The Application's private global declarations, their schema-derived value binder, and the
54
- * extension record each declaration's own call validated.
60
+ * The Application's private global declarations and the extension record each declaration's own
61
+ * call validated. The global options' value types live on the Application's type, and the action
62
+ * reads every global option's value through the built table's binder.
55
63
  */
56
- interface GlobalsState<Globals = unknown> {
57
- bind: (values: ValidatedInputs) => Globals;
64
+ interface GlobalsState {
58
65
  inputs: readonly OptionInput[];
59
66
  records: InputRecords;
60
67
  }
61
- declare function emptyGlobals(): GlobalsState<{}>;
68
+ declare function emptyGlobals(): GlobalsState;
62
69
  /** Where one global option was declared: its `globalOption()` call on the Application. */
63
70
  declare function globalSite(input: OptionInput): InputSite;
64
71
  /**
@@ -66,26 +73,45 @@ declare function globalSite(input: OptionInput): InputSite;
66
73
  * plugin's `plugin()` call, which is rebuilt from every option the plugin holds.
67
74
  */
68
75
  declare function pluginSites(identity: string, inputs: readonly OptionInput[]): (input: OptionInput) => InputSite;
76
+ /**
77
+ * A global option declares no presence rule, whether the application or a plugin declares it, and
78
+ * whatever the key's value. The fault marks the key on the call that declared the option.
79
+ */
80
+ declare function checkNoPresenceRule(site: FactSite, config: OptionConfig): void;
69
81
  /**
70
82
  * One global option's own facts, binding, and extension values, checked at its `globalOption()`
71
83
  * call against the Application's descriptors. The rules that pair it with another option belong to
72
84
  * the table, which the caller rebuilds with it.
73
85
  */
74
- declare function declareGlobalOption<Globals, Name extends string, Config extends OptionConfig>(state: GlobalsState<Globals>, declared: OptionInput<Name, Config>, descriptors: DescriptorRegistry): {
86
+ declare function declareGlobalOption<Name extends string, Config extends OptionConfig>(state: GlobalsState, declared: OptionInput<Name, Config>, descriptors: DescriptorRegistry): {
75
87
  readonly input: OptionInput<Name, Config>;
76
- readonly state: GlobalsState<Globals & Record<Name, OptionValue<Config>>>;
88
+ readonly state: GlobalsState;
77
89
  };
78
90
  /**
79
- * The one table the pre-scan reads: the application's global options in authoring order, then each
80
- * installed plugin's options in installation order. Every collision between the two scopes, by key
81
- * or by spelling, is reported here, so the pre-scan meets a table with one owner per name.
91
+ * The globals table: the application's global options in authoring order, then each installed
92
+ * plugin's options in installation order. Every collision between the two scopes, by key or by
93
+ * spelling, is reported here, so the parser meets a table with one owner per name.
82
94
  */
83
95
  declare function globalTable(inputs: readonly OptionInput[], plugins: readonly BuiltPlugin[]): GlobalTable;
84
- /** The table one graph build shares, which every earlier call already proved free of collisions. */
96
+ /**
97
+ * The validated value of each listed option, keyed by declared name. Entries become own keys even
98
+ * for a name such as `__proto__`, which assignment would not.
99
+ */
100
+ declare function optionValues(inputs: readonly OptionInput[], values: ValidatedInputs): Record<string, unknown>;
101
+ /**
102
+ * The validated value of each listed option, keyed by declared name, as a plugin reads it: each
103
+ * value a plain-data copy frozen to every depth, as the request's values are, so a plugin that
104
+ * reaches into one contributes nothing to what another plugin or the action receives.
105
+ */
106
+ declare function frozenValues(inputs: readonly OptionInput[], values: ValidatedInputs): Readonly<Record<string, unknown>>;
107
+ /**
108
+ * The table one graph build shares, which every earlier call already proved free of collisions.
109
+ * A plugin's options are global options, so they join the application's in every reading.
110
+ */
85
111
  declare function buildGlobals(node: GlobalsState, plugins: readonly BuiltPlugin[]): BuiltGlobals;
86
112
  /**
87
113
  * One Command's own options against the globals table, compiled for dispatch. The table holds the
88
- * application's globals and every plugin option, so a local collision reads the same sentence
114
+ * application's global options and every plugin's, so a local collision reads the same sentence
89
115
  * whichever scope on the other side claimed the name, the spelling, or the variable. One
90
116
  * invocation's scope is this Command's own options and the table, so a variable binds one option
91
117
  * there, while a sibling Command may bind it again. `scope` names the Command and places each of
@@ -95,4 +121,4 @@ declare function checkLocalOptions(declarations: readonly OptionInput[], table:
95
121
  /** The options in one list that bind a variable, each named and placed by its scope. */
96
122
  declare function boundOptions(inputs: readonly OptionInput[], describe: (input: OptionInput) => Omit<BoundOption, 'variable'>): BoundOption[];
97
123
  export type { BuiltGlobals, GlobalsState, GlobalTable, InputRecords, OptionOwner, TableEntry };
98
- export { boundOptions, buildGlobals, checkLocalOptions, declareGlobalOption, emptyGlobals, globalSite, globalTable, pluginSites, };
124
+ export { boundOptions, buildGlobals, checkLocalOptions, checkNoPresenceRule, declareGlobalOption, emptyGlobals, frozenValues, globalSite, globalTable, optionValues, pluginSites, };
package/dist/globals.js CHANGED
@@ -3,8 +3,9 @@ import { DeclarationError, quoted } from './errors.js';
3
3
  import { buildExtensions } from './extension.js';
4
4
  import { callSite, checkDeprecated, checkDescription, checkHidden, factFault, pluginOptionSite, siteFinding, } from './facts.js';
5
5
  import { globalPresenceRule, optionDeclaredTwice, spellingTaken } from './input-rules.js';
6
- import { checkOptionName, compileOptions, spellingMark } from './options.js';
7
- import { captureConfig, checkInputConfig } from './validation.js';
6
+ import { checkOptionName, compileOptions, spellingMark, tableEntries } from './options.js';
7
+ import { snapshot } from './plain.js';
8
+ import { captureInputConfig, configUnread } from './validation.js';
8
9
  const globalSubject = 'the global options';
9
10
  /**
10
11
  * The presence keys a global option may not declare, in the order its diagnostic names them. A
@@ -59,7 +60,7 @@ function correction(first, second) {
59
60
  const sideNotes = {
60
61
  application: 'the global option',
61
62
  local: 'the local option',
62
- plugin: 'the plugin option',
63
+ plugin: "the plugin's global option",
63
64
  };
64
65
  /** One key claimed twice, whichever two scopes claimed it. */
65
66
  function keyCollision(first, second) {
@@ -77,12 +78,12 @@ function spellingCollision(spelling, first, second) {
77
78
  const [leading, trailing] = pair;
78
79
  return new DeclarationError(spellingTaken, {
79
80
  correction: 'Change one declaration.',
80
- findings: pair.map(({ owner, role, site }) => siteFinding(site, spellingMark(site, role), sideNotes[owner.kind])),
81
+ findings: pair.map(({ origin, owner, site }) => siteFinding(site, spellingMark(site, origin), sideNotes[owner.kind])),
81
82
  sentence: `Option spelling ${quoted(spelling)} is used by ${usedBy(leading)} and ${usedBy(trailing)}.`,
82
83
  });
83
84
  }
84
85
  function emptyGlobals() {
85
- return { bind: () => ({}), inputs: [], records: new Map() };
86
+ return { inputs: [], records: new Map() };
86
87
  }
87
88
  /** Where one global option was declared: its `globalOption()` call on the Application. */
88
89
  function globalSite(input) {
@@ -99,7 +100,21 @@ function globalSite(input) {
99
100
  */
100
101
  function pluginSites(identity, inputs) {
101
102
  const options = Object.fromEntries(inputs.map(({ config, name }) => [name, config]));
102
- return (input) => pluginOptionSite({ identity, options }, input.name, `Plugin ${quoted(identity)} option "${input.name}"`);
103
+ return (input) => pluginOptionSite({ identity, options }, input.name, `Plugin ${quoted(identity)} option ${quoted(input.name)}`);
104
+ }
105
+ /**
106
+ * A global option declares no presence rule, whether the application or a plugin declares it, and
107
+ * whatever the key's value. The fault marks the key on the call that declared the option.
108
+ */
109
+ function checkNoPresenceRule(site, config) {
110
+ const rejected = omissionRules.find((key) => key in config);
111
+ if (rejected !== undefined) {
112
+ throw factFault(globalPresenceRule, site, {
113
+ correction: `Remove ${rejected}, and check for the value in each Command that needs it.`,
114
+ fact: rejected,
115
+ sentence: `${site.subject} declares ${rejected}.`,
116
+ });
117
+ }
103
118
  }
104
119
  /**
105
120
  * One global option's own facts, binding, and extension values, checked at its `globalOption()`
@@ -108,19 +123,14 @@ function pluginSites(identity, inputs) {
108
123
  */
109
124
  function declareGlobalOption(state, declared, descriptors) {
110
125
  // The name is judged before the config, as every other declaration judges its own name first.
111
- checkOptionName(declared.name, globalSite(declared));
112
- checkInputConfig(declared, { call: 'globalOption', path: [] });
113
- const input = { ...declared, config: captureConfig(declared.config) };
126
+ checkOptionName(declared.name, configUnread(globalSite(declared)));
127
+ const input = {
128
+ ...declared,
129
+ config: captureInputConfig(declared, { call: 'globalOption', path: [] }),
130
+ };
114
131
  const site = globalSite(input);
115
132
  const sentence = site.subject;
116
- const rejected = omissionRules.find((key) => key in input.config);
117
- if (rejected !== undefined) {
118
- throw factFault(globalPresenceRule, site, {
119
- correction: `Remove ${rejected}, and check for the value in each Command that needs it.`,
120
- fact: rejected,
121
- sentence: `${sentence} declares ${rejected}.`,
122
- });
123
- }
133
+ checkNoPresenceRule(site, input.config);
124
134
  checkDescription(site, input.config.description);
125
135
  checkHidden(site, input.config.hidden);
126
136
  checkDeprecated(site, input.config.deprecated);
@@ -136,16 +146,15 @@ function declareGlobalOption(state, declared, descriptors) {
136
146
  return {
137
147
  input,
138
148
  state: {
139
- bind: (values) => ({ ...state.bind(values), ...values.option(input) }),
140
149
  inputs: [...state.inputs, input],
141
150
  records: new Map([...state.records, [input, record]]),
142
151
  },
143
152
  };
144
153
  }
145
154
  /**
146
- * The one table the pre-scan reads: the application's global options in authoring order, then each
147
- * installed plugin's options in installation order. Every collision between the two scopes, by key
148
- * or by spelling, is reported here, so the pre-scan meets a table with one owner per name.
155
+ * The globals table: the application's global options in authoring order, then each installed
156
+ * plugin's options in installation order. Every collision between the two scopes, by key or by
157
+ * spelling, is reported here, so the parser meets a table with one owner per name.
149
158
  */
150
159
  function globalTable(inputs, plugins) {
151
160
  const names = new Map();
@@ -168,20 +177,54 @@ function globalTable(inputs, plugins) {
168
177
  ...plugins.flatMap((installed) => {
169
178
  const siteOf = pluginSites(installed.identity, installed.inputs);
170
179
  return boundOptions(installed.inputs, (input) => ({
171
- phrase: `plugin ${quoted(installed.identity)} option "${input.name}"`,
180
+ phrase: `plugin ${quoted(installed.identity)} option ${quoted(input.name)}`,
172
181
  site: siteOf(input),
173
182
  }));
174
183
  }),
175
184
  ]);
176
185
  return { names, options, variables };
177
186
  }
178
- /** The table one graph build shares, which every earlier call already proved free of collisions. */
187
+ /**
188
+ * The validated value of each listed option, keyed by declared name. Entries become own keys even
189
+ * for a name such as `__proto__`, which assignment would not.
190
+ */
191
+ function optionValues(inputs, values) {
192
+ return Object.fromEntries(inputs.map((input) => [input.name, values.read(input)]));
193
+ }
194
+ /**
195
+ * The validated value of each listed option, keyed by declared name, as a plugin reads it: each
196
+ * value a plain-data copy frozen to every depth, as the request's values are, so a plugin that
197
+ * reaches into one contributes nothing to what another plugin or the action receives.
198
+ */
199
+ function frozenValues(inputs, values) {
200
+ return Object.freeze(Object.fromEntries(inputs.map((input) => [input.name, snapshot(values.read(input))])));
201
+ }
202
+ /**
203
+ * The table one graph build shares, which every earlier call already proved free of collisions.
204
+ * A plugin's options are global options, so they join the application's in every reading.
205
+ */
179
206
  function buildGlobals(node, plugins) {
180
- return { ...globalTable(node.inputs, plugins), bind: node.bind, inputs: node.inputs, plugins };
207
+ const inputs = [...node.inputs, ...plugins.flatMap((installed) => installed.inputs)];
208
+ const sites = new Map(node.inputs.map((input) => [input, globalSite(input)]));
209
+ for (const installed of plugins) {
210
+ const siteOf = pluginSites(installed.identity, installed.inputs);
211
+ for (const input of installed.inputs) {
212
+ sites.set(input, siteOf(input));
213
+ }
214
+ }
215
+ const table = globalTable(node.inputs, plugins);
216
+ return {
217
+ ...table,
218
+ bind: (values) => optionValues(inputs, values),
219
+ inputs,
220
+ plugins,
221
+ sites,
222
+ table: new Map(tableEntries(table.options, true)),
223
+ };
181
224
  }
182
225
  /**
183
226
  * One Command's own options against the globals table, compiled for dispatch. The table holds the
184
- * application's globals and every plugin option, so a local collision reads the same sentence
227
+ * application's global options and every plugin's, so a local collision reads the same sentence
185
228
  * whichever scope on the other side claimed the name, the spelling, or the variable. One
186
229
  * invocation's scope is this Command's own options and the table, so a variable binds one option
187
230
  * there, while a sibling Command may bind it again. `scope` names the Command and places each of
@@ -202,11 +245,11 @@ function checkLocalOptions(declarations, table, scope) {
202
245
  const claimed = global && table.names.get(global.name);
203
246
  const declaration = declarations.find((input) => input.name === option.name);
204
247
  if (global && claimed && declaration) {
205
- throw spellingCollision(spelling, { ...claimed, name: global.name, role: global.role }, { name: option.name, owner: local, role: option.role, site: siteOf(declaration) });
248
+ throw spellingCollision(spelling, { ...claimed, name: global.name, origin: global }, { name: option.name, origin: option, owner: local, site: siteOf(declaration) });
206
249
  }
207
250
  }
208
251
  claimVariables(boundOptions(declarations, (input) => ({
209
- phrase: `${subject} option "${input.name}"`,
252
+ phrase: `${subject} option ${quoted(input.name)}`,
210
253
  site: siteOf(input),
211
254
  })), table.variables);
212
255
  return options;
@@ -234,9 +277,9 @@ function join(owner, inputs, table) {
234
277
  const claimed = existing && names.get(existing.name);
235
278
  const own = names.get(option.name);
236
279
  if (existing && claimed && own) {
237
- throw spellingCollision(spelling, { ...own, name: option.name, role: option.role }, { ...claimed, name: existing.name, role: existing.role });
280
+ throw spellingCollision(spelling, { ...own, name: option.name, origin: option }, { ...claimed, name: existing.name, origin: existing });
238
281
  }
239
282
  options.set(spelling, option);
240
283
  }
241
284
  }
242
- export { boundOptions, buildGlobals, checkLocalOptions, declareGlobalOption, emptyGlobals, globalSite, globalTable, pluginSites, };
285
+ export { boundOptions, buildGlobals, checkLocalOptions, checkNoPresenceRule, declareGlobalOption, emptyGlobals, frozenValues, globalSite, globalTable, optionValues, pluginSites, };
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/check-glyph-catalog.py from Inquirer a446ea9e273864a3653a26943f59b4fbe8003796.
2
- /*
2
+ /*!
3
3
  Copyright (c) 2025 Simon Boudrias
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { validationContext, validationContextKey } from './context.js';
5
5
  export { diagnosticRule } from './diagnostic.js';
6
6
  export { isRuleIdentity } from './identity.js';
7
7
  export type { DiagnosticParts, DiagnosticRule, Finding } from './diagnostic-text.js';
8
- export { DeclarationError, FatalError, InputError, InternalError, LoomError, MissingValueError, NonCallableCommandError, RepeatedOptionError, ResultError, ShortGroupError, UnexpectedArgumentError, UnexpectedValueError, UnknownCommandError, UnknownOptionError, UsageError, } from './errors.js';
8
+ export { DeclarationError, FatalError, InputError, InternalError, LoomError, MisplacedOptionError, MissingValueError, NonCallableCommandError, RepeatedOptionError, ResultError, UnexpectedArgumentError, UnexpectedValueError, UnknownCommandError, UnknownOptionError, UsageError, } from './errors.js';
9
9
  export { EX_CANTCREAT, EX_CONFIG, EX_DATAERR, EX_IOERR, EX_NOHOST, EX_NOINPUT, EX_NOPERM, EX_NOUSER, EX_OSERR, EX_OSFILE, EX_PROTOCOL, EX_SOFTWARE, EX_TEMPFAIL, EX_UNAVAILABLE, EX_USAGE, } from './exit-codes.js';
10
10
  export type { FailureExitCode } from './exit-codes.js';
11
11
  export { extension, readExtension } from './extension.js';
@@ -13,11 +13,13 @@ export { locate } from './locate.js';
13
13
  export { incompleteResult, lanes } from './lanes.js';
14
14
  export { override, view } from './view.js';
15
15
  export { plugin } from './plugin.js';
16
+ export { checkShortSetting } from './plugin-settings.js';
16
17
  export { translate } from './translators.js';
17
18
  export { glyph } from './glyphs.generated.js';
18
19
  export type { RenderingPolicy } from './rendering.js';
19
20
  export type { ViewContext } from './types.js';
20
21
  export { pad, style } from './style.js';
22
+ export { reportedSpelling } from './inspect.js';
21
23
  export { issuePath } from './validation.js';
22
24
  export type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
23
25
  export type { ApplicationMethod, ApplicationOptions, Packet } from './application.js';
@@ -30,9 +32,9 @@ export type { CancellationReason } from './signals.js';
30
32
  export type { ErrorClass, Translation, Translator } from './translators.js';
31
33
  export type { IncompleteResult } from './lanes.js';
32
34
  export type { WordPosition } from './locate.js';
33
- export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureView, FailureViewContext, ViewContribution, ViewOverride, } from './view.js';
35
+ export type { AnyDeclaredView, AnyOverrideKey, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureView, FailureViewContext, ReplacementView, ViewContribution, ViewOverride, } from './view.js';
34
36
  export type { ArgumentNode, CommandGraph, CommandNode, OptionNode, ResultNode } from './inspect.js';
35
37
  export type { Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, SourceAnswer, SourceContext, SourceResolver, } from './plugin.js';
36
- export type { Action, ActionArgs, ActionContext, ActionHandler, ActionOptions, ArgumentConfig, AttachedCommand, BooleanOption, CommandAttachHook, ExitCode, Host, InputIdentity, InputTerminal, Out, OptionConfig, OutputTerminal, Request, ResultInput, ResultView, ResultViews, RunOptions, ScalarArgument, StringOption, SuppliedInputs, RowView, RowViews, ValidationContext, VariadicArgument, View, } from './types.js';
38
+ export type { Action, ActionArgs, ActionContext, ActionHandler, ActionOptions, ArgumentConfig, AttachedCommand, BooleanOption, CommandAttachHook, CountOption, ExitCode, Host, InputIdentity, InputTerminal, Out, OptionConfig, OutputTerminal, Request, ResultInput, ResultView, ResultViews, RunOptions, ScalarArgument, StringOption, SuppliedInputs, RowView, RowViews, ValidationContext, VariadicArgument, View, } from './types.js';
37
39
  export type { ApplicationEnvironment, EnvironmentOf, Register, RegisteredEnvironment, } from './environment.js';
38
40
  export type { Ansi16Color, Ansi256Fallbacks, ColorFallbacks, ConcreteStyle, ContextualStyle, Style, ThemeMapping, ThemeConstraint, } from './style.js';
package/dist/index.js CHANGED
@@ -4,14 +4,16 @@ export { escapeControlCharacters } from './controls.js';
4
4
  export { validationContext, validationContextKey } from './context.js';
5
5
  export { diagnosticRule } from './diagnostic.js';
6
6
  export { isRuleIdentity } from './identity.js';
7
- export { DeclarationError, FatalError, InputError, InternalError, LoomError, MissingValueError, NonCallableCommandError, RepeatedOptionError, ResultError, ShortGroupError, UnexpectedArgumentError, UnexpectedValueError, UnknownCommandError, UnknownOptionError, UsageError, } from './errors.js';
7
+ export { DeclarationError, FatalError, InputError, InternalError, LoomError, MisplacedOptionError, MissingValueError, NonCallableCommandError, RepeatedOptionError, ResultError, UnexpectedArgumentError, UnexpectedValueError, UnknownCommandError, UnknownOptionError, UsageError, } from './errors.js';
8
8
  export { EX_CANTCREAT, EX_CONFIG, EX_DATAERR, EX_IOERR, EX_NOHOST, EX_NOINPUT, EX_NOPERM, EX_NOUSER, EX_OSERR, EX_OSFILE, EX_PROTOCOL, EX_SOFTWARE, EX_TEMPFAIL, EX_UNAVAILABLE, EX_USAGE, } from './exit-codes.js';
9
9
  export { extension, readExtension } from './extension.js';
10
10
  export { locate } from './locate.js';
11
11
  export { incompleteResult, lanes } from './lanes.js';
12
12
  export { override, view } from './view.js';
13
13
  export { plugin } from './plugin.js';
14
+ export { checkShortSetting } from './plugin-settings.js';
14
15
  export { translate } from './translators.js';
15
16
  export { glyph } from './glyphs.generated.js';
16
17
  export { pad, style } from './style.js';
18
+ export { reportedSpelling } from './inspect.js';
17
19
  export { issuePath } from './validation.js';
@@ -1,4 +1,4 @@
1
- /** An option declared with a type other than string or Boolean. */
1
+ /** An option declared with a type other than string, Boolean, or count. */
2
2
  declare const optionType: import("./diagnostic-text.js").DiagnosticRule;
3
3
  /** A short alias that is not one ASCII letter. */
4
4
  declare const shortAlias: import("./diagnostic-text.js").DiagnosticRule;
@@ -9,8 +9,18 @@ declare const shortAlias: import("./diagnostic-text.js").DiagnosticRule;
9
9
  declare const flagNotBoolean: import("./diagnostic-text.js").DiagnosticRule;
10
10
  /** `shortOnly` on an option that declares no short alias. */
11
11
  declare const shortOnlyWithoutShort: import("./diagnostic-text.js").DiagnosticRule;
12
+ /** `aliases` on an option that declares `shortOnly`. */
13
+ declare const shortOnlyWithAliases: import("./diagnostic-text.js").DiagnosticRule;
12
14
  /** `multiple` on a Boolean option. */
13
15
  declare const booleanOptionMultiple: import("./diagnostic-text.js").DiagnosticRule;
16
+ /** `multiple` on a counted option. */
17
+ declare const countOptionMultiple: import("./diagnostic-text.js").DiagnosticRule;
18
+ /** `polarity` on a counted option. */
19
+ declare const polarityOnCount: import("./diagnostic-text.js").DiagnosticRule;
20
+ /** `implied` on a Boolean or counted option. */
21
+ declare const impliedOnBooleanOrCount: import("./diagnostic-text.js").DiagnosticRule;
22
+ /** An `implied` value that is not a string. */
23
+ declare const impliedNotAString: import("./diagnostic-text.js").DiagnosticRule;
14
24
  /** `polarity` on a string option. */
15
25
  declare const polarityOnString: import("./diagnostic-text.js").DiagnosticRule;
16
26
  /** A polarity outside the three settings. */
@@ -51,14 +61,22 @@ declare const omissionAlreadyDecided: import("./diagnostic-text.js").DiagnosticR
51
61
  declare const omissionWithoutValidator: import("./diagnostic-text.js").DiagnosticRule;
52
62
  /** `validate`, `default`, `required`, or `validateOmitted` on a Boolean option. */
53
63
  declare const booleanOptionValueRule: import("./diagnostic-text.js").DiagnosticRule;
64
+ /** `validate`, `default`, `required`, or `validateOmitted` on a counted option. */
65
+ declare const countOptionValueRule: import("./diagnostic-text.js").DiagnosticRule;
54
66
  /** A required input that also declares a default. */
55
67
  declare const requiredWithDefault: import("./diagnostic-text.js").DiagnosticRule;
56
68
  /** A `validate` value that is not a Standard Schema v1 object. */
57
69
  declare const notAValidator: import("./diagnostic-text.js").DiagnosticRule;
58
70
  /** A default of the wrong raw shape for its declaration. */
59
71
  declare const defaultShape: import("./diagnostic-text.js").DiagnosticRule;
72
+ /** The most arrays and plain objects any path through a declared default may hold. */
73
+ declare const defaultLevels = 10;
74
+ /** A declared default nested deeper than `defaultLevels`. */
75
+ declare const defaultDepth: import("./diagnostic-text.js").DiagnosticRule;
60
76
  /** A declared default its validator rejected. */
61
77
  declare const invalidDefault: import("./diagnostic-text.js").DiagnosticRule;
78
+ /** A declared implied value its validator rejected. */
79
+ declare const invalidImplied: import("./diagnostic-text.js").DiagnosticRule;
62
80
  /** A validator's JSON Schema converter that threw or returned a value that is not a plain object. */
63
81
  declare const schemaConverterFailed: import("./diagnostic-text.js").DiagnosticRule;
64
- export { booleanOptionMultiple, booleanOptionValueRule, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
82
+ export { booleanOptionMultiple, booleanOptionValueRule, countOptionMultiple, countOptionValueRule, defaultDepth, defaultLevels, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, impliedNotAString, impliedOnBooleanOrCount, invalidDefault, invalidImplied, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnCount, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithAliases, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };