Support Pipes
In Unix-like operating systems (such as Linux and macOS), a pipe is a mechanism for interprocess communication (IPC) that allows the output of one process to be used as the input of another.
Pipes are represented by the |
symbol in shell commands. When you use a command like command1 | command2
, it means that the standard output of command1
is connected to the standard input of command2
.
For example:
cat file.txt | grep "pattern"
In this command, the content of file.txt
is piped as input to the grep
command, which searches for the specified pattern.
Modify bin/cmd.ts to make file
argument optional:
// Parse command-line options
const argv = await yargs(process.argv.slice(2))
.locale('en')
- .usage('Usage: $0 [options] <pattern> <file>')
+ .usage('Usage: $0 [options] <pattern> [<file>]')
.option('c', {
alias: 'count',
describe: 'Only a count of selected lines is written to standard output.',
@@ -48,10 +48,10 @@ const argv = await yargs(process.argv.slice(2))
type: 'boolean',
default: false
})
- .demandCommand(2, 'Please provide both pattern and file arguments.').argv
+ .demandCommand(1, 'Please provide pattern to search for.').argv
const pattern = argv._[0] as string
-const filePath = argv._[1] as string
+const filePath = argv._[1] !== undefined ? argv._[1] as string : ''
This makes the argument optional, allowing it to be provided or omitted on the command line.
You cannot do a recursive search when there's no file paths, modify bin/cmd.ts:
-const result = argv.recursive as boolean
+const result = (argv.recursive as boolean && filePath !== '')
? grepRecursive(pattern, filePath, options)
: grep(pattern, filePath, options)
Read the content from standard input when filePath
is empty in lib/grep.ts:
export async function grep (pattern: string, filePath: string, options: Options): Promise<MatchResult> {
const { ignoreCase, invertMatch } = options
- const lines = await _readFileLines(filePath)
+ const lines = filePath === '' ? await _readStdinLines() : await _readFileLines(filePath)
const regexFlags = ignoreCase ? 'gi' : 'g'
const regex = new RegExp(pattern, regexFlags)
let matchingLines: MatchItem[]
@@ -65,3 +66,22 @@ async function _readFileLines (filePath: string): Promise<string[]> {
}
return []
}
+
+async function _readStdinLines (): Promise<string[]> {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ terminal: false
+ })
+
+ return await new Promise((resolve) => {
+ const lines: string[] = []
+ rl.on('line', (line) => {
+ lines.push(line)
+ })
+
+ rl.on('close', () => {
+ resolve(lines)
+ })
+ })
+}
Now after re-compilation, you can do this:
cat lib/grep.ts | node dist/bin/cmd.js -in line
Its result:
3: import * as readline from 'readline'
16: const lines = filePath === '' ? await _readStdinLines() : await _readFileLines(filePath)
19: let matchingLines: MatchItem[]
21: matchingLines = _filterLines(regex, lines, false)
23: matchingLines = _filterLines(regex, lines, true)
25: return { [filePath]: matchingLines }
48: (count, lines) => count + lines.length,
53: function _filterLines (regexPattern: RegExp, lines: string[], flag: boolean): MatchItem[] {
54: const candidates: MatchItem[] = lines.map((line, index) => [index + 1, line.trim()])
56: .filter(([_, line]) => regexPattern.test(line) === flag)
59: async function _readFileLines (filePath: string): Promise<string[]> {
70: async function _readStdinLines (): Promise<string[]> {
78: const lines: string[] = []
79: rl.on('line', (line) => {
84: resolve(lines)