@loomcli/core 0.7.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/dist/application.d.ts +11 -6
- package/dist/application.js +11 -5
- 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 +136 -170
- package/dist/errors.d.ts +22 -21
- package/dist/errors.js +38 -18
- package/dist/globals.d.ts +48 -22
- package/dist/globals.js +63 -22
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/input-rules.d.ts +16 -2
- package/dist/input-rules.js +40 -5
- package/dist/inspect.d.ts +35 -11
- package/dist/inspect.js +69 -66
- package/dist/locate.js +52 -60
- package/dist/options.d.ts +94 -83
- package/dist/options.js +298 -260
- package/dist/parse.d.ts +162 -0
- package/dist/parse.js +601 -0
- package/dist/plugin-rules.d.ts +2 -4
- package/dist/plugin-rules.js +3 -8
- package/dist/plugin.d.ts +26 -21
- package/dist/plugin.js +10 -45
- package/dist/sources.d.ts +18 -9
- package/dist/sources.js +46 -16
- package/dist/types.d.ts +68 -28
- package/dist/validation.d.ts +84 -31
- package/dist/validation.js +222 -110
- package/package.json +1 -1
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.
|
|
@@ -110,13 +128,19 @@ interface CommandGraph {
|
|
|
110
128
|
readonly globals: readonly OptionNode[];
|
|
111
129
|
readonly root: CommandNode;
|
|
112
130
|
}
|
|
131
|
+
/**
|
|
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.
|
|
135
|
+
*/
|
|
136
|
+
export declare function reportedSpelling(option: OptionNode): string;
|
|
113
137
|
/** The declared result as plain data, or `null` on a Command that declares none. */
|
|
114
138
|
declare function resultNode(result: DeclaredResult | undefined): ResultNode | null;
|
|
115
139
|
/**
|
|
116
140
|
* Renders one built graph as frozen plain data. Nothing here reads a host fact; each validated
|
|
117
141
|
* input's converter is asked for its input schema, and in a development build a converter that
|
|
118
|
-
* fails is a declaration fault. The globals list holds the application's own
|
|
119
|
-
* 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.
|
|
120
144
|
*/
|
|
121
145
|
declare function inspectGraph(name: string, graph: BuiltGraph, facts: {
|
|
122
146
|
description: string | undefined;
|
package/dist/inspect.js
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
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 { reportedOf, spellingsOf } from './options.js';
|
|
4
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
20
|
* A declared default is wrapped, so `default: undefined` reads apart from no default at all. The
|
|
@@ -49,11 +45,11 @@ function publishesSchema(schema) {
|
|
|
49
45
|
* the converter threw, the thrown value as its cause.
|
|
50
46
|
*/
|
|
51
47
|
function converterFault(input, check, failed) {
|
|
52
|
-
const site =
|
|
48
|
+
const site = check.siteOf(input);
|
|
53
49
|
const parts = {
|
|
54
50
|
correction: 'Fix the converter so it returns a JSON Schema object, or declare a validator that publishes none.',
|
|
55
|
-
findings: [siteFinding(site, partOf(site, 'validate'))],
|
|
56
|
-
sentence: `${
|
|
51
|
+
findings: site === undefined ? [] : [siteFinding(site, partOf(site, 'validate'))],
|
|
52
|
+
sentence: `${declarationSubject(input)} validator's JSON Schema converter ${failed.failure}`,
|
|
57
53
|
};
|
|
58
54
|
return 'cause' in failed
|
|
59
55
|
? new DeclarationError(schemaConverterFailed, parts, { cause: failed.cause })
|
|
@@ -104,47 +100,58 @@ function inputSchema(input, check) {
|
|
|
104
100
|
function extensionsOf(records, declaration) {
|
|
105
101
|
return records.get(declaration) ?? noExtensions;
|
|
106
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
|
+
*/
|
|
107
108
|
function optionNode(input, read) {
|
|
108
|
-
const { check, records,
|
|
109
|
+
const { check, records, table } = read;
|
|
109
110
|
const { config, name } = input;
|
|
110
111
|
const { long, negative, short } = spellingsOf(table, name);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
});
|
|
127
137
|
}
|
|
128
|
-
: {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
validated: config.validate !== undefined,
|
|
146
|
-
};
|
|
147
|
-
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
|
+
}
|
|
148
155
|
}
|
|
149
156
|
/** The built slots already answer presence and arity, so the node repeats no config reading. */
|
|
150
157
|
function argumentNode(slot, read) {
|
|
@@ -172,7 +179,10 @@ function optionNodes(inputs, read) {
|
|
|
172
179
|
*/
|
|
173
180
|
function commandNode(command, place) {
|
|
174
181
|
const { development, nodes, path, records } = place;
|
|
175
|
-
const check = {
|
|
182
|
+
const check = {
|
|
183
|
+
development,
|
|
184
|
+
siteOf: (input) => declaringSite(input, inputPlace(input, path)),
|
|
185
|
+
};
|
|
176
186
|
const node = {
|
|
177
187
|
aliases: Object.freeze([...command.aliases]),
|
|
178
188
|
arguments: Object.freeze(command.arguments.map((slot) => argumentNode(slot, { check, records }))),
|
|
@@ -183,12 +193,7 @@ function commandNode(command, place) {
|
|
|
183
193
|
hasAction: command.dispatch !== undefined,
|
|
184
194
|
hidden: command.hidden,
|
|
185
195
|
name: command.name,
|
|
186
|
-
options: Object.freeze(optionNodes(command.inputs, {
|
|
187
|
-
check,
|
|
188
|
-
records,
|
|
189
|
-
scope: 'application',
|
|
190
|
-
table: command.options,
|
|
191
|
-
})),
|
|
196
|
+
options: Object.freeze(optionNodes(command.inputs, { check, records, table: command.table })),
|
|
192
197
|
path,
|
|
193
198
|
result: resultNode(command.result),
|
|
194
199
|
};
|
|
@@ -210,21 +215,19 @@ function resultNode(result) {
|
|
|
210
215
|
/**
|
|
211
216
|
* Renders one built graph as frozen plain data. Nothing here reads a host fact; each validated
|
|
212
217
|
* input's converter is asked for its input schema, and in a development build a converter that
|
|
213
|
-
* fails is a declaration fault. The globals list holds the application's own
|
|
214
|
-
* 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.
|
|
215
220
|
*/
|
|
216
221
|
function inspectGraph(name, graph, facts) {
|
|
217
222
|
const { development } = facts;
|
|
218
223
|
const records = graph.extensions;
|
|
219
224
|
const table = graph.globals.options;
|
|
220
225
|
const nodes = new WeakMap();
|
|
221
|
-
const
|
|
226
|
+
const { sites } = graph.globals;
|
|
227
|
+
const check = { development, siteOf: (input) => sites.get(input) };
|
|
222
228
|
const inspected = {
|
|
223
229
|
description: facts.description,
|
|
224
|
-
globals: Object.freeze(
|
|
225
|
-
...optionNodes(graph.globals.inputs, { check, records, scope: 'application', table }),
|
|
226
|
-
...graph.globals.plugins.flatMap((installed) => optionNodes(installed.inputs, { check, records, scope: 'plugin', table })),
|
|
227
|
-
]),
|
|
230
|
+
globals: Object.freeze(optionNodes(graph.globals.inputs, { check, records, table })),
|
|
228
231
|
name,
|
|
229
232
|
root: commandNode(graph.root, {
|
|
230
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 };
|