@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
package/dist/view.d.ts
CHANGED
|
@@ -39,11 +39,32 @@ type AnyDeclaredView = (View<never> | RowView<never>) & DeclaredViewBrand & {
|
|
|
39
39
|
/** A failure class as an override key, so `UsageError` and an application's subclass both fit. */
|
|
40
40
|
type FailureClass<Failure extends LoomError> = abstract new (...args: never[]) => Failure;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* What every failure view reads: the stderr view context, where the run was, and the hints the
|
|
43
|
+
* installed plugins' `onFailure` hooks returned. `run()` fills it where it catches the failure, so
|
|
44
|
+
* no failure class carries these facts. `path` holds the canonical names routing walked, `[]`
|
|
45
|
+
* before routing, and `hints` is `[]` when no hook contributed.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
interface FailureViewContext extends ViewContext {
|
|
48
|
+
readonly application: string;
|
|
49
|
+
readonly path: readonly string[];
|
|
50
|
+
readonly hints: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A failure class's view. A `View<Failure>` written against `ViewContext` is assignable to it,
|
|
54
|
+
* because its function reads less of the context.
|
|
55
|
+
*/
|
|
56
|
+
interface FailureView<Failure extends LoomError> {
|
|
57
|
+
render: (failure: Readonly<Failure>, context: FailureViewContext) => string;
|
|
58
|
+
/** A failure view has one shape, as a view does. */
|
|
59
|
+
row?: undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One stored view function, with the data type and the context erased. A registry holds one entry
|
|
63
|
+
* per key and cannot carry a type parameter per entry, so `override(key, replacement)` is where the
|
|
64
|
+
* replacement is typed against the key it answers: a failure class's view reads the failure view
|
|
65
|
+
* context, and every other view reads the view context.
|
|
66
|
+
*/
|
|
67
|
+
type ViewFunction = (data: never, context: never) => unknown;
|
|
47
68
|
/** One stored row function, with the row type erased for the same reason. */
|
|
48
69
|
type RowFunction = (row: never, index: number, context: ViewContext) => unknown;
|
|
49
70
|
/** One stored function that opens a sequence and reads the context alone. */
|
|
@@ -104,17 +125,22 @@ declare class OverrideDeclaration {
|
|
|
104
125
|
type ViewOverride = Pick<OverrideDeclaration, typeof viewOverride>;
|
|
105
126
|
/** What a plugin's own list holds: the views it declares and the overrides it makes. */
|
|
106
127
|
type ViewContribution = AnyDeclaredView | ViewOverride;
|
|
128
|
+
/** Every value an override can key on: a declared view of either shape or a failure class. */
|
|
129
|
+
type AnyOverrideKey = AnyDeclaredView | FailureClass<LoomError>;
|
|
130
|
+
/**
|
|
131
|
+
* The replacement view one key takes, derived from the key alone. The declared-view branches come
|
|
132
|
+
* first, so a declared view is never read as a failure class. Each check is wrapped in a tuple so a
|
|
133
|
+
* union key is not split into a union of replacements that each answer only one of its members.
|
|
134
|
+
*/
|
|
135
|
+
type ReplacementView<Key> = [Key] extends [DeclaredView<infer Data>] ? View<Data> : [Key] extends [DeclaredRowView<infer Row>] ? RowView<Row> : [Key] extends [FailureClass<infer Failure>] ? FailureView<Failure> : never;
|
|
107
136
|
/**
|
|
108
|
-
* One override pairing a key with a replacement view.
|
|
109
|
-
* typed from the view's data
|
|
110
|
-
* is the typed path for a class-keyed list, because an array literal cannot
|
|
111
|
-
* parameter per element.
|
|
137
|
+
* One override pairing a key with a replacement view. The replacement's type is derived from the
|
|
138
|
+
* key: under a declared view it is typed from the view's data, and under a failure class from the
|
|
139
|
+
* class's instances, which is the typed path for a class-keyed list, because an array literal cannot
|
|
140
|
+
* carry a different type parameter per element. One signature serves every key, so a mismatch
|
|
141
|
+
* names the replacement's type against the one the key expects.
|
|
112
142
|
*/
|
|
113
|
-
declare function override<
|
|
114
|
-
declare function override<Row>(key: DeclaredRowView<Row>, replacement: NoInfer<RowView<Row>>): ViewOverride;
|
|
115
|
-
declare function override<Failure extends LoomError>(key: FailureClass<Failure> & {
|
|
116
|
-
readonly [declaredView]?: never;
|
|
117
|
-
}, replacement: NoInfer<View<Failure>>): ViewOverride;
|
|
143
|
+
declare function override<Key extends AnyOverrideKey>(key: Key, replacement: NoInfer<ReplacementView<Key>>): ViewOverride;
|
|
118
144
|
/** One contributor's overrides, read once per build and consulted in contributor order. */
|
|
119
145
|
interface ViewContributions {
|
|
120
146
|
failures: Map<unknown, StoredView>;
|
|
@@ -127,13 +153,20 @@ interface ViewContributions {
|
|
|
127
153
|
type ViewRegistry = readonly ViewContributions[];
|
|
128
154
|
/** The identities one build has met, so a second object under one identity is visible. */
|
|
129
155
|
type ViewIdentities = Map<string, AnyDeclaredView>;
|
|
130
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* How one contributor's diagnostics name it, whether its list may declare a view, and the call
|
|
158
|
+
* that declared the list, `plugin(identity, …)` or `new Application(name, …)`, which a fault marks.
|
|
159
|
+
*/
|
|
131
160
|
interface ViewSubject {
|
|
132
161
|
/** A plugin declares views beside its overrides; an application overrides alone. */
|
|
133
162
|
declares: boolean;
|
|
134
163
|
sentence: string;
|
|
164
|
+
owner: {
|
|
165
|
+
call: string;
|
|
166
|
+
named: unknown;
|
|
167
|
+
};
|
|
135
168
|
}
|
|
136
|
-
/** The identity
|
|
169
|
+
/** The identity one build starts from, holding the views core itself declares. */
|
|
137
170
|
declare function viewIdentities(declared: readonly AnyDeclaredView[]): ViewIdentities;
|
|
138
171
|
/**
|
|
139
172
|
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
@@ -158,23 +191,29 @@ interface ResolvedRowView<Row> {
|
|
|
158
191
|
*/
|
|
159
192
|
declare function resolveRowView<Row>(registry: ViewRegistry, value: RowView<Row>): ResolvedRowView<Row>;
|
|
160
193
|
/**
|
|
161
|
-
* The report of one failure: the text core writes, and whether a view produced it.
|
|
162
|
-
* report
|
|
163
|
-
*
|
|
194
|
+
* The report of one failure: the text core writes, and whether a view produced it. A rendered
|
|
195
|
+
* report says whether core's own default text answered, which for a defect is the generic defect
|
|
196
|
+
* message. An unrendered report carries core's own text, which the plain fallback path writes
|
|
197
|
+
* beside the diagnostic naming the view that could not answer, and what the view threw.
|
|
164
198
|
*/
|
|
165
199
|
type FailureReport = {
|
|
166
200
|
kind: 'rendered';
|
|
167
201
|
text: string;
|
|
202
|
+
core: boolean;
|
|
168
203
|
} | {
|
|
169
204
|
kind: 'unrendered';
|
|
170
205
|
text: string;
|
|
171
206
|
reason: string;
|
|
207
|
+
cause: unknown;
|
|
172
208
|
};
|
|
173
209
|
/**
|
|
174
210
|
* The text core writes for one failure. Resolution walks the registry as `resolveFailure` defines
|
|
175
|
-
* it and falls to core's own default text, which
|
|
176
|
-
* `FatalError` keeps the authored
|
|
211
|
+
* it and falls to core's own default text, which opens a usage failure with the context's
|
|
212
|
+
* application name and escapes the raw facts it interpolates. A `FatalError` keeps the authored
|
|
213
|
+
* marked message it was given. The default text writes each hint on its own line under the
|
|
214
|
+
* sentence, as marked text it does not escape; an override decides for itself. An unrendered report
|
|
215
|
+
* carries the same default text without hints, which the plain fallback path writes.
|
|
177
216
|
*/
|
|
178
|
-
declare function describeFailure(registry: ViewRegistry, failure: LoomError, context
|
|
179
|
-
export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureReport, ResolvedRowView, ViewContribution, ViewContributions, ViewIdentities, ViewOverride, ViewRegistry, ViewShape, };
|
|
217
|
+
declare function describeFailure(registry: ViewRegistry, failure: LoomError, context: FailureViewContext): FailureReport;
|
|
218
|
+
export type { AnyDeclaredView, AnyOverrideKey, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureReport, FailureView, FailureViewContext, ReplacementView, ResolvedRowView, ViewContribution, ViewContributions, ViewSubject, ViewIdentities, ViewOverride, ViewRegistry, ViewShape, };
|
|
180
219
|
export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };
|
package/dist/view.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { viewShape } from './command-rules.js';
|
|
2
|
+
import { elided, quoteString, spelled } from './diagnostic-text.js';
|
|
3
|
+
import { DeclarationError, defaultText, FatalError, notTextReason, quoted, reasonOf, } from './errors.js';
|
|
4
|
+
import { partFinding, slotSite } from './facts.js';
|
|
5
|
+
import { checkIdentity } from './identity.js';
|
|
6
|
+
import { foreignValue, notAList, overrideKey, overrideTwice, twoPackageCopies, } from './plugin-rules.js';
|
|
7
|
+
import { prototypeChain } from './prototypes.js';
|
|
2
8
|
import { escapeText } from './style.js';
|
|
3
9
|
/** Authored declarations register here, so a hand-built object with an identity is a bare view. */
|
|
4
10
|
const declarations = new WeakMap();
|
|
@@ -48,12 +54,22 @@ function shapeOf(value) {
|
|
|
48
54
|
return render ? 'render' : 'neither';
|
|
49
55
|
}
|
|
50
56
|
function view(identity, definition) {
|
|
57
|
+
checkIdentity('view', identity);
|
|
51
58
|
const shape = shapeOf(definition);
|
|
59
|
+
const findings = [{ arguments: [identity, definition], call: 'view', mark: '1' }];
|
|
52
60
|
if (shape === 'both') {
|
|
53
|
-
throw new DeclarationError(
|
|
61
|
+
throw new DeclarationError(viewShape, {
|
|
62
|
+
correction: 'Supply one of the two.',
|
|
63
|
+
findings,
|
|
64
|
+
sentence: `View ${quoted(identity)} carries render and row.`,
|
|
65
|
+
});
|
|
54
66
|
}
|
|
55
67
|
if (shape === 'neither') {
|
|
56
|
-
throw new DeclarationError(
|
|
68
|
+
throw new DeclarationError(viewShape, {
|
|
69
|
+
correction: 'Supply a view with render or a row view with row.',
|
|
70
|
+
findings,
|
|
71
|
+
sentence: `View ${quoted(identity)} carries neither render nor row.`,
|
|
72
|
+
});
|
|
57
73
|
}
|
|
58
74
|
return typeof definition.row === 'function'
|
|
59
75
|
? new RowViewDeclaration(identity, definition)
|
|
@@ -68,12 +84,21 @@ class OverrideDeclaration {
|
|
|
68
84
|
Object.freeze(this);
|
|
69
85
|
}
|
|
70
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* One override pairing a key with a replacement view. The replacement's type is derived from the
|
|
89
|
+
* key: under a declared view it is typed from the view's data, and under a failure class from the
|
|
90
|
+
* class's instances, which is the typed path for a class-keyed list, because an array literal cannot
|
|
91
|
+
* carry a different type parameter per element. One signature serves every key, so a mismatch
|
|
92
|
+
* names the replacement's type against the one the key expects.
|
|
93
|
+
*/
|
|
71
94
|
function override(key, replacement) {
|
|
95
|
+
// Every replacement a key derives is a stored view, so it widens here with no check.
|
|
96
|
+
const supplied = replacement;
|
|
72
97
|
const stored = {
|
|
73
|
-
head:
|
|
74
|
-
render:
|
|
75
|
-
row:
|
|
76
|
-
tail:
|
|
98
|
+
head: supplied.head,
|
|
99
|
+
render: supplied.render,
|
|
100
|
+
row: supplied.row,
|
|
101
|
+
tail: supplied.tail,
|
|
77
102
|
};
|
|
78
103
|
const declared = declarations.get(key);
|
|
79
104
|
if (declared) {
|
|
@@ -97,7 +122,7 @@ function failureKey(key) {
|
|
|
97
122
|
const name = 'name' in key && typeof key.name === 'string' && key.name !== '' ? key.name : 'a failure class';
|
|
98
123
|
return { kind: 'failure', name, prototype };
|
|
99
124
|
}
|
|
100
|
-
/** The identity
|
|
125
|
+
/** The identity one build starts from, holding the views core itself declares. */
|
|
101
126
|
function viewIdentities(declared) {
|
|
102
127
|
const identities = new Map();
|
|
103
128
|
for (const value of declared) {
|
|
@@ -105,22 +130,63 @@ function viewIdentities(declared) {
|
|
|
105
130
|
}
|
|
106
131
|
return identities;
|
|
107
132
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
133
|
+
/**
|
|
134
|
+
* One identity means one declared view, wherever on the graph that view appears. `place` marks the
|
|
135
|
+
* list entry that names the view, when one does.
|
|
136
|
+
*/
|
|
137
|
+
function registerIdentity(identities, declared, place) {
|
|
110
138
|
const known = identities.get(declared.identity);
|
|
111
139
|
if (known === undefined) {
|
|
112
140
|
identities.set(declared.identity, declared);
|
|
113
141
|
return;
|
|
114
142
|
}
|
|
115
143
|
if (known !== declared) {
|
|
116
|
-
throw new DeclarationError(
|
|
144
|
+
throw new DeclarationError(twoPackageCopies, {
|
|
145
|
+
correction: 'Install one copy of the package that declares it.',
|
|
146
|
+
findings: place === undefined ? [] : [{ ...place, note: `another ${quoted(declared.identity)}` }],
|
|
147
|
+
sentence: `View ${quoted(declared.identity)} is declared by two distinct objects.`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** A name JavaScript source can spell as an identifier, which a finding prints a class key as. */
|
|
152
|
+
const identifier = /^[A-Za-z_$][\w$]*$/u;
|
|
153
|
+
/** How a finding prints an override's key: a failure class by its name, anything else elided. */
|
|
154
|
+
function keyCode(key) {
|
|
155
|
+
return key.kind === 'failure' && identifier.test(key.name) ? key.name : elided;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* One list entry as a finding prints it: a declared view as the `view()` call that made it, an
|
|
159
|
+
* override as its `override()` call, and any other value as it is.
|
|
160
|
+
*/
|
|
161
|
+
function entryCode(entry) {
|
|
162
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
163
|
+
return entry;
|
|
117
164
|
}
|
|
165
|
+
const listed = declarations.get(entry);
|
|
166
|
+
if (listed) {
|
|
167
|
+
return spelled(`view(${quoteString(listed.identity)}, ${elided})`);
|
|
168
|
+
}
|
|
169
|
+
const record = overrides.get(entry);
|
|
170
|
+
return record ? spelled(`override(${keyCode(record.key)}, ${elided})`) : entry;
|
|
171
|
+
}
|
|
172
|
+
/** Where one contributor's `views` list sits, with each entry printed as the call that made it. */
|
|
173
|
+
function listSite(subject, declared) {
|
|
174
|
+
const views = Array.isArray(declared) ? Array.from(declared, entryCode) : declared;
|
|
175
|
+
return slotSite({ ...subject.owner, subject: subject.sentence }, 'views', views);
|
|
118
176
|
}
|
|
119
|
-
/** The
|
|
120
|
-
function entryFault(subject) {
|
|
177
|
+
/** The fault of one list entry that is not a value this contributor's list may hold. */
|
|
178
|
+
function entryFault(subject, place) {
|
|
121
179
|
return subject.declares
|
|
122
|
-
?
|
|
123
|
-
|
|
180
|
+
? new DeclarationError(foreignValue, {
|
|
181
|
+
correction: 'Supply the value returned by view(identity, definition) or override(key, view).',
|
|
182
|
+
findings: [place],
|
|
183
|
+
sentence: `${subject.sentence} holds a value that is not a view.`,
|
|
184
|
+
})
|
|
185
|
+
: new DeclarationError(foreignValue, {
|
|
186
|
+
correction: 'Supply the value returned by override(key, view).',
|
|
187
|
+
findings: [place],
|
|
188
|
+
sentence: `${subject.sentence} holds a value that is not a view override.`,
|
|
189
|
+
});
|
|
124
190
|
}
|
|
125
191
|
/** A `views` slot holds a list, so anything else is the same declaration fault. */
|
|
126
192
|
function readContributions(subject, declared) {
|
|
@@ -128,50 +194,80 @@ function readContributions(subject, declared) {
|
|
|
128
194
|
return [];
|
|
129
195
|
}
|
|
130
196
|
if (!Array.isArray(declared)) {
|
|
131
|
-
throw new DeclarationError(
|
|
132
|
-
|
|
133
|
-
|
|
197
|
+
throw new DeclarationError(notAList, {
|
|
198
|
+
correction: subject.declares
|
|
199
|
+
? 'Supply a list of declared views and override values.'
|
|
200
|
+
: 'Supply a list of override values.',
|
|
201
|
+
findings: [partFinding(listSite(subject, declared), [])],
|
|
202
|
+
sentence: `${subject.sentence} declares views that are not an array.`,
|
|
203
|
+
});
|
|
134
204
|
}
|
|
135
205
|
return declared;
|
|
136
206
|
}
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
207
|
+
/**
|
|
208
|
+
* The entry a key's first override sits at, recording this one's when it is the first. One key
|
|
209
|
+
* answers to one override inside one contributor, so a second override marks both entries.
|
|
210
|
+
*/
|
|
211
|
+
function claimKey(build, key, index) {
|
|
212
|
+
const slot = key.kind === 'failure' ? key.prototype : key.view;
|
|
213
|
+
const first = build.positions.get(slot);
|
|
214
|
+
if (first === undefined) {
|
|
215
|
+
build.positions.set(slot, index);
|
|
216
|
+
return;
|
|
142
217
|
}
|
|
143
|
-
|
|
218
|
+
const clause = key.kind === 'failure'
|
|
219
|
+
? `the view for ${quoted(key.name)}`
|
|
220
|
+
: `view ${quoted(key.view.identity)}`;
|
|
221
|
+
throw new DeclarationError(overrideTwice, {
|
|
222
|
+
correction: 'Remove one override.',
|
|
223
|
+
findings: [
|
|
224
|
+
partFinding(build.site, [first], 'the first override'),
|
|
225
|
+
partFinding(build.site, [index], 'the second override'),
|
|
226
|
+
],
|
|
227
|
+
sentence: `${build.subject.sentence} overrides ${clause} twice.`,
|
|
228
|
+
});
|
|
144
229
|
}
|
|
145
230
|
/**
|
|
146
|
-
* One override recorded under the key it answers.
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* is the entry fault of the list that holds it, reported here rather than at the `override()` call.
|
|
231
|
+
* One override recorded under the key it answers. The same key overridden by two contributors
|
|
232
|
+
* resolves first-in-wins. A key that is neither a declared view nor a failure class is a fault of
|
|
233
|
+
* the list that holds it, reported here rather than at the `override()` call.
|
|
150
234
|
*/
|
|
151
|
-
function recordOverride(build, { key, replacement }) {
|
|
235
|
+
function recordOverride(build, { key, replacement }, index) {
|
|
152
236
|
if (key.kind === 'invalid') {
|
|
153
|
-
throw new DeclarationError(
|
|
237
|
+
throw new DeclarationError(overrideKey, {
|
|
238
|
+
correction: 'Key the override on a value view(identity, definition) returned, or on a failure class.',
|
|
239
|
+
findings: [partFinding(build.site, [index])],
|
|
240
|
+
sentence: `${build.subject.sentence} overrides a key that is neither a declared view nor a failure class.`,
|
|
241
|
+
});
|
|
154
242
|
}
|
|
243
|
+
claimKey(build, key, index);
|
|
155
244
|
if (key.kind === 'failure') {
|
|
156
|
-
|
|
245
|
+
build.contributions.failures.set(key.prototype, replacement);
|
|
157
246
|
return;
|
|
158
247
|
}
|
|
159
|
-
|
|
248
|
+
// Naming a declared view as a key registers its identity, as listing the declaration does.
|
|
249
|
+
registerIdentity(build.identities, key.view, partFinding(build.site, [index]));
|
|
250
|
+
build.contributions.views.set(key.view, replacement);
|
|
160
251
|
}
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
252
|
+
/**
|
|
253
|
+
* One entry of a contributor's list: a declared view, which only a plugin may list, or an
|
|
254
|
+
* override. Any other value is the list's entry fault.
|
|
255
|
+
*/
|
|
256
|
+
function recordEntry(build, entry, index) {
|
|
257
|
+
const { identities, site, subject } = build;
|
|
258
|
+
const place = partFinding(site, [index]);
|
|
259
|
+
const object = typeof entry === 'object' && entry !== null ? entry : undefined;
|
|
260
|
+
const listed = object === undefined ? undefined : declarations.get(object);
|
|
261
|
+
const record = object === undefined ? undefined : overrides.get(object);
|
|
262
|
+
if (listed && subject.declares) {
|
|
263
|
+
registerIdentity(identities, listed, place);
|
|
165
264
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (contributions.views.has(key)) {
|
|
172
|
-
throw new DeclarationError(`${subject.sentence} overrides view "${key.identity}" twice. Remove one override.`);
|
|
265
|
+
else if (record) {
|
|
266
|
+
recordOverride(build, record, index);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
throw entryFault(subject, place);
|
|
173
270
|
}
|
|
174
|
-
contributions.views.set(key, replacement);
|
|
175
271
|
}
|
|
176
272
|
/**
|
|
177
273
|
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
@@ -180,17 +276,10 @@ function recordViewOverride({ contributions, identities, subject }, key, replace
|
|
|
180
276
|
*/
|
|
181
277
|
function buildViews(subject, declared, identities) {
|
|
182
278
|
const contributions = { failures: new Map(), views: new Map() };
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
if (listed) {
|
|
189
|
-
registerIdentity(identities, listed);
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
recordOverride({ contributions, identities, subject }, overrideOf(subject, entry));
|
|
193
|
-
}
|
|
279
|
+
const site = listSite(subject, declared);
|
|
280
|
+
const build = { contributions, identities, positions: new Map(), site, subject };
|
|
281
|
+
for (const [index, entry] of readContributions(subject, declared).entries()) {
|
|
282
|
+
recordEntry(build, entry, index);
|
|
194
283
|
}
|
|
195
284
|
return contributions;
|
|
196
285
|
}
|
|
@@ -198,9 +287,10 @@ function buildViews(subject, declared, identities) {
|
|
|
198
287
|
function readStored(stored) {
|
|
199
288
|
// Last resort: no typed path exists.
|
|
200
289
|
// A registry holds one entry per key and cannot carry a type parameter per entry.
|
|
201
|
-
// A stored view value therefore reads back with its data type erased.
|
|
290
|
+
// A stored view value therefore reads back with its data type and its context erased.
|
|
202
291
|
// It holds because `override(key, replacement)` typed the replacement against its key's data.
|
|
203
292
|
// Resolution reaches a stored value through that key alone.
|
|
293
|
+
// A failure key is resolved by `describeFailure` alone, which passes the failure view context.
|
|
204
294
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
205
295
|
return stored;
|
|
206
296
|
}
|
|
@@ -217,16 +307,6 @@ function missing(name, key) {
|
|
|
217
307
|
function callView(stored, key) {
|
|
218
308
|
return readStored(stored).render ?? missing('render', key);
|
|
219
309
|
}
|
|
220
|
-
/** Every prototype in a failure's chain, most derived first, so one walk reads one contributor. */
|
|
221
|
-
function chainOf(failure) {
|
|
222
|
-
const chain = [];
|
|
223
|
-
let prototype = Object.getPrototypeOf(failure);
|
|
224
|
-
while (prototype !== null) {
|
|
225
|
-
chain.push(prototype);
|
|
226
|
-
prototype = Object.getPrototypeOf(prototype);
|
|
227
|
-
}
|
|
228
|
-
return chain;
|
|
229
|
-
}
|
|
230
310
|
/**
|
|
231
311
|
* The view function one declared view resolves to: the first contributor that overrides it, then
|
|
232
312
|
* its own default. A bare view is never overridden, so it resolves to its own function.
|
|
@@ -267,7 +347,8 @@ function resolveRowView(registry, value) {
|
|
|
267
347
|
* for a base class beats a plugin's override for a subclass.
|
|
268
348
|
*/
|
|
269
349
|
function resolveFailure(registry, failure) {
|
|
270
|
-
|
|
350
|
+
// A failure core reports has a readable chain, so the empty fallback only keeps the walk total.
|
|
351
|
+
const chain = prototypeChain(failure) ?? [];
|
|
271
352
|
for (const contributor of registry) {
|
|
272
353
|
for (const prototype of chain) {
|
|
273
354
|
const replacement = contributor.failures.get(prototype);
|
|
@@ -278,30 +359,36 @@ function resolveFailure(registry, failure) {
|
|
|
278
359
|
}
|
|
279
360
|
return undefined;
|
|
280
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Core's own default text for one failure, escaped unless it is the authored marked message of a
|
|
364
|
+
* `FatalError`, with each hint on its own line under it as marked text it does not escape.
|
|
365
|
+
*/
|
|
366
|
+
function coreText(failure, text, hints) {
|
|
367
|
+
const sentence = failure instanceof FatalError ? text : escapeText(text);
|
|
368
|
+
return `${sentence}${hints.map((hint) => `${hint}\n`).join('')}`;
|
|
369
|
+
}
|
|
281
370
|
/**
|
|
282
371
|
* The text core writes for one failure. Resolution walks the registry as `resolveFailure` defines
|
|
283
|
-
* it and falls to core's own default text, which
|
|
284
|
-
* `FatalError` keeps the authored
|
|
372
|
+
* it and falls to core's own default text, which opens a usage failure with the context's
|
|
373
|
+
* application name and escapes the raw facts it interpolates. A `FatalError` keeps the authored
|
|
374
|
+
* marked message it was given. The default text writes each hint on its own line under the
|
|
375
|
+
* sentence, as marked text it does not escape; an override decides for itself. An unrendered report
|
|
376
|
+
* carries the same default text without hints, which the plain fallback path writes.
|
|
285
377
|
*/
|
|
286
378
|
function describeFailure(registry, failure, context) {
|
|
379
|
+
const text = defaultText(failure, context.application);
|
|
287
380
|
const replacement = resolveFailure(registry, failure);
|
|
288
381
|
if (!replacement) {
|
|
289
|
-
return {
|
|
290
|
-
kind: 'rendered',
|
|
291
|
-
text: failure instanceof FatalError ? defaultText(failure) : escapeText(defaultText(failure)),
|
|
292
|
-
};
|
|
382
|
+
return { core: true, kind: 'rendered', text: coreText(failure, text, context.hints) };
|
|
293
383
|
}
|
|
294
384
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return typeof text === 'string'
|
|
300
|
-
? { kind: 'rendered', text }
|
|
301
|
-
: { kind: 'unrendered', reason: notTextReason(text), text: defaultText(failure) };
|
|
385
|
+
const rendered = callView(replacement, failure.name)(failure, context);
|
|
386
|
+
return typeof rendered === 'string'
|
|
387
|
+
? { core: false, kind: 'rendered', text: rendered }
|
|
388
|
+
: { cause: undefined, kind: 'unrendered', reason: notTextReason(rendered), text };
|
|
302
389
|
}
|
|
303
390
|
catch (error) {
|
|
304
|
-
return { kind: 'unrendered', reason: reasonOf(error), text
|
|
391
|
+
return { cause: error, kind: 'unrendered', reason: reasonOf(error), text };
|
|
305
392
|
}
|
|
306
393
|
}
|
|
307
394
|
export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
https://www.unicode.org/license.txt
|
|
2
|
+
|
|
3
|
+
UNICODE LICENSE V3
|
|
4
|
+
|
|
5
|
+
COPYRIGHT AND PERMISSION NOTICE
|
|
6
|
+
|
|
7
|
+
Copyright © 1991-2025 Unicode, Inc.
|
|
8
|
+
|
|
9
|
+
NOTICE TO USER: Carefully read the following legal agreement. BY
|
|
10
|
+
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
|
11
|
+
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
|
12
|
+
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
|
13
|
+
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
16
|
+
copy of data files and any associated documentation (the "Data Files") or
|
|
17
|
+
software and any associated documentation (the "Software") to deal in the
|
|
18
|
+
Data Files or Software without restriction, including without limitation
|
|
19
|
+
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
|
20
|
+
copies of the Data Files or Software, and to permit persons to whom the
|
|
21
|
+
Data Files or Software are furnished to do so, provided that either (a)
|
|
22
|
+
this copyright and permission notice appear with all copies of the Data
|
|
23
|
+
Files or Software, or (b) this copyright and permission notice appear in
|
|
24
|
+
associated Documentation.
|
|
25
|
+
|
|
26
|
+
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
27
|
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
28
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
29
|
+
THIRD PARTY RIGHTS.
|
|
30
|
+
|
|
31
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
|
32
|
+
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
|
33
|
+
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
34
|
+
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
35
|
+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
|
36
|
+
FILES OR SOFTWARE.
|
|
37
|
+
|
|
38
|
+
Except as contained in this notice, the name of a copyright holder shall
|
|
39
|
+
not be used in advertising or otherwise to promote the sale, use or other
|
|
40
|
+
dealings in these Data Files or Software without prior written
|
|
41
|
+
authorization of the copyright holder.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# uucode license
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Tim Culverhouse
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
8
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
9
|
+
the Software without restriction, including without limitation the rights to
|
|
10
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
11
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
12
|
+
so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
24
|
+
|
|
25
|
+
## Attribution
|
|
26
|
+
|
|
27
|
+
This TypeScript implementation is based on the design and table strategy of
|
|
28
|
+
Jacob Sandlund's `uucode`:
|
|
29
|
+
|
|
30
|
+
- https://github.com/jacobsandlund/uucode
|
|
31
|
+
|
|
32
|
+
The bundled Unicode Character Database files are provided by Unicode, Inc. and
|
|
33
|
+
are subject to the Unicode terms of use:
|
|
34
|
+
|
|
35
|
+
- https://www.unicode.org/terms_of_use.html
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcli/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The Loom CLI core package. Provides the scaffolding for creating new Loom CLI applications.",
|
|
5
|
-
"license": "MIT",
|
|
5
|
+
"license": "MIT AND Unicode-3.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/dbtlr/loomcli.git"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"LICENSE"
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"licenses",
|
|
14
|
+
"NOTICE"
|
|
13
15
|
],
|
|
14
16
|
"type": "module",
|
|
15
17
|
"exports": {
|
|
@@ -19,9 +21,11 @@
|
|
|
19
21
|
}
|
|
20
22
|
},
|
|
21
23
|
"dependencies": {
|
|
22
|
-
"@rockorager/uucode": "2.2.1",
|
|
23
24
|
"@standard-schema/spec": "^1.1.0",
|
|
24
|
-
"@types/node": "22.20.
|
|
25
|
+
"@types/node": "22.20.5"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@rockorager/uucode": "2.2.1"
|
|
25
29
|
},
|
|
26
30
|
"engines": {
|
|
27
31
|
"bun": ">=1.4.0",
|