Skip to content

Commit bfd06fa

Browse files
authored
Add -B, -A, and -C options to grep (#1206)
Adds the -B (before context), -A (after context), and -C (before+after context) options to grep. Example usage: ``` grep -B <num> [args...] grep -A <num> [args...] ```
1 parent 3149e09 commit bfd06fa

File tree

4 files changed

+381
-6
lines changed

4 files changed

+381
-6
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,18 @@ Available options:
419419
+ `-l`: Print only filenames of matching files.
420420
+ `-i`: Ignore case.
421421
+ `-n`: Print line numbers.
422+
+ `-B <num>`: Show `<num>` lines before each result.
423+
+ `-A <num>`: Show `<num>` lines after each result.
424+
+ `-C <num>`: Show `<num>` lines before and after each result. -B and -A override this option.
422425

423426
Examples:
424427

425428
```javascript
426429
grep('-v', 'GLOBAL_VARIABLE', '*.js');
427430
grep('GLOBAL_VARIABLE', '*.js');
431+
grep('-B', 3, 'GLOBAL_VARIABLE', '*.js');
432+
grep({ '-B': 3 }, 'GLOBAL_VARIABLE', '*.js');
433+
grep({ '-B': 3, '-C': 2 }, 'GLOBAL_VARIABLE', '*.js');
428434
```
429435

430436
Reads input string from given files and returns a

src/grep.js

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ common.register('grep', _grep, {
99
'l': 'nameOnly',
1010
'i': 'ignoreCase',
1111
'n': 'lineNumber',
12+
'B': 'beforeContext',
13+
'A': 'afterContext',
14+
'C': 'context',
1215
},
1316
});
1417

@@ -22,12 +25,18 @@ common.register('grep', _grep, {
2225
//@ + `-l`: Print only filenames of matching files.
2326
//@ + `-i`: Ignore case.
2427
//@ + `-n`: Print line numbers.
28+
//@ + `-B <num>`: Show `<num>` lines before each result.
29+
//@ + `-A <num>`: Show `<num>` lines after each result.
30+
//@ + `-C <num>`: Show `<num>` lines before and after each result. -B and -A override this option.
2531
//@
2632
//@ Examples:
2733
//@
2834
//@ ```javascript
2935
//@ grep('-v', 'GLOBAL_VARIABLE', '*.js');
3036
//@ grep('GLOBAL_VARIABLE', '*.js');
37+
//@ grep('-B', 3, 'GLOBAL_VARIABLE', '*.js');
38+
//@ grep({ '-B': 3 }, 'GLOBAL_VARIABLE', '*.js');
39+
//@ grep({ '-B': 3, '-C': 2 }, 'GLOBAL_VARIABLE', '*.js');
3140
//@ ```
3241
//@
3342
//@ Reads input string from given files and returns a
@@ -39,7 +48,41 @@ function _grep(options, regex, files) {
3948

4049
if (!files && !pipe) common.error('no paths given', 2);
4150

42-
files = [].slice.call(arguments, 2);
51+
var idx = 2;
52+
var contextError = ': invalid context length argument';
53+
// If the option has been found but not read, copy value from arguments
54+
if (options.beforeContext === true) {
55+
idx = 3;
56+
options.beforeContext = Number(arguments[1]);
57+
if (options.beforeContext < 0) {
58+
common.error(options.beforeContext + contextError, 2);
59+
}
60+
}
61+
if (options.afterContext === true) {
62+
idx = 3;
63+
options.afterContext = Number(arguments[1]);
64+
if (options.afterContext < 0) {
65+
common.error(options.afterContext + contextError, 2);
66+
}
67+
}
68+
if (options.context === true) {
69+
idx = 3;
70+
options.context = Number(arguments[1]);
71+
if (options.context < 0) {
72+
common.error(options.context + contextError, 2);
73+
}
74+
}
75+
// If before or after not given but context is, update values
76+
if (typeof options.context === 'number') {
77+
if (options.beforeContext === false) {
78+
options.beforeContext = options.context;
79+
}
80+
if (options.afterContext === false) {
81+
options.afterContext = options.context;
82+
}
83+
}
84+
regex = arguments[idx - 1];
85+
files = [].slice.call(arguments, idx);
4386

4487
if (pipe) {
4588
files.unshift('-');
@@ -62,23 +105,94 @@ function _grep(options, regex, files) {
62105
}
63106
} else {
64107
var lines = contents.split('\n');
108+
var matches = [];
109+
65110
lines.forEach(function (line, index) {
66111
var matched = line.match(regex);
67112
if ((options.inverse && !matched) || (!options.inverse && matched)) {
68-
var result = line;
69-
if (options.lineNumber) {
70-
result = '' + (index + 1) + ':' + line;
113+
var lineNumber = index + 1;
114+
var result = {};
115+
if (matches.length > 0) {
116+
// If the last result intersects, combine them
117+
var last = matches[matches.length - 1];
118+
var minimumLineNumber = Math.max(
119+
1,
120+
lineNumber - options.beforeContext - 1,
121+
);
122+
if (
123+
last.hasOwnProperty('' + lineNumber) ||
124+
last.hasOwnProperty('' + minimumLineNumber)
125+
) {
126+
result = last;
127+
}
128+
}
129+
result[lineNumber] = {
130+
line,
131+
match: true,
132+
};
133+
if (options.beforeContext > 0) {
134+
// Store the lines with their line numbers to check for overlap
135+
lines
136+
.slice(Math.max(index - options.beforeContext, 0), index)
137+
.forEach(function (v, i, a) {
138+
var lineNum = '' + (index - a.length + i + 1);
139+
if (!result.hasOwnProperty(lineNum)) {
140+
result[lineNum] = { line: v, match: false };
141+
}
142+
});
143+
}
144+
if (options.afterContext > 0) {
145+
// Store the lines with their line numbers to check for overlap
146+
lines
147+
.slice(
148+
index + 1,
149+
Math.min(index + options.afterContext + 1, lines.length - 1),
150+
)
151+
.forEach(function (v, i) {
152+
var lineNum = '' + (index + 1 + i + 1);
153+
if (!result.hasOwnProperty(lineNum)) {
154+
result[lineNum] = { line: v, match: false };
155+
}
156+
});
157+
}
158+
// Only add the result if it's new
159+
if (!matches.includes(result)) {
160+
matches.push(result);
71161
}
72-
grep.push(result);
73162
}
74163
});
164+
165+
// Loop through the matches and add them to the output
166+
Array.prototype.push.apply(
167+
grep,
168+
matches.map(function (result) {
169+
return Object.entries(result)
170+
.map(function (entry) {
171+
var lineNumber = entry[0];
172+
var line = entry[1].line;
173+
var match = entry[1].match;
174+
return options.lineNumber
175+
? lineNumber + (match ? ':' : '-') + line
176+
: line;
177+
})
178+
.join('\n');
179+
}),
180+
);
75181
}
76182
});
77183

78184
if (grep.length === 0 && common.state.errorCode !== 2) {
79185
// We didn't hit the error above, but pattern didn't match
80186
common.error('', { silent: true });
81187
}
82-
return grep.join('\n') + '\n';
188+
189+
var separator = '\n';
190+
if (
191+
typeof options.beforeContext === 'number' ||
192+
typeof options.afterContext === 'number'
193+
) {
194+
separator = '\n--\n';
195+
}
196+
return grep.join(separator) + '\n';
83197
}
84198
module.exports = _grep;

0 commit comments

Comments
 (0)