@loomcli/core 0.4.0 → 0.6.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/LICENSE +21 -0
- package/dist/application.d.ts +60 -31
- package/dist/application.js +356 -149
- package/dist/bindings.d.ts +31 -0
- package/dist/bindings.js +64 -0
- package/dist/chain.d.ts +26 -12
- package/dist/chain.js +59 -92
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +212 -93
- package/dist/command.js +1224 -454
- 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 +115 -26
- package/dist/errors.js +333 -54
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +44 -9
- package/dist/extension.js +147 -65
- package/dist/facts.d.ts +71 -11
- package/dist/facts.js +108 -25
- package/dist/globals.d.ts +63 -22
- package/dist/globals.js +164 -39
- 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 +15 -4
- package/dist/index.js +6 -0
- package/dist/input-rules.d.ts +64 -0
- package/dist/input-rules.js +145 -0
- package/dist/inspect.d.ts +36 -5
- package/dist/inspect.js +125 -27
- package/dist/lanes.js +1 -1
- package/dist/locate.d.ts +41 -0
- package/dist/locate.js +121 -0
- package/dist/options.d.ts +96 -2
- package/dist/options.js +259 -71
- package/dist/output.d.ts +11 -2
- package/dist/output.js +23 -3
- package/dist/plain.d.ts +6 -0
- package/dist/plain.js +12 -0
- package/dist/plugin-rules.d.ts +62 -0
- package/dist/plugin-rules.js +155 -0
- package/dist/plugin.d.ts +121 -55
- package/dist/plugin.js +496 -126
- 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 +58 -0
- package/dist/sources.js +258 -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 +76 -21
- package/dist/validation.d.ts +65 -10
- package/dist/validation.js +314 -108
- package/dist/view.d.ts +49 -15
- package/dist/view.js +157 -79
- package/package.json +3 -2
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?: never;
|
|
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. */
|
|
@@ -78,7 +99,7 @@ declare function view<Data>(identity: string, definition: View<Data>): DeclaredV
|
|
|
78
99
|
declare function view<Row>(identity: string, definition: RowView<Row>): DeclaredRowView<Row>;
|
|
79
100
|
/**
|
|
80
101
|
* What one override replaces: a declared view, a failure class read as its prototype, or a value
|
|
81
|
-
* that is neither, which
|
|
102
|
+
* that is neither, which the list that holds it reports as its entry fault.
|
|
82
103
|
*/
|
|
83
104
|
type OverrideKey = {
|
|
84
105
|
kind: 'view';
|
|
@@ -114,7 +135,7 @@ declare function override<Data>(key: DeclaredView<Data>, replacement: NoInfer<Vi
|
|
|
114
135
|
declare function override<Row>(key: DeclaredRowView<Row>, replacement: NoInfer<RowView<Row>>): ViewOverride;
|
|
115
136
|
declare function override<Failure extends LoomError>(key: FailureClass<Failure> & {
|
|
116
137
|
readonly [declaredView]?: never;
|
|
117
|
-
}, replacement: NoInfer<
|
|
138
|
+
}, replacement: NoInfer<FailureView<Failure>>): ViewOverride;
|
|
118
139
|
/** One contributor's overrides, read once per build and consulted in contributor order. */
|
|
119
140
|
interface ViewContributions {
|
|
120
141
|
failures: Map<unknown, StoredView>;
|
|
@@ -127,13 +148,20 @@ interface ViewContributions {
|
|
|
127
148
|
type ViewRegistry = readonly ViewContributions[];
|
|
128
149
|
/** The identities one build has met, so a second object under one identity is visible. */
|
|
129
150
|
type ViewIdentities = Map<string, AnyDeclaredView>;
|
|
130
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* How one contributor's diagnostics name it, whether its list may declare a view, and the call
|
|
153
|
+
* that declared the list, `plugin(identity, …)` or `new Application(name, …)`, which a fault marks.
|
|
154
|
+
*/
|
|
131
155
|
interface ViewSubject {
|
|
132
156
|
/** A plugin declares views beside its overrides; an application overrides alone. */
|
|
133
157
|
declares: boolean;
|
|
134
158
|
sentence: string;
|
|
159
|
+
owner: {
|
|
160
|
+
call: string;
|
|
161
|
+
named: unknown;
|
|
162
|
+
};
|
|
135
163
|
}
|
|
136
|
-
/** The identity
|
|
164
|
+
/** The identity one build starts from, holding the views core itself declares. */
|
|
137
165
|
declare function viewIdentities(declared: readonly AnyDeclaredView[]): ViewIdentities;
|
|
138
166
|
/**
|
|
139
167
|
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
@@ -158,23 +186,29 @@ interface ResolvedRowView<Row> {
|
|
|
158
186
|
*/
|
|
159
187
|
declare function resolveRowView<Row>(registry: ViewRegistry, value: RowView<Row>): ResolvedRowView<Row>;
|
|
160
188
|
/**
|
|
161
|
-
* The report of one failure: the text core writes, and whether a view produced it.
|
|
162
|
-
* report
|
|
163
|
-
*
|
|
189
|
+
* The report of one failure: the text core writes, and whether a view produced it. A rendered
|
|
190
|
+
* report says whether core's own default text answered, which for a defect is the generic defect
|
|
191
|
+
* message. An unrendered report carries core's own text, which the plain fallback path writes
|
|
192
|
+
* beside the diagnostic naming the view that could not answer, and what the view threw.
|
|
164
193
|
*/
|
|
165
194
|
type FailureReport = {
|
|
166
195
|
kind: 'rendered';
|
|
167
196
|
text: string;
|
|
197
|
+
core: boolean;
|
|
168
198
|
} | {
|
|
169
199
|
kind: 'unrendered';
|
|
170
200
|
text: string;
|
|
171
201
|
reason: string;
|
|
202
|
+
cause: unknown;
|
|
172
203
|
};
|
|
173
204
|
/**
|
|
174
205
|
* 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
|
|
206
|
+
* it and falls to core's own default text, which opens a usage failure with the context's
|
|
207
|
+
* application name and escapes the raw facts it interpolates. A `FatalError` keeps the authored
|
|
208
|
+
* marked message it was given. The default text writes each hint on its own line under the
|
|
209
|
+
* sentence, as marked text it does not escape; an override decides for itself. An unrendered report
|
|
210
|
+
* carries the same default text without hints, which the plain fallback path writes.
|
|
177
211
|
*/
|
|
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, };
|
|
212
|
+
declare function describeFailure(registry: ViewRegistry, failure: LoomError, context: FailureViewContext): FailureReport;
|
|
213
|
+
export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureReport, FailureView, FailureViewContext, ResolvedRowView, ViewContribution, ViewContributions, ViewSubject, ViewIdentities, ViewOverride, ViewRegistry, ViewShape, };
|
|
180
214
|
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)
|
|
@@ -83,8 +99,8 @@ function override(key, replacement) {
|
|
|
83
99
|
}
|
|
84
100
|
/**
|
|
85
101
|
* The key one failure-class override answers. A class is a function whose `prototype` is the
|
|
86
|
-
* object a thrown failure's chain holds, so anything else is no key at all and
|
|
87
|
-
*
|
|
102
|
+
* object a thrown failure's chain holds, so anything else is no key at all and the list that holds
|
|
103
|
+
* it reports it as its entry fault, rather than colliding with every other such value.
|
|
88
104
|
*/
|
|
89
105
|
function failureKey(key) {
|
|
90
106
|
if (typeof key !== 'function' || !('prototype' in key)) {
|
|
@@ -97,7 +113,7 @@ function failureKey(key) {
|
|
|
97
113
|
const name = 'name' in key && typeof key.name === 'string' && key.name !== '' ? key.name : 'a failure class';
|
|
98
114
|
return { kind: 'failure', name, prototype };
|
|
99
115
|
}
|
|
100
|
-
/** The identity
|
|
116
|
+
/** The identity one build starts from, holding the views core itself declares. */
|
|
101
117
|
function viewIdentities(declared) {
|
|
102
118
|
const identities = new Map();
|
|
103
119
|
for (const value of declared) {
|
|
@@ -105,22 +121,63 @@ function viewIdentities(declared) {
|
|
|
105
121
|
}
|
|
106
122
|
return identities;
|
|
107
123
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
124
|
+
/**
|
|
125
|
+
* One identity means one declared view, wherever on the graph that view appears. `place` marks the
|
|
126
|
+
* list entry that names the view, when one does.
|
|
127
|
+
*/
|
|
128
|
+
function registerIdentity(identities, declared, place) {
|
|
110
129
|
const known = identities.get(declared.identity);
|
|
111
130
|
if (known === undefined) {
|
|
112
131
|
identities.set(declared.identity, declared);
|
|
113
132
|
return;
|
|
114
133
|
}
|
|
115
134
|
if (known !== declared) {
|
|
116
|
-
throw new DeclarationError(
|
|
135
|
+
throw new DeclarationError(twoPackageCopies, {
|
|
136
|
+
correction: 'Install one copy of the package that declares it.',
|
|
137
|
+
findings: place === undefined ? [] : [{ ...place, note: `another ${quoted(declared.identity)}` }],
|
|
138
|
+
sentence: `View ${quoted(declared.identity)} is declared by two distinct objects.`,
|
|
139
|
+
});
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
142
|
+
/** A name JavaScript source can spell as an identifier, which a finding prints a class key as. */
|
|
143
|
+
const identifier = /^[A-Za-z_$][\w$]*$/u;
|
|
144
|
+
/** How a finding prints an override's key: a failure class by its name, anything else elided. */
|
|
145
|
+
function keyCode(key) {
|
|
146
|
+
return key.kind === 'failure' && identifier.test(key.name) ? key.name : elided;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One list entry as a finding prints it: a declared view as the `view()` call that made it, an
|
|
150
|
+
* override as its `override()` call, and any other value as it is.
|
|
151
|
+
*/
|
|
152
|
+
function entryCode(entry) {
|
|
153
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
154
|
+
return entry;
|
|
155
|
+
}
|
|
156
|
+
const listed = declarations.get(entry);
|
|
157
|
+
if (listed) {
|
|
158
|
+
return spelled(`view(${quoteString(listed.identity)}, ${elided})`);
|
|
159
|
+
}
|
|
160
|
+
const record = overrides.get(entry);
|
|
161
|
+
return record ? spelled(`override(${keyCode(record.key)}, ${elided})`) : entry;
|
|
162
|
+
}
|
|
163
|
+
/** Where one contributor's `views` list sits, with each entry printed as the call that made it. */
|
|
164
|
+
function listSite(subject, declared) {
|
|
165
|
+
const views = Array.isArray(declared) ? Array.from(declared, entryCode) : declared;
|
|
166
|
+
return slotSite({ ...subject.owner, subject: subject.sentence }, 'views', views);
|
|
167
|
+
}
|
|
168
|
+
/** The fault of one list entry that is not a value this contributor's list may hold. */
|
|
169
|
+
function entryFault(subject, place) {
|
|
121
170
|
return subject.declares
|
|
122
|
-
?
|
|
123
|
-
|
|
171
|
+
? new DeclarationError(foreignValue, {
|
|
172
|
+
correction: 'Supply the value returned by view(identity, definition) or override(key, view).',
|
|
173
|
+
findings: [place],
|
|
174
|
+
sentence: `${subject.sentence} holds a value that is not a view.`,
|
|
175
|
+
})
|
|
176
|
+
: new DeclarationError(foreignValue, {
|
|
177
|
+
correction: 'Supply the value returned by override(key, view).',
|
|
178
|
+
findings: [place],
|
|
179
|
+
sentence: `${subject.sentence} holds a value that is not a view override.`,
|
|
180
|
+
});
|
|
124
181
|
}
|
|
125
182
|
/** A `views` slot holds a list, so anything else is the same declaration fault. */
|
|
126
183
|
function readContributions(subject, declared) {
|
|
@@ -128,50 +185,80 @@ function readContributions(subject, declared) {
|
|
|
128
185
|
return [];
|
|
129
186
|
}
|
|
130
187
|
if (!Array.isArray(declared)) {
|
|
131
|
-
throw new DeclarationError(
|
|
132
|
-
|
|
133
|
-
|
|
188
|
+
throw new DeclarationError(notAList, {
|
|
189
|
+
correction: subject.declares
|
|
190
|
+
? 'Supply a list of declared views and override values.'
|
|
191
|
+
: 'Supply a list of override values.',
|
|
192
|
+
findings: [partFinding(listSite(subject, declared), [])],
|
|
193
|
+
sentence: `${subject.sentence} declares views that are not an array.`,
|
|
194
|
+
});
|
|
134
195
|
}
|
|
135
196
|
return declared;
|
|
136
197
|
}
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
198
|
+
/**
|
|
199
|
+
* The entry a key's first override sits at, recording this one's when it is the first. One key
|
|
200
|
+
* answers to one override inside one contributor, so a second override marks both entries.
|
|
201
|
+
*/
|
|
202
|
+
function claimKey(build, key, index) {
|
|
203
|
+
const slot = key.kind === 'failure' ? key.prototype : key.view;
|
|
204
|
+
const first = build.positions.get(slot);
|
|
205
|
+
if (first === undefined) {
|
|
206
|
+
build.positions.set(slot, index);
|
|
207
|
+
return;
|
|
142
208
|
}
|
|
143
|
-
|
|
209
|
+
const clause = key.kind === 'failure'
|
|
210
|
+
? `the view for ${quoted(key.name)}`
|
|
211
|
+
: `view ${quoted(key.view.identity)}`;
|
|
212
|
+
throw new DeclarationError(overrideTwice, {
|
|
213
|
+
correction: 'Remove one override.',
|
|
214
|
+
findings: [
|
|
215
|
+
partFinding(build.site, [first], 'the first override'),
|
|
216
|
+
partFinding(build.site, [index], 'the second override'),
|
|
217
|
+
],
|
|
218
|
+
sentence: `${build.subject.sentence} overrides ${clause} twice.`,
|
|
219
|
+
});
|
|
144
220
|
}
|
|
145
221
|
/**
|
|
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.
|
|
222
|
+
* One override recorded under the key it answers. The same key overridden by two contributors
|
|
223
|
+
* resolves first-in-wins. A key that is neither a declared view nor a failure class is a fault of
|
|
224
|
+
* the list that holds it, reported here rather than at the `override()` call.
|
|
150
225
|
*/
|
|
151
|
-
function recordOverride(build, { key, replacement }) {
|
|
226
|
+
function recordOverride(build, { key, replacement }, index) {
|
|
152
227
|
if (key.kind === 'invalid') {
|
|
153
|
-
throw new DeclarationError(
|
|
228
|
+
throw new DeclarationError(overrideKey, {
|
|
229
|
+
correction: 'Key the override on a value view(identity, definition) returned, or on a failure class.',
|
|
230
|
+
findings: [partFinding(build.site, [index])],
|
|
231
|
+
sentence: `${build.subject.sentence} overrides a key that is neither a declared view nor a failure class.`,
|
|
232
|
+
});
|
|
154
233
|
}
|
|
234
|
+
claimKey(build, key, index);
|
|
155
235
|
if (key.kind === 'failure') {
|
|
156
|
-
|
|
236
|
+
build.contributions.failures.set(key.prototype, replacement);
|
|
157
237
|
return;
|
|
158
238
|
}
|
|
159
|
-
|
|
239
|
+
// Naming a declared view as a key registers its identity, as listing the declaration does.
|
|
240
|
+
registerIdentity(build.identities, key.view, partFinding(build.site, [index]));
|
|
241
|
+
build.contributions.views.set(key.view, replacement);
|
|
160
242
|
}
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
243
|
+
/**
|
|
244
|
+
* One entry of a contributor's list: a declared view, which only a plugin may list, or an
|
|
245
|
+
* override. Any other value is the list's entry fault.
|
|
246
|
+
*/
|
|
247
|
+
function recordEntry(build, entry, index) {
|
|
248
|
+
const { identities, site, subject } = build;
|
|
249
|
+
const place = partFinding(site, [index]);
|
|
250
|
+
const object = typeof entry === 'object' && entry !== null ? entry : undefined;
|
|
251
|
+
const listed = object === undefined ? undefined : declarations.get(object);
|
|
252
|
+
const record = object === undefined ? undefined : overrides.get(object);
|
|
253
|
+
if (listed && subject.declares) {
|
|
254
|
+
registerIdentity(identities, listed, place);
|
|
165
255
|
}
|
|
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.`);
|
|
256
|
+
else if (record) {
|
|
257
|
+
recordOverride(build, record, index);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
throw entryFault(subject, place);
|
|
173
261
|
}
|
|
174
|
-
contributions.views.set(key, replacement);
|
|
175
262
|
}
|
|
176
263
|
/**
|
|
177
264
|
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
@@ -180,17 +267,10 @@ function recordViewOverride({ contributions, identities, subject }, key, replace
|
|
|
180
267
|
*/
|
|
181
268
|
function buildViews(subject, declared, identities) {
|
|
182
269
|
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
|
-
}
|
|
270
|
+
const site = listSite(subject, declared);
|
|
271
|
+
const build = { contributions, identities, positions: new Map(), site, subject };
|
|
272
|
+
for (const [index, entry] of readContributions(subject, declared).entries()) {
|
|
273
|
+
recordEntry(build, entry, index);
|
|
194
274
|
}
|
|
195
275
|
return contributions;
|
|
196
276
|
}
|
|
@@ -198,9 +278,10 @@ function buildViews(subject, declared, identities) {
|
|
|
198
278
|
function readStored(stored) {
|
|
199
279
|
// Last resort: no typed path exists.
|
|
200
280
|
// 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.
|
|
281
|
+
// A stored view value therefore reads back with its data type and its context erased.
|
|
202
282
|
// It holds because `override(key, replacement)` typed the replacement against its key's data.
|
|
203
283
|
// Resolution reaches a stored value through that key alone.
|
|
284
|
+
// A failure key is resolved by `describeFailure` alone, which passes the failure view context.
|
|
204
285
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
205
286
|
return stored;
|
|
206
287
|
}
|
|
@@ -217,16 +298,6 @@ function missing(name, key) {
|
|
|
217
298
|
function callView(stored, key) {
|
|
218
299
|
return readStored(stored).render ?? missing('render', key);
|
|
219
300
|
}
|
|
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
301
|
/**
|
|
231
302
|
* The view function one declared view resolves to: the first contributor that overrides it, then
|
|
232
303
|
* its own default. A bare view is never overridden, so it resolves to its own function.
|
|
@@ -267,7 +338,8 @@ function resolveRowView(registry, value) {
|
|
|
267
338
|
* for a base class beats a plugin's override for a subclass.
|
|
268
339
|
*/
|
|
269
340
|
function resolveFailure(registry, failure) {
|
|
270
|
-
|
|
341
|
+
// A failure core reports has a readable chain, so the empty fallback only keeps the walk total.
|
|
342
|
+
const chain = prototypeChain(failure) ?? [];
|
|
271
343
|
for (const contributor of registry) {
|
|
272
344
|
for (const prototype of chain) {
|
|
273
345
|
const replacement = contributor.failures.get(prototype);
|
|
@@ -278,30 +350,36 @@ function resolveFailure(registry, failure) {
|
|
|
278
350
|
}
|
|
279
351
|
return undefined;
|
|
280
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Core's own default text for one failure, escaped unless it is the authored marked message of a
|
|
355
|
+
* `FatalError`, with each hint on its own line under it as marked text it does not escape.
|
|
356
|
+
*/
|
|
357
|
+
function coreText(failure, text, hints) {
|
|
358
|
+
const sentence = failure instanceof FatalError ? text : escapeText(text);
|
|
359
|
+
return `${sentence}${hints.map((hint) => `${hint}\n`).join('')}`;
|
|
360
|
+
}
|
|
281
361
|
/**
|
|
282
362
|
* 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
|
|
363
|
+
* it and falls to core's own default text, which opens a usage failure with the context's
|
|
364
|
+
* application name and escapes the raw facts it interpolates. A `FatalError` keeps the authored
|
|
365
|
+
* marked message it was given. The default text writes each hint on its own line under the
|
|
366
|
+
* sentence, as marked text it does not escape; an override decides for itself. An unrendered report
|
|
367
|
+
* carries the same default text without hints, which the plain fallback path writes.
|
|
285
368
|
*/
|
|
286
369
|
function describeFailure(registry, failure, context) {
|
|
370
|
+
const text = defaultText(failure, context.application);
|
|
287
371
|
const replacement = resolveFailure(registry, failure);
|
|
288
372
|
if (!replacement) {
|
|
289
|
-
return {
|
|
290
|
-
kind: 'rendered',
|
|
291
|
-
text: failure instanceof FatalError ? defaultText(failure) : escapeText(defaultText(failure)),
|
|
292
|
-
};
|
|
373
|
+
return { core: true, kind: 'rendered', text: coreText(failure, text, context.hints) };
|
|
293
374
|
}
|
|
294
375
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return typeof text === 'string'
|
|
300
|
-
? { kind: 'rendered', text }
|
|
301
|
-
: { kind: 'unrendered', reason: notTextReason(text), text: defaultText(failure) };
|
|
376
|
+
const rendered = callView(replacement, failure.name)(failure, context);
|
|
377
|
+
return typeof rendered === 'string'
|
|
378
|
+
? { core: false, kind: 'rendered', text: rendered }
|
|
379
|
+
: { cause: undefined, kind: 'unrendered', reason: notTextReason(rendered), text };
|
|
302
380
|
}
|
|
303
381
|
catch (error) {
|
|
304
|
-
return { kind: 'unrendered', reason: reasonOf(error), text
|
|
382
|
+
return { cause: error, kind: 'unrendered', reason: reasonOf(error), text };
|
|
305
383
|
}
|
|
306
384
|
}
|
|
307
385
|
export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcli/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The Loom CLI core package. Provides the scaffolding for creating new Loom CLI applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"url": "git+https://github.com/dbtlr/loomcli.git"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"LICENSE"
|
|
12
13
|
],
|
|
13
14
|
"type": "module",
|
|
14
15
|
"exports": {
|