patram 0.0.2 → 0.2.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/bin/patram.js +25 -147
- package/lib/build-graph-identity.js +270 -0
- package/lib/build-graph.js +156 -77
- package/lib/check-graph.js +23 -7
- package/lib/claim-helpers.js +55 -0
- package/lib/cli-help-metadata.js +552 -0
- package/lib/command-output.js +83 -0
- package/lib/derived-summary.js +278 -0
- package/lib/format-derived-summary-row.js +9 -0
- package/lib/format-node-header.js +19 -0
- package/lib/format-output-item-block.js +22 -0
- package/lib/format-output-metadata.js +62 -0
- package/lib/layout-stored-queries.js +361 -0
- package/lib/list-queries.js +18 -0
- package/lib/list-source-files.js +50 -15
- package/lib/load-patram-config.js +505 -18
- package/lib/load-patram-config.types.ts +40 -0
- package/lib/load-project-graph.js +124 -0
- package/lib/output-view.types.ts +88 -0
- package/lib/parse-claims.js +38 -158
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-cli-arguments-helpers.js +446 -0
- package/lib/parse-cli-arguments.js +266 -0
- package/lib/parse-cli-arguments.types.ts +69 -0
- package/lib/parse-cli-color-options.js +44 -0
- package/lib/parse-cli-query-pagination.js +49 -0
- package/lib/parse-jsdoc-blocks.js +184 -0
- package/lib/parse-jsdoc-claims.js +280 -0
- package/lib/parse-jsdoc-prose.js +111 -0
- package/lib/parse-markdown-claims.js +242 -0
- package/lib/parse-markdown-directives.js +136 -0
- package/lib/parse-where-clause.js +707 -0
- package/lib/parse-where-clause.types.ts +70 -0
- package/lib/patram-cli.js +464 -0
- package/lib/patram-config.js +3 -1
- package/lib/patram-config.types.ts +2 -1
- package/lib/patram.js +6 -0
- package/lib/query-graph.js +368 -0
- package/lib/query-inspection.js +523 -0
- package/lib/render-check-output.js +315 -0
- package/lib/render-cli-help.js +419 -0
- package/lib/render-json-output.js +161 -0
- package/lib/render-output-view.js +222 -0
- package/lib/render-plain-output.js +182 -0
- package/lib/render-rich-output.js +240 -0
- package/lib/render-rich-source.js +1333 -0
- package/lib/resolve-check-target.js +190 -0
- package/lib/resolve-output-mode.js +60 -0
- package/lib/resolve-patram-graph-config.js +88 -0
- package/lib/resolve-where-clause.js +66 -0
- package/lib/show-document.js +311 -0
- package/lib/source-file-defaults.js +28 -0
- package/lib/tagged-fenced-block-error.js +17 -0
- package/lib/tagged-fenced-block-markdown.js +111 -0
- package/lib/tagged-fenced-block-metadata.js +97 -0
- package/lib/tagged-fenced-block-parser.js +292 -0
- package/lib/tagged-fenced-blocks.js +100 -0
- package/lib/tagged-fenced-blocks.types.ts +38 -0
- package/lib/write-paged-output.js +87 -0
- package/package.json +28 -12
- package/bin/patram.test.js +0 -184
- package/lib/build-graph.test.js +0 -141
- package/lib/check-graph.test.js +0 -103
- package/lib/list-source-files.test.js +0 -101
- package/lib/load-patram-config.test.js +0 -211
- package/lib/parse-claims.test.js +0 -113
- package/lib/patram-config.test.js +0 -147
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {import('./parse-cli-arguments.types.ts').CliCommandName} CliCommandName
|
|
4
|
+
* @typedef {import('./parse-cli-arguments.types.ts').CliOutputMode} CliOutputMode
|
|
5
|
+
* @typedef {import('./parse-cli-arguments.types.ts').CliParseError} CliParseError
|
|
6
|
+
* @typedef {import('./parse-cli-arguments.types.ts').ParsedCliHelpRequest} ParsedCliHelpRequest
|
|
7
|
+
* @typedef {{ kind: string, name?: string, rawName?: string, value?: string | boolean }} CliOptionToken
|
|
8
|
+
* @typedef {{ color?: string, explain?: boolean, help?: boolean, json?: boolean, limit?: string, lint?: boolean, 'no-color'?: boolean, offset?: string, plain?: boolean, where?: string }} CliOptionValues
|
|
9
|
+
* @typedef {{ option_tokens: CliOptionToken[], positionals: string[], values: CliOptionValues }} ParsedCommandLine
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
findCommandSuggestion,
|
|
14
|
+
findHelpTargetSuggestion,
|
|
15
|
+
findOptionSuggestion,
|
|
16
|
+
getCommandDefinition,
|
|
17
|
+
GLOBAL_OPTION_NAMES,
|
|
18
|
+
isCommandName,
|
|
19
|
+
isHelpTopicName,
|
|
20
|
+
} from './cli-help-metadata.js';
|
|
21
|
+
import { findInvalidColorMode } from './parse-cli-color-options.js';
|
|
22
|
+
import { findInvalidQueryPagination } from './parse-cli-query-pagination.js';
|
|
23
|
+
|
|
24
|
+
export const CLI_OPTIONS = /** @type {const} */ ({
|
|
25
|
+
color: { type: 'string' },
|
|
26
|
+
explain: { type: 'boolean' },
|
|
27
|
+
help: { type: 'boolean' },
|
|
28
|
+
json: { type: 'boolean' },
|
|
29
|
+
limit: { type: 'string' },
|
|
30
|
+
lint: { type: 'boolean' },
|
|
31
|
+
'no-color': { type: 'boolean' },
|
|
32
|
+
offset: { type: 'string' },
|
|
33
|
+
plain: { type: 'boolean' },
|
|
34
|
+
where: { type: 'string' },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {CliOptionToken[]} tokens
|
|
39
|
+
* @returns {CliOptionToken[]}
|
|
40
|
+
*/
|
|
41
|
+
export function collectOptionTokens(tokens) {
|
|
42
|
+
return tokens.filter((token) => token.kind === 'option');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {ParsedCommandLine} command_line
|
|
47
|
+
* @returns {CliParseError | null}
|
|
48
|
+
*/
|
|
49
|
+
export function validateRootCommandLine(command_line) {
|
|
50
|
+
return (
|
|
51
|
+
findUnknownOption(undefined, command_line.option_tokens) ??
|
|
52
|
+
findMissingOptionValue(command_line.option_tokens) ??
|
|
53
|
+
findOutputModeConflict(command_line.values) ??
|
|
54
|
+
createMessageParseError(findInvalidColorMode(command_line.option_tokens))
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {ParsedCommandLine} command_line
|
|
60
|
+
* @returns {CliParseError | null}
|
|
61
|
+
*/
|
|
62
|
+
export function validateHelpCommandLine(command_line) {
|
|
63
|
+
const help_validation_error = validateRootCommandLine(command_line);
|
|
64
|
+
|
|
65
|
+
if (help_validation_error) {
|
|
66
|
+
return help_validation_error;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (command_line.positionals.length > 2) {
|
|
70
|
+
return createMessageParseError(
|
|
71
|
+
'Help accepts at most one topic or command.',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {CliCommandName} command_name
|
|
80
|
+
* @param {ParsedCommandLine} command_line
|
|
81
|
+
* @returns {CliParseError | null}
|
|
82
|
+
*/
|
|
83
|
+
export function validateCommandLineBeforeHelp(command_name, command_line) {
|
|
84
|
+
return (
|
|
85
|
+
findUnknownOption(command_name, command_line.option_tokens) ??
|
|
86
|
+
findMissingOptionValue(command_line.option_tokens) ??
|
|
87
|
+
findOutputModeConflict(command_line.values) ??
|
|
88
|
+
createMessageParseError(findInvalidColorMode(command_line.option_tokens))
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {CliCommandName} command_name
|
|
94
|
+
* @param {ParsedCommandLine} command_line
|
|
95
|
+
* @returns {CliParseError | null}
|
|
96
|
+
*/
|
|
97
|
+
export function validateParsedCommand(command_name, command_line) {
|
|
98
|
+
const command_positionals = command_line.positionals.slice(1);
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
command_name === 'query' &&
|
|
102
|
+
command_line.values.where !== undefined &&
|
|
103
|
+
command_positionals.length === 0
|
|
104
|
+
) {
|
|
105
|
+
return (
|
|
106
|
+
createMessageParseError(
|
|
107
|
+
findInvalidQueryPagination(command_line.option_tokens),
|
|
108
|
+
) ??
|
|
109
|
+
findInvalidQueryMode(
|
|
110
|
+
command_name,
|
|
111
|
+
command_line.values,
|
|
112
|
+
command_positionals,
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
findInvalidCommandOption(command_name, command_line.option_tokens) ??
|
|
119
|
+
createMessageParseError(
|
|
120
|
+
findInvalidQueryPagination(command_line.option_tokens),
|
|
121
|
+
) ??
|
|
122
|
+
findInvalidQueryInspection(command_name, command_line.values) ??
|
|
123
|
+
findInvalidQueryMode(
|
|
124
|
+
command_name,
|
|
125
|
+
command_line.values,
|
|
126
|
+
command_positionals,
|
|
127
|
+
) ??
|
|
128
|
+
validateCommandPositionals(command_name, command_positionals)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {CliOptionValues} parsed_values
|
|
134
|
+
* @returns {CliOutputMode}
|
|
135
|
+
*/
|
|
136
|
+
export function resolveOutputMode(parsed_values) {
|
|
137
|
+
if (parsed_values.json) {
|
|
138
|
+
return 'json';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (parsed_values.plain) {
|
|
142
|
+
return 'plain';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return 'default';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {CliCommandName} command_name
|
|
150
|
+
* @param {string[]} command_positionals
|
|
151
|
+
* @param {CliOptionValues} parsed_values
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
export function buildCommandArguments(
|
|
155
|
+
command_name,
|
|
156
|
+
command_positionals,
|
|
157
|
+
parsed_values,
|
|
158
|
+
) {
|
|
159
|
+
if (command_name === 'query' && parsed_values.where !== undefined) {
|
|
160
|
+
return ['--where', parsed_values.where];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return command_positionals;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param {string | undefined} help_target
|
|
168
|
+
* @returns {CliParseError}
|
|
169
|
+
*/
|
|
170
|
+
export function createUnknownHelpTargetError(help_target) {
|
|
171
|
+
return {
|
|
172
|
+
code: 'unknown_help_target',
|
|
173
|
+
suggestion: help_target ? findHelpTargetSuggestion(help_target) : undefined,
|
|
174
|
+
token: help_target ?? '',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {string | undefined} command_name
|
|
180
|
+
* @returns {CliParseError}
|
|
181
|
+
*/
|
|
182
|
+
export function createUnknownCommandError(command_name) {
|
|
183
|
+
return {
|
|
184
|
+
code: 'unknown_command',
|
|
185
|
+
suggestion:
|
|
186
|
+
command_name && command_name.length > 0
|
|
187
|
+
? findCommandSuggestion(command_name)
|
|
188
|
+
: undefined,
|
|
189
|
+
token: command_name ?? '',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {CliCommandName} command_name
|
|
195
|
+
* @returns {ParsedCliHelpRequest}
|
|
196
|
+
*/
|
|
197
|
+
export function createCommandHelpRequest(command_name) {
|
|
198
|
+
return {
|
|
199
|
+
kind: 'help',
|
|
200
|
+
target_kind: 'command',
|
|
201
|
+
target_name: command_name,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {string | undefined} help_target
|
|
207
|
+
* @returns {ParsedCliHelpRequest | null}
|
|
208
|
+
*/
|
|
209
|
+
export function createNamedHelpRequest(help_target) {
|
|
210
|
+
if (isCommandName(help_target)) {
|
|
211
|
+
return createCommandHelpRequest(help_target);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isHelpTopicName(help_target)) {
|
|
215
|
+
return {
|
|
216
|
+
kind: 'help',
|
|
217
|
+
target_kind: 'topic',
|
|
218
|
+
target_name: help_target,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @returns {ParsedCliHelpRequest}
|
|
227
|
+
*/
|
|
228
|
+
export function createRootHelpRequest() {
|
|
229
|
+
return {
|
|
230
|
+
kind: 'help',
|
|
231
|
+
target_kind: 'root',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @param {string | null} message
|
|
237
|
+
* @returns {CliParseError | null}
|
|
238
|
+
*/
|
|
239
|
+
export function createMessageParseError(message) {
|
|
240
|
+
if (!message) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
code: 'message',
|
|
246
|
+
message,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {CliCommandName | undefined} command_name
|
|
252
|
+
* @param {CliOptionToken[]} option_tokens
|
|
253
|
+
* @returns {CliParseError | null}
|
|
254
|
+
*/
|
|
255
|
+
function findUnknownOption(command_name, option_tokens) {
|
|
256
|
+
for (const token of option_tokens) {
|
|
257
|
+
if (
|
|
258
|
+
token.name &&
|
|
259
|
+
token.rawName &&
|
|
260
|
+
!GLOBAL_OPTION_NAMES.has(token.name) &&
|
|
261
|
+
!isKnownCommandOptionName(token.name)
|
|
262
|
+
) {
|
|
263
|
+
return {
|
|
264
|
+
code: 'unknown_option',
|
|
265
|
+
command_name,
|
|
266
|
+
suggestion: findOptionSuggestion(token.rawName, command_name),
|
|
267
|
+
token: token.rawName,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {CliCommandName} command_name
|
|
277
|
+
* @param {CliOptionToken[]} option_tokens
|
|
278
|
+
* @returns {CliParseError | null}
|
|
279
|
+
*/
|
|
280
|
+
function findInvalidCommandOption(command_name, option_tokens) {
|
|
281
|
+
const command_definition = getCommandDefinition(command_name);
|
|
282
|
+
|
|
283
|
+
for (const token of option_tokens) {
|
|
284
|
+
if (!token.name || !token.rawName || GLOBAL_OPTION_NAMES.has(token.name)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!command_definition.allowed_option_names.has(token.name)) {
|
|
289
|
+
return {
|
|
290
|
+
code: 'option_not_valid_for_command',
|
|
291
|
+
command_name,
|
|
292
|
+
token: token.rawName,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {CliOptionToken[]} option_tokens
|
|
302
|
+
* @returns {CliParseError | null}
|
|
303
|
+
*/
|
|
304
|
+
function findMissingOptionValue(option_tokens) {
|
|
305
|
+
for (const token of option_tokens) {
|
|
306
|
+
if (token.name === 'where' && typeof token.value !== 'string') {
|
|
307
|
+
return {
|
|
308
|
+
argument_label: '<name> or --where "<clause>"',
|
|
309
|
+
code: 'missing_required_argument',
|
|
310
|
+
command_name: 'query',
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (token.name === 'offset' && typeof token.value !== 'string') {
|
|
315
|
+
return createMessageParseError('Offset requires a value.');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (token.name === 'limit' && typeof token.value !== 'string') {
|
|
319
|
+
return createMessageParseError('Limit requires a value.');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (token.name === 'color' && typeof token.value !== 'string') {
|
|
323
|
+
return createMessageParseError('Color requires a value.');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @param {CliOptionValues} parsed_values
|
|
332
|
+
* @returns {CliParseError | null}
|
|
333
|
+
*/
|
|
334
|
+
function findOutputModeConflict(parsed_values) {
|
|
335
|
+
if (parsed_values.plain && parsed_values.json) {
|
|
336
|
+
return createMessageParseError(
|
|
337
|
+
'Output mode accepts at most one of "--plain" or "--json".',
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @param {CliCommandName} command_name
|
|
346
|
+
* @param {CliOptionValues} parsed_values
|
|
347
|
+
* @param {string[]} command_positionals
|
|
348
|
+
* @returns {CliParseError | null}
|
|
349
|
+
*/
|
|
350
|
+
function findInvalidQueryMode(
|
|
351
|
+
command_name,
|
|
352
|
+
parsed_values,
|
|
353
|
+
command_positionals,
|
|
354
|
+
) {
|
|
355
|
+
if (
|
|
356
|
+
command_name === 'query' &&
|
|
357
|
+
parsed_values.where !== undefined &&
|
|
358
|
+
command_positionals.length > 0
|
|
359
|
+
) {
|
|
360
|
+
return createMessageParseError(
|
|
361
|
+
'Query accepts either "--where" or a stored query name.',
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* @param {CliCommandName} command_name
|
|
370
|
+
* @param {CliOptionValues} parsed_values
|
|
371
|
+
* @returns {CliParseError | null}
|
|
372
|
+
*/
|
|
373
|
+
function findInvalidQueryInspection(command_name, parsed_values) {
|
|
374
|
+
if (command_name !== 'query') {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (parsed_values.explain && parsed_values.lint) {
|
|
379
|
+
return createMessageParseError(
|
|
380
|
+
'Query accepts at most one of "--explain" or "--lint".',
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @param {CliCommandName} command_name
|
|
389
|
+
* @param {string[]} command_positionals
|
|
390
|
+
* @returns {CliParseError | null}
|
|
391
|
+
*/
|
|
392
|
+
function validateCommandPositionals(command_name, command_positionals) {
|
|
393
|
+
const command_definition = getCommandDefinition(command_name);
|
|
394
|
+
|
|
395
|
+
if (command_positionals.length < command_definition.min_positionals) {
|
|
396
|
+
if (command_name === 'query' && command_definition.missing_argument_label) {
|
|
397
|
+
return {
|
|
398
|
+
argument_label: command_definition.missing_argument_label,
|
|
399
|
+
code: 'missing_required_argument',
|
|
400
|
+
command_name: 'query',
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (command_name === 'show' && command_definition.missing_argument_label) {
|
|
405
|
+
return {
|
|
406
|
+
argument_label: command_definition.missing_argument_label,
|
|
407
|
+
code: 'missing_required_argument',
|
|
408
|
+
command_name: 'show',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return createMessageParseError(
|
|
413
|
+
command_definition.extra_positionals_message,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (command_positionals.length > command_definition.max_positionals) {
|
|
418
|
+
return createMessageParseError(
|
|
419
|
+
command_definition.extra_positionals_message,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (command_name === 'query' && command_positionals.length === 0) {
|
|
424
|
+
return {
|
|
425
|
+
argument_label: '<name> or --where "<clause>"',
|
|
426
|
+
code: 'missing_required_argument',
|
|
427
|
+
command_name: 'query',
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* @param {string} option_name
|
|
436
|
+
* @returns {boolean}
|
|
437
|
+
*/
|
|
438
|
+
function isKnownCommandOptionName(option_name) {
|
|
439
|
+
return (
|
|
440
|
+
option_name === 'explain' ||
|
|
441
|
+
option_name === 'limit' ||
|
|
442
|
+
option_name === 'lint' ||
|
|
443
|
+
option_name === 'offset' ||
|
|
444
|
+
option_name === 'where'
|
|
445
|
+
);
|
|
446
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function */
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {import('./parse-cli-arguments-helpers.js').CliOptionValues} CliOptionValues
|
|
4
|
+
* @typedef {import('./parse-cli-arguments-helpers.js').ParsedCommandLine} ParsedCommandLine
|
|
5
|
+
* @typedef {import('./parse-cli-arguments.types.ts').CliParseError} CliParseError
|
|
6
|
+
* @typedef {import('./parse-cli-arguments.types.ts').ParseCliArgumentsResult} ParseCliArgumentsResult
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parseArgs } from 'node:util';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
CLI_OPTIONS,
|
|
13
|
+
buildCommandArguments,
|
|
14
|
+
collectOptionTokens,
|
|
15
|
+
createCommandHelpRequest,
|
|
16
|
+
createMessageParseError,
|
|
17
|
+
createNamedHelpRequest,
|
|
18
|
+
createRootHelpRequest,
|
|
19
|
+
createUnknownCommandError,
|
|
20
|
+
createUnknownHelpTargetError,
|
|
21
|
+
resolveOutputMode,
|
|
22
|
+
validateCommandLineBeforeHelp,
|
|
23
|
+
validateHelpCommandLine,
|
|
24
|
+
validateParsedCommand,
|
|
25
|
+
validateRootCommandLine,
|
|
26
|
+
} from './parse-cli-arguments-helpers.js';
|
|
27
|
+
import { isCommandName } from './cli-help-metadata.js';
|
|
28
|
+
import { resolveColorMode } from './parse-cli-color-options.js';
|
|
29
|
+
import { buildQueryPagination } from './parse-cli-query-pagination.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* CLI argument parsing.
|
|
33
|
+
*
|
|
34
|
+
* Normalizes raw argv into one validated Patram command plus shared output and
|
|
35
|
+
* pagination options.
|
|
36
|
+
*
|
|
37
|
+
* Kind: cli
|
|
38
|
+
* Status: active
|
|
39
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
40
|
+
* Decided by: ../docs/decisions/cli-argument-parser.md
|
|
41
|
+
* @patram
|
|
42
|
+
* @see {@link ./patram-cli.js}
|
|
43
|
+
* @see {@link ../docs/decisions/cli-argument-parser.md}
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse the CLI arguments into one validated command result.
|
|
48
|
+
*
|
|
49
|
+
* @param {string[]} cli_arguments
|
|
50
|
+
* @returns {ParseCliArgumentsResult}
|
|
51
|
+
*/
|
|
52
|
+
export function parseCliArguments(cli_arguments) {
|
|
53
|
+
const command_line = parseCommandLine(cli_arguments);
|
|
54
|
+
|
|
55
|
+
if (!command_line.success) {
|
|
56
|
+
return command_line;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const root_request = resolveRootHelpRequest(command_line.value);
|
|
60
|
+
|
|
61
|
+
if (root_request) {
|
|
62
|
+
return root_request;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const help_request = resolveNamedHelpRequest(command_line.value);
|
|
66
|
+
|
|
67
|
+
if (help_request) {
|
|
68
|
+
return help_request;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const command_name = command_line.value.positionals[0];
|
|
72
|
+
|
|
73
|
+
if (!isCommandName(command_name)) {
|
|
74
|
+
return {
|
|
75
|
+
error: createUnknownCommandError(command_name),
|
|
76
|
+
success: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const pre_help_error = validateCommandLineBeforeHelp(
|
|
81
|
+
command_name,
|
|
82
|
+
command_line.value,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (pre_help_error) {
|
|
86
|
+
return {
|
|
87
|
+
error: pre_help_error,
|
|
88
|
+
success: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (command_line.value.values.help) {
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
value: createCommandHelpRequest(command_name),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const validation_error = validateParsedCommand(
|
|
100
|
+
command_name,
|
|
101
|
+
command_line.value,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (validation_error) {
|
|
105
|
+
return {
|
|
106
|
+
error: validation_error,
|
|
107
|
+
success: false,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const command_positionals = command_line.value.positionals.slice(1);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
value: {
|
|
116
|
+
kind: 'command',
|
|
117
|
+
color_mode: resolveColorMode(command_line.value.option_tokens),
|
|
118
|
+
command_arguments: buildCommandArguments(
|
|
119
|
+
command_name,
|
|
120
|
+
command_positionals,
|
|
121
|
+
command_line.value.values,
|
|
122
|
+
),
|
|
123
|
+
command_name,
|
|
124
|
+
output_mode: resolveOutputMode(command_line.value.values),
|
|
125
|
+
...buildQueryInspection(command_line.value),
|
|
126
|
+
...buildQueryPagination(command_line.value.values),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {string[]} cli_arguments
|
|
133
|
+
* @returns {{ success: true, value: ParsedCommandLine } | { error: CliParseError, success: false }}
|
|
134
|
+
*/
|
|
135
|
+
function parseCommandLine(cli_arguments) {
|
|
136
|
+
try {
|
|
137
|
+
const parsed_arguments = parseArgs({
|
|
138
|
+
allowPositionals: true,
|
|
139
|
+
args: cli_arguments,
|
|
140
|
+
options: CLI_OPTIONS,
|
|
141
|
+
strict: false,
|
|
142
|
+
tokens: true,
|
|
143
|
+
});
|
|
144
|
+
const parsed_values = /** @type {CliOptionValues} */ (
|
|
145
|
+
parsed_arguments.values
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
value: {
|
|
151
|
+
option_tokens: collectOptionTokens(parsed_arguments.tokens ?? []),
|
|
152
|
+
positionals: parsed_arguments.positionals,
|
|
153
|
+
values: parsed_values,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error instanceof Error) {
|
|
158
|
+
return {
|
|
159
|
+
error: /** @type {CliParseError} */ (
|
|
160
|
+
createMessageParseError(error.message)
|
|
161
|
+
),
|
|
162
|
+
success: false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {ParsedCommandLine} command_line
|
|
172
|
+
* @returns {ParseCliArgumentsResult | null}
|
|
173
|
+
*/
|
|
174
|
+
function resolveNamedHelpRequest(command_line) {
|
|
175
|
+
if (command_line.positionals[0] !== 'help') {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const validation_error = validateHelpCommandLine(command_line);
|
|
180
|
+
|
|
181
|
+
if (validation_error) {
|
|
182
|
+
return {
|
|
183
|
+
error: validation_error,
|
|
184
|
+
success: false,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const help_target = command_line.positionals[1];
|
|
189
|
+
|
|
190
|
+
if (!help_target) {
|
|
191
|
+
return {
|
|
192
|
+
success: true,
|
|
193
|
+
value: createRootHelpRequest(),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const help_request = createNamedHelpRequest(help_target);
|
|
198
|
+
|
|
199
|
+
if (help_request) {
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
value: help_request,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
error: createUnknownHelpTargetError(help_target),
|
|
208
|
+
success: false,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {ParsedCommandLine} command_line
|
|
214
|
+
* @returns {ParseCliArgumentsResult | null}
|
|
215
|
+
*/
|
|
216
|
+
function resolveRootHelpRequest(command_line) {
|
|
217
|
+
if (command_line.positionals.length > 0) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const validation_error = validateRootCommandLine(command_line);
|
|
222
|
+
|
|
223
|
+
if (validation_error) {
|
|
224
|
+
return {
|
|
225
|
+
error: validation_error,
|
|
226
|
+
success: false,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
success: true,
|
|
232
|
+
value: createRootHelpRequest(),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @param {ParsedCommandLine} command_line
|
|
238
|
+
* @returns {'explain' | 'lint' | undefined}
|
|
239
|
+
*/
|
|
240
|
+
function resolveQueryInspectionMode(command_line) {
|
|
241
|
+
if (command_line.values.explain) {
|
|
242
|
+
return 'explain';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (command_line.values.lint) {
|
|
246
|
+
return 'lint';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {ParsedCommandLine} command_line
|
|
254
|
+
* @returns {{ query_inspection_mode?: 'explain' | 'lint' }}
|
|
255
|
+
*/
|
|
256
|
+
function buildQueryInspection(command_line) {
|
|
257
|
+
const query_inspection_mode = resolveQueryInspectionMode(command_line);
|
|
258
|
+
|
|
259
|
+
if (!query_inspection_mode) {
|
|
260
|
+
return {};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
query_inspection_mode,
|
|
265
|
+
};
|
|
266
|
+
}
|