mirror of
https://github.com/shelljs/shelljs.git
synced 2026-01-18 16:03:37 +00:00
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...] ```
This commit is contained in:
parent
3149e09d5f
commit
bfd06fa096
@ -419,12 +419,18 @@ Available options:
|
||||
+ `-l`: Print only filenames of matching files.
|
||||
+ `-i`: Ignore case.
|
||||
+ `-n`: Print line numbers.
|
||||
+ `-B <num>`: Show `<num>` lines before each result.
|
||||
+ `-A <num>`: Show `<num>` lines after each result.
|
||||
+ `-C <num>`: Show `<num>` lines before and after each result. -B and -A override this option.
|
||||
|
||||
Examples:
|
||||
|
||||
```javascript
|
||||
grep('-v', 'GLOBAL_VARIABLE', '*.js');
|
||||
grep('GLOBAL_VARIABLE', '*.js');
|
||||
grep('-B', 3, 'GLOBAL_VARIABLE', '*.js');
|
||||
grep({ '-B': 3 }, 'GLOBAL_VARIABLE', '*.js');
|
||||
grep({ '-B': 3, '-C': 2 }, 'GLOBAL_VARIABLE', '*.js');
|
||||
```
|
||||
|
||||
Reads input string from given files and returns a
|
||||
|
||||
126
src/grep.js
126
src/grep.js
@ -9,6 +9,9 @@ common.register('grep', _grep, {
|
||||
'l': 'nameOnly',
|
||||
'i': 'ignoreCase',
|
||||
'n': 'lineNumber',
|
||||
'B': 'beforeContext',
|
||||
'A': 'afterContext',
|
||||
'C': 'context',
|
||||
},
|
||||
});
|
||||
|
||||
@ -22,12 +25,18 @@ common.register('grep', _grep, {
|
||||
//@ + `-l`: Print only filenames of matching files.
|
||||
//@ + `-i`: Ignore case.
|
||||
//@ + `-n`: Print line numbers.
|
||||
//@ + `-B <num>`: Show `<num>` lines before each result.
|
||||
//@ + `-A <num>`: Show `<num>` lines after each result.
|
||||
//@ + `-C <num>`: Show `<num>` lines before and after each result. -B and -A override this option.
|
||||
//@
|
||||
//@ Examples:
|
||||
//@
|
||||
//@ ```javascript
|
||||
//@ grep('-v', 'GLOBAL_VARIABLE', '*.js');
|
||||
//@ grep('GLOBAL_VARIABLE', '*.js');
|
||||
//@ grep('-B', 3, 'GLOBAL_VARIABLE', '*.js');
|
||||
//@ grep({ '-B': 3 }, 'GLOBAL_VARIABLE', '*.js');
|
||||
//@ grep({ '-B': 3, '-C': 2 }, 'GLOBAL_VARIABLE', '*.js');
|
||||
//@ ```
|
||||
//@
|
||||
//@ Reads input string from given files and returns a
|
||||
@ -39,7 +48,41 @@ function _grep(options, regex, files) {
|
||||
|
||||
if (!files && !pipe) common.error('no paths given', 2);
|
||||
|
||||
files = [].slice.call(arguments, 2);
|
||||
var idx = 2;
|
||||
var contextError = ': invalid context length argument';
|
||||
// If the option has been found but not read, copy value from arguments
|
||||
if (options.beforeContext === true) {
|
||||
idx = 3;
|
||||
options.beforeContext = Number(arguments[1]);
|
||||
if (options.beforeContext < 0) {
|
||||
common.error(options.beforeContext + contextError, 2);
|
||||
}
|
||||
}
|
||||
if (options.afterContext === true) {
|
||||
idx = 3;
|
||||
options.afterContext = Number(arguments[1]);
|
||||
if (options.afterContext < 0) {
|
||||
common.error(options.afterContext + contextError, 2);
|
||||
}
|
||||
}
|
||||
if (options.context === true) {
|
||||
idx = 3;
|
||||
options.context = Number(arguments[1]);
|
||||
if (options.context < 0) {
|
||||
common.error(options.context + contextError, 2);
|
||||
}
|
||||
}
|
||||
// If before or after not given but context is, update values
|
||||
if (typeof options.context === 'number') {
|
||||
if (options.beforeContext === false) {
|
||||
options.beforeContext = options.context;
|
||||
}
|
||||
if (options.afterContext === false) {
|
||||
options.afterContext = options.context;
|
||||
}
|
||||
}
|
||||
regex = arguments[idx - 1];
|
||||
files = [].slice.call(arguments, idx);
|
||||
|
||||
if (pipe) {
|
||||
files.unshift('-');
|
||||
@ -62,16 +105,79 @@ function _grep(options, regex, files) {
|
||||
}
|
||||
} else {
|
||||
var lines = contents.split('\n');
|
||||
var matches = [];
|
||||
|
||||
lines.forEach(function (line, index) {
|
||||
var matched = line.match(regex);
|
||||
if ((options.inverse && !matched) || (!options.inverse && matched)) {
|
||||
var result = line;
|
||||
if (options.lineNumber) {
|
||||
result = '' + (index + 1) + ':' + line;
|
||||
var lineNumber = index + 1;
|
||||
var result = {};
|
||||
if (matches.length > 0) {
|
||||
// If the last result intersects, combine them
|
||||
var last = matches[matches.length - 1];
|
||||
var minimumLineNumber = Math.max(
|
||||
1,
|
||||
lineNumber - options.beforeContext - 1,
|
||||
);
|
||||
if (
|
||||
last.hasOwnProperty('' + lineNumber) ||
|
||||
last.hasOwnProperty('' + minimumLineNumber)
|
||||
) {
|
||||
result = last;
|
||||
}
|
||||
}
|
||||
result[lineNumber] = {
|
||||
line,
|
||||
match: true,
|
||||
};
|
||||
if (options.beforeContext > 0) {
|
||||
// Store the lines with their line numbers to check for overlap
|
||||
lines
|
||||
.slice(Math.max(index - options.beforeContext, 0), index)
|
||||
.forEach(function (v, i, a) {
|
||||
var lineNum = '' + (index - a.length + i + 1);
|
||||
if (!result.hasOwnProperty(lineNum)) {
|
||||
result[lineNum] = { line: v, match: false };
|
||||
}
|
||||
});
|
||||
}
|
||||
if (options.afterContext > 0) {
|
||||
// Store the lines with their line numbers to check for overlap
|
||||
lines
|
||||
.slice(
|
||||
index + 1,
|
||||
Math.min(index + options.afterContext + 1, lines.length - 1),
|
||||
)
|
||||
.forEach(function (v, i) {
|
||||
var lineNum = '' + (index + 1 + i + 1);
|
||||
if (!result.hasOwnProperty(lineNum)) {
|
||||
result[lineNum] = { line: v, match: false };
|
||||
}
|
||||
});
|
||||
}
|
||||
// Only add the result if it's new
|
||||
if (!matches.includes(result)) {
|
||||
matches.push(result);
|
||||
}
|
||||
grep.push(result);
|
||||
}
|
||||
});
|
||||
|
||||
// Loop through the matches and add them to the output
|
||||
Array.prototype.push.apply(
|
||||
grep,
|
||||
matches.map(function (result) {
|
||||
return Object.entries(result)
|
||||
.map(function (entry) {
|
||||
var lineNumber = entry[0];
|
||||
var line = entry[1].line;
|
||||
var match = entry[1].match;
|
||||
return options.lineNumber
|
||||
? lineNumber + (match ? ':' : '-') + line
|
||||
: line;
|
||||
})
|
||||
.join('\n');
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -79,6 +185,14 @@ function _grep(options, regex, files) {
|
||||
// We didn't hit the error above, but pattern didn't match
|
||||
common.error('', { silent: true });
|
||||
}
|
||||
return grep.join('\n') + '\n';
|
||||
|
||||
var separator = '\n';
|
||||
if (
|
||||
typeof options.beforeContext === 'number' ||
|
||||
typeof options.afterContext === 'number'
|
||||
) {
|
||||
separator = '\n--\n';
|
||||
}
|
||||
return grep.join(separator) + '\n';
|
||||
}
|
||||
module.exports = _grep;
|
||||
|
||||
240
test/grep.js
240
test/grep.js
@ -57,6 +57,27 @@ test("multiple files, one doesn't exist, one doesn't match", t => {
|
||||
t.is(result.code, 2);
|
||||
});
|
||||
|
||||
test('-A option, negative value', t => {
|
||||
const result = shell.grep('-A', -2, 'test*', 'test/resources/grep/file3');
|
||||
t.truthy(shell.error());
|
||||
t.is(result.code, 2);
|
||||
t.is(result.stderr, 'grep: -2: invalid context length argument');
|
||||
});
|
||||
|
||||
test('-B option, negative value', t => {
|
||||
const result = shell.grep('-B', -3, 'test*', 'test/resources/grep/file3');
|
||||
t.truthy(shell.error());
|
||||
t.is(result.code, 2);
|
||||
t.is(result.stderr, 'grep: -3: invalid context length argument');
|
||||
});
|
||||
|
||||
test('-C option, negative value', t => {
|
||||
const result = shell.grep('-C', -1, 'test*', 'test/resources/grep/file3');
|
||||
t.truthy(shell.error());
|
||||
t.is(result.code, 2);
|
||||
t.is(result.stderr, 'grep: -1: invalid context length argument');
|
||||
});
|
||||
|
||||
//
|
||||
// Valids
|
||||
//
|
||||
@ -174,3 +195,222 @@ test('the pattern looks like an option', t => {
|
||||
t.falsy(shell.error());
|
||||
t.is(result.toString(), '-v\n-vv\n');
|
||||
});
|
||||
|
||||
//
|
||||
// Before & after contexts
|
||||
//
|
||||
test('-B option', t => {
|
||||
const result = shell.grep('-B', 3, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line1\n' +
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'--\n' +
|
||||
'line7\n' +
|
||||
'line8\n' +
|
||||
'line9\n' +
|
||||
'line10 test line\n' +
|
||||
'--\n' +
|
||||
'line12\n' +
|
||||
'line13\n' +
|
||||
'line14\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-B option, -n option', t => {
|
||||
const result = shell.grep('-nB', 3, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'1-line1\n' +
|
||||
'2:line2 test line\n' +
|
||||
'3:line3 test line\n' +
|
||||
'--\n' +
|
||||
'7-line7\n' +
|
||||
'8-line8\n' +
|
||||
'9-line9\n' +
|
||||
'10:line10 test line\n' +
|
||||
'--\n' +
|
||||
'12-line12\n' +
|
||||
'13-line13\n' +
|
||||
'14-line14\n' +
|
||||
'15:line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-A option', t => {
|
||||
const result = shell.grep('-A', 2, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'line4\n' +
|
||||
'line5\n' +
|
||||
'--\n' +
|
||||
'line10 test line\n' +
|
||||
'line11\n' +
|
||||
'line12\n' +
|
||||
'--\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-A option, -B option', t => {
|
||||
const result = shell.grep(
|
||||
{ '-A': 2, '-B': 3 },
|
||||
'test*',
|
||||
'test/resources/grep/file3',
|
||||
);
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line1\n' +
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'line4\n' +
|
||||
'line5\n' +
|
||||
'--\n' +
|
||||
'line7\n' +
|
||||
'line8\n' +
|
||||
'line9\n' +
|
||||
'line10 test line\n' +
|
||||
'line11\n' +
|
||||
'line12\n' +
|
||||
'line13\n' +
|
||||
'line14\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-A option, -B option, -n option', t => {
|
||||
const result = shell.grep(
|
||||
{ '-n': true, '-A': 2, '-B': 3 },
|
||||
'test*',
|
||||
'test/resources/grep/file3',
|
||||
);
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'1-line1\n' +
|
||||
'2:line2 test line\n' +
|
||||
'3:line3 test line\n' +
|
||||
'4-line4\n' +
|
||||
'5-line5\n' +
|
||||
'--\n' +
|
||||
'7-line7\n' +
|
||||
'8-line8\n' +
|
||||
'9-line9\n' +
|
||||
'10:line10 test line\n' +
|
||||
'11-line11\n' +
|
||||
'12-line12\n' +
|
||||
'13-line13\n' +
|
||||
'14-line14\n' +
|
||||
'15:line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-C option', t => {
|
||||
const result = shell.grep('-C', 3, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line1\n' +
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'line4\n' +
|
||||
'line5\n' +
|
||||
'line6\n' +
|
||||
'line7\n' +
|
||||
'line8\n' +
|
||||
'line9\n' +
|
||||
'line10 test line\n' +
|
||||
'line11\n' +
|
||||
'line12\n' +
|
||||
'line13\n' +
|
||||
'line14\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-C option, small value', t => {
|
||||
const result = shell.grep('-C', 1, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line1\n' +
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'line4\n' +
|
||||
'--\n' +
|
||||
'line9\n' +
|
||||
'line10 test line\n' +
|
||||
'line11\n' +
|
||||
'--\n' +
|
||||
'line14\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-C option, large value', t => {
|
||||
const result = shell.grep('-C', 100, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line1\n' +
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'line4\n' +
|
||||
'line5\n' +
|
||||
'line6\n' +
|
||||
'line7\n' +
|
||||
'line8\n' +
|
||||
'line9\n' +
|
||||
'line10 test line\n' +
|
||||
'line11\n' +
|
||||
'line12\n' +
|
||||
'line13\n' +
|
||||
'line14\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-C option, add line separators', t => {
|
||||
const result = shell.grep('-C', 0, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'line2 test line\n' +
|
||||
'line3 test line\n' +
|
||||
'--\n' +
|
||||
'line10 test line\n' +
|
||||
'--\n' +
|
||||
'line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('-C option, -n option', t => {
|
||||
const result = shell.grep('-nC', 3, 'test*', 'test/resources/grep/file3');
|
||||
t.falsy(shell.error());
|
||||
t.is(
|
||||
result.toString(),
|
||||
'1-line1\n' +
|
||||
'2:line2 test line\n' +
|
||||
'3:line3 test line\n' +
|
||||
'4-line4\n' +
|
||||
'5-line5\n' +
|
||||
'6-line6\n' +
|
||||
'7-line7\n' +
|
||||
'8-line8\n' +
|
||||
'9-line9\n' +
|
||||
'10:line10 test line\n' +
|
||||
'11-line11\n' +
|
||||
'12-line12\n' +
|
||||
'13-line13\n' +
|
||||
'14-line14\n' +
|
||||
'15:line15 test line\n',
|
||||
);
|
||||
});
|
||||
|
||||
15
test/resources/grep/file3
Normal file
15
test/resources/grep/file3
Normal file
@ -0,0 +1,15 @@
|
||||
line1
|
||||
line2 test line
|
||||
line3 test line
|
||||
line4
|
||||
line5
|
||||
line6
|
||||
line7
|
||||
line8
|
||||
line9
|
||||
line10 test line
|
||||
line11
|
||||
line12
|
||||
line13
|
||||
line14
|
||||
line15 test line
|
||||
Loading…
x
Reference in New Issue
Block a user