├── README.md ├── go.mod ├── go.sum └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # lsdgrep 2 | 3 | Fuzzy Grep using Levenshtein String Distance 4 | 5 | ## Usage 6 | 7 | ``` 8 | 9 | Usage of ./lsdgrep: 10 | -d int 11 | distance (default 2) 12 | ``` 13 | 14 | ## Installation 15 | 16 | ``` 17 | $ go get github.com/mattn/lsdgrep 18 | ``` 19 | 20 | ## License 21 | 22 | MIT 23 | 24 | ## Author 25 | 26 | Yasuhiro Matsumoto (a.k.a. mattn) 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/lsdgrep 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/fatih/color v1.13.0 7 | github.com/mattn/go-colorable v0.1.12 8 | github.com/mattn/go-isatty v0.0.14 9 | github.com/mattn/go-lsd v0.0.0-20211202020058-45013428513d 10 | github.com/mattn/go-unicodeclass v0.0.1 11 | ) 12 | 13 | require golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 2 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 3 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 4 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 5 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 6 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 7 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 8 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 9 | github.com/mattn/go-lsd v0.0.0-20211202020058-45013428513d h1:QYDZbKj0MCD3KtKj8P+/RjDqKY57ICYSc3Dc7fws2fw= 10 | github.com/mattn/go-lsd v0.0.0-20211202020058-45013428513d/go.mod h1:S8/v7Vp+DWtN9Jh6bIp7XzNfdTNylumKJFQXFI8mM4Y= 11 | github.com/mattn/go-unicodeclass v0.0.1 h1:BKdh58FOa0n4QRd39jSeVEF7ncxxV3l4GL05LxZu+XA= 12 | github.com/mattn/go-unicodeclass v0.0.1/go.mod h1:dDCkCgOKUwD3sYX4N+tVQdFh/xlFQ1+cWakbQzy98T8= 13 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 15 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= 17 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 18 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "github.com/fatih/color" 13 | "github.com/mattn/go-colorable" 14 | isatty "github.com/mattn/go-isatty" 15 | lsd "github.com/mattn/go-lsd" 16 | unicodeclass "github.com/mattn/go-unicodeclass" 17 | ) 18 | 19 | func main() { 20 | var distance int 21 | flag.IntVar(&distance, "d", 2, "distance") 22 | flag.Parse() 23 | 24 | if flag.NArg() == 0 || flag.NArg() > 2 { 25 | flag.Usage() 26 | os.Exit(2) 27 | } 28 | 29 | var out io.Writer 30 | var in io.Reader 31 | var file string 32 | 33 | if isatty.IsTerminal(os.Stdout.Fd()) { 34 | out = colorable.NewColorableStdout() 35 | } else { 36 | out = os.Stdout 37 | } 38 | 39 | if flag.NArg() == 1 { 40 | file = "stdin" 41 | in = os.Stdin 42 | } else { 43 | file = flag.Arg(1) 44 | var err error 45 | f, err := os.Open(file) 46 | if err != nil { 47 | fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err) 48 | os.Exit(1) 49 | } 50 | defer f.Close() 51 | in = f 52 | } 53 | 54 | type token struct { 55 | p int 56 | l int 57 | } 58 | words := unicodeclass.Split(strings.ToLower(flag.Arg(0))) 59 | scan := bufio.NewScanner(in) 60 | lno := 0 61 | for scan.Scan() { 62 | lno++ 63 | line := scan.Text() 64 | linewords := unicodeclass.Split(scan.Text()) 65 | tokens := []token{} 66 | pos := 0 67 | for i := 0; i < len(linewords); i++ { 68 | if i+len(words) >= len(linewords) { 69 | break 70 | } 71 | found := 0 72 | total := distance 73 | for j := 0; j < len(words); j++ { 74 | total -= lsd.StringDistance(words[j], strings.ToLower(linewords[i+found])) 75 | found++ 76 | } 77 | if total >= 0 { 78 | tokens = append(tokens, token{ 79 | p: pos, 80 | l: len(strings.Join(linewords[i:i+found], "")), 81 | }) 82 | } 83 | pos += len(linewords[i]) 84 | } 85 | if len(tokens) > 0 { 86 | fmt.Fprintf(out, "%s:%d:", 87 | file, 88 | lno) 89 | pos := 0 90 | for _, token := range tokens { 91 | fmt.Fprint(out, line[pos:token.p]) 92 | if isatty.IsTerminal(os.Stdout.Fd()) { 93 | fmt.Fprint(out, color.RedString(line[token.p:token.p+token.l])) 94 | } else { 95 | fmt.Fprint(out, line[token.p:token.p+token.l]) 96 | } 97 | pos = token.p + token.l 98 | } 99 | fmt.Fprintln(out, line[pos:]) 100 | } 101 | } 102 | if err := scan.Err(); err != nil { 103 | log.Fatal(err) 104 | } 105 | } 106 | --------------------------------------------------------------------------------