prints lines in order, highlights
This commit is contained in:
118
main.go
118
main.go
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -42,6 +43,34 @@ type Component struct {
|
||||
Rendered RenderedCharacterClass
|
||||
}
|
||||
|
||||
type LineMatch struct {
|
||||
Line string
|
||||
Start int
|
||||
End int
|
||||
LineNum int
|
||||
}
|
||||
|
||||
const (
|
||||
reset = "\x1b[0m"
|
||||
|
||||
bold = "\x1b[1m"
|
||||
red = "\x1b[31m"
|
||||
green = "\x1b[32m"
|
||||
yellow = "\x1b[33m"
|
||||
)
|
||||
|
||||
func highlight(src string, start, end int, style ...string) string {
|
||||
if start < 0 || end > len(src) || start >= end {
|
||||
return src
|
||||
}
|
||||
|
||||
s := ""
|
||||
for _, c := range style {
|
||||
s += c
|
||||
}
|
||||
return src[:start] + s + src[start:end] + reset + src[end:]
|
||||
}
|
||||
|
||||
func alphabetRange(start, end rune) []rune {
|
||||
if start > end {
|
||||
start, end = end, start
|
||||
@@ -153,33 +182,32 @@ func buildComponents(pattern string) ([]Component, error) {
|
||||
return comps, nil
|
||||
}
|
||||
|
||||
// ---------- match a single literal (with repeat) ----------
|
||||
func matchLiteral(l Character, runes []rune, pos *int) bool {
|
||||
func matchLiteral(l Character, runes []rune, pos *int) (bool, int) {
|
||||
switch l.Repeats {
|
||||
case NoRepeat:
|
||||
if *pos >= len(runes) || runes[*pos] != l.Character {
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
(*pos)++
|
||||
return true
|
||||
return true, *pos
|
||||
|
||||
case ZeroOrMore:
|
||||
for *pos < len(runes) && runes[*pos] == l.Character {
|
||||
(*pos)++
|
||||
}
|
||||
return true // zero occurrences are fine
|
||||
return true, *pos
|
||||
|
||||
case OneOrMore:
|
||||
if *pos >= len(runes) || runes[*pos] != l.Character {
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
for *pos < len(runes) && runes[*pos] == l.Character {
|
||||
(*pos)++
|
||||
}
|
||||
return true
|
||||
return true, *pos
|
||||
|
||||
default:
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,85 +220,94 @@ func inClass(rc RenderedCharacterClass, r rune) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func matchClass(rc RenderedCharacterClass, runes []rune, pos *int) bool {
|
||||
func matchClass(rc RenderedCharacterClass, runes []rune, pos *int) (bool, int) {
|
||||
switch rc.Repeats {
|
||||
case NoRepeat:
|
||||
if *pos >= len(runes) || !inClass(rc, runes[*pos]) {
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
(*pos)++
|
||||
return true
|
||||
return true, *pos
|
||||
|
||||
case ZeroOrMore:
|
||||
for *pos < len(runes) && inClass(rc, runes[*pos]) {
|
||||
(*pos)++
|
||||
}
|
||||
return true // zero occurrences are fine
|
||||
return true, *pos
|
||||
|
||||
case OneOrMore:
|
||||
if *pos >= len(runes) || !inClass(rc, runes[*pos]) {
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
for *pos < len(runes) && inClass(rc, runes[*pos]) {
|
||||
(*pos)++
|
||||
}
|
||||
return true
|
||||
return true, *pos
|
||||
|
||||
default:
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
|
||||
func matchFrom(components []Component, runes []rune, pos int) bool {
|
||||
func matchFrom(components []Component, runes []rune, pos int) (bool, int) {
|
||||
var end int
|
||||
for _, c := range components {
|
||||
switch c.Ctype {
|
||||
case Literal:
|
||||
if !matchLiteral(c.Literal, runes, &pos) {
|
||||
return false
|
||||
a, b := matchLiteral(c.Literal, runes, &pos)
|
||||
if !a {
|
||||
return false, 0
|
||||
}
|
||||
end = b
|
||||
case Rendered:
|
||||
if !matchClass(c.Rendered, runes, &pos) {
|
||||
return false
|
||||
a, b := matchClass(c.Rendered, runes, &pos)
|
||||
if !a {
|
||||
return false, 0
|
||||
}
|
||||
end = b
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true, end
|
||||
}
|
||||
|
||||
func matchComponents(components []Component, text string) bool {
|
||||
func matchComponents(components []Component, text string) (bool, int, int) {
|
||||
runes := []rune(text)
|
||||
for start := 0; start <= len(runes); start++ {
|
||||
if matchFrom(components, runes, start) {
|
||||
return true
|
||||
a, end := matchFrom(components, runes, start)
|
||||
if a {
|
||||
return true, start, end
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false, 0, 0
|
||||
}
|
||||
|
||||
func matchEngine(pattern string, line string, lineNum int, exp bool, components []Component) error {
|
||||
func matchEngine(pattern string, line string, lineNum int, exp bool, components []Component) (bool, LineMatch, error) {
|
||||
if !exp {
|
||||
// sliding window approach for simple full pattern matching
|
||||
for i := 0; i < len(line)-len(pattern); i++ {
|
||||
slice := line[i : i+len(pattern)]
|
||||
if slice == pattern {
|
||||
println(lineNum, line)
|
||||
return true, LineMatch{Line: line, Start: i, End: i + len(pattern), LineNum: lineNum}, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if matchComponents(components, line) {
|
||||
println(lineNum, line)
|
||||
a, s, e := matchComponents(components, line)
|
||||
if a {
|
||||
return true, LineMatch{Line: line, Start: s, End: e, LineNum: lineNum}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return false, LineMatch{}, nil
|
||||
}
|
||||
|
||||
func processReader(r *os.File, pattern string, exp bool) {
|
||||
func processReader(r *os.File, pattern string, exp bool) []LineMatch {
|
||||
var components []Component
|
||||
if exp {
|
||||
components, _ = buildComponents(pattern)
|
||||
}
|
||||
|
||||
var matched []LineMatch
|
||||
|
||||
var wg sync.WaitGroup
|
||||
scanner := bufio.NewScanner(r)
|
||||
lineN := 0
|
||||
@@ -280,18 +317,33 @@ func processReader(r *os.File, pattern string, exp bool) {
|
||||
wg.Add(1)
|
||||
go func(ln int) {
|
||||
defer wg.Done()
|
||||
matchEngine(pattern, text, ln, exp, components)
|
||||
a, l, _ := matchEngine(pattern, text, ln, exp, components)
|
||||
if a {
|
||||
matched = append(matched, l)
|
||||
}
|
||||
}(lineN)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
sort.Slice(matched, func(i, j int) bool { return matched[i].LineNum < matched[j].LineNum })
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
func main() {
|
||||
regexModePtr := flag.Bool("ex", false, "set if using g2eb expressions, leave for full string matching")
|
||||
noHighlightPtr := flag.Bool("noh", false, "no hightlght")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
pattern := flag.Args()[0]
|
||||
processReader(os.Stdin, pattern, *regexModePtr)
|
||||
m := processReader(os.Stdin, pattern, *regexModePtr)
|
||||
|
||||
for _, lm := range m {
|
||||
if *noHighlightPtr {
|
||||
fmt.Println(lm.Line)
|
||||
} else {
|
||||
fmt.Println(highlight(lm.Line, lm.Start, lm.End, green))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user