UNPKG

@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
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; } }