@loomcli/core 0.7.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.
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/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,7 +3,8 @@ 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';
6
+ import { checkOptionName, compileOptions, spellingMark, tableEntries } from './options.js';
7
+ import { snapshot } from './plain.js';
7
8
  import { captureInputConfig, configUnread } from './validation.js';
8
9
  const globalSubject = 'the global options';
9
10
  /**
@@ -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) {
@@ -101,6 +102,20 @@ function pluginSites(identity, inputs) {
101
102
  const options = Object.fromEntries(inputs.map(({ config, name }) => [name, config]));
102
103
  return (input) => pluginOptionSite({ identity, options }, input.name, `Plugin ${quoted(identity)} option ${quoted(input.name)}`);
103
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
+ }
118
+ }
104
119
  /**
105
120
  * One global option's own facts, binding, and extension values, checked at its `globalOption()`
106
121
  * call against the Application's descriptors. The rules that pair it with another option belong to
@@ -115,14 +130,7 @@ function declareGlobalOption(state, declared, descriptors) {
115
130
  };
116
131
  const site = globalSite(input);
117
132
  const sentence = site.subject;
118
- const rejected = omissionRules.find((key) => key in input.config);
119
- if (rejected !== undefined) {
120
- throw factFault(globalPresenceRule, site, {
121
- correction: `Remove ${rejected}, and check for the value in each Command that needs it.`,
122
- fact: rejected,
123
- sentence: `${sentence} declares ${rejected}.`,
124
- });
125
- }
133
+ checkNoPresenceRule(site, input.config);
126
134
  checkDescription(site, input.config.description);
127
135
  checkHidden(site, input.config.hidden);
128
136
  checkDeprecated(site, input.config.deprecated);
@@ -138,16 +146,15 @@ function declareGlobalOption(state, declared, descriptors) {
138
146
  return {
139
147
  input,
140
148
  state: {
141
- bind: (values) => ({ ...state.bind(values), ...values.option(input) }),
142
149
  inputs: [...state.inputs, input],
143
150
  records: new Map([...state.records, [input, record]]),
144
151
  },
145
152
  };
146
153
  }
147
154
  /**
148
- * The one table the pre-scan reads: the application's global options in authoring order, then each
149
- * installed plugin's options in installation order. Every collision between the two scopes, by key
150
- * 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.
151
158
  */
152
159
  function globalTable(inputs, plugins) {
153
160
  const names = new Map();
@@ -177,13 +184,47 @@ function globalTable(inputs, plugins) {
177
184
  ]);
178
185
  return { names, options, variables };
179
186
  }
180
- /** 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
+ */
181
206
  function buildGlobals(node, plugins) {
182
- 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
+ };
183
224
  }
184
225
  /**
185
226
  * One Command's own options against the globals table, compiled for dispatch. The table holds the
186
- * 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
187
228
  * whichever scope on the other side claimed the name, the spelling, or the variable. One
188
229
  * invocation's scope is this Command's own options and the table, so a variable binds one option
189
230
  * there, while a sibling Command may bind it again. `scope` names the Command and places each of
@@ -204,7 +245,7 @@ function checkLocalOptions(declarations, table, scope) {
204
245
  const claimed = global && table.names.get(global.name);
205
246
  const declaration = declarations.find((input) => input.name === option.name);
206
247
  if (global && claimed && declaration) {
207
- 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) });
208
249
  }
209
250
  }
210
251
  claimVariables(boundOptions(declarations, (input) => ({
@@ -236,9 +277,9 @@ function join(owner, inputs, table) {
236
277
  const claimed = existing && names.get(existing.name);
237
278
  const own = names.get(option.name);
238
279
  if (existing && claimed && own) {
239
- 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 });
240
281
  }
241
282
  options.set(spelling, option);
242
283
  }
243
284
  }
244
- export { boundOptions, buildGlobals, checkLocalOptions, declareGlobalOption, emptyGlobals, globalSite, globalTable, pluginSites, };
285
+ export { boundOptions, buildGlobals, checkLocalOptions, checkNoPresenceRule, declareGlobalOption, emptyGlobals, frozenValues, globalSite, globalTable, optionValues, pluginSites, };
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';
@@ -19,6 +19,7 @@ export { glyph } from './glyphs.generated.js';
19
19
  export type { RenderingPolicy } from './rendering.js';
20
20
  export type { ViewContext } from './types.js';
21
21
  export { pad, style } from './style.js';
22
+ export { reportedSpelling } from './inspect.js';
22
23
  export { issuePath } from './validation.js';
23
24
  export type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
24
25
  export type { ApplicationMethod, ApplicationOptions, Packet } from './application.js';
@@ -34,6 +35,6 @@ export type { WordPosition } from './locate.js';
34
35
  export type { AnyDeclaredView, AnyOverrideKey, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureView, FailureViewContext, ReplacementView, ViewContribution, ViewOverride, } from './view.js';
35
36
  export type { ArgumentNode, CommandGraph, CommandNode, OptionNode, ResultNode } from './inspect.js';
36
37
  export type { Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, SourceAnswer, SourceContext, SourceResolver, } from './plugin.js';
37
- 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';
38
39
  export type { ApplicationEnvironment, EnvironmentOf, Register, RegisteredEnvironment, } from './environment.js';
39
40
  export type { Ansi16Color, Ansi256Fallbacks, ColorFallbacks, ConcreteStyle, ContextualStyle, Style, ThemeMapping, ThemeConstraint, } from './style.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ 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';
@@ -15,4 +15,5 @@ export { checkShortSetting } from './plugin-settings.js';
15
15
  export { translate } from './translators.js';
16
16
  export { glyph } from './glyphs.generated.js';
17
17
  export { pad, style } from './style.js';
18
+ export { reportedSpelling } from './inspect.js';
18
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,6 +61,8 @@ 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. */
@@ -63,6 +75,8 @@ declare const defaultLevels = 10;
63
75
  declare const defaultDepth: import("./diagnostic-text.js").DiagnosticRule;
