@loomcli/core 0.6.0 → 0.8.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 +11 -6
- package/dist/application.js +57 -19
- package/dist/capture.d.ts +65 -0
- package/dist/capture.js +99 -0
- package/dist/chain.d.ts +28 -17
- package/dist/chain.js +11 -10
- package/dist/command-rules.d.ts +1 -1
- package/dist/command-rules.js +2 -2
- package/dist/command.d.ts +31 -47
- package/dist/command.js +204 -209
- package/dist/errors.d.ts +22 -21
- package/dist/errors.js +38 -18
- package/dist/facts.d.ts +5 -0
- package/dist/facts.js +8 -1
- package/dist/globals.d.ts +48 -22
- package/dist/globals.js +72 -29
- package/dist/glyphs.generated.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -1
- package/dist/input-rules.d.ts +20 -2
- package/dist/input-rules.js +47 -5
- package/dist/inspect.d.ts +33 -17
- package/dist/inspect.js +74 -87
- package/dist/locate.js +52 -60
- package/dist/options.d.ts +101 -83
- package/dist/options.js +311 -267
- package/dist/parse.d.ts +162 -0
- package/dist/parse.js +601 -0
- package/dist/plain.d.ts +52 -2
- package/dist/plain.js +228 -2
- package/dist/plugin-rules.d.ts +8 -4
- package/dist/plugin-rules.js +13 -9
- package/dist/plugin-settings.d.ts +18 -0
- package/dist/plugin-settings.js +38 -0
- package/dist/plugin.d.ts +32 -27
- package/dist/plugin.js +107 -89
- package/dist/sources.d.ts +18 -9
- package/dist/sources.js +50 -20
- package/dist/style-layout.js +2 -2
- package/dist/style-width.d.ts +13 -0
- package/dist/style-width.js +170 -0
- package/dist/types.d.ts +70 -30
- package/dist/unicode.generated.d.ts +27 -0
- package/dist/unicode.generated.js +1036 -0
- package/dist/validation.d.ts +108 -34
- package/dist/validation.js +282 -125
- package/dist/view.d.ts +16 -11
- package/dist/view.js +13 -4
- package/licenses/unicode-LICENSE.txt +41 -0
- package/licenses/uucode-LICENSE.md +35 -0
- package/package.json +9 -5
package/dist/input-rules.js
CHANGED
|
@@ -4,9 +4,9 @@ import { registerRule } from './diagnostic-text.js';
|
|
|
4
4
|
* options, and environment bindings. Each is declared once here and shared by every site that
|
|
5
5
|
* raises it, as a plugin's rules are.
|
|
6
6
|
*/
|
|
7
|
-
/** An option declared with a type other than string or
|
|
7
|
+
/** An option declared with a type other than string, Boolean, or count. */
|
|
8
8
|
const optionType = registerRule('@loomcli/core/option-type', {
|
|
9
|
-
explanation: 'The type decides how the parser reads an option: a string option consumes a value, and a
|
|
9
|
+
explanation: 'The type decides how the parser reads an option: a string option consumes a value, a Boolean option consumes none, and a counted option consumes none and counts its occurrences. Core reads no other kind.',
|
|
10
10
|
headline: 'Invalid option type',
|
|
11
11
|
});
|
|
12
12
|
/** A short alias that is not one ASCII letter. */
|
|
@@ -27,11 +27,36 @@ const shortOnlyWithoutShort = registerRule('@loomcli/core/short-only-without-sho
|
|
|
27
27
|
explanation: 'shortOnly removes every long spelling of an option, so an option with no short alias would leave an operator no spelling to type.',
|
|
28
28
|
headline: 'Short only with no short alias',
|
|
29
29
|
});
|
|
30
|
+
/** `aliases` on an option that declares `shortOnly`. */
|
|
31
|
+
const shortOnlyWithAliases = registerRule('@loomcli/core/short-only-with-aliases', {
|
|
32
|
+
explanation: 'shortOnly removes every long spelling of an option, and each alias adds a long spelling, so an option cannot declare both.',
|
|
33
|
+
headline: 'Short only with aliases',
|
|
34
|
+
});
|
|
30
35
|
/** `multiple` on a Boolean option. */
|
|
31
36
|
const booleanOptionMultiple = registerRule('@loomcli/core/boolean-option-multiple', {
|
|
32
37
|
explanation: 'A Boolean option reports whether its spelling was supplied, so a repeat has no second value to collect. multiple collects each occurrence of a string option into an array.',
|
|
33
38
|
headline: 'Boolean option takes one value',
|
|
34
39
|
});
|
|
40
|
+
/** `multiple` on a counted option. */
|
|
41
|
+
const countOptionMultiple = registerRule('@loomcli/core/count-option-multiple', {
|
|
42
|
+
explanation: 'A counted option already counts every occurrence of every spelling, so it has no values to collect. multiple collects each occurrence of a string option into an array.',
|
|
43
|
+
headline: 'Counted option takes no values',
|
|
44
|
+
});
|
|
45
|
+
/** `polarity` on a counted option. */
|
|
46
|
+
const polarityOnCount = registerRule('@loomcli/core/polarity-on-count', {
|
|
47
|
+
explanation: 'Polarity chooses which long forms a Boolean option accepts and what its absence means. A counted option reads how many times it was supplied, so it has no negative form and no polarity.',
|
|
48
|
+
headline: 'Polarity on a counted option',
|
|
49
|
+
});
|
|
50
|
+
/** `implied` on a Boolean or counted option. */
|
|
51
|
+
const impliedOnBooleanOrCount = registerRule('@loomcli/core/implied-on-boolean-or-count', {
|
|
52
|
+
explanation: 'An implied value is the value a bare spelling of a string option supplies. A Boolean option and a counted option take no value, so a bare spelling already says everything they read.',
|
|
53
|
+
headline: 'Implied on a valueless option',
|
|
54
|
+
});
|
|
55
|
+
/** An `implied` value that is not a string. */
|
|
56
|
+
const impliedNotAString = registerRule('@loomcli/core/implied-not-a-string', {
|
|
57
|
+
explanation: "An implied value stands in for the string an operator would otherwise attach to the spelling, so it is a string, in the validator's input type.",
|
|
58
|
+
headline: 'Implied value not a string',
|
|
59
|
+
});
|
|
35
60
|
/** `polarity` on a string option. */
|
|
36
61
|
const polarityOnString = registerRule('@loomcli/core/polarity-on-string', {
|
|
37
62
|
explanation: 'Polarity chooses which long forms a Boolean option accepts and what its absence means. A string option takes its value from the operator, so it has no polarity.',
|
|
@@ -52,7 +77,7 @@ const shortOnlyBothPolarities = registerRule('@loomcli/core/short-only-both-pola
|
|
|
52
77
|
* plugin's hook declared.
|
|
53
78
|
*/
|
|
54
79
|
const spellingTaken = registerRule('@loomcli/core/spelling-taken', {
|
|
55
|
-
explanation: "The parser reads each spelling as one option, and a Command's own options share one invocation with the global options and every installed plugin's options. A spelling two options claim, a short alias or a generated negative form included, would reach only one of them.",
|
|
80
|
+
explanation: "The parser reads each spelling as one option, and a Command's own options share one invocation with the global options and every installed plugin's options. A spelling two options claim, a short alias, an alias, or a generated negative form included, would reach only one of them.",
|
|
56
81
|
headline: 'Spelling used twice',
|
|
57
82
|
});
|
|
58
83
|
/**
|
|
@@ -60,7 +85,7 @@ const spellingTaken = registerRule('@loomcli/core/spelling-taken', {
|
|
|
60
85
|
* plugin's hook declared.
|
|
61
86
|
*/
|
|
62
87
|
const optionDeclaredTwice = registerRule('@loomcli/core/option-declared-twice', {
|
|
63
|
-
explanation: "An action reads the global options and its Command's own options from one options object, each under its declared name, and the
|
|
88
|
+
explanation: "An action reads the global options and its Command's own options from one options object, each under its declared name, and the global options, the application's and every installed plugin's, share every Command's one table of spellings. Two options with one name in either leave one of them unreadable.",
|
|
64
89
|
headline: 'Option declared twice',
|
|
65
90
|
});
|
|
66
91
|
/**
|
|
@@ -117,6 +142,11 @@ const booleanOptionValueRule = registerRule('@loomcli/core/boolean-option-value-
|
|
|
117
142
|
explanation: 'A Boolean option consumes no value, so there is nothing to validate, and its polarity decides the value an absent option reads. validate, default, required, and validateOmitted belong to inputs that take a value.',
|
|
118
143
|
headline: 'Value rule on a Boolean option',
|
|
119
144
|
});
|
|
145
|
+
/** `validate`, `default`, `required`, or `validateOmitted` on a counted option. */
|
|
146
|
+
const countOptionValueRule = registerRule('@loomcli/core/count-option-value-rule', {
|
|
147
|
+
explanation: 'A counted option consumes no value and reads how many times it was supplied, 0 when nothing supplied it, so there is nothing to validate and no absence to decide. validate, default, required, and validateOmitted belong to inputs that take a value.',
|
|
148
|
+
headline: 'Value rule on a counted option',
|
|
149
|
+
});
|
|
120
150
|
/** A required input that also declares a default. */
|
|
121
151
|
const requiredWithDefault = registerRule('@loomcli/core/required-with-default', {
|
|
122
152
|
explanation: 'A default fills an omitted value, and a required input fails when it is omitted, so a required input never reads its default.',
|
|
@@ -132,14 +162,26 @@ const defaultShape = registerRule('@loomcli/core/default-shape', {
|
|
|
132
162
|
explanation: "A default stands in for the value an operator would supply. Without a validator it is that raw value, a string or, for an input that takes several values, an array of strings; with one, it is the validator's input, and an input that takes several values still takes an array of them.",
|
|
133
163
|
headline: 'Default of the wrong shape',
|
|
134
164
|
});
|
|
165
|
+
/** The most arrays and plain objects any path through a declared default may hold. */
|
|
166
|
+
const defaultLevels = 10;
|
|
167
|
+
/** A declared default nested deeper than `defaultLevels`. */
|
|
168
|
+
const defaultDepth = registerRule('@loomcli/core/default-depth', {
|
|
169
|
+
explanation: `A default stands in for the value an operator would supply, and every reader of the graph, help and the manifest included, walks it. Core keeps every path through a default within ${String(defaultLevels)} levels of arrays and plain objects, and a default that holds itself nests without end, so every reader stays far inside the call stack on every runtime.`,
|
|
170
|
+
headline: 'Default nested too deep',
|
|
171
|
+
});
|
|
135
172
|
/** A declared default its validator rejected. */
|
|
136
173
|
const invalidDefault = registerRule('@loomcli/core/invalid-default', {
|
|
137
174
|
explanation: 'Each run passes every declared default through its validator before it reads a token, because a default reaches the action as a validated value. A default the validator rejects would reach no action, whatever the operator supplies.',
|
|
138
175
|
headline: 'Default rejected',
|
|
139
176
|
});
|
|
177
|
+
/** A declared implied value its validator rejected. */
|
|
178
|
+
const invalidImplied = registerRule('@loomcli/core/invalid-implied', {
|
|
179
|
+
explanation: 'Each run passes every implied value through its validator before it reads a token, because a bare spelling supplies it to the action as a validated value. An implied value the validator rejects is the declaration at fault, whatever the operator supplies, so the run reports it whether or not a bare spelling was typed.',
|
|
180
|
+
headline: 'Implied value rejected',
|
|
181
|
+
});
|
|
140
182
|
/** A validator's JSON Schema converter that threw or returned a value that is not a plain object. */
|
|
141
183
|
const schemaConverterFailed = registerRule('@loomcli/core/schema-converter-failed', {
|
|
142
184
|
explanation: "Help, the manifest, and completion read what an input accepts from the JSON Schema its validator publishes. A converter that throws or returns anything but a plain object publishes no shape, so a distributed build reads the input's schema as null.",
|
|
143
185
|
headline: 'Schema converter failed',
|
|
144
186
|
});
|
|
145
|
-
export { booleanOptionMultiple, booleanOptionValueRule, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
|
|
187
|
+
export { booleanOptionMultiple, booleanOptionValueRule, countOptionMultiple, countOptionValueRule, defaultDepth, defaultLevels, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, impliedNotAString, impliedOnBooleanOrCount, invalidDefault, invalidImplied, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnCount, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithAliases, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
|
package/dist/inspect.d.ts
CHANGED
|
@@ -21,17 +21,22 @@ interface ArgumentNode {
|
|
|
21
21
|
readonly extensions: Readonly<Record<string, unknown>>;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* One declared option, in the shape its type gives it. Spellings are the accepted CLI forms
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* One declared option, in the shape its type gives it. Spellings are the accepted CLI forms. A
|
|
25
|
+
* global option reads the same whether the application or a plugin declared it, and an option a
|
|
26
|
+
* plugin's lifecycle hook declared on a Command is that Command's own in every respect, so neither
|
|
27
|
+
* names a plugin.
|
|
28
28
|
* `hidden` is `false` unless the declaration says `true`, and `deprecated` is the declared
|
|
29
29
|
* migration message or `undefined`. A listing projection omits a hidden node and marks a
|
|
30
30
|
* deprecated one; parsing binds without reading either.
|
|
31
|
-
* `schema` is the input schema the validator publishes. A Boolean option
|
|
32
|
-
*
|
|
33
|
-
* contract lets
|
|
31
|
+
* `schema` is the input schema the validator publishes. A Boolean option and a counted option
|
|
32
|
+
* validate nothing, so their variants carry the field at `null`, and every projection built on the
|
|
33
|
+
* node holds if a later contract lets a Boolean option validate. A counted option has no negative
|
|
34
|
+
* spelling, polarity, default, or validator, so its variant carries none of them. A string option's
|
|
35
|
+
* `implied` is the value a bare spelling supplies, or `null` when it declares none.
|
|
34
36
|
* `env` is the variable the option's environment binding names, or `null` when it binds none.
|
|
37
|
+
* `aliases` holds the declared aliases as bare names in declaration order. An alias is
|
|
38
|
+
* unadvertised, so the spellings above are the ones the declared name derives and no listing reads
|
|
39
|
+
* `aliases`; parsing and `locate` read the table, which holds every alias's spellings.
|
|
35
40
|
*/
|
|
36
41
|
type OptionNode = {
|
|
37
42
|
readonly type: 'string';
|
|
@@ -39,9 +44,9 @@ type OptionNode = {
|
|
|
39
44
|
readonly description: string | undefined;
|
|
40
45
|
readonly hidden: boolean;
|
|
41
46
|
readonly deprecated: string | undefined;
|
|
42
|
-
readonly scope: 'application' | 'plugin';
|
|
43
47
|
readonly long: string | null;
|
|
44
48
|
readonly short: string | null;
|
|
49
|
+
readonly aliases: readonly string[];
|
|
45
50
|
readonly required: boolean;
|
|
46
51
|
readonly multiple: boolean;
|
|
47
52
|
readonly validated: boolean;
|
|
@@ -51,6 +56,7 @@ type OptionNode = {
|
|
|
51
56
|
readonly default: {
|
|
52
57
|
readonly value: unknown;
|
|
53
58
|
} | undefined;
|
|
59
|
+
readonly implied: string | null;
|
|
54
60
|
readonly extensions: Readonly<Record<string, unknown>>;
|
|
55
61
|
} | {
|
|
56
62
|
readonly type: 'boolean';
|
|
@@ -58,14 +64,26 @@ type OptionNode = {
|
|
|
58
64
|
readonly description: string | undefined;
|
|
59
65
|
readonly hidden: boolean;
|
|
60
66
|
readonly deprecated: string | undefined;
|
|
61
|
-
readonly scope: 'application' | 'plugin';
|
|
62
67
|
readonly long: string | null;
|
|
63
68
|
readonly short: string | null;
|
|
64
69
|
readonly negative: string | null;
|
|
70
|
+
readonly aliases: readonly string[];
|
|
65
71
|
readonly polarity: 'positive' | 'negative' | 'both';
|
|
66
72
|
readonly schema: InputSchema;
|
|
67
73
|
readonly env: string | null;
|
|
68
74
|
readonly extensions: Readonly<Record<string, unknown>>;
|
|
75
|
+
} | {
|
|
76
|
+
readonly type: 'count';
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly description: string | undefined;
|
|
79
|
+
readonly hidden: boolean;
|
|
80
|
+
readonly deprecated: string | undefined;
|
|
81
|
+
readonly long: string | null;
|
|
82
|
+
readonly short: string | null;
|
|
83
|
+
readonly aliases: readonly string[];
|
|
84
|
+
readonly schema: null;
|
|
85
|
+
readonly env: string | null;
|
|
86
|
+
readonly extensions: Readonly<Record<string, unknown>>;
|
|
69
87
|
};
|
|
70
88
|
/**
|
|
71
89
|
* One Command in the graph. `name` is `null` for the root, and `path` is its route from it.
|
|
@@ -111,20 +129,18 @@ interface CommandGraph {
|
|
|
111
129
|
readonly root: CommandNode;
|
|
112
130
|
}
|
|
113
131
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* reported as they are, because core cannot copy them meaningfully. The graph reads it for a
|
|
118
|
-
* declared value and the chain reads it for the request one middleware holds.
|
|
132
|
+
* The spelling every reported problem names one option by, the one core's own validation reports:
|
|
133
|
+
* its long form, a negative-only Boolean option's negative form, and otherwise its short form. A
|
|
134
|
+
* plugin that reports a problem for an option, such as an input source, names it this way too.
|
|
119
135
|
*/
|
|
120
|
-
export declare function
|
|
136
|
+
export declare function reportedSpelling(option: OptionNode): string;
|
|
121
137
|
/** The declared result as plain data, or `null` on a Command that declares none. */
|
|
122
138
|
declare function resultNode(result: DeclaredResult | undefined): ResultNode | null;
|
|
123
139
|
/**
|
|
124
140
|
* Renders one built graph as frozen plain data. Nothing here reads a host fact; each validated
|
|
125
141
|
* input's converter is asked for its input schema, and in a development build a converter that
|
|
126
|
-
* fails is a declaration fault. The globals list holds the application's own
|
|
127
|
-
* installed plugin's in installation order, which is the order the
|
|
142
|
+
* fails is a declaration fault. The globals list holds every global option, the application's own
|
|
143
|
+
* and then each installed plugin's in installation order, which is the order the table holds them.
|
|
128
144
|
*/
|
|
129
145
|
declare function inspectGraph(name: string, graph: BuiltGraph, facts: {
|
|
130
146
|
description: string | undefined;
|
package/dist/inspect.js
CHANGED
|
@@ -1,48 +1,28 @@
|
|
|
1
1
|
import { asSentence, DeclarationError, InternalError, reasonOf } from './errors.js';
|
|
2
2
|
import { partOf, siteFinding } from './facts.js';
|
|
3
3
|
import { schemaConverterFailed } from './input-rules.js';
|
|
4
|
-
import {
|
|
4
|
+
import { reportedOf, spellingsOf } from './options.js';
|
|
5
|
+
import { isPlainObject, snapshotRecord } from './plain.js';
|
|
5
6
|
import { foreignGraph, foreignGraphCorrection } from './rules.js';
|
|
6
|
-
import { declaringSite, inputPlace, validatesOmission } from './validation.js';
|
|
7
|
+
import { declarationSubject, declaringSite, inputPlace, validatesOmission } from './validation.js';
|
|
7
8
|
/** A declaration that carries no extension value publishes one shared, empty frozen record. */
|
|
8
9
|
const noExtensions = Object.freeze({});
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
* form
|
|
12
|
-
*
|
|
11
|
+
* The spelling every reported problem names one option by, the one core's own validation reports:
|
|
12
|
+
* its long form, a negative-only Boolean option's negative form, and otherwise its short form. A
|
|
13
|
+
* plugin that reports a problem for an option, such as an input source, names it this way too.
|
|
13
14
|
*/
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
if (option.name === name) {
|
|
18
|
-
spellings[option.role] = spelling;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return spellings;
|
|
15
|
+
export function reportedSpelling(option) {
|
|
16
|
+
const negative = option.type === 'boolean' ? option.negative : null;
|
|
17
|
+
return reportedOf({ long: option.long, negative, short: option.short }, option.name);
|
|
22
18
|
}
|
|
23
19
|
/**
|
|
24
|
-
* A
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* reported as they are, because core cannot copy them meaningfully. The graph reads it for a
|
|
28
|
-
* declared value and the chain reads it for the request one middleware holds.
|
|
20
|
+
* A declared default is wrapped, so `default: undefined` reads apart from no default at all. The
|
|
21
|
+
* value is the frozen snapshot the declaring call took, the one a run validates, so inspection
|
|
22
|
+
* copies nothing and every reader sees one value.
|
|
29
23
|
*/
|
|
30
|
-
export function snapshot(value) {
|
|
31
|
-
if (Array.isArray(value)) {
|
|
32
|
-
return Object.freeze(value.map((entry) => snapshot(entry)));
|
|
33
|
-
}
|
|
34
|
-
if (isPlainObject(value)) {
|
|
35
|
-
return snapshotRecord(value);
|
|
36
|
-
}
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
/** The snapshot of one plain object, under the record type the caller already established. */
|
|
40
|
-
function snapshotRecord(value) {
|
|
41
|
-
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, snapshot(entry)])));
|
|
42
|
-
}
|
|
43
|
-
/** A declared default is wrapped, so `default: undefined` reads apart from no default at all. */
|
|
44
24
|
function declaredDefault(config) {
|
|
45
|
-
return 'default' in config ? Object.freeze({ value:
|
|
25
|
+
return 'default' in config ? Object.freeze({ value: config.default }) : undefined;
|
|
46
26
|
}
|
|
47
27
|
/**
|
|
48
28
|
* The JSON Schema draft build asks every converter for, with no library options. Every converter
|
|
@@ -65,11 +45,11 @@ function publishesSchema(schema) {
|
|
|
65
45
|
* the converter threw, the thrown value as its cause.
|
|
66
46
|
*/
|
|
67
47
|
function converterFault(input, check, failed) {
|
|
68
|
-
const site =
|
|
48
|
+
const site = check.siteOf(input);
|
|
69
49
|
const parts = {
|
|
70
50
|
correction: 'Fix the converter so it returns a JSON Schema object, or declare a validator that publishes none.',
|
|
71
|
-
findings: [siteFinding(site, partOf(site, 'validate'))],
|
|
72
|
-
sentence: `${
|
|
51
|
+
findings: site === undefined ? [] : [siteFinding(site, partOf(site, 'validate'))],
|
|
52
|
+
sentence: `${declarationSubject(input)} validator's JSON Schema converter ${failed.failure}`,
|
|
73
53
|
};
|
|
74
54
|
return 'cause' in failed
|
|
75
55
|
? new DeclarationError(schemaConverterFailed, parts, { cause: failed.cause })
|
|
@@ -120,47 +100,58 @@ function inputSchema(input, check) {
|
|
|
120
100
|
function extensionsOf(records, declaration) {
|
|
121
101
|
return records.get(declaration) ?? noExtensions;
|
|
122
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* One option's node, in the shape its kind gives it. Every kind publishes the listing facts and the
|
|
105
|
+
* spellings the declared name derives; a string option adds its value facts, a Boolean option its
|
|
106
|
+
* negative spelling and polarity, and a counted option nothing more.
|
|
107
|
+
*/
|
|
123
108
|
function optionNode(input, read) {
|
|
124
|
-
const { check, records,
|
|
109
|
+
const { check, records, table } = read;
|
|
125
110
|
const { config, name } = input;
|
|
126
111
|
const { long, negative, short } = spellingsOf(table, name);
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
112
|
+
const shared = {
|
|
113
|
+
aliases: Object.freeze([...(config.aliases ?? [])]),
|
|
114
|
+
deprecated: config.deprecated,
|
|
115
|
+
description: config.description,
|
|
116
|
+
env: config.env ?? null,
|
|
117
|
+
extensions: extensionsOf(records, input),
|
|
118
|
+
hidden: config.hidden === true,
|
|
119
|
+
long,
|
|
120
|
+
name,
|
|
121
|
+
short,
|
|
122
|
+
};
|
|
123
|
+
switch (config.type) {
|
|
124
|
+
case 'string': {
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
...shared,
|
|
127
|
+
default: declaredDefault(config),
|
|
128
|
+
implied: config.implied ?? null,
|
|
129
|
+
// The parser reads the same test, so a collection reports as one here and there.
|
|
130
|
+
multiple: config.multiple === true,
|
|
131
|
+
required: config.required === true,
|
|
132
|
+
schema: inputSchema(input, check),
|
|
133
|
+
type: 'string',
|
|
134
|
+
validateOmitted: validatesOmission(input),
|
|
135
|
+
validated: config.validate !== undefined,
|
|
136
|
+
});
|
|
143
137
|
}
|
|
144
|
-
: {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
validated: config.validate !== undefined,
|
|
162
|
-
};
|
|
163
|
-
return Object.freeze(node);
|
|
138
|
+
case 'boolean': {
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
...shared,
|
|
141
|
+
negative,
|
|
142
|
+
polarity: config.polarity ?? 'positive',
|
|
143
|
+
schema: null,
|
|
144
|
+
type: 'boolean',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
case 'count': {
|
|
148
|
+
return Object.freeze({ ...shared, schema: null, type: 'count' });
|
|
149
|
+
}
|
|
150
|
+
default: {
|
|
151
|
+
const exhaustive = config;
|
|
152
|
+
return exhaustive;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
164
155
|
}
|
|
165
156
|
/** The built slots already answer presence and arity, so the node repeats no config reading. */
|
|
166
157
|
function argumentNode(slot, read) {
|
|
@@ -188,7 +179,10 @@ function optionNodes(inputs, read) {
|
|
|
188
179
|
*/
|
|
189
180
|
function commandNode(command, place) {
|
|
190
181
|
const { development, nodes, path, records } = place;
|
|
191
|
-
const check = {
|
|
182
|
+
const check = {
|
|
183
|
+
development,
|
|
184
|
+
siteOf: (input) => declaringSite(input, inputPlace(input, path)),
|
|
185
|
+
};
|
|
192
186
|
const node = {
|
|
193
187
|
aliases: Object.freeze([...command.aliases]),
|
|
194
188
|
arguments: Object.freeze(command.arguments.map((slot) => argumentNode(slot, { check, records }))),
|
|
@@ -199,12 +193,7 @@ function commandNode(command, place) {
|
|
|
199
193
|
hasAction: command.dispatch !== undefined,
|
|
200
194
|
hidden: command.hidden,
|
|
201
195
|
name: command.name,
|
|
202
|
-
options: Object.freeze(optionNodes(command.inputs, {
|
|
203
|
-
check,
|
|
204
|
-
records,
|
|
205
|
-
scope: 'application',
|
|
206
|
-
table: command.options,
|
|
207
|
-
})),
|
|
196
|
+
options: Object.freeze(optionNodes(command.inputs, { check, records, table: command.table })),
|
|
208
197
|
path,
|
|
209
198
|
result: resultNode(command.result),
|
|
210
199
|
};
|
|
@@ -226,21 +215,19 @@ function resultNode(result) {
|
|
|
226
215
|
/**
|
|
227
216
|
* Renders one built graph as frozen plain data. Nothing here reads a host fact; each validated
|
|
228
217
|
* input's converter is asked for its input schema, and in a development build a converter that
|
|
229
|
-
* fails is a declaration fault. The globals list holds the application's own
|
|
230
|
-
* installed plugin's in installation order, which is the order the
|
|
218
|
+
* fails is a declaration fault. The globals list holds every global option, the application's own
|
|
219
|
+
* and then each installed plugin's in installation order, which is the order the table holds them.
|
|
231
220
|
*/
|
|
232
221
|
function inspectGraph(name, graph, facts) {
|
|
233
222
|
const { development } = facts;
|
|
234
223
|
const records = graph.extensions;
|
|
235
224
|
const table = graph.globals.options;
|
|
236
225
|
const nodes = new WeakMap();
|
|
237
|
-
const
|
|
226
|
+
const { sites } = graph.globals;
|
|
227
|
+
const check = { development, siteOf: (input) => sites.get(input) };
|
|
238
228
|
const inspected = {
|
|
239
229
|
description: facts.description,
|
|
240
|
-
globals: Object.freeze(
|
|
241
|
-
...optionNodes(graph.globals.inputs, { check, records, scope: 'application', table }),
|
|
242
|
-
...graph.globals.plugins.flatMap((installed) => optionNodes(installed.inputs, { check, records, scope: 'plugin', table })),
|
|
243
|
-
]),
|
|
230
|
+
globals: Object.freeze(optionNodes(graph.globals.inputs, { check, records, table })),
|
|
244
231
|
name,
|
|
245
232
|
root: commandNode(graph.root, {
|
|
246
233
|
description: facts.description,
|
package/dist/locate.js
CHANGED
|
@@ -1,48 +1,17 @@
|
|
|
1
|
-
import { argumentSlot, readsAsChild, route } from './command.js';
|
|
2
1
|
import { UsageError } from './errors.js';
|
|
3
2
|
import { graphMismatch, linkOf } from './inspect.js';
|
|
4
|
-
import {
|
|
3
|
+
import { argumentSlot, isOptionWord, readOptionWord, readWords, refusesValue } from './parse.js';
|
|
5
4
|
const none = Object.freeze({ kind: 'none' });
|
|
6
5
|
/**
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
6
|
+
* The words before the last, as the parser reads them, stopped before any validation. Every
|
|
7
|
+
* structural fault among them, an unknown Command included, reads as no position. When they run
|
|
8
|
+
* out at a Command with children, the last word may still continue routing, whatever it holds, so
|
|
9
|
+
* routing has not ended and a parent's own option among them stays unbound.
|
|
9
10
|
*/
|
|
10
|
-
function
|
|
11
|
-
const { globals, local, offset } = scans;
|
|
12
|
-
const byToken = Array.from({ length: count }, () => []);
|
|
13
|
-
for (const { name, token } of globals.supplied) {
|
|
14
|
-
byToken[token]?.push(name);
|
|
15
|
-
}
|
|
16
|
-
for (const { name, token } of local.supplied) {
|
|
17
|
-
byToken[globals.positions[token + offset] ?? count]?.push(name);
|
|
18
|
-
}
|
|
19
|
-
return byToken.flat();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* The parser's own pre-scan, routing, and local scan over the complete words. An earlier
|
|
23
|
-
* positional that no slot accepts is the unexpected-argument fault, so it reads as no position.
|
|
24
|
-
*/
|
|
25
|
-
function readStructure(graph, earlier) {
|
|
26
|
-
const globals = scanGlobals(graph.globals.options, earlier);
|
|
27
|
-
const routed = route(graph.root, globals.rest);
|
|
28
|
-
const local = scanInputs(routed.command.options, routed.tokens);
|
|
29
|
-
const positionals = local.positionals.length;
|
|
30
|
-
if (positionals > 0 && !argumentSlot(routed.command.arguments, positionals - 1)) {
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
awaiting: globals.awaiting ?? local.awaiting,
|
|
35
|
-
command: routed.command,
|
|
36
|
-
committed: routed.tokens.length > 0,
|
|
37
|
-
delimited: local.delimited,
|
|
38
|
-
positionals,
|
|
39
|
-
supplied: suppliedOrder(earlier.length, { globals, local, offset: routed.path.length }),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/** Every structural fault the grammar raises is a usage error, and it reads as no position. */
|
|
43
|
-
function readEarlier(graph, earlier) {
|
|
11
|
+
function readEarlier(graph, words) {
|
|
44
12
|
try {
|
|
45
|
-
|
|
13
|
+
const read = readWords(graph, words.slice(0, -1), { partial: true });
|
|
14
|
+
return read.fault === undefined ? read : undefined;
|
|
46
15
|
}
|
|
47
16
|
catch (error) {
|
|
48
17
|
if (error instanceof UsageError) {
|
|
@@ -61,35 +30,55 @@ function valueOf(scope, name, word) {
|
|
|
61
30
|
}
|
|
62
31
|
return { command, kind: 'value', lead: word.lead, option, prefix: word.prefix };
|
|
63
32
|
}
|
|
64
|
-
/** A long token with an inline value is that option's value when it names a string option. */
|
|
65
|
-
function inlineValue(scope, spelling, inline) {
|
|
66
|
-
const name = longStringOption(scope.built.globals.options, spelling) ??
|
|
67
|
-
longStringOption(scope.earlier.command.options, spelling);
|
|
68
|
-
return name === undefined ? none : valueOf(scope, name, { lead: `${spelling}=`, prefix: inline });
|
|
69
|
-
}
|
|
70
33
|
/** The word after a string option that ended the earlier words is that option's value. */
|
|
71
34
|
function awaitedValue(scope, awaiting, last) {
|
|
72
|
-
return
|
|
35
|
+
return refusesValue(last) ? none : valueOf(scope, awaiting.name, { lead: '', prefix: last });
|
|
73
36
|
}
|
|
74
|
-
/** A
|
|
75
|
-
function
|
|
37
|
+
/** A plain word names a child until routing ends, and fills the next positional after. */
|
|
38
|
+
function plainWord(scope, last) {
|
|
76
39
|
const { command, earlier } = scope;
|
|
77
|
-
if (!earlier.committed &&
|
|
40
|
+
if (!earlier.committed && earlier.command.children.size > 0) {
|
|
78
41
|
return { command, kind: 'command', prefix: last };
|
|
79
42
|
}
|
|
80
|
-
const slot = argumentSlot(earlier.command.arguments, earlier.positionals);
|
|
43
|
+
const slot = argumentSlot(earlier.command.arguments, earlier.positionals.length);
|
|
81
44
|
const argument = slot && command.arguments[earlier.command.arguments.indexOf(slot)];
|
|
82
45
|
return argument ? { argument, command, kind: 'argument', prefix: last } : none;
|
|
83
46
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
47
|
+
/**
|
|
48
|
+
* A word the walk reads against the routed Command's table and the earlier words' values, as the
|
|
49
|
+
* parser reads it: no position where the walk faults, a repeat of an earlier option included, the
|
|
50
|
+
* value a long spelling carries after `=` or a short value letter carries after it, and otherwise
|
|
51
|
+
* `undefined`, an option spelling.
|
|
52
|
+
*/
|
|
53
|
+
function carriedValue(scope, last) {
|
|
54
|
+
const { earlier } = scope;
|
|
55
|
+
const context = { table: earlier.command.table, values: earlier.values };
|
|
56
|
+
const { occurrences } = readOptionWord(context, last, undefined);
|
|
57
|
+
for (const occurrence of occurrences) {
|
|
58
|
+
if (occurrence.kind !== 'value' && occurrence.kind !== 'awaiting') {
|
|
59
|
+
return none;
|
|
60
|
+
}
|
|
61
|
+
if (occurrence.kind === 'value' && occurrence.lead !== undefined) {
|
|
62
|
+
const { lead, option } = occurrence;
|
|
63
|
+
return valueOf(scope, option.name, { lead, prefix: last.slice(lead.length) });
|
|
64
|
+
}
|
|
89
65
|
}
|
|
90
|
-
return
|
|
66
|
+
return undefined;
|
|
91
67
|
}
|
|
92
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* An option word. A long spelling without `=` is still being typed, so it is an option spelling
|
|
70
|
+
* whatever it names; any other word is read by the walk.
|
|
71
|
+
*/
|
|
72
|
+
function optionWord(scope, last) {
|
|
73
|
+
const typing = last.startsWith('--') && !last.includes('=');
|
|
74
|
+
return ((typing ? undefined : carriedValue(scope, last)) ?? {
|
|
75
|
+
command: scope.command,
|
|
76
|
+
kind: 'option',
|
|
77
|
+
prefix: last,
|
|
78
|
+
supplied: scope.earlier.supplied,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** The last word, read as the parser would read the next word. */
|
|
93
82
|
function lastWord(scope, last) {
|
|
94
83
|
const { command, earlier } = scope;
|
|
95
84
|
if (earlier.delimited) {
|
|
@@ -98,7 +87,10 @@ function lastWord(scope, last) {
|
|
|
98
87
|
if (earlier.awaiting) {
|
|
99
88
|
return awaitedValue(scope, earlier.awaiting, last);
|
|
100
89
|
}
|
|
101
|
-
|
|
90
|
+
if (last === '-' || last === '--') {
|
|
91
|
+
return { command, kind: 'option', prefix: last, supplied: earlier.supplied };
|
|
92
|
+
}
|
|
93
|
+
return isOptionWord(last) ? optionWord(scope, last) : plainWord(scope, last);
|
|
102
94
|
}
|
|
103
95
|
/**
|
|
104
96
|
* Reads an unfinished invocation against a graph `inspect()` returned and reports where its last
|
|
@@ -108,7 +100,7 @@ function lastWord(scope, last) {
|
|
|
108
100
|
*/
|
|
109
101
|
function locate(graph, words) {
|
|
110
102
|
const link = linkOf(graph);
|
|
111
|
-
const earlier = readEarlier(link.graph, words
|
|
103
|
+
const earlier = readEarlier(link.graph, words);
|
|
112
104
|
if (!earlier) {
|
|
113
105
|
return none;
|
|
114
106
|
}
|
|
@@ -116,6 +108,6 @@ function locate(graph, words) {
|
|
|
116
108
|
if (!command) {
|
|
117
109
|
throw graphMismatch('The routed command is not in the inspected graph.');
|
|
118
110
|
}
|
|
119
|
-
return lastWord({
|
|
111
|
+
return lastWord({ command, earlier, graph }, words.at(-1) ?? '');
|
|
120
112
|
}
|
|
121
113
|
export { locate };
|