/* gg is a recursive grep written in Go, with some shortcuts for everyday use. */ package main import ( "bytes" "errors" "fmt" "strings" "unicode" "unicode/utf8" ) type notPlainTextBehaviour int const ( notPlainTextShort notPlainTextBehaviour = iota notPlainTextFull notPlainTextSkip ) type notPlainTextFlag struct { b notPlainTextBehaviour } func (nptf *notPlainTextFlag) String() string { switch nptf.b { case notPlainTextShort: return "short" case notPlainTextFull: return "full" case notPlainTextSkip: return "skip" } return "???" } func (nptf *notPlainTextFlag) Set(s string) error { switch s { case "short": nptf.b = notPlainTextShort case "full": nptf.b = notPlainTextFull case "skip": nptf.b = notPlainTextSkip default: return errors.New("must be one of short|full|skip") } return nil } func (nptf *notPlainTextFlag) Type() string { return "short|full|skip" // ??? } func file(path string, data []byte) { var short string switch { case isBinary(data): switch binaryFile.b { case notPlainTextShort: short = "Binary" case notPlainTextSkip: return } case isMinified(data): switch minifiedFile.b { case notPlainTextShort: short = "Minified" case notPlainTextSkip: return } } var ( lineNum int printedHeader bool b, b2 strings.Builder ) // split into lines for len(data) > 0 { eol := bytes.IndexByte(data, '\n') lineNum++ var line []byte if eol == -1 { line = data data = nil } else { line = data[:eol] data = data[eol+1:] } loc := findMatches(line) if loc == nil { continue } switch { case !printedHeader && short != "": if printedFull { fmt.Println("") printedFull = false } fmt.Printf("%s file %s matches.\n", short, path) return case !printedHeader: if printedFull { fmt.Println("") } printedFull = true fmt.Println(display.Filename(path)) printedHeader = true } b.Reset() fmt.Fprintf(&b, "%4d: ", display.LineNumber(lineNum)) if loc[0] < 128 { escape(&b, line[0:loc[0]]) } else { start := loc[0] - 128 for i := 0; i < 5; i++ { if utf8.RuneStart(line[start]) { break } start++ } b.WriteString(display.TruncatedBytes(start).String()) b.WriteString(display.TruncatedMarker().String()) escape(&b, line[start:loc[0]]) } if loc[1]-loc[0] < 128 { b2.Reset() escape(&b2, line[loc[0]:loc[1]]) b.WriteString(display.Match(b2.String()).String()) if loc[1]+128 > len(line) { escape(&b, line[loc[1]:]) } else { end := loc[1] + 128 for i := 0; i < 5; i++ { if utf8.RuneStart(line[end]) { break } end-- } escape(&b, line[loc[1]:end]) b.WriteString(display.TruncatedBytes(len(line) - end).String()) b.WriteString(display.TruncatedMarker().String()) } } else { end := loc[1] for i := 0; i < 5; i++ { if utf8.RuneStart(line[end]) { break } end-- } b2.Reset() escape(&b2, line[loc[0]:end]) b.WriteString(display.Match(b2.String()).String()) b.WriteString(display.TruncatedMarker().String()) b.WriteString(display.TruncatedBytes(len(line) - end).String()) } b.WriteRune('\n') fmt.Print(b.String()) } } func isBinary(data []byte) bool { bytesToExamine := 4096 for bytesToExamine > 0 { r, s := utf8.DecodeRune(data) switch { case s == 0: // end of string return false case s == 1 && r == utf8.RuneError: // invalid UTF-8 return true case r == '\r', r == '\n', r == '\t': // valid control chars case r < ' ': // invalid control chars return true } data = data[s:] bytesToExamine -= s } return false } func isMinified(data []byte) bool { const longLine = 256 bytesToExamine := 4096 var lineLength int for bytesToExamine > 0 { r, s := utf8.DecodeRune(data) switch { case s == 0: // end of string return false case r == '\n': lineLength = 0 default: lineLength++ if lineLength >= longLine { return true } } data = data[s:] bytesToExamine -= s } return false } func findMatches(data []byte) (loc []int) { for _, re := range regexps { loc := re.FindIndex(data) if loc != nil { return loc } } for _, s := range searchBytes { pos := bytes.Index(data, s) if pos != -1 { return []int{pos, pos + len(s)} } } return nil } func escape(b *strings.Builder, s []byte) { for len(s) > 0 { r, size := utf8.DecodeRune(s) s = s[size:] switch { case r == utf8.RuneError && size == 1: b.WriteString(display.BadUTF8Char().String()) case r == '\r': b.WriteString(display.CarriageReturn().String()) case r == '\t', unicode.IsPrint(r): b.WriteRune(r) default: b.WriteString(display.UnprintableChar().String()) } } }