64
76
  /** A declared default its validator rejected. */
65
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;
66
80
  /** A validator's JSON Schema converter that threw or returned a value that is not a plain object. */
67
81
  declare const schemaConverterFailed: import("./diagnostic-text.js").DiagnosticRule;
68
- export { booleanOptionMultiple, booleanOptionValueRule, defaultDepth, defaultLevels, 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, };
@@ -4,9 +4,9 @@ import { registerRule } from './diagnostic-text.js';
4
4
  * options, and environment bindings. Each is declared once here and shared by every site that
5
5
  * raises it, as a plugin's rules are.
6
6
  */
7
- /** An option declared with a type other than string or Boolean. */
7
+ /** An option declared with a type other than string, Boolean, or count. */
8
8
  const optionType = registerRule('@loomcli/core/option-type', {
9
- explanation: 'The type decides how the parser reads an option: a string option consumes a value, and a Boolean option consumes none. Core reads no other kind.',
9
+ explanation: 'The type decides how the parser reads an option: a string option consumes a value, a Boolean option consumes none, and a counted option consumes none and counts its occurrences. Core reads no other kind.',
10
10
  headline: 'Invalid option type',
11
11
  });
12
12
  /** A short alias that is not one ASCII letter. */
@@ -27,11 +27,36 @@ const shortOnlyWithoutShort = registerRule('@loomcli/core/short-only-without-sho
27
27
  explanation: 'shortOnly removes every long spelling of an option, so an option with no short alias would leave an operator no spelling to type.',
28
28
  headline: 'Short only with no short alias',
29
29
  });
30
+ /** `aliases` on an option that declares `shortOnly`. */
31
+ const shortOnlyWithAliases = registerRule('@loomcli/core/short-only-with-aliases', {
32
+ explanation: 'shortOnly removes every long spelling of an option, and each alias adds a long spelling, so an option cannot declare both.',
33
+ headline: 'Short only with aliases',
34
+ });
30
35
  /** `multiple` on a Boolean option. */
31
36
  const booleanOptionMultiple = registerRule('@loomcli/core/boolean-option-multiple', {
32
37
  explanation: 'A Boolean option reports whether its spelling was supplied, so a repeat has no second value to collect. multiple collects each occurrence of a string option into an array.',
33
38
  headline: 'Boolean option takes one value',
34
39
  });
40
+ /** `multiple` on a counted option. */
41
+ const countOptionMultiple = registerRule('@loomcli/core/count-option-multiple', {
42
+ explanation: 'A counted option already counts every occurrence of every spelling, so it has no values to collect. multiple collects each occurrence of a string option into an array.',
43
+ headline: 'Counted option takes no values',
44
+ });
45
+ /** `polarity` on a counted option. */
46
+ const polarityOnCount = registerRule('@loomcli/core/polarity-on-count', {
47
+ explanation: 'Polarity chooses which long forms a Boolean option accepts and what its absence means. A counted option reads how many times it was supplied, so it has no negative form and no polarity.',
48
+ headline: 'Polarity on a counted option',
49
+ });
50
+ /** `implied` on a Boolean or counted option. */
51
+ const impliedOnBooleanOrCount = registerRule('@loomcli/core/implied-on-boolean-or-count', {
52
+ explanation: 'An implied value is the value a bare spelling of a string option supplies. A Boolean option and a counted option take no value, so a bare spelling already says everything they read.',
53
+ headline: 'Implied on a valueless option',
54
+ });
55
+ /** An `implied` value that is not a string. */
56
+ const impliedNotAString = registerRule('@loomcli/core/implied-not-a-string', {
57
+ explanation: "An implied value stands in for the string an operator would otherwise attach to the spelling, so it is a string, in the validator's input type.",
58
+ headline: 'Implied value not a string',
59
+ });
35
60
  /** `polarity` on a string option. */
