@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/errors.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { escapeControlCharacters } from './controls.js';
|
|
2
|
+
import { diagnosticText, isDiagnosticRule, valueCode } from './diagnostic-text.js';
|
|
3
|
+
import { isFailureExitCode } from './exit-codes.js';
|
|
4
|
+
import { failureExitCode, foreignThrow, foreignThrowCorrection, resultContract, unconstructedFailure, } from './rules.js';
|
|
5
|
+
import { ignoreRejection, isThenable } from './thenable.js';
|
|
1
6
|
/** The same subject at the start of a sentence, where a token fault names its Command. */
|
|
2
7
|
function routedSentence(command) {
|
|
3
8
|
const subject = routedSubject(command);
|
|
@@ -18,33 +23,81 @@ function resultMessage(kind, command) {
|
|
|
18
23
|
return `${caller} called out.results() on ${routedSubject(command)}. Only the action emits a result.`;
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
26
|
+
* A value a sentence quotes: raw text an operator typed, or a name or identity an author declared.
|
|
27
|
+
* Text is escaped, so a control character or a bidirectional control in it cannot reorder or break
|
|
28
|
+
* the line. A value of any other kind prints as the code a finding prints for it, because a rule
|
|
29
|
+
* that rejects a value that is not a string quotes that value too. The failure's public field
|
|
30
|
+
* keeps the raw value.
|
|
23
31
|
*/
|
|
24
|
-
function
|
|
25
|
-
return
|
|
32
|
+
export function quoted(value) {
|
|
33
|
+
return typeof value === 'string' ? `"${escapeControlCharacters(value)}"` : valueCode(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The clause that states a routing fault's fix: the candidates, or, when the Command offers none
|
|
37
|
+
* because every child is hidden or deprecated, the kind of name to supply.
|
|
38
|
+
*/
|
|
39
|
+
function offering(candidates, kind) {
|
|
40
|
+
return candidates.length === 0
|
|
41
|
+
? ` Supply the name of a declared ${kind}.`
|
|
42
|
+
: ` Use one of: ${candidates.join(', ')}.`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The sentence for a group routed without a subcommand. The root has no name to repeat, and the
|
|
46
|
+
* default text already opens with the application name, so the root's sentence names no Command.
|
|
47
|
+
*/
|
|
48
|
+
function nonCallableMessage(command, candidates) {
|
|
49
|
+
return command.length === 0
|
|
50
|
+
? `A command is required.${offering(candidates, 'command')}`
|
|
51
|
+
: `${routedSentence(command)} requires a subcommand.${offering(candidates, 'subcommand')}`;
|
|
26
52
|
}
|
|
27
53
|
function shortGroupMessage(fault) {
|
|
28
54
|
return fault.reason === 'value-position'
|
|
29
|
-
? `Value option
|
|
30
|
-
: `
|
|
55
|
+
? `Value option ${quoted(fault.token)} must be last in its short group. Supply its value in the next token.`
|
|
56
|
+
: `A short group mixes the global option ${quoted(`-${fault.global}`)} with ${quoted(`-${fault.other}`)}, which is not a global option. Supply global options as separate tokens, and local options after their command name.`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The sentence for a class whose declared code no failure may exit with. The class is named by its
|
|
60
|
+
* constructor, because the subclass has not yet set the instance's `name`.
|
|
61
|
+
*/
|
|
62
|
+
function undeclarableSentence(className, declared) {
|
|
63
|
+
const clause = typeof declared === 'number' && Number.isFinite(declared)
|
|
64
|
+
? `declares exit code ${String(declared)}.`
|
|
65
|
+
: 'declares an exit code that is not a finite number.';
|
|
66
|
+
return `Failure class ${quoted(className)} ${clause}`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The one message a distributed build shows an operator for a defect or a declaration fault: the
|
|
70
|
+
* application name and a fixed phrase, with no reason, class name, code, or path.
|
|
71
|
+
*/
|
|
72
|
+
export function genericDefectText(application) {
|
|
73
|
+
return `${application}: Something went wrong.\n`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether one failure is only the author's to fix: a declaration fault or a defect. A development
|
|
77
|
+
* build shows the author its Developer Diagnostic; a distributed one shows the generic message.
|
|
78
|
+
*/
|
|
79
|
+
export function isAuthorFault(failure) {
|
|
80
|
+
return failure instanceof DeclarationError || failure instanceof InternalError;
|
|
31
81
|
}
|
|
32
82
|
/**
|
|
33
|
-
* Core's own text for one failure
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* the
|
|
37
|
-
*
|
|
83
|
+
* Core's own text for one failure, with the trailing newline every view's text carries. The
|
|
84
|
+
* application name opens every line of a usage failure's message, one line for each problem it
|
|
85
|
+
* reports, so the operator reads who is speaking on each. A declaration fault and a defect read
|
|
86
|
+
* the generic defect message, because only the author can act on their detail, and a development
|
|
87
|
+
* build shows that detail ahead of every view. Every other class writes its message alone, even
|
|
88
|
+
* one that declares a usage error's exit code. It is the default view of every failure class and
|
|
89
|
+
* the text the plain fallback path writes, so it runs no application code and nothing downstream
|
|
90
|
+
* composes its newline.
|
|
38
91
|
*/
|
|
39
|
-
export function defaultText(failure) {
|
|
92
|
+
export function defaultText(failure, application) {
|
|
40
93
|
if (failure instanceof UsageError) {
|
|
41
|
-
return
|
|
94
|
+
return failure.message
|
|
95
|
+
.split('\n')
|
|
96
|
+
.map((line) => `${application}: ${line}\n`)
|
|
97
|
+
.join('');
|
|
42
98
|
}
|
|
43
|
-
if (failure
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
if (failure instanceof InternalError) {
|
|
47
|
-
return `Internal error: ${failure.message}\n`;
|
|
99
|
+
if (isAuthorFault(failure)) {
|
|
100
|
+
return genericDefectText(application);
|
|
48
101
|
}
|
|
49
102
|
return `${failure.message}\n`;
|
|
50
103
|
}
|
|
@@ -62,32 +115,90 @@ export function commandSentence(name) {
|
|
|
62
115
|
const subject = commandSubject(name);
|
|
63
116
|
return `${subject.slice(0, 1).toUpperCase()}${subject.slice(1)}`;
|
|
64
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Each failure class's code, captured at the first construction of the class or of a subclass that
|
|
120
|
+
* declares none, so a static changed afterward cannot give one class a second code. Core's own
|
|
121
|
+
* classes are captured when this module loads, so no write to their statics reaches a failure.
|
|
122
|
+
*/
|
|
123
|
+
const classCodes = new WeakMap();
|
|
124
|
+
/** Each constructed failure's code, which its `exitCode` reports and `run()` resolves. */
|
|
125
|
+
const failureCodes = new WeakMap();
|
|
126
|
+
/**
|
|
127
|
+
* The code one class exits with: the code captured for it, or else its own static when it declares
|
|
128
|
+
* one, or else its parent's. `constructed` names the class the diagnostic reports, the one the
|
|
129
|
+
* failing construction named.
|
|
130
|
+
*/
|
|
131
|
+
function classCode(target, constructed) {
|
|
132
|
+
const captured = classCodes.get(target);
|
|
133
|
+
if (captured !== undefined) {
|
|
134
|
+
return captured;
|
|
135
|
+
}
|
|
136
|
+
const parent = Reflect.getPrototypeOf(target);
|
|
137
|
+
const declared = Object.hasOwn(target, 'exitCode') || parent === null
|
|
138
|
+
? Reflect.get(target, 'exitCode')
|
|
139
|
+
: classCode(parent, constructed);
|
|
140
|
+
if (!isFailureExitCode(declared)) {
|
|
141
|
+
// A class declaration is no call, so no finding stands for it.
|
|
142
|
+
throw new DeclarationError(failureExitCode, {
|
|
143
|
+
correction: 'Declare a whole number from 1 through 125.',
|
|
144
|
+
sentence: undeclarableSentence(constructed.name, declared),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
classCodes.set(target, declared);
|
|
148
|
+
return declared;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The code a failure exits with. A value that inherits from a failure class without having been
|
|
152
|
+
* constructed holds none, and `toFailure` reports it as an internal error, so it reads 1.
|
|
153
|
+
*/
|
|
154
|
+
export function exitCodeOf(failure) {
|
|
155
|
+
return failureCodes.get(failure) ?? 1;
|
|
156
|
+
}
|
|
65
157
|
/**
|
|
66
158
|
* Every failure `run()` reports is an instance of a public class. Each class carries the facts its
|
|
67
|
-
* sentence interpolates, so a view reads them instead of parsing prose
|
|
68
|
-
*
|
|
69
|
-
*
|
|
159
|
+
* sentence interpolates, so a view reads them instead of parsing prose. The exit code is a static
|
|
160
|
+
* field the class declares, read from the nearest ancestor that declares one and captured at the
|
|
161
|
+
* class's first construction, so one class exits with one code and a projection reads it without
|
|
162
|
+
* an instance. The instance reports the same value through a read-only accessor, and no subclass
|
|
163
|
+
* property or assignment changes the code `run()` resolves. `message` never carries a category
|
|
164
|
+
* prefix; the default views add it.
|
|
70
165
|
*/
|
|
71
166
|
export class LoomError extends Error {
|
|
72
|
-
exitCode;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
167
|
+
static exitCode = 1;
|
|
168
|
+
/**
|
|
169
|
+
* Reads the constructed class's code, captured at its first construction. A code outside 1
|
|
170
|
+
* through 125 throws a `DeclarationError` in place of the failure and captures nothing, because
|
|
171
|
+
* core never clamps or replaces a code. `options` is the platform's own, so a failure that
|
|
172
|
+
* replaces another error keeps it as `cause` only when its author passes one.
|
|
173
|
+
*/
|
|
174
|
+
constructor(message, options) {
|
|
175
|
+
const code = classCode(new.target, new.target);
|
|
176
|
+
super(message, options);
|
|
177
|
+
failureCodes.set(this, code);
|
|
76
178
|
this.name = 'LoomError';
|
|
77
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* The code this failure exits with. An accessor without a setter, so a TypeScript subclass cannot
|
|
182
|
+
* declare it as a property, and an assignment throws in strict mode code and is ignored in sloppy
|
|
183
|
+
* mode code.
|
|
184
|
+
*/
|
|
185
|
+
get exitCode() {
|
|
186
|
+
return exitCodeOf(this);
|
|
187
|
+
}
|
|
78
188
|
}
|
|
79
189
|
/** Exit 2: the invocation, not the application, is wrong. */
|
|
80
190
|
export class UsageError extends LoomError {
|
|
81
|
-
|
|
82
|
-
|
|
191
|
+
static exitCode = 2;
|
|
192
|
+
constructor(message, options) {
|
|
193
|
+
super(message, options);
|
|
83
194
|
this.name = 'UsageError';
|
|
84
195
|
}
|
|
85
196
|
}
|
|
86
197
|
/** The whole validation phase in authoring order, so one failure reports every rejected input. */
|
|
87
198
|
export class InputError extends UsageError {
|
|
88
199
|
problems;
|
|
89
|
-
constructor(message, problems) {
|
|
90
|
-
super(message);
|
|
200
|
+
constructor(message, problems, options) {
|
|
201
|
+
super(message, options);
|
|
91
202
|
this.name = 'InputError';
|
|
92
203
|
this.problems = problems;
|
|
93
204
|
}
|
|
@@ -96,7 +207,7 @@ export class UnknownCommandError extends UsageError {
|
|
|
96
207
|
token;
|
|
97
208
|
candidates;
|
|
98
209
|
constructor(token, candidates) {
|
|
99
|
-
super(`Unknown command
|
|
210
|
+
super(`Unknown command ${quoted(token)}.${offering(candidates, 'command')}`);
|
|
100
211
|
this.candidates = candidates;
|
|
101
212
|
this.name = 'UnknownCommandError';
|
|
102
213
|
this.token = token;
|
|
@@ -107,7 +218,7 @@ export class NonCallableCommandError extends UsageError {
|
|
|
107
218
|
command;
|
|
108
219
|
candidates;
|
|
109
220
|
constructor(command, candidates) {
|
|
110
|
-
super(
|
|
221
|
+
super(nonCallableMessage(command, candidates));
|
|
111
222
|
this.candidates = candidates;
|
|
112
223
|
this.command = command;
|
|
113
224
|
this.name = 'NonCallableCommandError';
|
|
@@ -130,7 +241,7 @@ export class UnexpectedArgumentError extends UsageError {
|
|
|
130
241
|
export class UnknownOptionError extends UsageError {
|
|
131
242
|
spelling;
|
|
132
243
|
constructor(spelling) {
|
|
133
|
-
super(`Unknown option
|
|
244
|
+
super(`Unknown option ${quoted(spelling)}. Supply a declared option; prefix a hyphenated path with "./".`);
|
|
134
245
|
this.name = 'UnknownOptionError';
|
|
135
246
|
this.spelling = spelling;
|
|
136
247
|
}
|
|
@@ -138,7 +249,7 @@ export class UnknownOptionError extends UsageError {
|
|
|
138
249
|
export class MissingValueError extends UsageError {
|
|
139
250
|
spelling;
|
|
140
251
|
constructor(spelling) {
|
|
141
|
-
super(`Option
|
|
252
|
+
super(`Option ${quoted(spelling)} requires a value. Supply a value after ${quoted(spelling)}.`);
|
|
142
253
|
this.name = 'MissingValueError';
|
|
143
254
|
this.spelling = spelling;
|
|
144
255
|
}
|
|
@@ -148,7 +259,7 @@ export class UnexpectedValueError extends UsageError {
|
|
|
148
259
|
spelling;
|
|
149
260
|
value;
|
|
150
261
|
constructor(spelling, value) {
|
|
151
|
-
super(`Boolean option
|
|
262
|
+
super(`Boolean option ${quoted(spelling)} does not accept a value. Supply the flag alone.`);
|
|
152
263
|
this.name = 'UnexpectedValueError';
|
|
153
264
|
this.spelling = spelling;
|
|
154
265
|
this.value = value;
|
|
@@ -157,7 +268,7 @@ export class UnexpectedValueError extends UsageError {
|
|
|
157
268
|
export class RepeatedOptionError extends UsageError {
|
|
158
269
|
spelling;
|
|
159
270
|
constructor(spelling) {
|
|
160
|
-
super(`Option
|
|
271
|
+
super(`Option ${quoted(spelling)} can be supplied only once. Remove the repeated option.`);
|
|
161
272
|
this.name = 'RepeatedOptionError';
|
|
162
273
|
this.spelling = spelling;
|
|
163
274
|
}
|
|
@@ -172,27 +283,105 @@ export class ShortGroupError extends UsageError {
|
|
|
172
283
|
this.token = fault.token;
|
|
173
284
|
}
|
|
174
285
|
}
|
|
175
|
-
/**
|
|
286
|
+
/** The platform's error options one value carries, read without trusting its shape. */
|
|
287
|
+
function errorOptions(value) {
|
|
288
|
+
return typeof value === 'object' && value !== null && 'cause' in value
|
|
289
|
+
? { cause: value.cause }
|
|
290
|
+
: undefined;
|
|
291
|
+
}
|
|
292
|
+
/** A correction as a caller supplied it: one sentence, a copied list of them, or none. */
|
|
293
|
+
function readCorrection(value) {
|
|
294
|
+
if (typeof value === 'string') {
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
return Array.isArray(value)
|
|
298
|
+
? Object.freeze(value.filter((fix) => typeof fix === 'string'))
|
|
299
|
+
: undefined;
|
|
300
|
+
}
|
|
301
|
+
/** The findings a caller supplied, copied into a frozen list, or none for a value that holds none. */
|
|
302
|
+
function readFindings(value) {
|
|
303
|
+
return Object.freeze(Array.isArray(value) ? value.filter(isFinding) : []);
|
|
304
|
+
}
|
|
305
|
+
/** Whether one value is a finding a diagnostic can print: an object naming its call and arguments. */
|
|
306
|
+
function isFinding(value) {
|
|
307
|
+
return (typeof value === 'object' &&
|
|
308
|
+
value !== null &&
|
|
309
|
+
'call' in value &&
|
|
310
|
+
typeof value.call === 'string' &&
|
|
311
|
+
'arguments' in value &&
|
|
312
|
+
Array.isArray(value.arguments));
|
|
313
|
+
}
|
|
314
|
+
function faultParts(first, second, third) {
|
|
315
|
+
if (!isDiagnosticRule(first)) {
|
|
316
|
+
return {
|
|
317
|
+
correction: undefined,
|
|
318
|
+
findings: Object.freeze([]),
|
|
319
|
+
options: errorOptions(second),
|
|
320
|
+
rule: undefined,
|
|
321
|
+
sentence: String(first),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const parts = typeof second === 'object' && second !== null ? { ...second } : {};
|
|
325
|
+
return {
|
|
326
|
+
correction: readCorrection(parts.correction),
|
|
327
|
+
findings: readFindings(parts.findings),
|
|
328
|
+
options: errorOptions(third),
|
|
329
|
+
rule: first,
|
|
330
|
+
sentence: String(parts.sentence),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Exit 1: the declaration is wrong, so the author reads its Developer Diagnostic. A rule and the
|
|
335
|
+
* fault's own parts build it, or a sentence alone does for a fault with no rule. `message` holds
|
|
336
|
+
* the whole diagnostic as plain text at 80 columns, so a fault thrown at an authoring call prints
|
|
337
|
+
* it through the runtime's own uncaught-error output, and `sentence` holds the sentence alone.
|
|
338
|
+
*/
|
|
176
339
|
export class DeclarationError extends LoomError {
|
|
177
|
-
|
|
178
|
-
|
|
340
|
+
rule;
|
|
341
|
+
sentence;
|
|
342
|
+
findings;
|
|
343
|
+
correction;
|
|
344
|
+
constructor(first, second, third) {
|
|
345
|
+
const parts = faultParts(first, second, third);
|
|
346
|
+
super(diagnosticText({ ...parts, evidence: [], fallback: 'INVALID DECLARATION' }), parts.options);
|
|
179
347
|
this.name = 'DeclarationError';
|
|
348
|
+
this.rule = parts.rule;
|
|
349
|
+
this.sentence = parts.sentence;
|
|
350
|
+
this.findings = parts.findings;
|
|
351
|
+
this.correction = parts.correction;
|
|
180
352
|
}
|
|
181
353
|
}
|
|
182
|
-
/**
|
|
354
|
+
/**
|
|
355
|
+
* Exit 1: the application ended the invocation itself. An application may subclass it, and the
|
|
356
|
+
* subclass may declare its own exit code.
|
|
357
|
+
*/
|
|
183
358
|
export class FatalError extends LoomError {
|
|
184
|
-
constructor(message) {
|
|
185
|
-
super(message,
|
|
359
|
+
constructor(message, options) {
|
|
360
|
+
super(message, options);
|
|
186
361
|
this.name = 'FatalError';
|
|
187
362
|
}
|
|
188
363
|
}
|
|
189
|
-
/**
|
|
364
|
+
/**
|
|
365
|
+
* Exit 1: a defect, such as an unexpected exception, a non-error throw, or a view that could not
|
|
366
|
+
* answer. A rule and the defect's own parts build it, or a sentence and the thrown value do for a
|
|
367
|
+
* defect with no rule. `message` stays the sentence, because only `run()` reports a defect, and a
|
|
368
|
+
* development build renders its Developer Diagnostic from the parts.
|
|
369
|
+
*/
|
|
190
370
|
export class InternalError extends LoomError {
|
|
191
371
|
cause;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
372
|
+
rule;
|
|
373
|
+
sentence;
|
|
374
|
+
correction;
|
|
375
|
+
constructor(first,
|
|
376
|
+
// The rule form's parts or the sentence form's thrown value, read by shape below.
|
|
377
|
+
second) {
|
|
378
|
+
const parts = faultParts(first, second, undefined);
|
|
379
|
+
super(parts.sentence);
|
|
380
|
+
this.cause = parts.rule === undefined ? second : errorOptions(second)?.cause;
|
|
195
381
|
this.name = 'InternalError';
|
|
382
|
+
this.rule = parts.rule;
|
|
383
|
+
this.sentence = parts.sentence;
|
|
384
|
+
this.correction = parts.correction;
|
|
196
385
|
}
|
|
197
386
|
}
|
|
198
387
|
/**
|
|
@@ -205,7 +394,7 @@ export class ResultError extends InternalError {
|
|
|
205
394
|
path;
|
|
206
395
|
kind;
|
|
207
396
|
constructor(kind, path) {
|
|
208
|
-
super(resultMessage(kind, path)
|
|
397
|
+
super(resultContract, { cause: undefined, sentence: resultMessage(kind, path) });
|
|
209
398
|
this.kind = kind;
|
|
210
399
|
this.name = 'ResultError';
|
|
211
400
|
this.path = path;
|
|
@@ -218,20 +407,102 @@ export class ResultError extends InternalError {
|
|
|
218
407
|
export function asSentence(text) {
|
|
219
408
|
return text.endsWith('.') ? text : `${text}.`;
|
|
220
409
|
}
|
|
221
|
-
/**
|
|
410
|
+
/**
|
|
411
|
+
* What a diagnostic says about an unexpected value, whether or not it was an Error, with every
|
|
412
|
+
* control character escaped. A `DeclarationError` answers its sentence, because its message holds
|
|
413
|
+
* its whole diagnostic. Every sentence that quotes a thrown value reads it here, so a reason stays
|
|
414
|
+
* on one line and no bidirectional control reaches a terminal, whichever rule's sentence carries
|
|
415
|
+
* it, while the author's words around it keep their line breaks. Reading it never throws: an Error
|
|
416
|
+
* whose message is not a string or cannot be read, and a value whose prototype cannot be read, such
|
|
417
|
+
* as a proxy whose trap throws, answer one fixed sentence.
|
|
418
|
+
*/
|
|
222
419
|
export function reasonOf(thrown) {
|
|
223
|
-
return thrown
|
|
420
|
+
return escapeControlCharacters(rawReasonOf(thrown));
|
|
421
|
+
}
|
|
422
|
+
/** The thrown value's reason as it was written, read without throwing. */
|
|
423
|
+
function rawReasonOf(thrown) {
|
|
424
|
+
const unreadableReason = 'The thrown value has no readable message.';
|
|
425
|
+
try {
|
|
426
|
+
if (!(thrown instanceof Error)) {
|
|
427
|
+
return 'An unknown error occurred.';
|
|
428
|
+
}
|
|
429
|
+
if (thrown instanceof DeclarationError) {
|
|
430
|
+
return thrown.sentence;
|
|
431
|
+
}
|
|
432
|
+
const { message } = thrown;
|
|
433
|
+
return typeof message === 'string' ? message : unreadableReason;
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return unreadableReason;
|
|
437
|
+
}
|
|
224
438
|
}
|
|
225
439
|
/**
|
|
226
440
|
* Why a returned value is not the text a view owes. A view is synchronous, so a returned promise is
|
|
227
|
-
* a non-string return like any other
|
|
228
|
-
* unobserved rejection would end the process before the invocation could report anything.
|
|
441
|
+
* a non-string return like any other: it receives a rejection handler and is otherwise ignored.
|
|
229
442
|
*/
|
|
230
443
|
export function notTextReason(value) {
|
|
231
|
-
|
|
444
|
+
if (isThenable(value)) {
|
|
445
|
+
ignoreRejection(value);
|
|
446
|
+
}
|
|
232
447
|
return `The view returned ${typeof value} instead of a string.`;
|
|
233
448
|
}
|
|
234
|
-
/**
|
|
449
|
+
/**
|
|
450
|
+
* Whether a thrown value is a failure class by its prototype chain, read defensively, because a
|
|
451
|
+
* revoked proxy or a proxy whose prototype trap throws answers no chain. Such a value is foreign.
|
|
452
|
+
*/
|
|
453
|
+
function isLoomError(thrown) {
|
|
454
|
+
try {
|
|
455
|
+
return thrown instanceof LoomError;
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Every thrown value reaches reporting as a failure class; anything else is internal. A value that
|
|
463
|
+
* inherits from a failure class without having been constructed holds no code, so it is internal
|
|
464
|
+
* too.
|
|
465
|
+
*/
|
|
235
466
|
export function toFailure(thrown) {
|
|
236
|
-
|
|
467
|
+
if (!isLoomError(thrown)) {
|
|
468
|
+
return foreignFailure(thrown);
|
|
469
|
+
}
|
|
470
|
+
return failureCodes.has(thrown)
|
|
471
|
+
? thrown
|
|
472
|
+
: new InternalError(unconstructedFailure, {
|
|
473
|
+
cause: thrown,
|
|
474
|
+
correction: 'Construct the failure with new before throwing it.',
|
|
475
|
+
sentence: 'A thrown value inherits from a failure class but was never constructed as one.',
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The defect a foreign throw reports: its reason as the sentence, and the thrown value as the
|
|
480
|
+
* cause a development build's diagnostic shows.
|
|
481
|
+
*/
|
|
482
|
+
export function foreignFailure(thrown) {
|
|
483
|
+
return new InternalError(foreignThrow, {
|
|
484
|
+
cause: thrown,
|
|
485
|
+
correction: foreignThrowCorrection,
|
|
486
|
+
sentence: reasonOf(thrown),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
// Core's own classes are captured now, before any application code can write their statics.
|
|
490
|
+
for (const Class of [
|
|
491
|
+
LoomError,
|
|
492
|
+
UsageError,
|
|
493
|
+
InputError,
|
|
494
|
+
UnknownCommandError,
|
|
495
|
+
NonCallableCommandError,
|
|
496
|
+
UnexpectedArgumentError,
|
|
497
|
+
UnknownOptionError,
|
|
498
|
+
MissingValueError,
|
|
499
|
+
UnexpectedValueError,
|
|
500
|
+
RepeatedOptionError,
|
|
501
|
+
ShortGroupError,
|
|
502
|
+
DeclarationError,
|
|
503
|
+
FatalError,
|
|
504
|
+
InternalError,
|
|
505
|
+
ResultError,
|
|
506
|
+
]) {
|
|
507
|
+
classCodes.set(Class, Class.exitCode);
|
|
237
508
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first code no failure class may declare. 126 and 127 belong to the shell, and 128 plus a
|
|
3
|
+
* signal number reports a signal, which covers 130 and 143.
|
|
4
|
+
*/
|
|
5
|
+
declare const firstReserved = 126;
|
|
6
|
+
/** Every whole number below `Bound`, counted up from 0, as one union of literal types. */
|
|
7
|
+
type WholeNumbersBelow<Bound extends number, Counted extends number[] = []> = Counted['length'] extends Bound ? Counted[number] : WholeNumbersBelow<Bound, [...Counted, Counted['length']]>;
|
|
8
|
+
/** The codes a failure class may declare, 1 through 125. 0 is success, and the rest are reserved. */
|
|
9
|
+
export type FailureExitCode = Exclude<WholeNumbersBelow<typeof firstReserved>, 0>;
|
|
10
|
+
/**
|
|
11
|
+
* Whether one declared value is a code a failure class may exit with. A JavaScript class, or one
|
|
12
|
+
* that escaped the type check, may declare any value at all, so the check reads it as unknown.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isFailureExitCode(value: unknown): value is FailureExitCode;
|
|
15
|
+
/** The command was used incorrectly. */
|
|
16
|
+
export declare const EX_USAGE = 64;
|
|
17
|
+
/** The input data was incorrect. */
|
|
18
|
+
export declare const EX_DATAERR = 65;
|
|
19
|
+
/** An input file did not exist or could not be read. */
|
|
20
|
+
export declare const EX_NOINPUT = 66;
|
|
21
|
+
/** An addressee is unknown. */
|
|
22
|
+
export declare const EX_NOUSER = 67;
|
|
23
|
+
/** A host name is unknown. */
|
|
24
|
+
export declare const EX_NOHOST = 68;
|
|
25
|
+
/** A service is unavailable. */
|
|
26
|
+
export declare const EX_UNAVAILABLE = 69;
|
|
27
|
+
/** An internal software error. */
|
|
28
|
+
export declare const EX_SOFTWARE = 70;
|
|
29
|
+
/** An operating system error. */
|
|
30
|
+
export declare const EX_OSERR = 71;
|
|
31
|
+
/** A system file is missing or malformed. */
|
|
32
|
+
export declare const EX_OSFILE = 72;
|
|
33
|
+
/** An output file cannot be created. */
|
|
34
|
+
export declare const EX_CANTCREAT = 73;
|
|
35
|
+
/** An input or output error. */
|
|
36
|
+
export declare const EX_IOERR = 74;
|
|
37
|
+
/** A temporary failure; a retry may succeed. */
|
|
38
|
+
export declare const EX_TEMPFAIL = 75;
|
|
39
|
+
/** A remote system broke the protocol. */
|
|
40
|
+
export declare const EX_PROTOCOL = 76;
|
|
41
|
+
/** Permission is insufficient. */
|
|
42
|
+
export declare const EX_NOPERM = 77;
|
|
43
|
+
/** The configuration is wrong. */
|
|
44
|
+
export declare const EX_CONFIG = 78;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first code no failure class may declare. 126 and 127 belong to the shell, and 128 plus a
|
|
3
|
+
* signal number reports a signal, which covers 130 and 143.
|
|
4
|
+
*/
|
|
5
|
+
const firstReserved = 126;
|
|
6
|
+
/**
|
|
7
|
+
* Whether one declared value is a code a failure class may exit with. A JavaScript class, or one
|
|
8
|
+
* that escaped the type check, may declare any value at all, so the check reads it as unknown.
|
|
9
|
+
*/
|
|
10
|
+
export function isFailureExitCode(value) {
|
|
11
|
+
return (typeof value === 'number' && Number.isInteger(value) && value >= 1 && value < firstReserved);
|
|
12
|
+
}
|
|
13
|
+
// The failure codes BSD's `sysexits.h` names, as flat constants with literal types.
|
|
14
|
+
// A failure class declares one by name: `static override readonly exitCode = EX_UNAVAILABLE;`.
|
|
15
|
+
// `EX_OK` is not here, because no failure declares 0.
|
|
16
|
+
// Core raises invalid input as 2 and never raises `EX_USAGE`.
|
|
17
|
+
/** The command was used incorrectly. */
|
|
18
|
+
export const EX_USAGE = 64;
|
|
19
|
+
/** The input data was incorrect. */
|
|
20
|
+
export const EX_DATAERR = 65;
|
|
21
|
+
/** An input file did not exist or could not be read. */
|
|
22
|
+
export const EX_NOINPUT = 66;
|
|
23
|
+
/** An addressee is unknown. */
|
|
24
|
+
export const EX_NOUSER = 67;
|
|
25
|
+
/** A host name is unknown. */
|
|
26
|
+
export const EX_NOHOST = 68;
|
|
27
|
+
/** A service is unavailable. */
|
|
28
|
+
export const EX_UNAVAILABLE = 69;
|
|
29
|
+
/** An internal software error. */
|
|
30
|
+
export const EX_SOFTWARE = 70;
|
|
31
|
+
/** An operating system error. */
|
|
32
|
+
export const EX_OSERR = 71;
|
|
33
|
+
/** A system file is missing or malformed. */
|
|
34
|
+
export const EX_OSFILE = 72;
|
|
35
|
+
/** An output file cannot be created. */
|
|
36
|
+
export const EX_CANTCREAT = 73;
|
|
37
|
+
/** An input or output error. */
|
|
38
|
+
export const EX_IOERR = 74;
|
|
39
|
+
/** A temporary failure; a retry may succeed. */
|
|
40
|
+
export const EX_TEMPFAIL = 75;
|
|
41
|
+
/** A remote system broke the protocol. */
|
|
42
|
+
export const EX_PROTOCOL = 76;
|
|
43
|
+
/** Permission is insufficient. */
|
|
44
|
+
export const EX_NOPERM = 77;
|
|
45
|
+
/** The configuration is wrong. */
|
|
46
|
+
export const EX_CONFIG = 78;
|