@loomcli/core 0.5.0 → 0.7.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 +18 -2
- package/dist/application.js +302 -75
- package/dist/bindings.d.ts +15 -10
- package/dist/bindings.js +34 -15
- package/dist/capture.d.ts +65 -0
- package/dist/capture.js +99 -0
- package/dist/chain.d.ts +15 -6
- package/dist/chain.js +40 -20
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +65 -23
- package/dist/command.js +734 -236
- package/dist/controls.d.ts +8 -0
- package/dist/controls.js +23 -0
- package/dist/defect.d.ts +18 -0
- package/dist/defect.js +272 -0
- package/dist/developer.d.ts +24 -0
- package/dist/developer.js +52 -0
- package/dist/diagnostic-text.d.ts +81 -0
- package/dist/diagnostic-text.js +283 -0
- package/dist/diagnostic.d.ts +11 -0
- package/dist/diagnostic.js +70 -0
- package/dist/errors.d.ts +108 -24
- package/dist/errors.js +324 -53
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +41 -8
- package/dist/extension.js +142 -57
- package/dist/facts.d.ts +71 -11
- package/dist/facts.js +106 -23
- package/dist/globals.d.ts +29 -21
- package/dist/globals.js +117 -46
- package/dist/glyphs.generated.js +1 -1
- package/dist/hints.d.ts +79 -0
- package/dist/hints.js +247 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +43 -1
- package/dist/identity.d.ts +19 -0
- package/dist/identity.js +72 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +6 -0
- package/dist/input-rules.d.ts +68 -0
- package/dist/input-rules.js +152 -0
- package/dist/inspect.d.ts +12 -12
- package/dist/inspect.js +92 -48
- package/dist/lanes.js +1 -1
- package/dist/locate.js +4 -4
- package/dist/options.d.ts +30 -2
- package/dist/options.js +143 -46
- package/dist/output.d.ts +9 -2
- package/dist/output.js +18 -2
- package/dist/plain.d.ts +56 -0
- package/dist/plain.js +238 -0
- package/dist/plugin-rules.d.ts +68 -0
- package/dist/plugin-rules.js +164 -0
- package/dist/plugin-settings.d.ts +18 -0
- package/dist/plugin-settings.js +38 -0
- package/dist/plugin.d.ts +27 -15
- package/dist/plugin.js +429 -144
- package/dist/prototypes.d.ts +7 -0
- package/dist/prototypes.js +29 -0
- package/dist/rendering.d.ts +6 -1
- package/dist/rendering.js +23 -5
- package/dist/rules.d.ts +51 -0
- package/dist/rules.js +115 -0
- package/dist/sequence.js +6 -1
- package/dist/sources.d.ts +6 -4
- package/dist/sources.js +25 -16
- package/dist/style-layout.js +2 -2
- package/dist/style-width.d.ts +13 -0
- package/dist/style-width.js +170 -0
- package/dist/style-wire.js +1 -1
- package/dist/style.js +1 -1
- package/dist/theme.d.ts +4 -0
- package/dist/theme.js +25 -5
- package/dist/thenable.d.ts +15 -0
- package/dist/thenable.js +29 -0
- package/dist/translators.d.ts +69 -0
- package/dist/translators.js +253 -0
- package/dist/types.d.ts +13 -3
- package/dist/unicode.generated.d.ts +27 -0
- package/dist/unicode.generated.js +1036 -0
- package/dist/validation.d.ts +69 -7
- package/dist/validation.js +211 -67
- package/dist/view.d.ts +61 -22
- package/dist/view.js +168 -81
- package/licenses/unicode-LICENSE.txt +41 -0
- package/licenses/uucode-LICENSE.md +35 -0
- package/package.json +9 -5
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every prototype in one value's chain, most derived first, or `undefined` when the chain cannot
|
|
3
|
+
* be read: a proxy trap throws, a link repeats, or the chain runs past `maxChainLinks` links. A
|
|
4
|
+
* value with a `null` prototype has an empty chain. Override resolution and translator resolution
|
|
5
|
+
* both walk the chain this returns.
|
|
6
|
+
*/
|
|
7
|
+
export declare function prototypeChain(value: object): object[] | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The most links a prototype chain holds before core reads it as a chain that never ends. */
|
|
2
|
+
const maxChainLinks = 1024;
|
|
3
|
+
/** The walk behind `prototypeChain`, which throws where a proxy trap throws. */
|
|
4
|
+
function walkChain(value) {
|
|
5
|
+
const chain = [];
|
|
6
|
+
const seen = new Set([value]);
|
|
7
|
+
for (let link = Reflect.getPrototypeOf(value); link !== null; link = Reflect.getPrototypeOf(link)) {
|
|
8
|
+
if (seen.has(link) || chain.length >= maxChainLinks) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
seen.add(link);
|
|
12
|
+
chain.push(link);
|
|
13
|
+
}
|
|
14
|
+
return chain;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Every prototype in one value's chain, most derived first, or `undefined` when the chain cannot
|
|
18
|
+
* be read: a proxy trap throws, a link repeats, or the chain runs past `maxChainLinks` links. A
|
|
19
|
+
* value with a `null` prototype has an empty chain. Override resolution and translator resolution
|
|
20
|
+
* both walk the chain this returns.
|
|
21
|
+
*/
|
|
22
|
+
export function prototypeChain(value) {
|
|
23
|
+
try {
|
|
24
|
+
return walkChain(value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/rendering.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FactSite } from './facts.js';
|
|
1
2
|
import type { Host } from './types.js';
|
|
2
3
|
type SwitchPolicy = 'auto' | 'always' | 'never';
|
|
3
4
|
interface RenderingPolicy {
|
|
@@ -14,7 +15,11 @@ interface Capabilities {
|
|
|
14
15
|
depth: 16 | 256 | 'truecolor';
|
|
15
16
|
mainGlyphs: boolean;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* One declared rendering policy, checked against its closed sets. `site` is where it was declared,
|
|
20
|
+
* the Application's `rendering` option or `run()`'s, which a fault marks.
|
|
21
|
+
*/
|
|
22
|
+
declare function renderingPolicy(value: unknown, site: FactSite): RenderingPolicy;
|
|
18
23
|
/** A run owns captured facts; stdout and stderr each resolve this one policy independently. */
|
|
19
24
|
declare function capabilities(host: Host, destination: 'stdout' | 'stderr', policy: RenderingPolicy): Capabilities;
|
|
20
25
|
export type { Capabilities, RenderingPolicy };
|
package/dist/rendering.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { DeclarationError } from './errors.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { partFinding } from './facts.js';
|
|
3
|
+
import { isPlainObject } from './plain.js';
|
|
4
|
+
import { renderingPolicyRule } from './plugin-rules.js';
|
|
5
|
+
/**
|
|
6
|
+
* One declared rendering policy, checked against its closed sets. `site` is where it was declared,
|
|
7
|
+
* the Application's `rendering` option or `run()`'s, which a fault marks.
|
|
8
|
+
*/
|
|
9
|
+
function renderingPolicy(value, site) {
|
|
4
10
|
if (value === undefined) {
|
|
5
11
|
return {};
|
|
6
12
|
}
|
|
7
13
|
if (!isPlainObject(value)) {
|
|
8
|
-
throw new DeclarationError(
|
|
14
|
+
throw new DeclarationError(renderingPolicyRule, {
|
|
15
|
+
correction: 'Supply an object, or omit rendering.',
|
|
16
|
+
findings: [partFinding(site, [])],
|
|
17
|
+
sentence: 'The rendering policy is not an object.',
|
|
18
|
+
});
|
|
9
19
|
}
|
|
10
20
|
const result = {};
|
|
11
21
|
for (const field of ['color', 'modifiers', 'hyperlinks']) {
|
|
12
22
|
const policy = value[field];
|
|
13
23
|
if (policy !== undefined && policy !== 'auto' && policy !== 'always' && policy !== 'never') {
|
|
14
|
-
throw new DeclarationError(
|
|
24
|
+
throw new DeclarationError(renderingPolicyRule, {
|
|
25
|
+
correction: `Supply one of the three, or omit ${field}.`,
|
|
26
|
+
findings: [partFinding(site, [field])],
|
|
27
|
+
sentence: `Rendering ${field} is not auto, always, or never.`,
|
|
28
|
+
});
|
|
15
29
|
}
|
|
16
30
|
if (policy !== undefined) {
|
|
17
31
|
result[field] = policy;
|
|
@@ -19,7 +33,11 @@ function renderingPolicy(value) {
|
|
|
19
33
|
}
|
|
20
34
|
const controls = value.terminalControls;
|
|
21
35
|
if (controls !== undefined && controls !== 'strip' && controls !== 'preserve') {
|
|
22
|
-
throw new DeclarationError(
|
|
36
|
+
throw new DeclarationError(renderingPolicyRule, {
|
|
37
|
+
correction: 'Supply strip or preserve, or omit terminalControls.',
|
|
38
|
+
findings: [partFinding(site, ['terminalControls'])],
|
|
39
|
+
sentence: 'Rendering terminalControls is not strip or preserve.',
|
|
40
|
+
});
|
|
23
41
|
}
|
|
24
42
|
if (controls !== undefined) {
|
|
25
43
|
result.terminalControls = controls;
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** A value thrown from an action, a middleware, or a source that no translator answered. */
|
|
2
|
+
declare const foreignThrow: import("./diagnostic-text.js").DiagnosticRule;
|
|
3
|
+
/** The fix every foreign throw shares. */
|
|
4
|
+
declare const foreignThrowCorrection = "Catch the error where it is thrown and throw a failure class, such as FatalError, with a sentence the operator can act on.";
|
|
5
|
+
/** A thrown value that inherits from a failure class without having been constructed by one. */
|
|
6
|
+
declare const unconstructedFailure: import("./diagnostic-text.js").DiagnosticRule;
|
|
7
|
+
/** A translator that threw or answered with something other than a failure. */
|
|
8
|
+
declare const brokenTranslator: import("./diagnostic-text.js").DiagnosticRule;
|
|
9
|
+
/** The fix every broken translator shares. */
|
|
10
|
+
declare const brokenTranslatorCorrection = "Return a failure, or undefined to pass, and throw nothing from the translator.";
|
|
11
|
+
/** A failure view that threw or returned a value that is not a string. */
|
|
12
|
+
declare const brokenFailureView: import("./diagnostic-text.js").DiagnosticRule;
|
|
13
|
+
/** An output or lane view that threw or returned a value that is not a string. */
|
|
14
|
+
declare const brokenOutputView: import("./diagnostic-text.js").DiagnosticRule;
|
|
15
|
+
/** The fix a broken failure view and a broken output view share. */
|
|
16
|
+
declare const viewCorrection = "Return a string from the view's render function, and throw nothing from it.";
|
|
17
|
+
/** An `onFailure` hook that threw or returned a value that is not hints. */
|
|
18
|
+
declare const brokenFailureHook: import("./diagnostic-text.js").DiagnosticRule;
|
|
19
|
+
/** A plugin's middleware or source loader that rejected or exported no default function. */
|
|
20
|
+
declare const pluginLoaderFailed: import("./diagnostic-text.js").DiagnosticRule;
|
|
21
|
+
/** A middleware that called `next()` twice, or after it returned. */
|
|
22
|
+
declare const nextMisuse: import("./diagnostic-text.js").DiagnosticRule;
|
|
23
|
+
/** An action or a plugin that broke the promise a declared result makes. */
|
|
24
|
+
declare const resultContract: import("./diagnostic-text.js").DiagnosticRule;
|
|
25
|
+
/** A validator that threw, rejected, or returned a malformed result. */
|
|
26
|
+
declare const validatorFailed: import("./diagnostic-text.js").DiagnosticRule;
|
|
27
|
+
/** A middleware that assigned `view` a name the routed Command's result cannot render. */
|
|
28
|
+
declare const viewSelection: import("./diagnostic-text.js").DiagnosticRule;
|
|
29
|
+
/** The fix every invalid view selection shares. */
|
|
30
|
+
declare const viewSelectionCorrection = "Assign view one of the view names the routed Command's result declares, and only on a Command that declares a result.";
|
|
31
|
+
/** A `run()` option that holds a value of the wrong kind. */
|
|
32
|
+
declare const runOptions: import("./diagnostic-text.js").DiagnosticRule;
|
|
33
|
+
/** A configuration source whose answers break the answers rule. */
|
|
34
|
+
declare const sourceAnswers: import("./diagnostic-text.js").DiagnosticRule;
|
|
35
|
+
/** The fix every source answers fault shares. */
|
|
36
|
+
declare const sourceAnswersCorrection = "Return a record that maps each requested option's name to { value, label }, with a value of the option's raw type.";
|
|
37
|
+
/** A graph that `inspect()` did not return, or whose nodes disagree with the build it came from. */
|
|
38
|
+
declare const foreignGraph: import("./diagnostic-text.js").DiagnosticRule;
|
|
39
|
+
/** The fix every foreign graph shares. */
|
|
40
|
+
declare const foreignGraphCorrection = "Pass the graph inspect() returned, unchanged.";
|
|
41
|
+
/** A host stream that failed a write the run owed it. */
|
|
42
|
+
declare const brokenDestination: import("./diagnostic-text.js").DiagnosticRule;
|
|
43
|
+
/** A failure class whose exit code is outside 1 through 125. */
|
|
44
|
+
declare const failureExitCode: import("./diagnostic-text.js").DiagnosticRule;
|
|
45
|
+
/** A diagnostic rule identity outside the `<package>[/<subpath>...]/<kebab-case-rule>` grammar. */
|
|
46
|
+
declare const ruleIdentity: import("./diagnostic-text.js").DiagnosticRule;
|
|
47
|
+
/** A diagnostic rule whose headline or explanation holds no prose. */
|
|
48
|
+
declare const ruleProse: import("./diagnostic-text.js").DiagnosticRule;
|
|
49
|
+
/** A diagnostic rule whose docs value is not a web address. */
|
|
50
|
+
declare const ruleDocs: import("./diagnostic-text.js").DiagnosticRule;
|
|
51
|
+
export { brokenDestination, brokenFailureHook, brokenFailureView, brokenOutputView, brokenTranslator, brokenTranslatorCorrection, failureExitCode, foreignGraph, foreignGraphCorrection, foreignThrow, foreignThrowCorrection, nextMisuse, pluginLoaderFailed, resultContract, ruleDocs, ruleIdentity, ruleProse, runOptions, sourceAnswers, sourceAnswersCorrection, unconstructedFailure, validatorFailed, viewCorrection, viewSelection, viewSelectionCorrection, };
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { registerRule } from './diagnostic-text.js';
|
|
2
|
+
/*
|
|
3
|
+
* Core's own rules for the defects it raises, the run-time declaration faults it finds, and the
|
|
4
|
+
* faults of the two things an author declares to describe a failure: a failure class's exit code
|
|
5
|
+
* and a diagnostic rule. Each is declared once here and shared by every site that raises it, as a
|
|
6
|
+
* plugin's rules are.
|
|
7
|
+
*/
|
|
8
|
+
/** A value thrown from an action, a middleware, or a source that no translator answered. */
|
|
9
|
+
const foreignThrow = registerRule('@loomcli/core/foreign-throw', {
|
|
10
|
+
explanation: 'Core reports a value that an action, a middleware, or a configuration source threw, and that no translator answered, as a defect, because only the author knows what it means. A distributed build shows the operator one generic message in its place.',
|
|
11
|
+
headline: 'Unhandled exception',
|
|
12
|
+
});
|
|
13
|
+
/** The fix every foreign throw shares. */
|
|
14
|
+
const foreignThrowCorrection = 'Catch the error where it is thrown and throw a failure class, such as FatalError, with a sentence the operator can act on.';
|
|
15
|
+
/** A thrown value that inherits from a failure class without having been constructed by one. */
|
|
16
|
+
const unconstructedFailure = registerRule('@loomcli/core/unconstructed-failure', {
|
|
17
|
+
explanation: 'A failure class records its exit code when a failure is constructed, so a value that only inherits from one, such as one made with Object.create(), carries no code core can trust.',
|
|
18
|
+
headline: 'Failure never constructed',
|
|
19
|
+
});
|
|
20
|
+
/** A translator that threw or answered with something other than a failure. */
|
|
21
|
+
const brokenTranslator = registerRule('@loomcli/core/broken-translator', {
|
|
22
|
+
explanation: 'A translator turns a foreign throw into a failure synchronously. A throw, or any answer other than a failure or undefined, leaves core no failure to report, so it consults no later translator.',
|
|
23
|
+
headline: 'Broken translator',
|
|
24
|
+
});
|
|
25
|
+
/** The fix every broken translator shares. */
|
|
26
|
+
const brokenTranslatorCorrection = 'Return a failure, or undefined to pass, and throw nothing from the translator.';
|
|
27
|
+
/** A failure view that threw or returned a value that is not a string. */
|
|
28
|
+
const brokenFailureView = registerRule('@loomcli/core/broken-failure-view', {
|
|
29
|
+
explanation: "A failure view turns a failure into the text the operator reads, synchronously and without throwing. Core wrote its own text for the failure in the view's place.",
|
|
30
|
+
headline: 'Broken failure view',
|
|
31
|
+
});
|
|
32
|
+
/** An output or lane view that threw or returned a value that is not a string. */
|
|
33
|
+
const brokenOutputView = registerRule('@loomcli/core/broken-output-view', {
|
|
34
|
+
explanation: 'A view turns a value into text synchronously and without throwing. Core wrote nothing for the call whose view broke, and a run whose output is incomplete cannot succeed.',
|
|
35
|
+
headline: 'Broken output view',
|
|
36
|
+
});
|
|
37
|
+
/** The fix a broken failure view and a broken output view share. */
|
|
38
|
+
const viewCorrection = "Return a string from the view's render function, and throw nothing from it.";
|
|
39
|
+
/** An `onFailure` hook that threw or returned a value that is not hints. */
|
|
40
|
+
const brokenFailureHook = registerRule('@loomcli/core/broken-failure-hook', {
|
|
41
|
+
explanation: "An onFailure hook adds hint lines under a failure, synchronously and without throwing. Core dropped this hook's hints and rendered the failure with every other plugin's.",
|
|
42
|
+
headline: 'Broken failure hook',
|
|
43
|
+
});
|
|
44
|
+
/** A plugin's middleware or source loader that rejected or exported no default function. */
|
|
45
|
+
const pluginLoaderFailed = registerRule('@loomcli/core/plugin-loader-failed', {
|
|
46
|
+
explanation: "Core loads a plugin's middleware or source module the first time a run reaches it, and the module owes its function as its default export.",
|
|
47
|
+
headline: 'Plugin loader failed',
|
|
48
|
+
});
|
|
49
|
+
/** A middleware that called `next()` twice, or after it returned. */
|
|
50
|
+
const nextMisuse = registerRule('@loomcli/core/next-misuse', {
|
|
51
|
+
explanation: 'next() continues the middleware chain once, while the middleware that received it runs. A second call, or a call after the middleware returned, has no chain left to continue.',
|
|
52
|
+
headline: 'next() misused',
|
|
53
|
+
});
|
|
54
|
+
/** An action or a plugin that broke the promise a declared result makes. */
|
|
55
|
+
const resultContract = registerRule('@loomcli/core/result-contract', {
|
|
56
|
+
explanation: 'A Command that declares a result promises its consumer one value, or one sequence of rows, that its action emits once through out.results().',
|
|
57
|
+
headline: 'Result contract broken',
|
|
58
|
+
});
|
|
59
|
+
/** A validator that threw, rejected, or returned a malformed result. */
|
|
60
|
+
const validatorFailed = registerRule('@loomcli/core/validator-failed', {
|
|
61
|
+
explanation: 'Only an issue a validator returns states a validation verdict. A validator that throws, rejects, or returns anything but a Standard Schema result leaves core no verdict to report, so validation stops and the action does not run.',
|
|
62
|
+
headline: 'Validator failed',
|
|
63
|
+
});
|
|
64
|
+
/** A middleware that assigned `view` a name the routed Command's result cannot render. */
|
|
65
|
+
const viewSelection = registerRule('@loomcli/core/view-selection', {
|
|
66
|
+
explanation: "A middleware selects one of the views the routed Command's result declares, by name, before the action runs. A Command that declares no result has no view to select, and a name its result does not declare has no view to render.",
|
|
67
|
+
headline: 'Invalid view selection',
|
|
68
|
+
});
|
|
69
|
+
/** The fix every invalid view selection shares. */
|
|
70
|
+
const viewSelectionCorrection = "Assign view one of the view names the routed Command's result declares, and only on a Command that declares a result.";
|
|
71
|
+
/** A `run()` option that holds a value of the wrong kind. */
|
|
72
|
+
const runOptions = registerRule('@loomcli/core/run-options', {
|
|
73
|
+
explanation: 'run() reads its options before it builds the graph. A caller that embeds the application, such as a test, passes them, and a value of the wrong kind leaves the run nothing to act on.',
|
|
74
|
+
headline: 'Invalid run options',
|
|
75
|
+
});
|
|
76
|
+
/** A configuration source whose answers break the answers rule. */
|
|
77
|
+
const sourceAnswers = registerRule('@loomcli/core/source-answers', {
|
|
78
|
+
explanation: 'A configuration source answers with a record that maps each option core requested to { value, label }, where the value has the raw type the option takes and the label is one line of prose. Core fills no option from any other answer.',
|
|
79
|
+
headline: 'Invalid source answers',
|
|
80
|
+
});
|
|
81
|
+
/** The fix every source answers fault shares. */
|
|
82
|
+
const sourceAnswersCorrection = "Return a record that maps each requested option's name to { value, label }, with a value of the option's raw type.";
|
|
83
|
+
/** A graph that `inspect()` did not return, or whose nodes disagree with the build it came from. */
|
|
84
|
+
const foreignGraph = registerRule('@loomcli/core/foreign-graph', {
|
|
85
|
+
explanation: 'Core reads a Command graph through the build inspect() rendered it from. A graph inspect() did not return, or one whose nodes do not match that build, leaves core no build to read, so the run stops rather than hand a plugin the wrong Command.',
|
|
86
|
+
headline: 'Graph not from inspect()',
|
|
87
|
+
});
|
|
88
|
+
/** The fix every foreign graph shares. */
|
|
89
|
+
const foreignGraphCorrection = 'Pass the graph inspect() returned, unchanged.';
|
|
90
|
+
/** A host stream that failed a write the run owed it. */
|
|
91
|
+
const brokenDestination = registerRule('@loomcli/core/broken-destination', {
|
|
92
|
+
explanation: "Core writes the run's output and its failure reports to the host streams run() captured. A stream that fails a write loses whatever followed it, so the run cannot succeed.",
|
|
93
|
+
headline: 'Broken destination',
|
|
94
|
+
});
|
|
95
|
+
/** A failure class whose exit code is outside 1 through 125. */
|
|
96
|
+
const failureExitCode = registerRule('@loomcli/core/failure-exit-code', {
|
|
97
|
+
explanation: "A failure's exit code tells the shell how the run ended: 0 means success, and 126 and above belong to the shell and to signals, so a failure exits with a code from 1 through 125. Core never clamps or replaces the code a failure class declares, so a code no failure may exit with is rejected where the class is first constructed.",
|
|
98
|
+
headline: 'Undeclarable exit code',
|
|
99
|
+
});
|
|
100
|
+
/** A diagnostic rule identity outside the `<package>[/<subpath>...]/<kebab-case-rule>` grammar. */
|
|
101
|
+
const ruleIdentity = registerRule('@loomcli/core/rule-identity', {
|
|
102
|
+
explanation: "A rule's identity names the package that declares it and the rule inside it, so tooling keys on it and two packages never share one. It is an identity, a package name and any kebab-case subpath segments that name the part of the package that owns the rule, then a kebab-case rule name, joined by /.",
|
|
103
|
+
headline: 'Invalid rule identity',
|
|
104
|
+
});
|
|
105
|
+
/** A diagnostic rule whose headline or explanation holds no prose. */
|
|
106
|
+
const ruleProse = registerRule('@loomcli/core/rule-prose', {
|
|
107
|
+
explanation: "A rule's banner prints its headline, and its explanation teaches why the rule exists, so each holds prose.",
|
|
108
|
+
headline: 'Empty rule text',
|
|
109
|
+
});
|
|
110
|
+
/** A diagnostic rule whose docs value is not a web address. */
|
|
111
|
+
const ruleDocs = registerRule('@loomcli/core/rule-docs', {
|
|
112
|
+
explanation: "A diagnostic prints a rule's docs as a link the author follows, so it is an absolute http or https URL.",
|
|
113
|
+
headline: 'Invalid rule docs',
|
|
114
|
+
});
|
|
115
|
+
export { brokenDestination, brokenFailureHook, brokenFailureView, brokenOutputView, brokenTranslator, brokenTranslatorCorrection, failureExitCode, foreignGraph, foreignGraphCorrection, foreignThrow, foreignThrowCorrection, nextMisuse, pluginLoaderFailed, resultContract, ruleDocs, ruleIdentity, ruleProse, runOptions, sourceAnswers, sourceAnswersCorrection, unconstructedFailure, validatorFailed, viewCorrection, viewSelection, viewSelectionCorrection, };
|
package/dist/sequence.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InternalError, routedSubject } from './errors.js';
|
|
2
|
+
import { resultContract } from './rules.js';
|
|
2
3
|
/**
|
|
3
4
|
* A failure the source raised, so the writer tells it apart from the view and write faults its own
|
|
4
5
|
* pieces raise, which the output path has accounted for already.
|
|
@@ -44,7 +45,11 @@ function stepsOf(writer) {
|
|
|
44
45
|
throw new SourceFault(error);
|
|
45
46
|
}
|
|
46
47
|
if (!steps) {
|
|
47
|
-
throw new SourceFault(new InternalError(
|
|
48
|
+
throw new SourceFault(new InternalError(resultContract, {
|
|
49
|
+
cause: undefined,
|
|
50
|
+
correction: 'Pass out.results() an iterable or an async iterable of rows.',
|
|
51
|
+
sentence: `The result of ${routedSubject(writer.path)} is not iterable.`,
|
|
52
|
+
}));
|
|
48
53
|
}
|
|
49
54
|
return steps;
|
|
50
55
|
}
|
package/dist/sources.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LoomError } from './errors.js';
|
|
2
2
|
import type { ExtensionRecords } from './extension.js';
|
|
3
3
|
import type { CommandGraph, OptionNode } from './inspect.js';
|
|
4
4
|
import type { OptionValues } from './options.js';
|
|
@@ -25,6 +25,8 @@ interface SourceStage {
|
|
|
25
25
|
inspected: () => CommandGraph;
|
|
26
26
|
/** The routed Command's own options, or `undefined` while local parsing holds a fault. */
|
|
27
27
|
locals: StageScope | undefined;
|
|
28
|
+
/** Offers the resolver's foreign throw to the translators, ahead of the plugin-fault wrap. */
|
|
29
|
+
offer: (thrown: unknown) => LoomError | undefined;
|
|
28
30
|
/** The channel a source writes through, whose results call names the source. */
|
|
29
31
|
out: Out;
|
|
30
32
|
plugins: readonly BuiltPlugin[];
|
|
@@ -38,11 +40,11 @@ interface SourceStage {
|
|
|
38
40
|
* What the stage found beside the values it filled. `labels` names where each filled option's value
|
|
39
41
|
* came from, by option name, and `rejected` names the variable of each Boolean option whose value
|
|
40
42
|
* is outside the grammar. Both are internal to core's failure messages. `fault` is a configuration
|
|
41
|
-
* source's own fault, or the
|
|
42
|
-
*
|
|
43
|
+
* source's own fault, or the failure its resolver threw, which stops the stage and takes the place
|
|
44
|
+
* of every validation problem.
|
|
43
45
|
*/
|
|
44
46
|
interface SourceOutcome {
|
|
45
|
-
fault:
|
|
47
|
+
fault: LoomError | undefined;
|
|
46
48
|
labels: ReadonlyMap<string, string>;
|
|
47
49
|
rejected: ReadonlyMap<string, string>;
|
|
48
50
|
}
|
package/dist/sources.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { asSentence,
|
|
2
|
-
import {
|
|
1
|
+
import { asSentence, foreignFailure, InternalError, LoomError, quoted, reasonOf, } from './errors.js';
|
|
2
|
+
import { isProseLine } from './facts.js';
|
|
3
3
|
import { isSupplied } from './options.js';
|
|
4
|
+
import { isPlainObject } from './plain.js';
|
|
4
5
|
import { loadDefault, pluginSentence, pluginValues } from './plugin.js';
|
|
6
|
+
import { foreignThrow, foreignThrowCorrection, sourceAnswers, sourceAnswersCorrection, } from './rules.js';
|
|
5
7
|
/** The Boolean grammar a bound variable is read through: the whole value, case-insensitive. */
|
|
6
8
|
const truths = new Map([
|
|
7
9
|
['0', false],
|
|
@@ -85,7 +87,11 @@ function rawValue(kind, value) {
|
|
|
85
87
|
const ruleFaults = new WeakSet();
|
|
86
88
|
/** A fault the answers rule names, recorded so that reading the answers rethrows it unframed. */
|
|
87
89
|
function ruleFault(message) {
|
|
88
|
-
const fault = new InternalError(
|
|
90
|
+
const fault = new InternalError(sourceAnswers, {
|
|
91
|
+
cause: undefined,
|
|
92
|
+
correction: sourceAnswersCorrection,
|
|
93
|
+
sentence: message,
|
|
94
|
+
});
|
|
89
95
|
ruleFaults.add(fault);
|
|
90
96
|
return fault;
|
|
91
97
|
}
|
|
@@ -94,12 +100,12 @@ function readAnswer(sentence, input, answer) {
|
|
|
94
100
|
const shape = isPlainObject(answer) ? answer : undefined;
|
|
95
101
|
const label = shape?.label;
|
|
96
102
|
if (!shape || !isProseLine(label)) {
|
|
97
|
-
throw ruleFault(`${sentence} answered option
|
|
103
|
+
throw ruleFault(`${sentence} answered option ${quoted(input.name)} with an answer that is not { value, label }.`);
|
|
98
104
|
}
|
|
99
105
|
const kind = rawKind(input);
|
|
100
106
|
const value = rawValue(kind, shape.value);
|
|
101
107
|
if (value === undefined) {
|
|
102
|
-
throw ruleFault(`${sentence} answered option
|
|
108
|
+
throw ruleFault(`${sentence} answered option ${quoted(input.name)} with a value that is not ${owed[kind]}.`);
|
|
103
109
|
}
|
|
104
110
|
return { label, value };
|
|
105
111
|
}
|
|
@@ -115,7 +121,7 @@ function readAnswers(sentence, answers, requested) {
|
|
|
115
121
|
return Object.entries(answers).map(([name, answer]) => {
|
|
116
122
|
const target = byName.get(name);
|
|
117
123
|
if (!target) {
|
|
118
|
-
throw ruleFault(`${sentence} answered option
|
|
124
|
+
throw ruleFault(`${sentence} answered option ${quoted(name)}, which core did not request.`);
|
|
119
125
|
}
|
|
120
126
|
const { label, value } = readAnswer(sentence, target.input, answer);
|
|
121
127
|
return { label, target, value };
|
|
@@ -130,7 +136,11 @@ function isResolverExport(value) {
|
|
|
130
136
|
}
|
|
131
137
|
/** The fault of a source that threw, while it ran or while core read what it returned. */
|
|
132
138
|
function sourceFailure(sentence, error) {
|
|
133
|
-
return new InternalError(
|
|
139
|
+
return new InternalError(foreignThrow, {
|
|
140
|
+
cause: error,
|
|
141
|
+
correction: foreignThrowCorrection,
|
|
142
|
+
sentence: `${sentence} failed in its configuration source: ${asSentence(reasonOf(error))}`,
|
|
143
|
+
});
|
|
134
144
|
}
|
|
135
145
|
/**
|
|
136
146
|
* Asks the one configuration source about every requested option, and answers with what it said.
|
|
@@ -162,13 +172,14 @@ async function askSource(stage, call) {
|
|
|
162
172
|
answers = await resolver(context);
|
|
163
173
|
}
|
|
164
174
|
catch (error) {
|
|
165
|
-
// The resolver's own
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
175
|
+
// The resolver's own failure reports with its class's code, as an action's does.
|
|
176
|
+
// That covers an InputError, the FatalError out.fatal() throws, and its out.results() fault.
|
|
177
|
+
// A foreign throw is offered to the translators.
|
|
178
|
+
// One that no translator answers is the plugin's fault.
|
|
179
|
+
if (error instanceof LoomError) {
|
|
169
180
|
throw error;
|
|
170
181
|
}
|
|
171
|
-
throw sourceFailure(sentence, error);
|
|
182
|
+
throw stage.offer(error) ?? sourceFailure(sentence, error);
|
|
172
183
|
}
|
|
173
184
|
try {
|
|
174
185
|
return readAnswers(sentence, answers, requested);
|
|
@@ -238,11 +249,9 @@ async function fillInputs(stage) {
|
|
|
238
249
|
return { fault: undefined, labels, rejected };
|
|
239
250
|
}
|
|
240
251
|
catch (error) {
|
|
241
|
-
// The resolver's
|
|
252
|
+
// The resolver's own failure keeps its class, and so its code.
|
|
242
253
|
// Every other fault above is raised as an internal error, and anything else is wrapped the same way.
|
|
243
|
-
const fault = error instanceof
|
|
244
|
-
? error
|
|
245
|
-
: new InternalError(reasonOf(error), error);
|
|
254
|
+
const fault = error instanceof LoomError ? error : foreignFailure(error);
|
|
246
255
|
return { fault, labels, rejected };
|
|
247
256
|
}
|
|
248
257
|
}
|
package/dist/style-layout.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { graphemeWidths } from '
|
|
1
|
+
import { graphemeWidths } from './style-width.js';
|
|
2
2
|
const offsets = [0, 1, 2, 3, 4, 5, 6, 7];
|
|
3
3
|
const empty = {
|
|
4
4
|
advance: offsets.map(() => 0),
|
|
@@ -117,6 +117,7 @@ function padBlock(block, padding, attributes) {
|
|
|
117
117
|
if (!block.tabs && block.minimum >= padding.width) {
|
|
118
118
|
return block;
|
|
119
119
|
}
|
|
120
|
+
const space = (count) => leaf({ attributes, kind: 'text', text: ' '.repeat(count) }, count);
|
|
120
121
|
const lines = block.lines.map((line, index) => {
|
|
121
122
|
// A terminal newline has no extra line to align; interior blank lines do.
|
|
122
123
|
if (index > 0 && index === block.lines.length - 1 && !line.content.text) {
|
|
@@ -134,7 +135,6 @@ function padBlock(block, padding, attributes) {
|
|
|
134
135
|
else if (padding.align === 'center') {
|
|
135
136
|
before = Math.floor(missing / 2);
|
|
136
137
|
}
|
|
137
|
-
const space = (count) => leaf({ attributes, kind: 'text', text: ' '.repeat(count) }, count);
|
|
138
138
|
// Re-segment inserted spaces too: a trailing Unicode Prepend character can absorb one.
|
|
139
139
|
const spaced = join([space(before), content, space(missing - before)]);
|
|
140
140
|
const row = measured([{ content: spaced, ending: [] }]);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One grapheme cluster of a string: where it starts and ends, and the columns it occupies. */
|
|
2
|
+
interface GraphemeWidth {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
width: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Each grapheme cluster of a string with the terminal columns it occupies. Printable ASCII that no
|
|
9
|
+
* non-ASCII character follows is its own one-column cluster. Any other run of clusters is read by
|
|
10
|
+
* one cursor, which starts at the run and carries the break state from cluster to cluster.
|
|
11
|
+
*/
|
|
12
|
+
export declare function graphemeWidths(text: string): Generator<GraphemeWidth>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
Ported from the grapheme width iterator of @rockorager/uucode 2.2.1, src/width.ts.
|
|
3
|
+
Copyright (c) 2026 Tim Culverhouse. Licensed under the MIT License,
|
|
4
|
+
in licenses/uucode-LICENSE.md of @loomcli/core.
|
|
5
|
+
*/
|
|
6
|
+
import { graphemeTable, widthTable } from './unicode.generated.js';
|
|
7
|
+
/** The break state after a regional indicator, which pairs with the next one into a flag. */
|
|
8
|
+
const regionalIndicatorState = 1;
|
|
9
|
+
/** The break state inside an extended pictographic sequence, which a ZWJ continues. */
|
|
10
|
+
const pictographicState = 2;
|
|
11
|
+
// The table members, read once, so each lookup indexes the arrays directly.
|
|
12
|
+
const { stage1, stage1Shift, stage2, stage2Mask, stage3, widthMask, zeroWidthFlag, emojiVSFlag } = widthTable;
|
|
13
|
+
const { breakTable, graphemeBreakPropertyCount } = graphemeTable;
|
|
14
|
+
/** A code point's packed row: its grapheme break property, width, and emoji flags. */
|
|
15
|
+
function lookup(codePoint) {
|
|
16
|
+
const offset = stage1[codePoint >> stage1Shift] ?? 0;
|
|
17
|
+
return stage3[stage2[offset + (codePoint & stage2Mask)] ?? 0] ?? 0;
|
|
18
|
+
}
|
|
19
|
+
/** The row an empty string starts from: the Other break property at width one. */
|
|
20
|
+
const defaultRow = 1 << 5;
|
|
21
|
+
const graphemeBreak = (row) => row & 0x1f;
|
|
22
|
+
const rowWidth = (row) => (row >> 5) & widthMask;
|
|
23
|
+
const zeroInGrapheme = (row) => ((row >> 5) & zeroWidthFlag) !== 0;
|
|
24
|
+
const emojiSelectorBase = (row) => ((row >> 8) & emojiVSFlag) !== 0;
|
|
25
|
+
/** The next break state shifted left once, with whether a cluster boundary falls between. */
|
|
26
|
+
function transition(before, after, state) {
|
|
27
|
+
const count = graphemeBreakPropertyCount;
|
|
28
|
+
return breakTable[(state * count + before) * count + after] ?? 0;
|
|
29
|
+
}
|
|
30
|
+
/** A cursor at the start of a run, with the run's first code point read ahead. */
|
|
31
|
+
function cursor(text, start) {
|
|
32
|
+
const hasNext = start < text.length;
|
|
33
|
+
const codePoint = hasNext ? (text.codePointAt(start) ?? 0) : 0;
|
|
34
|
+
return {
|
|
35
|
+
codePoint: 0,
|
|
36
|
+
hasNext,
|
|
37
|
+
index: start,
|
|
38
|
+
isBreak: false,
|
|
39
|
+
nextCodePoint: codePoint,
|
|
40
|
+
nextIndex: hasNext ? start + (codePoint > 0xff_ff ? 2 : 1) : start,
|
|
41
|
+
nextRow: hasNext ? lookup(codePoint) : defaultRow,
|
|
42
|
+
row: defaultRow,
|
|
43
|
+
state: 0,
|
|
44
|
+
text,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Moves to the next code point and reads whether a cluster boundary follows it. */
|
|
48
|
+
function advance(at) {
|
|
49
|
+
if (!at.hasNext) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
at.codePoint = at.nextCodePoint;
|
|
53
|
+
at.row = at.nextRow;
|
|
54
|
+
const index = at.nextIndex;
|
|
55
|
+
at.index = index;
|
|
56
|
+
if (index >= at.text.length) {
|
|
57
|
+
at.hasNext = false;
|
|
58
|
+
at.isBreak = true;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const first = at.text.charCodeAt(index);
|
|
62
|
+
let codePoint = first;
|
|
63
|
+
let nextIndex = index + 1;
|
|
64
|
+
if (first >= 0xd8_00 && first <= 0xdb_ff && nextIndex < at.text.length) {
|
|
65
|
+
const second = at.text.charCodeAt(nextIndex);
|
|
66
|
+
if (second >= 0xdc_00 && second <= 0xdf_ff) {
|
|
67
|
+
codePoint = ((first - 0xd8_00) << 10) + second - 0xdc_00 + 0x1_00_00;
|
|
68
|
+
nextIndex += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const row = lookup(codePoint);
|
|
72
|
+
const packed = transition(graphemeBreak(at.row), graphemeBreak(row), at.state);
|
|
73
|
+
at.state = packed >> 1;
|
|
74
|
+
at.nextCodePoint = codePoint;
|
|
75
|
+
at.nextIndex = nextIndex;
|
|
76
|
+
at.nextRow = row;
|
|
77
|
+
at.isBreak = (packed & 1) !== 0;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/** The width of the next grapheme cluster, leaving the cursor at its end. */
|
|
81
|
+
function clusterWidth(at) {
|
|
82
|
+
if (!advance(at)) {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
const standalone = rowWidth(at.row);
|
|
86
|
+
if (at.isBreak) {
|
|
87
|
+
return standalone;
|
|
88
|
+
}
|
|
89
|
+
let width = zeroInGrapheme(at.row) ? 0 : standalone;
|
|
90
|
+
let previousRow = at.row;
|
|
91
|
+
let previousState = at.state;
|
|
92
|
+
for (;;) {
|
|
93
|
+
if (!advance(at)) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
switch (at.codePoint) {
|
|
97
|
+
case 0xfe_0f: {
|
|
98
|
+
if (emojiSelectorBase(previousRow)) {
|
|
99
|
+
width = 2;
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 0xfe_0e: {
|
|
104
|
+
if (emojiSelectorBase(previousRow)) {
|
|
105
|
+
width = 1;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 0x20_0d: {
|
|
110
|
+
if (previousState === pictographicState && !at.isBreak) {
|
|
111
|
+
if (!advance(at) || at.isBreak) {
|
|
112
|
+
return width;
|
|
113
|
+
}
|
|
114
|
+
previousRow = at.row;
|
|
115
|
+
previousState = at.state;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 0x1_f3_fb:
|
|
121
|
+
case 0x1_f3_fc:
|
|
122
|
+
case 0x1_f3_fd:
|
|
123
|
+
case 0x1_f3_fe:
|
|
124
|
+
case 0x1_f3_ff: {
|
|
125
|
+
width = 2;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
default: {
|
|
129
|
+
if (previousState === regionalIndicatorState) {
|
|
130
|
+
width = 2;
|
|
131
|
+
}
|
|
132
|
+
else if (!zeroInGrapheme(at.row)) {
|
|
133
|
+
width += rowWidth(at.row);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (at.isBreak) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
previousRow = at.row;
|
|
141
|
+
previousState = at.state;
|
|
142
|
+
}
|
|
143
|
+
return width;
|
|
144
|
+
}
|
|
145
|
+
/** Whether the character at an index is printable ASCII that no non-ASCII character follows. */
|
|
146
|
+
function asciiAlone(text, index) {
|
|
147
|
+
const code = text.charCodeAt(index);
|
|
148
|
+
return (code >= 0x20 && code < 0x7f && (index + 1 >= text.length || text.charCodeAt(index + 1) < 0x80));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Each grapheme cluster of a string with the terminal columns it occupies. Printable ASCII that no
|
|
152
|
+
* non-ASCII character follows is its own one-column cluster. Any other run of clusters is read by
|
|
153
|
+
* one cursor, which starts at the run and carries the break state from cluster to cluster.
|
|
154
|
+
*/
|
|
155
|
+
export function* graphemeWidths(text) {
|
|
156
|
+
let start = 0;
|
|
157
|
+
while (start < text.length) {
|
|
158
|
+
if (asciiAlone(text, start)) {
|
|
159
|
+
yield { end: start + 1, start, width: 1 };
|
|
160
|
+
start += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const at = cursor(text, start);
|
|
164
|
+
do {
|
|
165
|
+
const width = clusterWidth(at);
|
|
166
|
+
yield { end: at.index, start, width };
|
|
167
|
+
start = at.index;
|
|
168
|
+
} while (start < text.length && !asciiAlone(text, start));
|
|
169
|
+
}
|
|
170
|
+
}
|
package/dist/style-wire.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isPlainObject } from './facts.js';
|
|
2
1
|
import { glyphForms } from './glyphs.generated.js';
|
|
2
|
+
import { isPlainObject } from './plain.js';
|
|
3
3
|
import { applyOperations, emptyAttributes } from './style-state.js';
|
|
4
4
|
import { close, colorFallbacks, escape, headerEnd, isColorName, modifiers, open, tokens, } from './style.js';
|
|
5
5
|
function isByte(value) {
|
package/dist/style.js
CHANGED