36
61
  const polarityOnString = registerRule('@loomcli/core/polarity-on-string', {
37
62
  explanation: 'Polarity chooses which long forms a Boolean option accepts and what its absence means. A string option takes its value from the operator, so it has no polarity.',
@@ -52,7 +77,7 @@ const shortOnlyBothPolarities = registerRule('@loomcli/core/short-only-both-pola
52
77
  * plugin's hook declared.
53
78
  */
54
79
  const spellingTaken = registerRule('@loomcli/core/spelling-taken', {
55
- explanation: "The parser reads each spelling as one option, and a Command's own options share one invocation with the global options and every installed plugin's options. A spelling two options claim, a short alias or a generated negative form included, would reach only one of them.",
80
+ explanation: "The parser reads each spelling as one option, and a Command's own options share one invocation with the global options and every installed plugin's options. A spelling two options claim, a short alias, an alias, or a generated negative form included, would reach only one of them.",
56
81
  headline: 'Spelling used twice',
57
82
  });
58
83
  /**
@@ -60,7 +85,7 @@ const spellingTaken = registerRule('@loomcli/core/spelling-taken', {
60
85
  * plugin's hook declared.
61
86
  */
62
87
  const optionDeclaredTwice = registerRule('@loomcli/core/option-declared-twice', {
63
- explanation: "An action reads the global options and its Command's own options from one options object, each under its declared name, and the pre-scan reads the global options and every installed plugin's options from one table. Two options with one name in either leave one of them unreadable.",
88
+ explanation: "An action reads the global options and its Command's own options from one options object, each under its declared name, and the global options, the application's and every installed plugin's, share every Command's one table of spellings. Two options with one name in either leave one of them unreadable.",
64
89
  headline: 'Option declared twice',
65
90
  });
66
91
  /**
@@ -117,6 +142,11 @@ const booleanOptionValueRule = registerRule('@loomcli/core/boolean-option-value-
117
142
  explanation: 'A Boolean option consumes no value, so there is nothing to validate, and its polarity decides the value an absent option reads. validate, default, required, and validateOmitted belong to inputs that take a value.',
118
143
  headline: 'Value rule on a Boolean option',
119
144
  });
145
+ /** `validate`, `default`, `required`, or `validateOmitted` on a counted option. */
146
+ const countOptionValueRule = registerRule('@loomcli/core/count-option-value-rule', {
147
+ explanation: 'A counted option consumes no value and reads how many times it was supplied, 0 when nothing supplied it, so there is nothing to validate and no absence to decide. validate, default, required, and validateOmitted belong to inputs that take a value.',
148
+ headline: 'Value rule on a counted option',
149
+ });
120
150
  /** A required input that also declares a default. */
121
151
  const requiredWithDefault = registerRule('@loomcli/core/required-with-default', {
122
152
  explanation: 'A default fills an omitted value, and a required input fails when it is omitted, so a required input never reads its default.',
@@ -144,9 +174,14 @@ const invalidDefault = registerRule('@loomcli/core/invalid-default', {
144
174
  explanation: 'Each run passes every declared default through its validator before it reads a token, because a default reaches the action as a validated value. A default the validator rejects would reach no action, whatever the operator supplies.',
145
175
  headline: 'Default rejected',
146
176
  });
177
+ /** A declared implied value its validator rejected. */
178
+ const invalidImplied = registerRule('@loomcli/core/invalid-implied', {
179
+ explanation: 'Each run passes every implied value through its validator before it reads a token, because a bare spelling supplies it to the action as a validated value. An implied value the validator rejects is the declaration at fault, whatever the operator supplies, so the run reports it whether or not a bare spelling was typed.',
180
+ headline: 'Implied value rejected',
181
+ });
147
182
  /** A validator's JSON Schema converter that threw or returned a value that is not a plain object. */
148
183
  const schemaConverterFailed = registerRule('@loomcli/core/schema-converter-failed', {
149
184
  explanation: "Help, the manifest, and completion read what an input accepts from the JSON Schema its validator publishes. A converter that throws or returns anything but a plain object publishes no shape, so a distributed build reads the input's schema as null.",
150
185
  headline: 'Schema converter failed',
151
186
  });
152
- export { booleanOptionMultiple, booleanOptionValueRule, defaultDepth, defaultLevels, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
187
+ 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, };