添加基本功能
想要达到项目目标,需要实现以下功能:
-c, --count
-i, --ignore-case
-n, --line-number
-r, --recursive
-v, --invert-match
添加 yargs
依赖项以实现复杂参数的解析:
npm i yargs
bin/grepjs:
#!/usr/bin/env node
const fs = require("fs");
const yargs = require("yargs");
const { grep, grepCount, grepRecursive } = require("../lib/grep");
// 解析命令行参数
const argv = yargs
.usage("Usage: $0 [options] <pattern> <file>")
.option("c", {
alias: "count",
describe: "Only a count of selected lines is written to standard output.",
type: "boolean",
})
.option("h", {
alias: "help",
describe: "Print a brief help message.",
type: "boolean",
})
.option("i", {
alias: "ignore-case",
describe:
"Perform case insensitive matching. By default, it is case sensitive.",
type: "boolean",
})
.option("n", {
alias: "line-number",
describe:
"Each output line is preceded by its relative line number in the file, starting at line 1. The line number counter is reset for each file processed. This option is ignored if -c is specified.",
type: "boolean",
})
.option("r", {
alias: "recursive",
describe: "Recursively search subdirectories listed.",
type: "boolean",
})
.option("v", {
alias: "invert-match",
describe:
"Selected lines are those not matching any of the specified patterns.",
type: "boolean",
})
.demandCommand(2, "Please provide both pattern and file arguments.").argv;
const pattern = argv._[0];
const filePath = argv._[1];
if (argv.help) {
// 打印帮助信息并退出进程
console.log(argv.help);
process.exit(0);
}
const options = {
ignoreCase: argv["ignore-case"],
invertMatch: argv["invert-match"],
};
const result = argv.recursive
? grepRecursive(pattern, filePath, options)
: grep(pattern, filePath, options);
result
.then((result) => {
if (argv.count) {
console.log(grepCount(result));
} else {
printResult(result, argv["line-number"]);
}
})
.catch((error) => {
console.error("Error:", error.message);
});
function printResult(result, showLineNumber) {
let currentFile = null;
const fileCount = Object.keys(result).length;
for (const [filePath, lines] of Object.entries(result)) {
for (const [lineNumber, line] of lines) {
if (fileCount > 1 && filePath !== currentFile) {
currentFile = filePath;
console.log(`\n${filePath}:`);
}
if (showLineNumber) {
console.log(`${lineNumber}: ${line}`);
} else {
console.log(line);
}
}
}
}
yargs
加入所有可选参数的解析。printResult
函数解析结果对象并按需打印 filePath
和 lineNumber
。
lib/grep.js:
const fs = require("fs");
const path = require("path");
async function grep(pattern, filePath, options = {}) {
const { ignoreCase, invertMatch } = options;
const lines = await _readFileLines(filePath);
const regexFlags = ignoreCase ? "gi" : "g";
const regex = new RegExp(pattern, regexFlags);
if (invertMatch) {
matchingLines = _filterLines(regex, lines, false);
} else {
matchingLines = _filterLines(regex, lines, true);
}
return { [filePath]: matchingLines };
}
async function grepRecursive(pattern, dirPath, options = {}) {
let results = {};
try {
const files = await fs.promises.readdir(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const isSubDir = (await fs.promises.stat(filePath)).isDirectory();
const result = !isSubDir
? await grep(pattern, filePath, options)
: await grepRecursive(pattern, filePath, options);
results = { ...results, ...result };
}
} catch (err) {
console.error(err);
}
return results;
}
function grepCount(result) {
return Object.values(result).reduce(
(count, lines) => count + lines.length,
0
);
}
function _filterLines(regexPattern, lines, flag) {
return lines
.map((line, lineNumber) => {
const match = regexPattern.test(line);
return flag === match ? [lineNumber + 1, line.trim()] : null;
})
.filter(Boolean);
}
async function _readFileLines(filePath) {
try {
// 读取文件
const data = await fs.promises.readFile(filePath, "utf8");
return data.split("\n");
} catch (error) {
console.error("Error reading the file:", error.message);
}
return [];
}
module.exports = {
grep,
grepCount,
grepRecursive,
};
grep
函数添加“不区分大小写匹配”和“反向匹配”逻辑。grepRecursive
函数递归地列出所有文件,执行 grep
操作,然后输出到结果对象中。
常规用法:
./bin/grepjs filePath lib/grep.js
结果:
async function grep(pattern, filePath, options = {}) {
const lines = await _readFileLines(filePath);
return { [filePath]: matchingLines };
const filePath = path.join(dirPath, file);
const isSubDir = (await fs.promises.stat(filePath)).isDirectory();
? await grep(pattern, filePath, options)
: await grepRecursive(pattern, filePath, options);
async function _readFileLines(filePath) {
const data = await fs.promises.readFile(filePath, "utf8");
计数:
./bin/grepjs -c filePath lib/grep.js
结果数字:9。
显示行号:
./bin/grepjs -n filePath lib/grep.js
结果:
4: async function grep(pattern, filePath, options = {}) {
6: const lines = await _readFileLines(filePath);
14: return { [filePath]: matchingLines };
22: const filePath = path.join(dirPath, file);
23: const isSubDir = (await fs.promises.stat(filePath)).isDirectory();
25: ? await grep(pattern, filePath, options)
26: : await grepRecursive(pattern, filePath, options);
51: async function _readFileLines(filePath) {
54: const data = await fs.promises.readFile(filePath, "utf8");
使用正则:
./bin/grepjs -n "\br[a-z]+t" lib/grep.js
结果:
14: return { [filePath]: matchingLines };
18: let results = {};
24: const result = !isSubDir
27: results = { ...results, ...result };
32: return results;
35: function grepCount(result) {
43: return lines
46: return flag === match ? [lineNumber + 1, line.trim()] : null;
55: return data.split("\n");
59: return [];
反向匹配:
./bin/grepjs -vn result lib/grep.js
结果:
1: const fs = require("fs");
2: const path = require("path");
3:
4: async function grep(pattern, filePath, options = {}) {
5: const { ignoreCase, invertMatch } = options;
...
8: const regex = new RegExp(pattern, regexFlags);
9: if (invertMatch) {
...
62: module.exports = {
63: grep,
64: grepCount,
65: grepRecursive,
66: };
不区分大小写匹配:
./bin/grepjs -i only bin/grepjs
结果:
describe: "Only a count of selected lines is written to standard output.",
递归匹配:
./bin/grepjs -r filePath .
结果:
bin/grepjs:
const filePath = argv._[1];
? grepRecursive(pattern, filePath, options)
for (const [filePath, lines] of Object.entries(result)) {
if (fileCount > 1 && filePath !== currentFile) {
console.log(`\n${filePath}:`);
lib/grep.js:
async function grep(pattern, filePath, options = {}) {
const lines = await _readFileLines(filePath);
return { [filePath]: matchingLines };
const filePath = path.join(dirPath, file);
const isSubDir = (await fs.promises.stat(filePath)).isDirectory();
? await grep(pattern, filePath, options)
: await grepRecursive(pattern, filePath, options);
async function _readFileLines(filePath) {
const data = await fs.promises.readFile(filePath, "utf8");