@@ -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}
84198module . exports = _grep ;
0 commit comments