@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.
- package/NOTICE +34 -0
- package/dist/application.d.ts +11 -6
- package/dist/application.js +57 -19
- package/dist/capture.d.ts +65 -0
- package/dist/capture.js +99 -0
- package/dist/chain.d.ts +28 -17
- package/dist/chain.js +11 -10
- package/dist/command-rules.d.ts +1 -1
- package/dist/command-rules.js +2 -2
- package/dist/command.d.ts +31 -47
- package/dist/command.js +204 -209
- package/dist/errors.d.ts +22 -21
- package/dist/errors.js +38 -18
- package/dist/facts.d.ts +5 -0
- package/dist/facts.js +8 -1
- package/dist/globals.d.ts +48 -22
- package/dist/globals.js +72 -29
- package/dist/glyphs.generated.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -1
- package/dist/input-rules.d.ts +20 -2
- package/dist/input-rules.js +47 -5
- package/dist/inspect.d.ts +33 -17
- package/dist/inspect.js +74 -87
- package/dist/locate.js +52 -60
- package/dist/options.d.ts +101 -83
- package/dist/options.js +311 -267
- package/dist/parse.d.ts +162 -0
- package/dist/parse.js +601 -0
- package/dist/plain.d.ts +52 -2
- package/dist/plain.js +228 -2
- package/dist/plugin-rules.d.ts +8 -4
- package/dist/plugin-rules.js +13 -9
- package/dist/plugin-settings.d.ts +18 -0
- package/dist/plugin-settings.js +38 -0
- package/dist/plugin.d.ts +32 -27
- package/dist/plugin.js +107 -89
- package/dist/sources.d.ts +18 -9
- package/dist/sources.js +50 -20
- package/dist/style-layout.js +2 -2
- package/dist/style-width.d.ts +13 -0
- package/dist/style-width.js +170 -0
- package/dist/types.d.ts +70 -30
- package/dist/unicode.generated.d.ts +27 -0
- package/dist/unicode.generated.js +1036 -0
- package/dist/validation.d.ts +108 -34
- package/dist/validation.js +282 -125
- package/dist/view.d.ts +16 -11
- package/dist/view.js +13 -4
- package/licenses/unicode-LICENSE.txt +41 -0
- package/licenses/uucode-LICENSE.md +35 -0
- package/package.json +9 -5
package/NOTICE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@loomcli/core
|
|
2
|
+
|
|
3
|
+
This package includes material from uucode and from the Unicode Character
|
|
4
|
+
Database. Its license field reads "MIT AND Unicode-3.0": the Unicode tables
|
|
5
|
+
named below stay under the Unicode License v3, and every other file is under
|
|
6
|
+
the MIT License in LICENSE or, for the ported width iterator, in
|
|
7
|
+
licenses/uucode-LICENSE.md. Neither license asks anything of an application
|
|
8
|
+
that installs this package beyond keeping these notices with the files they
|
|
9
|
+
cover.
|
|
10
|
+
|
|
11
|
+
Unicode Character Database
|
|
12
|
+
Copyright © 1991-2025 Unicode, Inc.
|
|
13
|
+
Licensed under the Unicode License v3.
|
|
14
|
+
A copy of the License is in licenses/unicode-LICENSE.txt, and at
|
|
15
|
+
https://www.unicode.org/license.txt
|
|
16
|
+
|
|
17
|
+
uucode
|
|
18
|
+
Copyright (c) 2026 Tim Culverhouse
|
|
19
|
+
Licensed under the MIT License.
|
|
20
|
+
A copy of the License is in licenses/uucode-LICENSE.md.
|
|
21
|
+
Source: @rockorager/uucode 2.2.1 on npm
|
|
22
|
+
|
|
23
|
+
The Unicode tables core measures text with, in dist/unicode.generated.js, are
|
|
24
|
+
generated by the Loom repository's scripts/generate-unicode-tables.mjs from
|
|
25
|
+
the data files of @rockorager/uucode 2.2.1, which derive from Unicode 17 data.
|
|
26
|
+
The generator writes the same values as a JavaScript module, so a bundle
|
|
27
|
+
carries them.
|
|
28
|
+
|
|
29
|
+
The grapheme width iterator in dist/style-width.js is ported from the
|
|
30
|
+
@rockorager/uucode 2.2.1 width module. Changes made to it:
|
|
31
|
+
|
|
32
|
+
- It is written in TypeScript and reads the generated tables module.
|
|
33
|
+
- It yields each grapheme's start, end, and width, and no longer yields the
|
|
34
|
+
grapheme's text or offers clone, peek, or a standalone string width.
|
package/dist/application.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ import type { ApplicationEnvironment, applicationEnvironment } from './environme
|
|
|
3
3
|
import type { ExtensionValue } from './extension.js';
|
|
4
4
|
import type { GlobalsState } from './globals.js';
|
|
5
5
|
import type { CommandGraph } from './inspect.js';
|
|
6
|
-
import type { BuiltPlugin, Plugin } from './plugin.js';
|
|
6
|
+
import type { BuiltPlugin, InstalledOptionValues, Plugin } from './plugin.js';
|
|
7
7
|
import type { RenderingPolicy } from './rendering.js';
|
|
8
8
|
import type { Translation, TranslatorRegistry } from './translators.js';
|
|
9
|
-
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, GlobalNameConstraint, GlobalOmissionConstraint, PerValueConstraint, NameConstraint, ExitCode, OptionConfig, OptionValue, ResultViews, ResultViewsOf, RowViews, RunOptions, ValidateOmittedConstraint } from './types.js';
|
|
9
|
+
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, ImpliedConstraint, GlobalNameConstraint, GlobalOmissionConstraint, PerValueConstraint, NameConstraint, ExitCode, OptionConfig, OptionValue, ResultViews, ResultViewsOf, RowViews, RunOptions, ValidateOmittedConstraint } from './types.js';
|
|
10
10
|
import type { ViewContributions, ViewOverride } from './view.js';
|
|
11
11
|
/**
|
|
12
12
|
* Every authoring call an Application can publish, beside `run()` and `name`, which always remain.
|
|
@@ -65,15 +65,15 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
65
65
|
readonly [declaredTypes]: DeclaredTypes<Args, Options, Globals, Result>;
|
|
66
66
|
constructor(name: string, declared: {
|
|
67
67
|
config: ApplicationConfig;
|
|
68
|
-
globals: GlobalsState
|
|
68
|
+
globals: GlobalsState;
|
|
69
69
|
root: CommandState<Args, Options, Globals>;
|
|
70
70
|
});
|
|
71
71
|
get name(): string;
|
|
72
72
|
argument<const Name extends string, const Config extends ArgumentConfig>(name: Name, config: Config & NameConstraint<Name> & NoInfer<DefaultConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args & Record<Name, ArgumentValue<Config>>, Options, Globals, AfterArgument<State>, Plugins, Result>;
|
|
73
|
-
option<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & GlobalNameConstraint<Name, Globals> & NoInfer<DefaultConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args, Options & Record<Name, OptionValue<Config>>, Globals, State, Plugins, Result>;
|
|
73
|
+
option<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & GlobalNameConstraint<Name, Globals> & NoInfer<DefaultConstraint<Config>> & NoInfer<ImpliedConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args, Options & Record<Name, OptionValue<Config>>, Globals, State, Plugins, Result>;
|
|
74
74
|
globalOption<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & (Name extends keyof Options ? {
|
|
75
75
|
'This option name is already declared as a local option': Name;
|
|
76
|
-
} : unknown) & NoInfer<DefaultConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>> & NoInfer<GlobalOmissionConstraint<Config>>): Application<Args, Options, Globals & Record<Name, OptionValue<Config>>, State, Plugins, Result>;
|
|
76
|
+
} : unknown) & NoInfer<DefaultConstraint<Config>> & NoInfer<ImpliedConstraint<Config>> & NoInfer<PerValueConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>> & NoInfer<GlobalOmissionConstraint<Config>>): Application<Args, Options, Globals & Record<Name, OptionValue<Config>>, State, Plugins, Result>;
|
|
77
77
|
/** Registering the action closes input authoring; extension configuration remains available. */
|
|
78
78
|
action(handler: Action<Args, Globals & Options, Result>): Application<Args, Options, Globals, AfterAction, Plugins, Result>;
|
|
79
79
|
/** A child arrives in any type state, because its own action is the call that finished it. */
|
|
@@ -133,9 +133,14 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
133
133
|
* `Application<A, O, G>` accepts an application in any state, a finished one included.
|
|
134
134
|
*/
|
|
135
135
|
export type Application<Args = {}, Options = {}, Globals = {}, State extends ApplicationMethod = AfterAction, Plugins extends readonly Plugin[] = readonly Plugin[], Result = unknown> = Pick<ApplicationBuilder<Args, Options, Globals, State, Plugins, Result>, typeof applicationEnvironment | typeof declaredTypes | 'extend' | 'inspect' | 'name' | 'run' | State | ResultMethod<Result>>;
|
|
136
|
+
/**
|
|
137
|
+
* An Application starts with the option values its installed plugins contribute as its globals,
|
|
138
|
+
* computed once from the constructor's `plugins`, so every action reads them typed and each
|
|
139
|
+
* `globalOption()` call adds its own to them.
|
|
140
|
+
*/
|
|
136
141
|
interface ApplicationConstructor {
|
|
137
142
|
new (name: string): Application<{}, {}, {}, ApplicationMethod, readonly []>;
|
|
138
|
-
new <const Plugins extends readonly Plugin[] = readonly []>(name: string, options: ApplicationOptions<Plugins>): Application<{}, {},
|
|
143
|
+
new <const Plugins extends readonly Plugin[] = readonly []>(name: string, options: ApplicationOptions<Plugins>): Application<{}, {}, InstalledOptionValues<Plugins>, ApplicationMethod, Plugins>;
|
|
139
144
|
}
|
|
140
145
|
/** The core facts one Application declares, validated at construction and reported by `inspect()`. */
|
|
141
146
|
interface ApplicationFacts {
|
package/dist/application.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { captureDeclaration, unreadableArgument } from './capture.js';
|
|
1
2
|
import { runInvocation } from './chain.js';
|
|
2
3
|
import { portableName } from './command-rules.js';
|
|
3
4
|
import { attachToRoot, callArguments, childNode, buildGraph, checkDeclaredOptions, collectInputs, commandPlacement, inputPlaces, declareAction, declareExtensions, declareArgument, declareOption, declareResult, declareResultViews, freshState, isPortableName, layerOf, portableNameCorrection, } from './command.js';
|
|
4
5
|
import { escapeControlCharacters } from './controls.js';
|
|
6
|
+
import { elided, spelled } from './diagnostic-text.js';
|
|
5
7
|
import { DeclarationError, exitCodeOf, InternalError, quoted, reasonOf, toFailure, } from './errors.js';
|
|
6
8
|
import { storeCommandLayers } from './extension.js';
|
|
7
9
|
import { checkDescription, checkNoListingFacts, checkVersion, partFinding, slotSite, } from './facts.js';
|
|
@@ -12,14 +14,14 @@ import { globalOptionAfterCommand } from './input-rules.js';
|
|
|
12
14
|
import { inspectGraph } from './inspect.js';
|
|
13
15
|
import { coreViews } from './lanes.js';
|
|
14
16
|
import { Output, reportPlainly } from './output.js';
|
|
15
|
-
import { isPlainObject } from './plain.js';
|
|
17
|
+
import { declaring, isPlainObject, shallowList, shallowRecord } from './plain.js';
|
|
16
18
|
import { invalidPacket, notAnObject, retiredApplicationOption } from './plugin-rules.js';
|
|
17
19
|
import { installPlugins, ownedSignals, pluginViews } from './plugin.js';
|
|
18
20
|
import { renderingPolicy } from './rendering.js';
|
|
19
21
|
import { brokenOutputView, runOptions, viewCorrection } from './rules.js';
|
|
20
22
|
import { bracketRun, cancellationCode, isCancellationEcho } from './signals.js';
|
|
21
23
|
import { readTranslations, translateThrow } from './translators.js';
|
|
22
|
-
import { checkDeclarations,
|
|
24
|
+
import { checkDeclarations, prepareDeclaredValues } from './validation.js';
|
|
23
25
|
import { buildViews, viewIdentities } from './view.js';
|
|
24
26
|
/** The registry a failure is reported through when the application's own could not be built. */
|
|
25
27
|
const noViews = [];
|
|
@@ -133,11 +135,17 @@ class ApplicationBuilder {
|
|
|
133
135
|
}
|
|
134
136
|
globalOption(name, config) {
|
|
135
137
|
// The plugins' Commands attach at construction, so only the application's own calls close it.
|
|
138
|
+
// The config is judged after the order, so the finding prints it elided and reads none of it.
|
|
136
139
|
if (this.#config.composed) {
|
|
137
140
|
throw new DeclarationError(globalOptionAfterCommand, {
|
|
138
141
|
correction: 'Declare global options before attaching Commands or registering an action.',
|
|
139
142
|
findings: [
|
|
140
|
-
{
|
|
143
|
+
{
|
|
144
|
+
arguments: callArguments(name, config === undefined ? undefined : spelled(elided)),
|
|
145
|
+
call: 'globalOption',
|
|
146
|
+
mark: '0',
|
|
147
|
+
path: [],
|
|
148
|
+
},
|
|
141
149
|
],
|
|
142
150
|
sentence: `The Application declares global option ${quoted(name)} after command() or action().`,
|
|
143
151
|
});
|
|
@@ -331,7 +339,8 @@ class ApplicationBuilder {
|
|
|
331
339
|
inspected();
|
|
332
340
|
}
|
|
333
341
|
const inputs = { globals: graph.globals.inputs, locals: collectInputs(graph.root) };
|
|
334
|
-
const
|
|
342
|
+
const places = inputPlaces(graph);
|
|
343
|
+
const declaredValues = await prepareDeclaredValues(inputs, host, places);
|
|
335
344
|
graphBuilt = true;
|
|
336
345
|
if (!controller.signal.aborted) {
|
|
337
346
|
/**
|
|
@@ -341,12 +350,13 @@ class ApplicationBuilder {
|
|
|
341
350
|
signals.install(ownedSignals(built.plugins));
|
|
342
351
|
await runInvocation({
|
|
343
352
|
channel: (binding) => invocationOutput.channel(binding),
|
|
344
|
-
|
|
353
|
+
declaredValues,
|
|
345
354
|
graph,
|
|
346
355
|
host,
|
|
347
356
|
inspected,
|
|
348
357
|
offer,
|
|
349
358
|
out: output.out,
|
|
359
|
+
places,
|
|
350
360
|
plugins: built.plugins,
|
|
351
361
|
report: (fault) => faults.push(fault),
|
|
352
362
|
route: (path) => {
|
|
@@ -481,7 +491,10 @@ function runRendering(rendering) {
|
|
|
481
491
|
subject: 'The run',
|
|
482
492
|
};
|
|
483
493
|
}
|
|
484
|
-
/**
|
|
494
|
+
/**
|
|
495
|
+
* Reject obsolete wiring before silently losing options that invocations depend on. `options` is the
|
|
496
|
+
* copy `captureOptions` took, or `undefined` for an Application declared without options.
|
|
497
|
+
*/
|
|
485
498
|
function checkOptions(name, options) {
|
|
486
499
|
const site = {
|
|
487
500
|
at: '1',
|
|
@@ -491,13 +504,6 @@ function checkOptions(name, options) {
|
|
|
491
504
|
if (options === undefined) {
|
|
492
505
|
return { description: undefined, version: checkVersion(site, undefined) };
|
|
493
506
|
}
|
|
494
|
-
if (!isPlainObject(options)) {
|
|
495
|
-
throw new DeclarationError(notAnObject, {
|
|
496
|
-
correction: 'Supply an Application options object.',
|
|
497
|
-
findings: [{ ...site.declaration, mark: '1' }],
|
|
498
|
-
sentence: 'The Application declares options that are not an object.',
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
507
|
for (const [key, correction] of retired) {
|
|
502
508
|
if (key in options) {
|
|
503
509
|
const retiredSite = optionSite(name, key, Reflect.get(options, key));
|
|
@@ -546,15 +552,44 @@ function readPacket(name, packet) {
|
|
|
546
552
|
sentence: found,
|
|
547
553
|
});
|
|
548
554
|
}
|
|
555
|
+
/** The parts of the Application options that are lists, each copied with its entries as built. */
|
|
556
|
+
const optionLists = ['plugins', 'views', 'translators', 'extensions'];
|
|
557
|
+
/**
|
|
558
|
+
* The one copy of the options that `new Application(name, options)` reads: the options object, its
|
|
559
|
+
* packet and rendering policy, and each list it holds. An entry of a list is what its factory built
|
|
560
|
+
* and is not copied. A read that throws is the unreadable fault of the options, and a value that is
|
|
561
|
+
* not a plain object is the not-an-object fault. An Application declared without options has none
|
|
562
|
+
* to copy.
|
|
563
|
+
*/
|
|
564
|
+
function captureOptions(name, options) {
|
|
565
|
+
if (options === undefined) {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
return captureDeclaration(options, (copy, read) => {
|
|
569
|
+
read.nested(copy, 'packet', shallowRecord);
|
|
570
|
+
read.nested(copy, 'rendering', shallowRecord);
|
|
571
|
+
for (const list of optionLists) {
|
|
572
|
+
read.nested(copy, list, shallowList);
|
|
573
|
+
}
|
|
574
|
+
}, {
|
|
575
|
+
notAnObject: () => new DeclarationError(notAnObject, {
|
|
576
|
+
correction: 'Supply an Application options object.',
|
|
577
|
+
findings: [{ arguments: [name, options], call: 'new Application', mark: '1' }],
|
|
578
|
+
sentence: 'The Application declares options that are not an object.',
|
|
579
|
+
}),
|
|
580
|
+
unreadable: unreadableArgument({ call: 'new Application', named: name, subject: 'The Application' }, 'options'),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
549
583
|
/**
|
|
550
584
|
* Every rule `new Application(name, options)` applies, in the order it reads the slot: the
|
|
551
585
|
* application's own view overrides, its translations, the rendering policy, the options slot and
|
|
552
586
|
* its facts, the installed list and every rule between two plugins, the root's extension values,
|
|
553
587
|
* and then each plugin's Commands, which attach to the root first, in installation order and list
|
|
554
|
-
* order.
|
|
588
|
+
* order. Each reads the one copy of the options `captureOptions` takes. The root's globals are
|
|
589
|
+
* the option values the declared `plugins` tuple contributes.
|
|
555
590
|
*/
|
|
556
|
-
function declareApplication(name,
|
|
557
|
-
const slot =
|
|
591
|
+
function declareApplication(name, declared) {
|
|
592
|
+
const slot = captureOptions(name, declared);
|
|
558
593
|
const identities = viewIdentities(coreViews);
|
|
559
594
|
const views = buildViews({
|
|
560
595
|
declares: false,
|
|
@@ -563,7 +598,7 @@ function declareApplication(name, options) {
|
|
|
563
598
|
}, slot?.views, identities);
|
|
564
599
|
const translations = readTranslations(optionSite(name, 'translators', slot?.translators), slot?.translators);
|
|
565
600
|
const rendering = renderingPolicy(slot?.rendering, optionSite(name, 'rendering', slot?.rendering));
|
|
566
|
-
const facts = checkOptions(name,
|
|
601
|
+
const facts = checkOptions(name, slot);
|
|
567
602
|
const development = readPacket(name, slot?.packet);
|
|
568
603
|
const installed = installPlugins(name, slot?.plugins ?? []);
|
|
569
604
|
const { plugins } = installed;
|
|
@@ -619,12 +654,15 @@ function checkApplicationName(name) {
|
|
|
619
654
|
}
|
|
620
655
|
return name;
|
|
621
656
|
}
|
|
622
|
-
/**
|
|
657
|
+
/**
|
|
658
|
+
* Constructor inference preserves the installed plugin tuple, and the globals start as the option
|
|
659
|
+
* values the installed plugins contribute.
|
|
660
|
+
*/
|
|
623
661
|
class ApplicationDeclaration extends ApplicationBuilder {
|
|
624
662
|
constructor(name, options) {
|
|
625
663
|
// The arguments evaluate in order, so the name is checked before any option is read.
|
|
626
664
|
const checked = checkApplicationName(name);
|
|
627
|
-
super(checked, declareApplication(checked, options));
|
|
665
|
+
super(checked, declaring(() => declareApplication(checked, options)));
|
|
628
666
|
}
|
|
629
667
|
}
|
|
630
668
|
export const Application = ApplicationDeclaration;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Finding } from './diagnostic-text.js';
|
|
2
|
+
import { DeclarationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* One declaring call's read of the object an author passed it, under way. It remembers the slot it
|
|
5
|
+
* is reading, so a read that throws names the slot it threw in, or no slot when the object itself
|
|
6
|
+
* threw.
|
|
7
|
+
*/
|
|
8
|
+
declare class DeclarationRead {
|
|
9
|
+
/** The top-level key being read, or `undefined` while the object itself is read. */
|
|
10
|
+
slot: string | undefined;
|
|
11
|
+
/** The copy of every own string key of one object, each read under its own slot. */
|
|
12
|
+
record(declared: object): Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Replaces the value one key of a copy holds with `copy` of it, read under that key's slot. An
|
|
15
|
+
* absent key stays absent, so a finding prints the copy with the keys the author wrote.
|
|
16
|
+
*/
|
|
17
|
+
nested(captured: Record<string, unknown>, key: string, copy: (value: unknown) => unknown): void;
|
|
18
|
+
}
|
|
19
|
+
/** The two faults one declaring call raises when it cannot capture what it was given. */
|
|
20
|
+
interface CaptureFaults {
|
|
21
|
+
/** The value is not a plain object, so it has no keys for core to read. */
|
|
22
|
+
readonly notAnObject: () => DeclarationError;
|
|
23
|
+
/** A read threw the value it receives, in the top-level slot it names, or in the object itself. */
|
|
24
|
+
readonly unreadable: (thrown: unknown, slot: string | undefined) => DeclarationError;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Authoring's one read of an object a declaring call receives, inside one try: the verdict on its
|
|
28
|
+
* prototype, the copy of every own string key, and the copies `nested` takes of the parts it holds,
|
|
29
|
+
* each judged plain once by `decidePlain`. Every later check, stored value, and finding reads that
|
|
30
|
+
* copy and never the author's object again, so a getter runs once and a read that threw is never
|
|
31
|
+
* repeated. A value that is not a plain object is the not-an-object fault, and a read that throws,
|
|
32
|
+
* from a getter or a proxy trap, is the unreadable fault, named by the slot it threw in.
|
|
33
|
+
*/
|
|
34
|
+
declare function captureDeclaration<Declared>(declared: Declared, nested: (copy: Record<string, unknown>, read: DeclarationRead) => void, faults: CaptureFaults): Declared & Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* What a finding prints for a declaration whose read threw: the slot it threw in, elided, under the
|
|
37
|
+
* keys that lead to it, or the whole declaration elided. A read that threw is never repeated.
|
|
38
|
+
*/
|
|
39
|
+
declare function elidedRead(slot: string | undefined): {
|
|
40
|
+
keys: readonly string[];
|
|
41
|
+
shown: unknown;
|
|
42
|
+
};
|
|
43
|
+
/** What one unreadable declaration is: an input's config, a plugin's definition, or an options object. */
|
|
44
|
+
type Unreadable = 'config' | 'definition' | 'options';
|
|
45
|
+
/**
|
|
46
|
+
* The fault for a declaration whose read threw, named by `subject` and marked by `findings`. The
|
|
47
|
+
* thrown value's reason ends the sentence and the value itself is the fault's cause.
|
|
48
|
+
*/
|
|
49
|
+
declare function unreadableFault(report: {
|
|
50
|
+
readonly declared: Unreadable;
|
|
51
|
+
readonly findings: readonly Finding[];
|
|
52
|
+
readonly subject: string;
|
|
53
|
+
}, thrown: unknown): DeclarationError;
|
|
54
|
+
/**
|
|
55
|
+
* The unreadable fault of the object a `plugin()` call or a constructor receives as its second
|
|
56
|
+
* argument. Its finding marks the top-level slot whose read threw, or the whole argument when the
|
|
57
|
+
* object itself threw, and prints that part elided.
|
|
58
|
+
*/
|
|
59
|
+
declare function unreadableArgument(declaration: {
|
|
60
|
+
readonly call: string;
|
|
61
|
+
readonly named: unknown;
|
|
62
|
+
readonly subject: string;
|
|
63
|
+
}, declared: 'definition' | 'options'): (thrown: unknown, slot: string | undefined) => DeclarationError;
|
|
64
|
+
export type { CaptureFaults };
|
|
65
|
+
export { captureDeclaration, elidedRead, unreadableArgument, unreadableFault };
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { elided, spelled } from './diagnostic-text.js';
|
|
2
|
+
import { asSentence, DeclarationError, reasonOf } from './errors.js';
|
|
3
|
+
import { copyOwnKeys, decidePlain } from './plain.js';
|
|
4
|
+
import { unreadableDeclaration } from './plugin-rules.js';
|
|
5
|
+
/**
|
|
6
|
+
* One declaring call's read of the object an author passed it, under way. It remembers the slot it
|
|
7
|
+
* is reading, so a read that throws names the slot it threw in, or no slot when the object itself
|
|
8
|
+
* threw.
|
|
9
|
+
*/
|
|
10
|
+
class DeclarationRead {
|
|
11
|
+
/** The top-level key being read, or `undefined` while the object itself is read. */
|
|
12
|
+
slot = undefined;
|
|
13
|
+
/** The copy of every own string key of one object, each read under its own slot. */
|
|
14
|
+
record(declared) {
|
|
15
|
+
const copy = copyOwnKeys(declared, (key) => {
|
|
16
|
+
this.slot = key;
|
|
17
|
+
});
|
|
18
|
+
this.slot = undefined;
|
|
19
|
+
return copy;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the value one key of a copy holds with `copy` of it, read under that key's slot. An
|
|
23
|
+
* absent key stays absent, so a finding prints the copy with the keys the author wrote.
|
|
24
|
+
*/
|
|
25
|
+
nested(captured, key, copy) {
|
|
26
|
+
if (Object.hasOwn(captured, key)) {
|
|
27
|
+
this.slot = key;
|
|
28
|
+
captured[key] = copy(captured[key]);
|
|
29
|
+
this.slot = undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Authoring's one read of an object a declaring call receives, inside one try: the verdict on its
|
|
35
|
+
* prototype, the copy of every own string key, and the copies `nested` takes of the parts it holds,
|
|
36
|
+
* each judged plain once by `decidePlain`. Every later check, stored value, and finding reads that
|
|
37
|
+
* copy and never the author's object again, so a getter runs once and a read that threw is never
|
|
38
|
+
* repeated. A value that is not a plain object is the not-an-object fault, and a read that throws,
|
|
39
|
+
* from a getter or a proxy trap, is the unreadable fault, named by the slot it threw in.
|
|
40
|
+
*/
|
|
41
|
+
function captureDeclaration(declared, nested, faults) {
|
|
42
|
+
const read = new DeclarationRead();
|
|
43
|
+
let copy = undefined;
|
|
44
|
+
try {
|
|
45
|
+
if (decidePlain(declared)) {
|
|
46
|
+
copy = read.record(declared);
|
|
47
|
+
nested(copy, read);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw faults.unreadable(error, read.slot);
|
|
52
|
+
}
|
|
53
|
+
if (copy === undefined) {
|
|
54
|
+
throw faults.notAnObject();
|
|
55
|
+
}
|
|
56
|
+
// Last resort: no typed path exists.
|
|
57
|
+
// The copy is built key by key, so the compiler types it as a record of unknown values.
|
|
58
|
+
// A spread would keep the declared type, but it reads enumerable keys alone and names no slot.
|
|
59
|
+
// It holds because the copy holds every own string key of the declared plain object.
|
|
60
|
+
// Each holds the value read from it once, or that value's copy of the same kind.
|
|
61
|
+
// No declaration type declares a symbol key, and none reads its prototype.
|
|
62
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
63
|
+
return copy;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What a finding prints for a declaration whose read threw: the slot it threw in, elided, under the
|
|
67
|
+
* keys that lead to it, or the whole declaration elided. A read that threw is never repeated.
|
|
68
|
+
*/
|
|
69
|
+
function elidedRead(slot) {
|
|
70
|
+
return slot === undefined
|
|
71
|
+
? { keys: [], shown: spelled(elided) }
|
|
72
|
+
: { keys: [slot], shown: Object.fromEntries([[slot, spelled(elided)]]) };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The fault for a declaration whose read threw, named by `subject` and marked by `findings`. The
|
|
76
|
+
* thrown value's reason ends the sentence and the value itself is the fault's cause.
|
|
77
|
+
*/
|
|
78
|
+
function unreadableFault(report, thrown) {
|
|
79
|
+
const { declared, findings, subject } = report;
|
|
80
|
+
return new DeclarationError(unreadableDeclaration, {
|
|
81
|
+
correction: `Declare the ${declared} as a plain object literal whose properties read without throwing.`,
|
|
82
|
+
findings,
|
|
83
|
+
sentence: `${subject} ${declared} could not be read: ${asSentence(reasonOf(thrown))}`,
|
|
84
|
+
}, { cause: thrown });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The unreadable fault of the object a `plugin()` call or a constructor receives as its second
|
|
88
|
+
* argument. Its finding marks the top-level slot whose read threw, or the whole argument when the
|
|
89
|
+
* object itself threw, and prints that part elided.
|
|
90
|
+
*/
|
|
91
|
+
function unreadableArgument(declaration, declared) {
|
|
92
|
+
const { call, named, subject } = declaration;
|
|
93
|
+
return (thrown, slot) => {
|
|
94
|
+
const { keys, shown } = elidedRead(slot);
|
|
95
|
+
const finding = { arguments: [named, shown], call, mark: ['1', ...keys].join('.') };
|
|
96
|
+
return unreadableFault({ declared, findings: [finding], subject }, thrown);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export { captureDeclaration, elidedRead, unreadableArgument, unreadableFault };
|
package/dist/chain.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { CommandGraph, CommandNode } from './inspect.js';
|
|
|
4
4
|
import type { BuiltPlugin, PluginOptions, PluginOptionSpellings, PluginOptionValues } from './plugin.js';
|
|
5
5
|
import type { ContextualStyle } from './style.js';
|
|
6
6
|
import type { ActionChannel, Host, OpenResult, Out, Request, ResultBinding } from './types.js';
|
|
7
|
-
import type {
|
|
7
|
+
import type { InputPlaces, DeclaredValues } from './validation.js';
|
|
8
8
|
/**
|
|
9
9
|
* What the rest of one chain did: the action ran, a later middleware took over by returning without
|
|
10
10
|
* calling its own `next()`, or the run was cancelled before the action ran.
|
|
@@ -12,17 +12,21 @@ import type { DefaultValues } from './validation.js';
|
|
|
12
12
|
type ChainOutcome = 'cancelled' | 'dispatched' | 'taken-over';
|
|
13
13
|
/**
|
|
14
14
|
* What one middleware receives. `graph` is the frozen graph `inspect()` returns, built once for the
|
|
15
|
-
* run, and `command` is the routed node inside it. `options` holds
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
15
|
+
* run, and `command` is the routed node inside it. `options` holds the validated value of every
|
|
16
|
+
* global option, the application's and every plugin's, keyed by declared name: the plugin's own
|
|
17
|
+
* options are typed from its declaration and every other key reads `unknown`, because a plugin
|
|
18
|
+
* compiles without the Application that installs it. It is `null` when a global option has a
|
|
19
|
+
* structural fault or was rejected, so a middleware never reads a global value no validator
|
|
20
|
+
* accepted, and a fault on a local option alone leaves it set. `spellings` holds the spelling that
|
|
21
|
+
* supplied each of the plugin's own options as a token, which a filled or defaulted option never
|
|
22
|
+
* has. `request` is the routed Command's invocation, parsed and validated ahead of the chain, and
|
|
23
|
+
* `null` while core holds a fault and on a group. `view` names the view the result renders
|
|
24
|
+
* through: it reads as the declaration's default until a middleware assigns one, and as `null` on
|
|
25
|
+
* a Command that declares none. The last assignment before the dispatch boundary wins, and one
|
|
26
|
+
* made after it changes nothing.
|
|
23
27
|
*/
|
|
24
28
|
interface MiddlewareContext<Options extends PluginOptions = PluginOptions> {
|
|
25
|
-
readonly options: PluginOptionValues<Options
|
|
29
|
+
readonly options: (PluginOptionValues<Options> & Readonly<Record<string, unknown>>) | null;
|
|
26
30
|
readonly spellings: PluginOptionSpellings<Options>;
|
|
27
31
|
readonly graph: CommandGraph;
|
|
28
32
|
readonly command: CommandNode;
|
|
@@ -34,12 +38,14 @@ interface MiddlewareContext<Options extends PluginOptions = PluginOptions> {
|
|
|
34
38
|
readonly signal: AbortSignal;
|
|
35
39
|
readonly next: () => Promise<ChainOutcome>;
|
|
36
40
|
}
|
|
37
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Everything one invocation needs after its graph is built and its defaults and implied values are
|
|
43
|
+
* validated.
|
|
44
|
+
*/
|
|
38
45
|
interface Invocation {
|
|
39
46
|
style: ContextualStyle;
|
|
40
47
|
/** The action's own channel, built from the routed Command's declaration when it dispatches. */
|
|
41
48
|
channel: (binding: ResultBinding) => ActionChannel;
|
|
42
|
-
defaults: DefaultValues;
|
|
43
49
|
graph: BuiltGraph;
|
|
44
50
|
host: Host;
|
|
45
51
|
/**
|
|
@@ -60,7 +66,11 @@ interface Invocation {
|
|
|
60
66
|
out: Out<OpenResult>;
|
|
61
67
|
/** The channel a configuration source receives, whose results call names the source. */
|
|
62
68
|
sourceOut: Out<OpenResult>;
|
|
69
|
+
/** Where every input of the graph was declared, which a broken validator's finding rebuilds. */
|
|
70
|
+
places: InputPlaces;
|
|
63
71
|
plugins: readonly BuiltPlugin[];
|
|
72
|
+
/** Every declared default and implied value, validated before any token was read. */
|
|
73
|
+
declaredValues: DeclaredValues;
|
|
64
74
|
/** A fault reported after the primary outcome, which turns a would-be 0 into 1. */
|
|
65
75
|
report: (fault: LoomError) => void;
|
|
66
76
|
/**
|
|
@@ -71,11 +81,12 @@ interface Invocation {
|
|
|
71
81
|
signal: AbortSignal;
|
|
72
82
|
}
|
|
73
83
|
/**
|
|
74
|
-
* Runs one invocation: the global
|
|
75
|
-
*
|
|
76
|
-
* middleware
|
|
77
|
-
* middleware
|
|
78
|
-
*
|
|
84
|
+
* Runs one invocation: routing on the global options and a parent's own options, the routed
|
|
85
|
+
* Command's words read against its table, the dispatch this invocation prepares, and the
|
|
86
|
+
* middleware chain it then runs. Only an unknown Command is raised before the chain. Parsing and validation run ahead of the chain so
|
|
87
|
+
* that a middleware reads the request, and every other fault they find is held until the dispatch
|
|
88
|
+
* boundary. A middleware that returns without calling `next()` has taken over, so the held fault is
|
|
89
|
+
* never raised and nothing later in the chain runs.
|
|
79
90
|
*/
|
|
80
91
|
declare function runInvocation(invocation: Invocation): Promise<void>;
|
|
81
92
|
export type { ChainOutcome, Invocation, MiddlewareContext };
|
package/dist/chain.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { prepareDispatch
|
|
1
|
+
import { prepareDispatch } from './command.js';
|
|
2
2
|
import { foreignFailure, InternalError, routedSubject } from './errors.js';
|
|
3
3
|
import { nodeAt } from './inspect.js';
|
|
4
4
|
import { isSupplied } from './options.js';
|
|
5
|
-
import {
|
|
5
|
+
import { parseInvocation } from './parse.js';
|
|
6
|
+
import { loadDefault, pluginSentence, pluginSpellings } from './plugin.js';
|
|
6
7
|
import { nextMisuse, viewSelection, viewSelectionCorrection } from './rules.js';
|
|
7
8
|
/**
|
|
8
9
|
* The view one run selects, which is one value whichever middleware wrote it. The assignment is
|
|
@@ -96,7 +97,6 @@ function activatedEntries(plugins, values) {
|
|
|
96
97
|
identity: installed.identity,
|
|
97
98
|
// Activation proved the middleware exists, so the empty loader is never the one core calls.
|
|
98
99
|
load: installed.middleware?.load ?? (() => undefined),
|
|
99
|
-
options: pluginValues(installed.inputs, values),
|
|
100
100
|
spellings: pluginSpellings(installed.inputs, values),
|
|
101
101
|
}));
|
|
102
102
|
}
|
|
@@ -276,7 +276,7 @@ async function runChain(invocation, routed, prepared) {
|
|
|
276
276
|
graph,
|
|
277
277
|
host: invocation.host,
|
|
278
278
|
next,
|
|
279
|
-
options:
|
|
279
|
+
options: prepared.options,
|
|
280
280
|
out: invocation.out,
|
|
281
281
|
request: prepared.request,
|
|
282
282
|
signal: invocation.signal,
|
|
@@ -312,14 +312,15 @@ async function runChain(invocation, routed, prepared) {
|
|
|
312
312
|
return run.raised;
|
|
313
313
|
}
|
|
314
314
|
/**
|
|
315
|
-
* Runs one invocation: the global
|
|
316
|
-
*
|
|
317
|
-
* middleware
|
|
318
|
-
* middleware
|
|
319
|
-
*
|
|
315
|
+
* Runs one invocation: routing on the global options and a parent's own options, the routed
|
|
316
|
+
* Command's words read against its table, the dispatch this invocation prepares, and the
|
|
317
|
+
* middleware chain it then runs. Only an unknown Command is raised before the chain. Parsing and validation run ahead of the chain so
|
|
318
|
+
* that a middleware reads the request, and every other fault they find is held until the dispatch
|
|
319
|
+
* boundary. A middleware that returns without calling `next()` has taken over, so the held fault is
|
|
320
|
+
* never raised and nothing later in the chain runs.
|
|
320
321
|
*/
|
|
321
322
|
async function runInvocation(invocation) {
|
|
322
|
-
const routed =
|
|
323
|
+
const routed = parseInvocation(invocation.graph, invocation.host.argv, invocation.route);
|
|
323
324
|
const prepared = await prepareDispatch(invocation.graph, routed, invocation);
|
|
324
325
|
let raised = undefined;
|
|
325
326
|
try {
|
package/dist/command-rules.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare const variadicArgumentLast: import("./diagnostic-text.js").DiagnosticRul
|
|
|
18
18
|
declare const optionalArgumentLast: import("./diagnostic-text.js").DiagnosticRule;
|
|
19
19
|
/** An `alias()` call that names no alias. */
|
|
20
20
|
declare const aliasWithoutNames: import("./diagnostic-text.js").DiagnosticRule;
|
|
21
|
-
/** An alias that repeats its own Command's name or another of its aliases. */
|
|
21
|
+
/** An alias that repeats its own Command's or option's name or another of its aliases. */
|
|
22
22
|
declare const repeatedAlias: import("./diagnostic-text.js").DiagnosticRule;
|
|
23
23
|
/** A child whose name or alias repeats a sibling's name or alias. */
|
|
24
24
|
declare const siblingNameTaken: import("./diagnostic-text.js").DiagnosticRule;
|
package/dist/command-rules.js
CHANGED
|
@@ -54,9 +54,9 @@ const aliasWithoutNames = registerRule('@loomcli/core/alias-without-names', {
|
|
|
54
54
|
explanation: 'alias() adds each name it receives to the names that route to its Command, so a call with none adds nothing.',
|
|
55
55
|
headline: 'Alias with no names',
|
|
56
56
|
});
|
|
57
|
-
/** An alias that repeats its own Command's name or another of its aliases. */
|
|
57
|
+
/** An alias that repeats its own Command's or option's name or another of its aliases. */
|
|
58
58
|
const repeatedAlias = registerRule('@loomcli/core/repeated-alias', {
|
|
59
|
-
explanation: 'A Command answers to its name and to each of its aliases, so an alias that repeats one of them
|
|
59
|
+
explanation: 'A Command or an option answers to its name and to each of its aliases, so an alias that repeats one of them adds nothing new.',
|
|
60
60
|
headline: 'Alias repeats a name',
|
|
61
61
|
});
|
|
62
62
|
/** A child whose name or alias repeats a sibling's name or alias. */
|