@augment-vir/node
Version:
A collection of augments, helpers types, functions, and classes only for Node.js (backend) JavaScript environments.
154 lines (153 loc) • 5.5 kB
JavaScript
import { assert, check } from '@augment-vir/assert';
import { arrayToObject, getOrSet, log, safeMatch, } from '@augment-vir/common';
import { join } from 'node:path';
import { runShellCommand } from '../terminal/shell.js';
function escape(input) {
return input.replaceAll('"', String.raw `\"`).replaceAll('\n', '');
}
/**
* Run `grep`, matching patterns to specific lines in files or directories.
*
* @category Node : File
* @category Package : @augment-vir/node
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
*/
export async function grep(grepSearchPattern, grepSearchLocation, options = {}) {
const searchPatterns = (grepSearchPattern.patterns || [grepSearchPattern.pattern]).filter(check.isTruthy);
if (!searchPatterns.length) {
return {};
}
const searchLocation = grepSearchLocation.files
? {
files: grepSearchLocation.files,
}
: grepSearchLocation.file
? {
files: [grepSearchLocation.file],
}
: grepSearchLocation.dirs
? {
dirs: grepSearchLocation.dirs,
}
: grepSearchLocation.dir
? {
dirs: [grepSearchLocation.dir],
}
: undefined;
if (!searchLocation ||
(searchLocation.dirs && !searchLocation.dirs.length) ||
(searchLocation.files && !searchLocation.files.length)) {
return {};
}
const searchParts = searchLocation.dirs
? options.recursive
? searchLocation.dirs
: searchLocation.dirs.map((dir) => join(dir, '*'))
: searchLocation.files;
const fullCommand = [
'grep',
options.patternSyntax?.basicRegExp
? '--basic-regexp'
: options.patternSyntax?.extendedRegExp
? '--extended-regexp'
: options.patternSyntax?.fixedStrings
? '--fixed-strings'
: '',
options.ignoreCase ? '--ignore-case' : '',
options.invertMatch && !options.output?.filesOnly ? '--invert-match' : '',
options.matchType?.wordRegExp
? '--word-regexp'
: options.matchType?.lineRegExp
? '--line-regexp'
: '',
options.output?.countOnly
? '--count'
: options.output?.filesOnly
? options.invertMatch
? '--files-without-match'
: '--files-with-matches'
: '',
'--color=never',
options.maxCount ? `--max-count=${options.maxCount}` : '',
'--no-messages',
'--with-filename',
'--null',
...(options.excludePatterns?.length
? options.excludePatterns.map((excludePattern) => `--exclude="${escape(excludePattern)}"`)
: []),
options.recursive
? options.followSymLinks
? '-RS'
: '--recursive'
: '',
...(options.excludeDirs?.length
? options.excludeDirs.map((excludeDir) => `--exclude-dir="${escape(excludeDir)}"`)
: []),
...(options.includeFiles?.length
? options.includeFiles.map((includeFile) => `--include="${escape(includeFile)}"`)
: []),
options.binary ? '--binary' : '',
...searchPatterns.map((searchPattern) => `-e "${searchPattern}"`),
...searchParts,
]
.filter(check.isTruthy)
.join(' ');
if (options.printCommand) {
log.faint(`> ${fullCommand}`);
}
const result = await runShellCommand(fullCommand, {
cwd: options.cwd,
});
const trimmedOutput = result.stdout.trim();
if (result.exitCode === 1 || !trimmedOutput) {
/** No matches. */
return {};
}
else if (options.output?.countOnly) {
return arrayToObject(trimmedOutput.split(/[\0\n]/), (entry) => {
/** Ignore empty strings. */
/* node:coverage ignore next 3 */
if (!entry) {
return undefined;
}
const [, fileName, countString,] = safeMatch(entry, /(^.+):(\d+)$/);
assert.isDefined(fileName, `Failed parse grep file name from: '${entry}'`);
const count = Number(countString);
assert.isNumber(count, `Failed to parse grep number from: '${entry}'`);
if (!count) {
return undefined;
}
return {
key: fileName,
value: count,
};
}, {
useRequired: true,
});
}
else if (options.output?.filesOnly) {
return arrayToObject(trimmedOutput.split(/[\0\n]/), (entry) => {
/** Ignore empty strings. */
if (!entry) {
return undefined;
}
return {
key: entry,
value: [],
};
}, {
useRequired: true,
});
}
else {
const outputLines = trimmedOutput.split(/[\0\n]/);
const fileMatches = {};
outputLines.forEach((line, index) => {
if (!(index % 2)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getOrSet(fileMatches, line, () => []).push(outputLines[index + 1]);
}
});
return fileMatches;
}
}