/
main.go
132 lines (107 loc) · 2.87 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main
import (
"flag"
"fmt"
"github.com/lithammer/fuzzysearch/fuzzy"
"os"
"path"
"path/filepath"
"strings"
)
// Individual path prefixes we are searching for.
var components []string
// This is like filepath.Glob, but it's a bit quirky. It tries to find
// project root directories using prefixes stored in `components`.
func quirkyGlob(dir string, index int) (m []string, e error) {
m = []string{}
e = nil
// Do not iterate through hidden directories.
if strings.HasPrefix(path.Base(dir), ".") {
return
}
fi, err := os.Stat(dir)
// Ignore the directory if we couldn't stat it.
if err != nil {
return
}
// Only directories are considered, we can't CD into files.
if !fi.IsDir() {
return
}
// If the current directory matches the component, increment the
// component index and keep searching. Do not continue searching
// sub-directories for the same component index since they will
// all result in longer paths than this one, and we have already
// satisfied the search for this component.
if fuzzy.Match(components[index], path.Base(dir)) {
index = index + 1
}
// If we hit the end of the component list, then we have a match
// and are done searching.
if index == len(components) {
return []string{dir}, nil
}
directories, err := filepath.Glob(fmt.Sprint(dir, "/*"))
if err != nil {
return m, err
}
// If any directory is .git, then we are at a project root and
// we do not want to continue searching subdirectories.
for _, fl := range directories {
matched, err := filepath.Match(".git", path.Base(fl))
if err != nil {
continue
}
if matched {
return
}
}
// For each subdirectory, check for matches.
for _, path := range directories {
matches, err := quirkyGlob(path, index)
if err != nil {
continue
}
if len(matches) == 0 {
continue
}
m = append(m, matches...)
}
return
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] [SEARCH]\n", os.Args[0])
fmt.Fprintf(os.Stderr, " SEARCH string\n")
fmt.Fprintf(os.Stderr, " a fuzzy path to search for (example \"s/foo\")\n")
flag.PrintDefaults()
}
var root string
flag.StringVar(&root, "root", os.Getenv("HOME"), "root directory to search from")
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Println(root)
return
}
arg := flag.Args()[0]
// Split the search expression into components on the "/" character.
for _, c := range strings.Split(arg, "/") {
components = append(components, c)
}
matches, err := quirkyGlob(root, 0)
// If we didn't get any matches, or we got an error, just return root.
if err != nil || len(matches) == 0 {
fmt.Println(root)
return
}
// Find the shortest path that matches.
shortest := matches[0]
jumps := strings.Count(shortest, "/")
for _, path := range matches {
if j := strings.Count(path, "/"); j < jumps {
shortest = path
jumps = j
}
}
fmt.Println(shortest)
}