github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/pkg/model/matcher.go (about) 1 package model 2 3 import ( 4 "path/filepath" 5 6 "github.com/tilt-dev/tilt/internal/ospath" 7 ) 8 9 type PathMatcher interface { 10 Matches(f string) (bool, error) 11 12 // If this matches the entire dir, we can often optimize filetree walks a bit 13 MatchesEntireDir(file string) (bool, error) 14 } 15 16 // A Matcher that matches nothing. 17 type emptyMatcher struct{} 18 19 func (m emptyMatcher) Matches(f string) (bool, error) { 20 return false, nil 21 } 22 func (emptyMatcher) MatchesEntireDir(p string) (bool, error) { return false, nil } 23 24 var EmptyMatcher PathMatcher = emptyMatcher{} 25 26 // This matcher will match a path if it is: 27 // A. an exact match for one of matcher.paths, or 28 // B. the child of a path in matcher.paths 29 // e.g. if paths = {"foo.bar", "baz/"}, will match both 30 // A. "foo.bar" (exact match), and 31 // B. "baz/qux" (child of one of the paths) 32 type fileOrChildMatcher struct { 33 paths map[string]bool 34 } 35 36 func (m fileOrChildMatcher) Matches(f string) (bool, error) { 37 // (A) Exact match 38 if m.paths[f] { 39 return true, nil 40 } 41 42 // (B) f is child of any of m.paths 43 for path := range m.paths { 44 if ospath.IsChild(path, f) { 45 return true, nil 46 } 47 } 48 49 return false, nil 50 } 51 52 func (m fileOrChildMatcher) MatchesEntireDir(f string) (bool, error) { 53 return m.Matches(f) 54 } 55 56 // NewRelativeFileOrChildMatcher returns a matcher for the given paths (with any 57 // relative paths converted to absolute, relative to the given baseDir). 58 func NewRelativeFileOrChildMatcher(baseDir string, paths ...string) fileOrChildMatcher { 59 pathMap := make(map[string]bool, len(paths)) 60 for _, path := range paths { 61 if !filepath.IsAbs(path) { 62 path = filepath.Join(baseDir, path) 63 } 64 pathMap[path] = true 65 } 66 return fileOrChildMatcher{paths: pathMap} 67 } 68 69 // A PathSet stores one or more filepaths, along with the directory that any 70 // relative paths are relative to 71 // NOTE(maia): in its current usage (for LiveUpdate.Run.Triggers, LiveUpdate.FallBackOnFiles()) 72 // this isn't strictly necessary, could just as easily convert paths to Abs when specified in 73 // the Tiltfile--but leaving this code in place for now because it was already written and 74 // may help with complicated future cases (glob support, etc.) 75 type PathSet struct { 76 Paths []string 77 BaseDirectory string 78 } 79 80 func NewPathSet(paths []string, baseDir string) PathSet { 81 return PathSet{ 82 Paths: paths, 83 BaseDirectory: baseDir, 84 } 85 } 86 87 func (ps PathSet) Empty() bool { return len(ps.Paths) == 0 } 88 89 // AnyMatch returns true if any of the given filepaths match any paths contained in the pathset 90 // (along with the first path that matched). 91 func (ps PathSet) AnyMatch(paths []string) (bool, string, error) { 92 matcher := NewRelativeFileOrChildMatcher(ps.BaseDirectory, ps.Paths...) 93 94 for _, path := range paths { 95 match, err := matcher.Matches(path) 96 if err != nil { 97 return false, "", err 98 } 99 if match { 100 return true, path, nil 101 } 102 } 103 return false, "", nil 104 } 105 106 // Intersection returns the set of paths that are in both the PathSet and the passed set of paths. 107 func (ps PathSet) Intersection(paths []string) ([]string, error) { 108 var matches []string 109 matcher := NewRelativeFileOrChildMatcher(ps.BaseDirectory, ps.Paths...) 110 for _, path := range paths { 111 match, err := matcher.Matches(path) 112 if err != nil { 113 return nil, err 114 } 115 if match { 116 matches = append(matches, path) 117 } 118 } 119 return matches, nil 120 } 121 122 type CompositePathMatcher struct { 123 Matchers []PathMatcher 124 } 125 126 func NewCompositeMatcher(matchers []PathMatcher) PathMatcher { 127 if len(matchers) == 0 { 128 return EmptyMatcher 129 } 130 return CompositePathMatcher{Matchers: matchers} 131 } 132 133 func (c CompositePathMatcher) Matches(f string) (bool, error) { 134 for _, t := range c.Matchers { 135 ret, err := t.Matches(f) 136 if err != nil { 137 return false, err 138 } 139 if ret { 140 return true, nil 141 } 142 } 143 return false, nil 144 } 145 146 func (c CompositePathMatcher) MatchesEntireDir(f string) (bool, error) { 147 for _, t := range c.Matchers { 148 matches, err := t.MatchesEntireDir(f) 149 if matches || err != nil { 150 return matches, err 151 } 152 } 153 return false, nil 154 } 155 156 var _ PathMatcher = CompositePathMatcher{}