github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/glob.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "strings" 8 "unicode" 9 10 "src.elv.sh/pkg/eval/vals" 11 "src.elv.sh/pkg/glob" 12 "src.elv.sh/pkg/parse" 13 ) 14 15 // An ephemeral value generated when evaluating tilde and wildcards. 16 type globPattern struct { 17 glob.Pattern 18 Flags globFlag 19 Buts []string 20 TypeCb func(os.FileMode) bool 21 } 22 23 type globFlag uint 24 25 var typeCbMap = map[string]func(os.FileMode) bool{ 26 "dir": os.FileMode.IsDir, 27 "regular": os.FileMode.IsRegular, 28 } 29 30 const ( 31 // noMatchOK indicates that the "nomatch-ok" glob index modifer was 32 // present. 33 noMatchOK globFlag = 1 << iota 34 ) 35 36 func (f globFlag) Has(g globFlag) bool { 37 return (f & g) == g 38 } 39 40 var _ vals.ErrIndexer = globPattern{} 41 42 var ( 43 ErrMustFollowWildcard = errors.New("must follow wildcard") 44 ErrModifierMustBeString = errors.New("modifier must be string") 45 ErrWildcardNoMatch = errors.New("wildcard has no match") 46 ErrMultipleTypeModifiers = errors.New("only one type modifier allowed") 47 ErrUnknownTypeModifier = errors.New("unknown type modifier") 48 ) 49 50 var runeMatchers = map[string]func(rune) bool{ 51 "control": unicode.IsControl, 52 "digit": unicode.IsDigit, 53 "graphic": unicode.IsGraphic, 54 "letter": unicode.IsLetter, 55 "lower": unicode.IsDigit, 56 "mark": unicode.IsMark, 57 "number": unicode.IsNumber, 58 "print": unicode.IsPrint, 59 "punct": unicode.IsPunct, 60 "space": unicode.IsSpace, 61 "symbol": unicode.IsSymbol, 62 "title": unicode.IsTitle, 63 "upper": unicode.IsUpper, 64 } 65 66 func (gp globPattern) Index(k interface{}) (interface{}, error) { 67 modifierv, ok := k.(string) 68 if !ok { 69 return nil, ErrModifierMustBeString 70 } 71 modifier := modifierv 72 switch { 73 case modifier == "nomatch-ok": 74 gp.Flags |= noMatchOK 75 case strings.HasPrefix(modifier, "but:"): 76 gp.Buts = append(gp.Buts, modifier[len("but:"):]) 77 case modifier == "match-hidden": 78 lastSeg, err := gp.lastWildSeg() 79 if err != nil { 80 return nil, err 81 } 82 gp.Segments[len(gp.Segments)-1] = glob.Wild{ 83 Type: lastSeg.Type, MatchHidden: true, Matchers: lastSeg.Matchers, 84 } 85 case strings.HasPrefix(modifier, "type:"): 86 if gp.TypeCb != nil { 87 return nil, ErrMultipleTypeModifiers 88 } 89 typeName := modifier[len("type:"):] 90 cb, ok := typeCbMap[typeName] 91 if !ok { 92 return nil, ErrUnknownTypeModifier 93 } 94 gp.TypeCb = cb 95 default: 96 var matcher func(rune) bool 97 if m, ok := runeMatchers[modifier]; ok { 98 matcher = m 99 } else if strings.HasPrefix(modifier, "set:") { 100 set := modifier[len("set:"):] 101 matcher = func(r rune) bool { 102 return strings.ContainsRune(set, r) 103 } 104 } else if strings.HasPrefix(modifier, "range:") { 105 rangeExpr := modifier[len("range:"):] 106 badRangeExpr := fmt.Errorf("bad range modifier: %s", parse.Quote(rangeExpr)) 107 runes := []rune(rangeExpr) 108 if len(runes) != 3 { 109 return nil, badRangeExpr 110 } 111 from, sep, to := runes[0], runes[1], runes[2] 112 switch sep { 113 case '-': 114 matcher = func(r rune) bool { 115 return from <= r && r <= to 116 } 117 case '~': 118 matcher = func(r rune) bool { 119 return from <= r && r < to 120 } 121 default: 122 return nil, badRangeExpr 123 } 124 } else { 125 return nil, fmt.Errorf("unknown modifier %s", vals.Repr(modifierv, vals.NoPretty)) 126 } 127 err := gp.addMatcher(matcher) 128 return gp, err 129 } 130 return gp, nil 131 } 132 133 func (gp globPattern) Concat(v interface{}) (interface{}, error) { 134 switch rhs := v.(type) { 135 case string: 136 gp.append(stringToSegments(rhs)...) 137 return gp, nil 138 case globPattern: 139 // We know rhs contains exactly one segment. 140 gp.append(rhs.Segments[0]) 141 gp.Flags |= rhs.Flags 142 gp.Buts = append(gp.Buts, rhs.Buts...) 143 // This handles illegal cases such as `**[type:regular]x*[type:directory]`. 144 if gp.TypeCb != nil && rhs.TypeCb != nil { 145 return nil, ErrMultipleTypeModifiers 146 } 147 if rhs.TypeCb != nil { 148 gp.TypeCb = rhs.TypeCb 149 } 150 return gp, nil 151 } 152 153 return nil, vals.ErrConcatNotImplemented 154 } 155 156 func (gp globPattern) RConcat(v interface{}) (interface{}, error) { 157 switch lhs := v.(type) { 158 case string: 159 segs := stringToSegments(lhs) 160 // We know gp contains exactly one segment. 161 segs = append(segs, gp.Segments[0]) 162 return globPattern{Pattern: glob.Pattern{Segments: segs}, Flags: gp.Flags, 163 Buts: gp.Buts, TypeCb: gp.TypeCb}, nil 164 } 165 166 return nil, vals.ErrConcatNotImplemented 167 } 168 169 func (gp *globPattern) lastWildSeg() (glob.Wild, error) { 170 if len(gp.Segments) == 0 { 171 return glob.Wild{}, ErrBadglobPattern 172 } 173 if !glob.IsWild(gp.Segments[len(gp.Segments)-1]) { 174 return glob.Wild{}, ErrMustFollowWildcard 175 } 176 return gp.Segments[len(gp.Segments)-1].(glob.Wild), nil 177 } 178 179 func (gp *globPattern) addMatcher(matcher func(rune) bool) error { 180 lastSeg, err := gp.lastWildSeg() 181 if err != nil { 182 return err 183 } 184 gp.Segments[len(gp.Segments)-1] = glob.Wild{ 185 Type: lastSeg.Type, MatchHidden: lastSeg.MatchHidden, 186 Matchers: append(lastSeg.Matchers, matcher), 187 } 188 return nil 189 } 190 191 func (gp *globPattern) append(segs ...glob.Segment) { 192 gp.Segments = append(gp.Segments, segs...) 193 } 194 195 func wildcardToSegment(s string) (glob.Segment, error) { 196 switch s { 197 case "*": 198 return glob.Wild{Type: glob.Star, MatchHidden: false, Matchers: nil}, nil 199 case "**": 200 return glob.Wild{Type: glob.StarStar, MatchHidden: false, Matchers: nil}, nil 201 case "?": 202 return glob.Wild{Type: glob.Question, MatchHidden: false, Matchers: nil}, nil 203 default: 204 return nil, fmt.Errorf("bad wildcard: %q", s) 205 } 206 } 207 208 func stringToSegments(s string) []glob.Segment { 209 segs := []glob.Segment{} 210 for i := 0; i < len(s); { 211 j := i 212 for ; j < len(s) && s[j] != '/'; j++ { 213 } 214 if j > i { 215 segs = append(segs, glob.Literal{Data: s[i:j]}) 216 } 217 if j < len(s) { 218 for ; j < len(s) && s[j] == '/'; j++ { 219 } 220 segs = append(segs, glob.Slash{}) 221 i = j 222 } else { 223 break 224 } 225 } 226 return segs 227 } 228 229 func doGlob(gp globPattern, abort <-chan struct{}) ([]interface{}, error) { 230 but := make(map[string]struct{}) 231 for _, s := range gp.Buts { 232 but[s] = struct{}{} 233 } 234 235 vs := make([]interface{}, 0) 236 if !gp.Glob(func(pathInfo glob.PathInfo) bool { 237 select { 238 case <-abort: 239 logger.Println("glob aborted") 240 return false 241 default: 242 } 243 244 if _, ignore := but[pathInfo.Path]; ignore { 245 return true 246 } 247 248 if gp.TypeCb == nil || gp.TypeCb(pathInfo.Info.Mode()) { 249 vs = append(vs, pathInfo.Path) 250 } 251 return true 252 }) { 253 return nil, ErrInterrupted 254 } 255 if len(vs) == 0 && !gp.Flags.Has(noMatchOK) { 256 return nil, ErrWildcardNoMatch 257 } 258 return vs, nil 259 }