@@ -9,10 +9,13 @@ import (
99 "encoding/binary"
1010 "flag"
1111 "fmt"
12+ "html"
1213 "io"
1314 "os"
1415 "regexp/syntax"
1516 "sort"
17+ "strconv"
18+ "strings"
1619
1720 "github.com/google/codesearch/sparse"
1821)
@@ -338,9 +341,9 @@ func (m *matcher) matchString(b string, beginText, endText bool) (end int) {
338341
339342// isWordByte reports whether the byte c is a word character: ASCII only.
340343// This is used to implement \b and \B. This is not right for Unicode, but:
341- // - it's hard to get right in a byte-at-a-time matching world
342- // (the DFA has only one-byte lookahead)
343- // - this crude approximation is the same one PCRE uses
344+ // - it's hard to get right in a byte-at-a-time matching world
345+ // (the DFA has only one-byte lookahead)
346+ // - this crude approximation is the same one PCRE uses
344347func isWordByte (c int ) bool {
345348 return 'A' <= c && c <= 'Z' ||
346349 'a' <= c && c <= 'z' ||
@@ -358,8 +361,16 @@ type Grep struct {
358361 C bool // C flag - print count of matches
359362 N bool // N flag - print line numbers
360363 H bool // H flag - do not print file names
364+ V bool // V flag - print non-matching lines (only for cgrep, not csearch)
361365
362- Match bool
366+ HTML bool // emit HTML output for csweb
367+ Match bool // were any matches found?
368+ Matches int // how many matches were found?
369+ Limit int // stop after this many matches
370+ Limited bool // stopped because of limit
371+
372+ PreContext int // number of lines to print after
373+ PostContext int // number of lines to print before
363374
364375 buf []byte
365376}
@@ -369,12 +380,34 @@ func (g *Grep) AddFlags() {
369380 flag .BoolVar (& g .C , "c" , false , "print match counts only" )
370381 flag .BoolVar (& g .N , "n" , false , "show line numbers" )
371382 flag .BoolVar (& g .H , "h" , false , "omit file names" )
383+ flag .IntVar (& g .PreContext , "B" , 0 , "show `n` lines before match" )
384+ flag .IntVar (& g .PostContext , "A" , 0 , "show `n` lines after match" )
385+ flag .Func ("C" , "show `n` lines before and after match" , func (s string ) error {
386+ n , err := strconv .Atoi (s )
387+ if err != nil {
388+ return err
389+ }
390+ g .PreContext = n
391+ g .PostContext = n
392+ return nil
393+ })
394+ }
395+
396+ func (g * Grep ) AddVFlag () {
397+ flag .BoolVar (& g .V , "v" , false , "show non-matching lines" )
398+ }
399+
400+ func (g * Grep ) esc (s string ) string {
401+ if g .HTML {
402+ return html .EscapeString (s )
403+ }
404+ return s
372405}
373406
374407func (g * Grep ) File (name string ) {
375408 f , err := os .Open (name )
376409 if err != nil {
377- fmt .Fprintf (g .Stderr , "%s\n " , err )
410+ fmt .Fprintf (g .Stderr , "%s\n " , g . esc ( err . Error ()) )
378411 return
379412 }
380413 defer f .Close ()
@@ -402,7 +435,7 @@ func (g *Grep) Reader(r io.Reader, name string) {
402435 }
403436 var (
404437 buf = g .buf [:0 ]
405- needLineno = g .N
438+ needLineno = g .N || g . HTML
406439 lineno = 1
407440 count = 0
408441 prefix = ""
@@ -412,28 +445,40 @@ func (g *Grep) Reader(r io.Reader, name string) {
412445 if ! g .H {
413446 prefix = name + ":"
414447 }
448+ chunkStart := 0
415449 for {
416450 n , err := io .ReadFull (r , buf [len (buf ):cap (buf )])
417451 buf = buf [:len (buf )+ n ]
418452 end := len (buf )
419453 if err == nil {
420- i := bytes .LastIndex (buf , nl )
421- if i >= 0 {
422- end = i + 1
454+ // Stop scan before trailing fragment of a line;
455+ // also stop before g.PostContext whole lines,
456+ // so we know we'll have the context we need to print.
457+ d := lineSuffixLen (buf , g .PostContext + 1 )
458+ if d < len (buf ) {
459+ end = len (buf ) - d
423460 }
424461 } else {
425462 endText = true
426463 }
427- chunkStart := 0
428464 for chunkStart < end {
429465 m1 := g .Regexp .Match (buf [chunkStart :end ], beginText , endText ) + chunkStart
430466 beginText = false
431467 if m1 < chunkStart {
432468 break
433469 }
434470 g .Match = true
471+ if g .Limit > 0 && g .Matches >= g .Limit {
472+ g .Limited = true
473+ return
474+ }
475+ g .Matches ++
435476 if g .L {
436- fmt .Fprintf (g .Stdout , "%s\n " , name )
477+ if g .HTML {
478+ fmt .Fprintf (g .Stdout , "<a href=\" show/%s\" >%s</a>\n " , g .esc (name ), g .esc (name ))
479+ } else {
480+ fmt .Fprintf (g .Stdout , "%s\n " , name )
481+ }
437482 return
438483 }
439484 lineStart := bytes .LastIndex (buf [chunkStart :m1 ], nl ) + 1 + chunkStart
@@ -452,6 +497,18 @@ func (g *Grep) Reader(r io.Reader, name string) {
452497 switch {
453498 case g .C :
454499 count ++
500+ case g .PreContext + g .PostContext > 0 :
501+ fmt .Fprintf (g .Stdout , "%s%d:\n " , prefix , lineno )
502+ before , match , after := lineContext (g .PreContext , g .PostContext , buf , lineStart , lineEnd )
503+ for _ , line := range before {
504+ fmt .Fprintf (g .Stdout , "\t \t %s\n " , line )
505+ }
506+ fmt .Fprintf (g .Stdout , "\t >>\t %s\n " , match )
507+ for _ , line := range after {
508+ fmt .Fprintf (g .Stdout , "\t \t %s\n " , line )
509+ }
510+ case g .HTML :
511+ fmt .Fprintf (g .Stdout , "<a href=\" /show/%s?q=%s#L%d\" >%s:%d</a>:%s%s" , g .esc (strings .ReplaceAll (name , "#" , ">" )), g .esc (g .Regexp .String ()), lineno , g .esc (name ), lineno , g .esc (string (line )), nl )
455512 case g .N :
456513 fmt .Fprintf (g .Stdout , "%s%d:%s%s" , prefix , lineno , line , nl )
457514 default :
@@ -465,16 +522,127 @@ func (g *Grep) Reader(r io.Reader, name string) {
465522 if needLineno && err == nil {
466523 lineno += countNL (buf [chunkStart :end ])
467524 }
468- n = copy (buf , buf [end :])
525+ // Slide pre-context and unprocessed bytes down to start of buffer.
526+ d := lineSuffixLen (buf [:end ], g .PreContext )
527+ if d == end {
528+ // Not enough room; give up on context.
529+ d = 0
530+ }
531+ n = copy (buf , buf [end - d :])
469532 buf = buf [:n ]
470- if len (buf ) == 0 && err != nil {
533+ chunkStart = d
534+ if endText && err != nil {
471535 if err != io .EOF && err != io .ErrUnexpectedEOF {
472- fmt .Fprintf (g .Stderr , "%s: %v\n " , name , err )
536+ fmt .Fprintf (g .Stderr , "%s: %v\n " , g . esc ( name ) , err )
473537 }
474538 break
475539 }
476540 }
477541 if g .C && count > 0 {
478- fmt .Fprintf (g .Stdout , "%s: %d\n " , name , count )
542+ if g .HTML {
543+ fmt .Fprintf (g .Stdout , "<a href=\" show/%s?q=%s\" >%s</a>: %d\n " , g .esc (name ), g .esc (g .Regexp .String ()), g .esc (name ), count )
544+ } else {
545+ fmt .Fprintf (g .Stdout , "%s: %d\n " , name , count )
546+ }
547+ }
548+ }
549+
550+ func lineSuffixLen (buf []byte , n int ) int {
551+ end := len (buf )
552+ for i := 0 ; i < n ; i ++ {
553+ j := bytes .LastIndex (buf [:end ], nl )
554+ if j < 0 {
555+ break
556+ }
557+ end = j
558+ }
559+ if j := bytes .LastIndex (buf [:end ], nl ); j >= 0 {
560+ return len (buf ) - (j + 1 )
561+ }
562+ return len (buf )
563+ }
564+
565+ func linePrefixLen (buf []byte , lines int ) int {
566+ start := 0
567+ for i := 0 ; i < lines ; i ++ {
568+ j := bytes .IndexByte (buf [start :], '\n' )
569+ if j < 0 {
570+ return len (buf )
571+ }
572+ start += j + 1
573+ }
574+ return start
575+ }
576+
577+ func lineContext (numBefore , numAfter int , buf []byte , lineStart , lineEnd int ) (before [][]byte , line []byte , after [][]byte ) {
578+ beforeChunk := buf [lineStart - lineSuffixLen (buf [:lineStart ], numBefore ) : lineStart ]
579+ afterChunk := buf [lineEnd : lineEnd + linePrefixLen (buf [lineEnd :], numAfter )]
580+
581+ line = chomp (buf [lineStart :lineEnd ])
582+ before = bytes .SplitAfter (beforeChunk , nl )
583+ if len (before [len (before )- 1 ]) == 0 {
584+ before = before [:len (before )- 1 ]
585+ }
586+ for i := range before {
587+ before [i ] = chomp (before [i ])
588+ }
589+ after = bytes .Split (afterChunk , nl )
590+ if len (after [len (after )- 1 ]) == 0 {
591+ after = after [:len (after )- 1 ]
592+ }
593+ for i := range after {
594+ after [i ] = chomp (after [i ])
595+ }
596+
597+ var prefix []byte
598+ prefix = updatePrefix (prefix , line )
599+ for _ , l := range before {
600+ prefix = updatePrefix (prefix , l )
601+ }
602+ for _ , l := range after {
603+ prefix = updatePrefix (prefix , l )
604+ }
605+
606+ line = cutPrefix (line , prefix )
607+ for i , l := range before {
608+ before [i ] = cutPrefix (l , prefix )
609+ }
610+ for i , l := range after {
611+ after [i ] = cutPrefix (l , prefix )
612+ }
613+ return
614+ }
615+
616+ func updatePrefix (prefix , line []byte ) []byte {
617+ if prefix == nil {
618+ i := 0
619+ for i < len (line ) && (line [i ] == ' ' || line [i ] == '\t' ) {
620+ i ++
621+ }
622+ return line [:i ]
623+ }
624+
625+ i := 0
626+ for i < len (line ) && i < len (prefix ) && line [i ] == prefix [i ] {
627+ i ++
628+ }
629+ if i >= len (line ) {
630+ return prefix
631+ }
632+ return prefix [:i ]
633+ }
634+
635+ func cutPrefix (line , prefix []byte ) []byte {
636+ if len (prefix ) > len (line ) {
637+ return nil
638+ }
639+ return line [len (prefix ):]
640+ }
641+
642+ func chomp (s []byte ) []byte {
643+ i := len (s )
644+ for i > 0 && (s [i - 1 ] == ' ' || s [i - 1 ] == '\t' || s [i - 1 ] == '\r' || s [i - 1 ] == '\n' ) {
645+ i --
479646 }
647+ return s [:i ]
480648}
0 commit comments