@loomcli/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application.d.ts +59 -34
- package/dist/application.js +162 -59
- package/dist/chain.d.ts +25 -6
- package/dist/chain.js +99 -15
- package/dist/command.d.ts +146 -42
- package/dist/command.js +620 -78
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +1 -0
- package/dist/errors.d.ts +31 -59
- package/dist/errors.js +49 -106
- package/dist/extension.d.ts +5 -1
- package/dist/extension.js +18 -1
- package/dist/globals.d.ts +14 -25
- package/dist/globals.js +10 -61
- package/dist/glyphs.generated.d.ts +464 -0
- package/dist/glyphs.generated.js +491 -0
- package/dist/host.js +2 -1
- package/dist/index.d.ts +14 -5
- package/dist/index.js +5 -2
- package/dist/inspect.d.ts +26 -3
- package/dist/inspect.js +19 -5
- package/dist/lanes.d.ts +26 -0
- package/dist/lanes.js +45 -0
- package/dist/output.d.ts +93 -15
- package/dist/output.js +307 -34
- package/dist/plugin.d.ts +32 -16
- package/dist/plugin.js +46 -18
- package/dist/rendering.d.ts +21 -0
- package/dist/rendering.js +72 -0
- package/dist/sequence.d.ts +41 -0
- package/dist/sequence.js +225 -0
- package/dist/style-ansi.d.ts +13 -0
- package/dist/style-ansi.js +306 -0
- package/dist/style-layout.d.ts +29 -0
- package/dist/style-layout.js +228 -0
- package/dist/style-resolve.d.ts +6 -0
- package/dist/style-resolve.js +26 -0
- package/dist/style-state.d.ts +14 -0
- package/dist/style-state.js +179 -0
- package/dist/style-wire.d.ts +31 -0
- package/dist/style-wire.js +201 -0
- package/dist/style.d.ts +86 -0
- package/dist/style.js +201 -0
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +22 -0
- package/dist/types.d.ts +169 -21
- package/dist/validation.d.ts +8 -1
- package/dist/validation.js +17 -2
- package/dist/view.d.ts +180 -0
- package/dist/view.js +307 -0
- package/package.json +2 -1
package/dist/view.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { DeclarationError, defaultText, FatalError, notTextReason, reasonOf } from './errors.js';
|
|
2
|
+
import { escapeText } from './style.js';
|
|
3
|
+
/** Authored declarations register here, so a hand-built object with an identity is a bare view. */
|
|
4
|
+
const declarations = new WeakMap();
|
|
5
|
+
/** The runtime value `view()` returns for a whole view. Its identity and function are public. */
|
|
6
|
+
class ViewDeclaration {
|
|
7
|
+
identity;
|
|
8
|
+
render;
|
|
9
|
+
constructor(identity, definition) {
|
|
10
|
+
this.identity = identity;
|
|
11
|
+
this.render = definition.render;
|
|
12
|
+
declarations.set(this, this);
|
|
13
|
+
Object.freeze(this);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** The runtime value `view()` returns for a row view, which carries its three functions. */
|
|
17
|
+
class RowViewDeclaration {
|
|
18
|
+
identity;
|
|
19
|
+
row;
|
|
20
|
+
head;
|
|
21
|
+
tail;
|
|
22
|
+
constructor(identity, definition) {
|
|
23
|
+
this.identity = identity;
|
|
24
|
+
this.row = definition.row;
|
|
25
|
+
if (definition.head) {
|
|
26
|
+
this.head = definition.head;
|
|
27
|
+
}
|
|
28
|
+
if (definition.tail) {
|
|
29
|
+
this.tail = definition.tail;
|
|
30
|
+
}
|
|
31
|
+
declarations.set(this, this);
|
|
32
|
+
Object.freeze(this);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The shape one value names. The argument is read defensively, because every call that takes a view
|
|
37
|
+
* is reachable from JavaScript and `null` is one such value.
|
|
38
|
+
*/
|
|
39
|
+
function shapeOf(value) {
|
|
40
|
+
const row = typeof value?.row === 'function';
|
|
41
|
+
const render = typeof value?.render === 'function';
|
|
42
|
+
if (row && render) {
|
|
43
|
+
return 'both';
|
|
44
|
+
}
|
|
45
|
+
if (row) {
|
|
46
|
+
return 'row';
|
|
47
|
+
}
|
|
48
|
+
return render ? 'render' : 'neither';
|
|
49
|
+
}
|
|
50
|
+
function view(identity, definition) {
|
|
51
|
+
const shape = shapeOf(definition);
|
|
52
|
+
if (shape === 'both') {
|
|
53
|
+
throw new DeclarationError(`View "${identity}" carries render and row. Supply one of the two.`);
|
|
54
|
+
}
|
|
55
|
+
if (shape === 'neither') {
|
|
56
|
+
throw new DeclarationError(`View "${identity}" carries neither render nor row. Supply a view with render or a row view with row.`);
|
|
57
|
+
}
|
|
58
|
+
return typeof definition.row === 'function'
|
|
59
|
+
? new RowViewDeclaration(identity, definition)
|
|
60
|
+
: new ViewDeclaration(identity, definition);
|
|
61
|
+
}
|
|
62
|
+
/** Authored overrides register here, so the public type publishes nothing to reach. */
|
|
63
|
+
const overrides = new WeakMap();
|
|
64
|
+
/** The runtime value `override()` returns. Its pair lives in the registry above. */
|
|
65
|
+
class OverrideDeclaration {
|
|
66
|
+
constructor(record) {
|
|
67
|
+
overrides.set(this, record);
|
|
68
|
+
Object.freeze(this);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function override(key, replacement) {
|
|
72
|
+
const stored = {
|
|
73
|
+
head: replacement.head,
|
|
74
|
+
render: replacement.render,
|
|
75
|
+
row: replacement.row,
|
|
76
|
+
tail: replacement.tail,
|
|
77
|
+
};
|
|
78
|
+
const declared = declarations.get(key);
|
|
79
|
+
if (declared) {
|
|
80
|
+
return new OverrideDeclaration({ key: { kind: 'view', view: declared }, replacement: stored });
|
|
81
|
+
}
|
|
82
|
+
return new OverrideDeclaration({ key: failureKey(key), replacement: stored });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 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 build reports it as
|
|
87
|
+
* the entry fault of the list that holds it, rather than colliding with every other such value.
|
|
88
|
+
*/
|
|
89
|
+
function failureKey(key) {
|
|
90
|
+
if (typeof key !== 'function' || !('prototype' in key)) {
|
|
91
|
+
return { kind: 'invalid' };
|
|
92
|
+
}
|
|
93
|
+
const prototype = key.prototype;
|
|
94
|
+
if (typeof prototype !== 'object' || prototype === null) {
|
|
95
|
+
return { kind: 'invalid' };
|
|
96
|
+
}
|
|
97
|
+
const name = 'name' in key && typeof key.name === 'string' && key.name !== '' ? key.name : 'a failure class';
|
|
98
|
+
return { kind: 'failure', name, prototype };
|
|
99
|
+
}
|
|
100
|
+
/** The identity register one build starts from, holding the views core itself declares. */
|
|
101
|
+
function viewIdentities(declared) {
|
|
102
|
+
const identities = new Map();
|
|
103
|
+
for (const value of declared) {
|
|
104
|
+
registerIdentity(identities, value);
|
|
105
|
+
}
|
|
106
|
+
return identities;
|
|
107
|
+
}
|
|
108
|
+
/** One identity means one declared view, wherever on the graph that view appears. */
|
|
109
|
+
function registerIdentity(identities, declared) {
|
|
110
|
+
const known = identities.get(declared.identity);
|
|
111
|
+
if (known === undefined) {
|
|
112
|
+
identities.set(declared.identity, declared);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (known !== declared) {
|
|
116
|
+
throw new DeclarationError(`View "${declared.identity}" is declared by two distinct objects. Install one copy of the package that declares it.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** The sentence one contributor's list reports for a value it cannot read. */
|
|
120
|
+
function entryFault(subject) {
|
|
121
|
+
return subject.declares
|
|
122
|
+
? `${subject.sentence} holds a value that is not a view. Supply the value returned by view(identity, definition) or override(key, view).`
|
|
123
|
+
: `${subject.sentence} holds a value that is not a view override. Supply the value returned by override(key, view).`;
|
|
124
|
+
}
|
|
125
|
+
/** A `views` slot holds a list, so anything else is the same declaration fault. */
|
|
126
|
+
function readContributions(subject, declared) {
|
|
127
|
+
if (declared === undefined) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
if (!Array.isArray(declared)) {
|
|
131
|
+
throw new DeclarationError(subject.declares
|
|
132
|
+
? `${subject.sentence} declares views that are not an array. Supply a list of declared views and override values.`
|
|
133
|
+
: entryFault(subject));
|
|
134
|
+
}
|
|
135
|
+
return declared;
|
|
136
|
+
}
|
|
137
|
+
/** The override one entry carries; anything else is a declaration fault of the slot. */
|
|
138
|
+
function overrideOf(subject, entry) {
|
|
139
|
+
const record = typeof entry === 'object' && entry !== null ? overrides.get(entry) : undefined;
|
|
140
|
+
if (!record) {
|
|
141
|
+
throw new DeclarationError(entryFault(subject));
|
|
142
|
+
}
|
|
143
|
+
return record;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* One override recorded under the key it answers. One key answers to one override inside one
|
|
147
|
+
* contributor, so a second override for it is a declaration fault; the same key overridden by two
|
|
148
|
+
* contributors resolves first-in-wins. A key that is neither a declared view nor a failure class
|
|
149
|
+
* is the entry fault of the list that holds it, reported here rather than at the `override()` call.
|
|
150
|
+
*/
|
|
151
|
+
function recordOverride(build, { key, replacement }) {
|
|
152
|
+
if (key.kind === 'invalid') {
|
|
153
|
+
throw new DeclarationError(entryFault(build.subject));
|
|
154
|
+
}
|
|
155
|
+
if (key.kind === 'failure') {
|
|
156
|
+
recordFailureOverride(build, key, replacement);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
recordViewOverride(build, key.view, replacement);
|
|
160
|
+
}
|
|
161
|
+
/** One failure class answers to one override inside one contributor, keyed by its prototype. */
|
|
162
|
+
function recordFailureOverride({ contributions, subject }, key, replacement) {
|
|
163
|
+
if (contributions.failures.has(key.prototype)) {
|
|
164
|
+
throw new DeclarationError(`${subject.sentence} overrides the view for "${key.name}" twice. Remove one override.`);
|
|
165
|
+
}
|
|
166
|
+
contributions.failures.set(key.prototype, replacement);
|
|
167
|
+
}
|
|
168
|
+
/** Naming a declared view as a key registers its identity, as listing the declaration does. */
|
|
169
|
+
function recordViewOverride({ contributions, identities, subject }, key, replacement) {
|
|
170
|
+
registerIdentity(identities, key);
|
|
171
|
+
if (contributions.views.has(key)) {
|
|
172
|
+
throw new DeclarationError(`${subject.sentence} overrides view "${key.identity}" twice. Remove one override.`);
|
|
173
|
+
}
|
|
174
|
+
contributions.views.set(key, replacement);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
178
|
+
* key registered. Listing a declared view is what puts its identity on the graph; an application
|
|
179
|
+
* lists overrides alone, so a declaration in its list is the same fault as any other value.
|
|
180
|
+
*/
|
|
181
|
+
function buildViews(subject, declared, identities) {
|
|
182
|
+
const contributions = { failures: new Map(), views: new Map() };
|
|
183
|
+
for (const entry of readContributions(subject, declared)) {
|
|
184
|
+
const listed = typeof entry === 'object' && entry !== null ? declarations.get(entry) : undefined;
|
|
185
|
+
if (listed && !subject.declares) {
|
|
186
|
+
throw new DeclarationError(entryFault(subject));
|
|
187
|
+
}
|
|
188
|
+
if (listed) {
|
|
189
|
+
registerIdentity(identities, listed);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
recordOverride({ contributions, identities, subject }, overrideOf(subject, entry));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return contributions;
|
|
196
|
+
}
|
|
197
|
+
/** One stored view value, read back over the data its own key carries. */
|
|
198
|
+
function readStored(stored) {
|
|
199
|
+
// Last resort: no typed path exists.
|
|
200
|
+
// 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.
|
|
202
|
+
// It holds because `override(key, replacement)` typed the replacement against its key's data.
|
|
203
|
+
// Resolution reaches a stored value through that key alone.
|
|
204
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
205
|
+
return stored;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The stand-in for a function the stored shape does not carry. A JavaScript author's replacement
|
|
209
|
+
* of the wrong shape reaches it, and the throw is the output-view fault of the write site.
|
|
210
|
+
*/
|
|
211
|
+
function missing(name, key) {
|
|
212
|
+
return () => {
|
|
213
|
+
throw new Error(`The replacement view for "${key}" supplies no ${name} function.`);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** One stored whole view, read back as the function the write site calls, named by its key. */
|
|
217
|
+
function callView(stored, key) {
|
|
218
|
+
return readStored(stored).render ?? missing('render', key);
|
|
219
|
+
}
|
|
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
|
+
/**
|
|
231
|
+
* The view function one declared view resolves to: the first contributor that overrides it, then
|
|
232
|
+
* its own default. A bare view is never overridden, so it resolves to its own function.
|
|
233
|
+
*/
|
|
234
|
+
function resolveView(registry, value) {
|
|
235
|
+
const declared = declarations.get(value);
|
|
236
|
+
if (declared) {
|
|
237
|
+
for (const contributor of registry) {
|
|
238
|
+
const replacement = contributor.views.get(declared);
|
|
239
|
+
if (replacement) {
|
|
240
|
+
return callView(replacement, declared.identity);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return (data, context) => value.render(data, context);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The row functions one row view resolves to, by the walk `resolveView` defines. A replacement
|
|
248
|
+
* supplies the whole shape, so an override that omits `head` drops the default's `head` with it.
|
|
249
|
+
*/
|
|
250
|
+
function resolveRowView(registry, value) {
|
|
251
|
+
const declared = declarations.get(value);
|
|
252
|
+
if (declared) {
|
|
253
|
+
for (const contributor of registry) {
|
|
254
|
+
const replacement = contributor.views.get(declared);
|
|
255
|
+
if (replacement) {
|
|
256
|
+
const resolved = readStored(replacement);
|
|
257
|
+
const row = resolved.row ?? missing('row', declared.identity);
|
|
258
|
+
return { head: resolved.head, row, tail: resolved.tail };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { head: value.head, row: value.row, tail: value.tail };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The override one failure resolves to, or nothing when core's own text answers it. The chain is
|
|
266
|
+
* walked in full at each contributor before the next is consulted, so an application's override
|
|
267
|
+
* for a base class beats a plugin's override for a subclass.
|
|
268
|
+
*/
|
|
269
|
+
function resolveFailure(registry, failure) {
|
|
270
|
+
const chain = chainOf(failure);
|
|
271
|
+
for (const contributor of registry) {
|
|
272
|
+
for (const prototype of chain) {
|
|
273
|
+
const replacement = contributor.failures.get(prototype);
|
|
274
|
+
if (replacement) {
|
|
275
|
+
return replacement;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 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 escapes the raw facts it interpolates. A
|
|
284
|
+
* `FatalError` keeps the authored marked message it was given.
|
|
285
|
+
*/
|
|
286
|
+
function describeFailure(registry, failure, context) {
|
|
287
|
+
const replacement = resolveFailure(registry, failure);
|
|
288
|
+
if (!replacement) {
|
|
289
|
+
return {
|
|
290
|
+
kind: 'rendered',
|
|
291
|
+
text: failure instanceof FatalError ? defaultText(failure) : escapeText(defaultText(failure)),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
if (context === undefined) {
|
|
296
|
+
throw new Error('Missing rendering context.');
|
|
297
|
+
}
|
|
298
|
+
const text = callView(replacement, failure.name)(failure, context);
|
|
299
|
+
return typeof text === 'string'
|
|
300
|
+
? { kind: 'rendered', text }
|
|
301
|
+
: { kind: 'unrendered', reason: notTextReason(text), text: defaultText(failure) };
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
return { kind: 'unrendered', reason: reasonOf(error), text: defaultText(failure) };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
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.3.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": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"@rockorager/uucode": "2.2.1",
|
|
21
22
|
"@standard-schema/spec": "^1.1.0",
|
|
22
23
|
"@types/node": "22.20.1"
|
|
23
24
|
},
|