@augment-vir/node 31.73.3 → 31.73.4
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/augments/fs/grep.d.ts +2 -2
- package/dist/augments/fs/grep.js +107 -559
- package/dist/augments/fs/read-dir.js +3 -9
- package/dist/augments/path/contains.d.ts +7 -11
- package/dist/augments/path/contains.js +1 -1
- package/dist/augments/path/resolve-import.d.ts +1 -4
- package/dist/augments/path/resolve-import.js +3 -7
- package/dist/augments/terminal/run-cli-script.d.ts +8 -5
- package/dist/augments/terminal/run-cli-script.js +8 -1
- package/dist/augments/terminal/shell.d.ts +11 -20
- package/dist/augments/terminal/shell.js +4 -10
- package/package.json +6 -6
- package/src/augments/fs/grep.ts +135 -810
- package/src/augments/fs/read-dir.ts +3 -12
- package/src/augments/path/contains.ts +6 -10
- package/src/augments/path/resolve-import.ts +10 -14
- package/src/augments/terminal/run-cli-script.ts +10 -5
- package/src/augments/terminal/shell.ts +27 -47
package/dist/augments/fs/grep.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { assert, check } from '@augment-vir/assert';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { lstat, readdir, stat } from 'node:fs/promises';
|
|
5
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
2
|
+
import { arrayToObject, getOrSet, log, safeMatch, } from '@augment-vir/common';
|
|
3
|
+
import { join } from 'node:path';
|
|
6
4
|
import { isOperatingSystem, OperatingSystem } from '../os/operating-system.js';
|
|
7
|
-
|
|
8
|
-
function
|
|
9
|
-
return
|
|
10
|
-
"'",
|
|
11
|
-
input.replaceAll("'", String.raw `'\''`),
|
|
12
|
-
"'",
|
|
13
|
-
].join('');
|
|
5
|
+
import { runShellCommand } from '../terminal/shell.js';
|
|
6
|
+
function escape(input) {
|
|
7
|
+
return input.replaceAll('"', String.raw `\"`).replaceAll('\n', '');
|
|
14
8
|
}
|
|
15
9
|
function recursiveFlag({ recursive, followSymLinks, }) {
|
|
16
10
|
if (!recursive) {
|
|
@@ -27,484 +21,6 @@ function recursiveFlag({ recursive, followSymLinks, }) {
|
|
|
27
21
|
/* node:coverage ignore next */
|
|
28
22
|
return isOperatingSystem(OperatingSystem.Mac) ? '-RS' : '-R';
|
|
29
23
|
}
|
|
30
|
-
function isValidMaxCount(maxCount) {
|
|
31
|
-
return (maxCount == undefined ||
|
|
32
|
-
(check.isNumber(maxCount) && Number.isInteger(maxCount) && maxCount >= -1));
|
|
33
|
-
}
|
|
34
|
-
function isOptionalBoolean(input) {
|
|
35
|
-
return input == undefined || check.isBoolean(input);
|
|
36
|
-
}
|
|
37
|
-
function isValidTrueOnlyOptionGroup({ input, values, }) {
|
|
38
|
-
return (input == undefined ||
|
|
39
|
-
(check.isObject(input) &&
|
|
40
|
-
values.every((value) => value == undefined || value === true) &&
|
|
41
|
-
values.filter((value) => value === true).length === 1));
|
|
42
|
-
}
|
|
43
|
-
function isValidPatternSyntax(input) {
|
|
44
|
-
return isValidTrueOnlyOptionGroup({
|
|
45
|
-
input,
|
|
46
|
-
values: check.isObject(input)
|
|
47
|
-
? [
|
|
48
|
-
input.basicRegExp,
|
|
49
|
-
input.extendedRegExp,
|
|
50
|
-
input.fixedStrings,
|
|
51
|
-
]
|
|
52
|
-
: [],
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function isValidMatchType(input) {
|
|
56
|
-
return isValidTrueOnlyOptionGroup({
|
|
57
|
-
input,
|
|
58
|
-
values: check.isObject(input)
|
|
59
|
-
? [
|
|
60
|
-
input.lineRegExp,
|
|
61
|
-
input.wordRegExp,
|
|
62
|
-
]
|
|
63
|
-
: [],
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function isValidOutput(input) {
|
|
67
|
-
return isValidTrueOnlyOptionGroup({
|
|
68
|
-
input,
|
|
69
|
-
values: check.isObject(input)
|
|
70
|
-
? [
|
|
71
|
-
input.countOnly,
|
|
72
|
-
input.filesOnly,
|
|
73
|
-
]
|
|
74
|
-
: [],
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function didGrepFail({ exitCode }) {
|
|
78
|
-
return exitCode == undefined || exitCode > 1;
|
|
79
|
-
}
|
|
80
|
-
function spawnGrepProcess({ args, cwd, }) {
|
|
81
|
-
try {
|
|
82
|
-
return spawn(grepBinPath, args, {
|
|
83
|
-
cwd,
|
|
84
|
-
stdio: [
|
|
85
|
-
'ignore',
|
|
86
|
-
'pipe',
|
|
87
|
-
'pipe',
|
|
88
|
-
],
|
|
89
|
-
});
|
|
90
|
-
/* node:coverage ignore next 3 */
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async function runGrepCommand({ args, cwd, }) {
|
|
97
|
-
return new Promise((resolveOutput) => {
|
|
98
|
-
const stdoutChunks = [];
|
|
99
|
-
const grepProcess = spawnGrepProcess({
|
|
100
|
-
args,
|
|
101
|
-
cwd,
|
|
102
|
-
});
|
|
103
|
-
if (!grepProcess) {
|
|
104
|
-
resolveOutput({
|
|
105
|
-
exitCode: undefined,
|
|
106
|
-
stdout: '',
|
|
107
|
-
});
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
assert.isDefined(grepProcess.stdout, 'stdout emitter was not created for grep.');
|
|
111
|
-
assert.isDefined(grepProcess.stderr, 'stderr emitter was not created for grep.');
|
|
112
|
-
grepProcess.stdout.on('data', (chunk) => {
|
|
113
|
-
stdoutChunks.push(Buffer.from(chunk));
|
|
114
|
-
});
|
|
115
|
-
grepProcess.stderr.on('data', () => { });
|
|
116
|
-
/* node:coverage ignore next 5 */
|
|
117
|
-
grepProcess.on('error', () => {
|
|
118
|
-
resolveOutput({
|
|
119
|
-
exitCode: undefined,
|
|
120
|
-
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
grepProcess.on('close', (rawExitCode) => {
|
|
124
|
-
resolveOutput({
|
|
125
|
-
/* node:coverage ignore next */
|
|
126
|
-
exitCode: rawExitCode ?? undefined,
|
|
127
|
-
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
function redactGrepArgForLogging({ arg, index, operandDelimiterIndex, previousArg, }) {
|
|
133
|
-
if (previousArg === '-e') {
|
|
134
|
-
return '<pattern>';
|
|
135
|
-
}
|
|
136
|
-
else if (operandDelimiterIndex >= 0 && index > operandDelimiterIndex) {
|
|
137
|
-
return '<path>';
|
|
138
|
-
}
|
|
139
|
-
else if (arg.startsWith('--exclude-dir=')) {
|
|
140
|
-
return '--exclude-dir=<glob>';
|
|
141
|
-
}
|
|
142
|
-
else if (arg.startsWith('--exclude=')) {
|
|
143
|
-
return '--exclude=<glob>';
|
|
144
|
-
}
|
|
145
|
-
else if (arg.startsWith('--include=')) {
|
|
146
|
-
return '--include=<glob>';
|
|
147
|
-
}
|
|
148
|
-
else if (arg.startsWith('--max-count=')) {
|
|
149
|
-
return '--max-count=<count>';
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
return arg;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function formatGrepCommand(args) {
|
|
156
|
-
const operandDelimiterIndex = args.indexOf('--');
|
|
157
|
-
return [
|
|
158
|
-
'grep',
|
|
159
|
-
...args.map((arg, index) => shellQuote(redactGrepArgForLogging({
|
|
160
|
-
arg,
|
|
161
|
-
index,
|
|
162
|
-
operandDelimiterIndex,
|
|
163
|
-
previousArg: args[index - 1],
|
|
164
|
-
}))),
|
|
165
|
-
].join(' ');
|
|
166
|
-
}
|
|
167
|
-
function replaceGrepCountOutputArg({ args, replacement, }) {
|
|
168
|
-
const countArgIndex = args.indexOf('--count');
|
|
169
|
-
/* node:coverage ignore next */
|
|
170
|
-
return countArgIndex < 0 ? [...args] : args.toSpliced(countArgIndex, 1, replacement);
|
|
171
|
-
}
|
|
172
|
-
function replaceGrepSearchOperands({ args, searchParts, }) {
|
|
173
|
-
const operandDelimiterIndex = args.indexOf('--');
|
|
174
|
-
return [
|
|
175
|
-
...args.slice(0, operandDelimiterIndex + 1),
|
|
176
|
-
...searchParts,
|
|
177
|
-
];
|
|
178
|
-
}
|
|
179
|
-
function extractStringArray(input) {
|
|
180
|
-
return check.isArray(input) ? input.filter(check.isString).filter(check.isTruthy) : [];
|
|
181
|
-
}
|
|
182
|
-
function extractOptionalStringArray(input) {
|
|
183
|
-
if (input == undefined) {
|
|
184
|
-
return [];
|
|
185
|
-
}
|
|
186
|
-
else if (!check.isArray(input) || !input.every(check.isString)) {
|
|
187
|
-
return undefined;
|
|
188
|
-
}
|
|
189
|
-
return input.filter(check.isTruthy);
|
|
190
|
-
}
|
|
191
|
-
function extractString(input) {
|
|
192
|
-
return check.isString(input) && input ? input : undefined;
|
|
193
|
-
}
|
|
194
|
-
function extractGrepOptionArrays({ excludeDirs, excludePatterns, includeFiles, }) {
|
|
195
|
-
const extractedExcludeDirs = extractOptionalStringArray(excludeDirs);
|
|
196
|
-
const extractedExcludePatterns = extractOptionalStringArray(excludePatterns);
|
|
197
|
-
const extractedIncludeFiles = extractOptionalStringArray(includeFiles);
|
|
198
|
-
return extractedExcludeDirs && extractedExcludePatterns && extractedIncludeFiles
|
|
199
|
-
? {
|
|
200
|
-
excludeDirs: extractedExcludeDirs,
|
|
201
|
-
excludePatterns: extractedExcludePatterns,
|
|
202
|
-
includeFiles: extractedIncludeFiles,
|
|
203
|
-
}
|
|
204
|
-
: undefined;
|
|
205
|
-
}
|
|
206
|
-
function areGrepOptionsValid({ grepOptions, }) {
|
|
207
|
-
return (isValidMaxCount(grepOptions.maxCount) &&
|
|
208
|
-
(grepOptions.cwd == undefined || !!extractString(grepOptions.cwd)) &&
|
|
209
|
-
[
|
|
210
|
-
grepOptions.binary,
|
|
211
|
-
grepOptions.followSymLinks,
|
|
212
|
-
grepOptions.ignoreCase,
|
|
213
|
-
grepOptions.invertMatch,
|
|
214
|
-
grepOptions.printCommand,
|
|
215
|
-
grepOptions.recursive,
|
|
216
|
-
].every(isOptionalBoolean) &&
|
|
217
|
-
isValidPatternSyntax(grepOptions.patternSyntax) &&
|
|
218
|
-
isValidMatchType(grepOptions.matchType) &&
|
|
219
|
-
isValidOutput(grepOptions.output));
|
|
220
|
-
}
|
|
221
|
-
function createSearchPatterns(grepSearchPattern) {
|
|
222
|
-
if (!check.isObject(grepSearchPattern)) {
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
const rawPatterns = check.isArray(grepSearchPattern.patterns)
|
|
226
|
-
? grepSearchPattern.patterns
|
|
227
|
-
: [
|
|
228
|
-
grepSearchPattern.pattern,
|
|
229
|
-
];
|
|
230
|
-
return extractStringArray(rawPatterns);
|
|
231
|
-
}
|
|
232
|
-
function createSearchLocation(grepSearchLocation) {
|
|
233
|
-
if (!check.isObject(grepSearchLocation)) {
|
|
234
|
-
return undefined;
|
|
235
|
-
}
|
|
236
|
-
const file = extractString(grepSearchLocation.file);
|
|
237
|
-
const dir = extractString(grepSearchLocation.dir);
|
|
238
|
-
if (grepSearchLocation.files != undefined) {
|
|
239
|
-
return {
|
|
240
|
-
files: extractStringArray(grepSearchLocation.files),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
else if (file) {
|
|
244
|
-
return {
|
|
245
|
-
files: [
|
|
246
|
-
file,
|
|
247
|
-
],
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
else if (grepSearchLocation.dirs != undefined) {
|
|
251
|
-
return {
|
|
252
|
-
dirs: extractStringArray(grepSearchLocation.dirs),
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
return dir
|
|
256
|
-
? {
|
|
257
|
-
dirs: [
|
|
258
|
-
dir,
|
|
259
|
-
],
|
|
260
|
-
}
|
|
261
|
-
: undefined;
|
|
262
|
-
}
|
|
263
|
-
function resolveSearchPart({ cwd, searchPart, }) {
|
|
264
|
-
return cwd && !isAbsolute(searchPart) ? resolve(cwd, searchPart) : searchPart;
|
|
265
|
-
}
|
|
266
|
-
async function shouldSearchPart({ cwd, followSymLinks, includeDirectories, searchPart, }) {
|
|
267
|
-
try {
|
|
268
|
-
const fileStats = await (followSymLinks ? stat : lstat)(resolveSearchPart({
|
|
269
|
-
cwd,
|
|
270
|
-
searchPart,
|
|
271
|
-
}));
|
|
272
|
-
return fileStats.isFile() || (includeDirectories && fileStats.isDirectory());
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async function filterSearchParts({ cwd, followSymLinks, includeDirectories, searchParts, }) {
|
|
279
|
-
return (await awaitedBlockingMap(searchParts, async (searchPart) => {
|
|
280
|
-
return (await shouldSearchPart({
|
|
281
|
-
cwd,
|
|
282
|
-
followSymLinks,
|
|
283
|
-
includeDirectories,
|
|
284
|
-
searchPart,
|
|
285
|
-
}))
|
|
286
|
-
? searchPart
|
|
287
|
-
: undefined;
|
|
288
|
-
})).filter(check.isTruthy);
|
|
289
|
-
}
|
|
290
|
-
async function readDirectDirSearchParts({ cwd, dir, followSymLinks, }) {
|
|
291
|
-
try {
|
|
292
|
-
const readDirPath = resolveSearchPart({
|
|
293
|
-
cwd,
|
|
294
|
-
searchPart: dir,
|
|
295
|
-
});
|
|
296
|
-
return (await awaitedBlockingMap((await readdir(readDirPath)).toSorted().filter((entry) => !entry.startsWith('.')), async (entry) => {
|
|
297
|
-
const searchPart = join(dir, entry);
|
|
298
|
-
return (await shouldSearchPart({
|
|
299
|
-
cwd: readDirPath,
|
|
300
|
-
followSymLinks,
|
|
301
|
-
includeDirectories: false,
|
|
302
|
-
searchPart: entry,
|
|
303
|
-
}))
|
|
304
|
-
? searchPart
|
|
305
|
-
: undefined;
|
|
306
|
-
})).filter(check.isTruthy);
|
|
307
|
-
/* node:coverage ignore next 3 */
|
|
308
|
-
}
|
|
309
|
-
catch {
|
|
310
|
-
return [];
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
async function createSearchParts({ cwd, followSymLinks, recursive, searchLocation, }) {
|
|
314
|
-
const searchParts = searchLocation.dirs || searchLocation.files;
|
|
315
|
-
assert.isDefined(searchParts, 'Grep search location was not resolved.');
|
|
316
|
-
const filteredSearchParts = await filterSearchParts({
|
|
317
|
-
cwd,
|
|
318
|
-
followSymLinks,
|
|
319
|
-
includeDirectories: !!searchLocation.dirs,
|
|
320
|
-
searchParts,
|
|
321
|
-
});
|
|
322
|
-
return searchLocation.dirs
|
|
323
|
-
? recursive
|
|
324
|
-
? filteredSearchParts
|
|
325
|
-
: (await awaitedBlockingMap(filteredSearchParts, (dir) => readDirectDirSearchParts({
|
|
326
|
-
cwd,
|
|
327
|
-
dir,
|
|
328
|
-
followSymLinks,
|
|
329
|
-
}))).flat()
|
|
330
|
-
: filteredSearchParts;
|
|
331
|
-
}
|
|
332
|
-
function createGrepCountEntry({ countString, fileName }) {
|
|
333
|
-
assert.isDefined(fileName, 'Failed parse grep file name.');
|
|
334
|
-
const count = Number(countString);
|
|
335
|
-
assert.isNumber(count, `Failed to parse grep number from: '${countString}'`);
|
|
336
|
-
/* node:coverage ignore next 3 */
|
|
337
|
-
if (!count) {
|
|
338
|
-
return undefined;
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
key: fileName,
|
|
342
|
-
value: count,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
function parseNullDelimitedGrepRecords({ stdout }) {
|
|
346
|
-
const records = [];
|
|
347
|
-
let recordStartIndex = 0;
|
|
348
|
-
while (recordStartIndex < stdout.length) {
|
|
349
|
-
const delimiterIndex = stdout.indexOf('\0', recordStartIndex);
|
|
350
|
-
/* node:coverage ignore next 3 */
|
|
351
|
-
if (delimiterIndex < 0) {
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
const valueStartIndex = delimiterIndex + 1;
|
|
355
|
-
const newlineIndex = stdout.indexOf('\n', valueStartIndex);
|
|
356
|
-
/* node:coverage ignore next */
|
|
357
|
-
const valueEndIndex = newlineIndex < 0 ? stdout.length : newlineIndex;
|
|
358
|
-
records.push({
|
|
359
|
-
fileName: stdout.slice(recordStartIndex, delimiterIndex),
|
|
360
|
-
value: stdout.slice(valueStartIndex, valueEndIndex),
|
|
361
|
-
});
|
|
362
|
-
/* node:coverage ignore next */
|
|
363
|
-
recordStartIndex = newlineIndex < 0 ? stdout.length : newlineIndex + 1;
|
|
364
|
-
}
|
|
365
|
-
return records;
|
|
366
|
-
}
|
|
367
|
-
/* node:coverage ignore next 26 */
|
|
368
|
-
function parseColonDelimitedGrepCountOutput(stdout) {
|
|
369
|
-
return typedObjectFromEntries(stdout
|
|
370
|
-
.trimEnd()
|
|
371
|
-
.split('\n')
|
|
372
|
-
.map((entry) => {
|
|
373
|
-
if (!entry) {
|
|
374
|
-
return undefined;
|
|
375
|
-
}
|
|
376
|
-
const countDelimiterIndex = entry.lastIndexOf(':');
|
|
377
|
-
return createGrepCountEntry({
|
|
378
|
-
countString: countDelimiterIndex < 0 ? undefined : entry.slice(countDelimiterIndex + 1),
|
|
379
|
-
fileName: countDelimiterIndex < 0 ? undefined : entry.slice(0, countDelimiterIndex),
|
|
380
|
-
});
|
|
381
|
-
})
|
|
382
|
-
.filter(check.isTruthy)
|
|
383
|
-
.map((entry) => [
|
|
384
|
-
entry.key,
|
|
385
|
-
entry.value,
|
|
386
|
-
]));
|
|
387
|
-
}
|
|
388
|
-
/* node:coverage ignore next 20 */
|
|
389
|
-
function parseGrepCountOutput(stdout) {
|
|
390
|
-
return stdout.includes('\0')
|
|
391
|
-
? typedObjectFromEntries(parseNullDelimitedGrepRecords({
|
|
392
|
-
stdout,
|
|
393
|
-
})
|
|
394
|
-
.map((record) => {
|
|
395
|
-
return createGrepCountEntry({
|
|
396
|
-
countString: record.value,
|
|
397
|
-
fileName: record.fileName,
|
|
398
|
-
});
|
|
399
|
-
})
|
|
400
|
-
.filter(check.isTruthy)
|
|
401
|
-
.map((entry) => [
|
|
402
|
-
entry.key,
|
|
403
|
-
entry.value,
|
|
404
|
-
]))
|
|
405
|
-
: parseColonDelimitedGrepCountOutput(stdout);
|
|
406
|
-
}
|
|
407
|
-
/* node:coverage ignore next 7 */
|
|
408
|
-
function tryParseGrepCountOutput(stdout) {
|
|
409
|
-
try {
|
|
410
|
-
return parseGrepCountOutput(stdout);
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
return undefined;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
function parseKnownFileCountOutput({ filePath, stdout, }) {
|
|
417
|
-
/* node:coverage ignore next 3 */
|
|
418
|
-
if (stdout.includes('\0')) {
|
|
419
|
-
return parseGrepCountOutput(stdout)[filePath];
|
|
420
|
-
}
|
|
421
|
-
const countPrefix = `${filePath}:`;
|
|
422
|
-
/* node:coverage ignore next */
|
|
423
|
-
const outputLine = stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout;
|
|
424
|
-
/* node:coverage ignore next 3 */
|
|
425
|
-
if (!outputLine.startsWith(countPrefix)) {
|
|
426
|
-
return undefined;
|
|
427
|
-
}
|
|
428
|
-
/* node:coverage ignore next */
|
|
429
|
-
return createGrepCountEntry({
|
|
430
|
-
countString: outputLine.slice(countPrefix.length),
|
|
431
|
-
fileName: filePath,
|
|
432
|
-
})?.value;
|
|
433
|
-
}
|
|
434
|
-
async function runKnownFileGrepCount({ cwd, filePath, grepArgs, }) {
|
|
435
|
-
const result = await runGrepCommand({
|
|
436
|
-
args: replaceGrepSearchOperands({
|
|
437
|
-
args: grepArgs,
|
|
438
|
-
searchParts: [
|
|
439
|
-
filePath,
|
|
440
|
-
],
|
|
441
|
-
}),
|
|
442
|
-
cwd,
|
|
443
|
-
});
|
|
444
|
-
/* node:coverage ignore next 3 */
|
|
445
|
-
if (didGrepFail(result)) {
|
|
446
|
-
return undefined;
|
|
447
|
-
}
|
|
448
|
-
const count = parseKnownFileCountOutput({
|
|
449
|
-
filePath,
|
|
450
|
-
stdout: result.stdout,
|
|
451
|
-
});
|
|
452
|
-
/* node:coverage ignore next 3 */
|
|
453
|
-
if (!count) {
|
|
454
|
-
return undefined;
|
|
455
|
-
}
|
|
456
|
-
return {
|
|
457
|
-
key: filePath,
|
|
458
|
-
value: count,
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
async function runGrepCountFallback({ cwd, grepArgs, }) {
|
|
462
|
-
const filesOnlyResult = await runGrepCommand({
|
|
463
|
-
args: replaceGrepCountOutputArg({
|
|
464
|
-
args: grepArgs,
|
|
465
|
-
replacement: '--files-with-matches',
|
|
466
|
-
}),
|
|
467
|
-
cwd,
|
|
468
|
-
});
|
|
469
|
-
/* node:coverage ignore next 3 */
|
|
470
|
-
if (didGrepFail(filesOnlyResult) || filesOnlyResult.exitCode === 1 || !filesOnlyResult.stdout) {
|
|
471
|
-
return {};
|
|
472
|
-
}
|
|
473
|
-
return typedObjectFromEntries((await awaitedBlockingMap(getObjectTypedKeys(parseGrepFilesOnlyOutput(filesOnlyResult.stdout)), (filePath) => {
|
|
474
|
-
return runKnownFileGrepCount({
|
|
475
|
-
cwd,
|
|
476
|
-
filePath,
|
|
477
|
-
grepArgs,
|
|
478
|
-
});
|
|
479
|
-
}))
|
|
480
|
-
.filter(check.isTruthy)
|
|
481
|
-
.map((entry) => [
|
|
482
|
-
entry.key,
|
|
483
|
-
entry.value,
|
|
484
|
-
]));
|
|
485
|
-
}
|
|
486
|
-
function parseGrepFilesOnlyOutput(stdout) {
|
|
487
|
-
return typedObjectFromEntries(
|
|
488
|
-
/* node:coverage ignore next */
|
|
489
|
-
(stdout.includes('\0') ? stdout.split('\0') : stdout.trimEnd().split('\n'))
|
|
490
|
-
.filter(check.isTruthy)
|
|
491
|
-
.map((entry) => [
|
|
492
|
-
entry,
|
|
493
|
-
[],
|
|
494
|
-
]));
|
|
495
|
-
}
|
|
496
|
-
function parseGrepNormalOutput(stdout) {
|
|
497
|
-
const fileMatches = new Map();
|
|
498
|
-
parseNullDelimitedGrepRecords({
|
|
499
|
-
stdout,
|
|
500
|
-
}).forEach((record) => {
|
|
501
|
-
fileMatches.set(record.fileName, [
|
|
502
|
-
...(fileMatches.get(record.fileName) || []),
|
|
503
|
-
record.value,
|
|
504
|
-
]);
|
|
505
|
-
});
|
|
506
|
-
return typedObjectFromEntries([...fileMatches.entries()]);
|
|
507
|
-
}
|
|
508
24
|
/**
|
|
509
25
|
* Run `grep`, matching patterns to specific lines in files or directories.
|
|
510
26
|
*
|
|
@@ -513,109 +29,141 @@ function parseGrepNormalOutput(stdout) {
|
|
|
513
29
|
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
514
30
|
*/
|
|
515
31
|
export async function grep(grepSearchPattern, grepSearchLocation, options = {}) {
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
: {};
|
|
519
|
-
const searchPatterns = createSearchPatterns(grepSearchPattern);
|
|
520
|
-
const grepOptionArrays = extractGrepOptionArrays(grepOptions);
|
|
521
|
-
const cwd = extractString(grepOptions.cwd);
|
|
522
|
-
if (!searchPatterns.length ||
|
|
523
|
-
!grepOptionArrays ||
|
|
524
|
-
!areGrepOptionsValid({
|
|
525
|
-
grepOptions,
|
|
526
|
-
})) {
|
|
32
|
+
const searchPatterns = (grepSearchPattern.patterns || [grepSearchPattern.pattern]).filter(check.isTruthy);
|
|
33
|
+
if (!searchPatterns.length) {
|
|
527
34
|
return {};
|
|
528
35
|
}
|
|
529
|
-
const searchLocation =
|
|
36
|
+
const searchLocation = grepSearchLocation.files
|
|
37
|
+
? {
|
|
38
|
+
files: grepSearchLocation.files,
|
|
39
|
+
}
|
|
40
|
+
: grepSearchLocation.file
|
|
41
|
+
? {
|
|
42
|
+
files: [grepSearchLocation.file],
|
|
43
|
+
}
|
|
44
|
+
: grepSearchLocation.dirs
|
|
45
|
+
? {
|
|
46
|
+
dirs: grepSearchLocation.dirs,
|
|
47
|
+
}
|
|
48
|
+
: grepSearchLocation.dir
|
|
49
|
+
? {
|
|
50
|
+
dirs: [grepSearchLocation.dir],
|
|
51
|
+
}
|
|
52
|
+
: undefined;
|
|
530
53
|
if (!searchLocation ||
|
|
531
54
|
(searchLocation.dirs && !searchLocation.dirs.length) ||
|
|
532
55
|
(searchLocation.files && !searchLocation.files.length)) {
|
|
533
56
|
return {};
|
|
534
57
|
}
|
|
535
|
-
const searchParts =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
searchLocation
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
const grepArgs = [
|
|
545
|
-
grepOptions.patternSyntax?.basicRegExp
|
|
58
|
+
const searchParts = searchLocation.dirs
|
|
59
|
+
? options.recursive
|
|
60
|
+
? searchLocation.dirs
|
|
61
|
+
: searchLocation.dirs.map((dir) => join(dir, '*'))
|
|
62
|
+
: searchLocation.files;
|
|
63
|
+
const fullCommand = [
|
|
64
|
+
'grep',
|
|
65
|
+
options.patternSyntax?.basicRegExp
|
|
546
66
|
? '--basic-regexp'
|
|
547
|
-
:
|
|
67
|
+
: options.patternSyntax?.extendedRegExp
|
|
548
68
|
? '--extended-regexp'
|
|
549
|
-
:
|
|
69
|
+
: options.patternSyntax?.fixedStrings
|
|
550
70
|
? '--fixed-strings'
|
|
551
71
|
: '',
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
72
|
+
options.ignoreCase ? '--ignore-case' : '',
|
|
73
|
+
options.invertMatch && !options.output?.filesOnly ? '--invert-match' : '',
|
|
74
|
+
options.matchType?.wordRegExp
|
|
555
75
|
? '--word-regexp'
|
|
556
|
-
:
|
|
76
|
+
: options.matchType?.lineRegExp
|
|
557
77
|
? '--line-regexp'
|
|
558
78
|
: '',
|
|
559
|
-
|
|
79
|
+
options.output?.countOnly
|
|
560
80
|
? '--count'
|
|
561
|
-
:
|
|
562
|
-
?
|
|
81
|
+
: options.output?.filesOnly
|
|
82
|
+
? options.invertMatch
|
|
563
83
|
? '--files-without-match'
|
|
564
84
|
: '--files-with-matches'
|
|
565
85
|
: '',
|
|
566
86
|
'--color=never',
|
|
567
|
-
|
|
87
|
+
options.maxCount ? `--max-count=${options.maxCount}` : '',
|
|
568
88
|
'--no-messages',
|
|
569
|
-
'--devices=skip',
|
|
570
89
|
'--with-filename',
|
|
571
90
|
'--null',
|
|
572
|
-
...(
|
|
573
|
-
?
|
|
91
|
+
...(options.excludePatterns?.length
|
|
92
|
+
? options.excludePatterns.map((excludePattern) => `--exclude="${escape(excludePattern)}"`)
|
|
574
93
|
: []),
|
|
575
|
-
recursiveFlag(
|
|
576
|
-
...(
|
|
577
|
-
?
|
|
94
|
+
recursiveFlag(options),
|
|
95
|
+
...(options.excludeDirs?.length
|
|
96
|
+
? options.excludeDirs.map((excludeDir) => `--exclude-dir="${escape(excludeDir)}"`)
|
|
578
97
|
: []),
|
|
579
|
-
...(
|
|
580
|
-
?
|
|
98
|
+
...(options.includeFiles?.length
|
|
99
|
+
? options.includeFiles.map((includeFile) => `--include="${escape(includeFile)}"`)
|
|
581
100
|
: []),
|
|
582
|
-
|
|
583
|
-
...searchPatterns.
|
|
584
|
-
'-e',
|
|
585
|
-
searchPattern,
|
|
586
|
-
]),
|
|
587
|
-
'--',
|
|
101
|
+
options.binary ? '--binary' : '',
|
|
102
|
+
...searchPatterns.map((searchPattern) => `-e "${searchPattern}"`),
|
|
588
103
|
...searchParts,
|
|
589
|
-
]
|
|
590
|
-
|
|
591
|
-
|
|
104
|
+
]
|
|
105
|
+
.filter(check.isTruthy)
|
|
106
|
+
.join(' ');
|
|
107
|
+
if (options.printCommand) {
|
|
108
|
+
log.faint(`> ${fullCommand}`);
|
|
592
109
|
}
|
|
593
|
-
const result = await
|
|
594
|
-
|
|
595
|
-
cwd,
|
|
110
|
+
const result = await runShellCommand(fullCommand, {
|
|
111
|
+
cwd: options.cwd,
|
|
596
112
|
});
|
|
597
|
-
|
|
113
|
+
const trimmedOutput = result.stdout.trim();
|
|
114
|
+
if (result.exitCode === 1 || !trimmedOutput) {
|
|
598
115
|
/** No matches. */
|
|
599
116
|
return {};
|
|
600
117
|
}
|
|
601
|
-
else if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
118
|
+
else if (options.output?.countOnly) {
|
|
119
|
+
return arrayToObject(trimmedOutput.split('\n'), (entry) => {
|
|
120
|
+
/** Ignore empty strings. */
|
|
121
|
+
/* node:coverage ignore next 3 */
|
|
122
|
+
if (!entry) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* GNU `grep` (Linux) separates the file name from its count with a null byte when
|
|
127
|
+
* `--null` is set, while BSD `grep` (macOS) uses a colon. Accept either.
|
|
128
|
+
*/
|
|
129
|
+
const [, fileName, countString,] = safeMatch(entry, /^(.+)[\0:](\d+)$/);
|
|
130
|
+
assert.isDefined(fileName, `Failed parse grep file name from: '${entry}'`);
|
|
131
|
+
const count = Number(countString);
|
|
132
|
+
assert.isNumber(count, `Failed to parse grep number from: '${entry}'`);
|
|
133
|
+
if (!count) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
key: fileName,
|
|
138
|
+
value: count,
|
|
139
|
+
};
|
|
140
|
+
}, {
|
|
141
|
+
useRequired: true,
|
|
142
|
+
});
|
|
614
143
|
}
|
|
615
|
-
else if (
|
|
616
|
-
return
|
|
144
|
+
else if (options.output?.filesOnly) {
|
|
145
|
+
return arrayToObject(trimmedOutput.split(/[\0\n]/), (entry) => {
|
|
146
|
+
/** Ignore empty strings. */
|
|
147
|
+
if (!entry) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
key: entry,
|
|
152
|
+
value: [],
|
|
153
|
+
};
|
|
154
|
+
}, {
|
|
155
|
+
useRequired: true,
|
|
156
|
+
});
|
|
617
157
|
}
|
|
618
158
|
else {
|
|
619
|
-
|
|
159
|
+
const outputLines = trimmedOutput.split(/[\0\n]/);
|
|
160
|
+
const fileMatches = {};
|
|
161
|
+
outputLines.forEach((line, index) => {
|
|
162
|
+
if (!(index % 2)) {
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
164
|
+
getOrSet(fileMatches, line, () => []).push(outputLines[index + 1]);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return fileMatches;
|
|
620
168
|
}
|
|
621
169
|
}
|