github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/glob.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "strings" 8 "unicode" 9 10 "github.com/markusbkk/elvish/pkg/eval/vals" 11 "github.com/markusbkk/elvish/pkg/glob" 12 "github.com/markusbkk/elvish/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 modifier 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) Kind() string { return "glob-pattern" } 67 68 func (gp globPattern) Index(k interface{}) (interface{}, error) { 69 modifierv, ok := k.(string) 70 if !ok { 71 return nil, ErrModifierMustBeString 72 } 73 modifier := modifierv 74 switch { 75 case modifier == "nomatch-ok": 76 gp.Flags |= noMatchOK 77 case strings.HasPrefix(modifier, "but:"): 78 gp.Buts = append(gp.Buts, modifier[len("but:"):]) 79 case modifier == "match-hidden": 80 lastSeg, err := gp.lastWildSeg() 81 if err != nil { 82 return nil, err 83 } 84 gp.Segments[len(gp.Segments)-1] = glob.Wild{ 85 Type: lastSeg.Type, MatchHidden: true, Matchers: lastSeg.Matchers, 86 } 87 case strings.HasPrefix(modifier, "type:"): 88 if gp.TypeCb != nil { 89 return nil, ErrMultipleTypeModifiers 90 } 91 typeName := modifier[len("type:"):] 92 cb, ok := typeCbMap[typeName] 93 if !ok { 94 return nil, ErrUnknownTypeModifier 95 } 96 gp.TypeCb = cb 97 default: 98 var matcher func(rune) bool 99 if m, ok := runeMatchers[modifier]; ok { 100 matcher = m 101 } else if strings.HasPrefix(modifier, "set:") { 102 set := modifier[len("set:"):] 103 matcher = func(r rune) bool { 104 return strings.ContainsRune(set, r) 105 } 106 } else if strings.HasPrefix(modifier, "range:") { 107 rangeExpr := modifier[len("range:"):] 108 badRangeExpr := fmt.Errorf("bad range modifier: %s", parse.Quote(rangeExpr)) 109 runes := []rune(rangeExpr) 110 if len(runes) != 3 { 111 return nil, badRangeExpr 112 } 113 from, sep, to := runes[0], runes[1], runes[2] 114 switch sep { 115 case '-': 116 matcher = func(r rune) bool { 117 return from <= r && r <= to 118 } 119 case '~': 120 matcher = func(r rune) bool { 121 return from <= r && r < to 122 } 123 default: 124 return nil, badRangeExpr 125 } 126 } else { 127 return nil, fmt.Errorf("unknown modifier %s", vals.ReprPlain(modifierv)) 128 } 129 err := gp.addMatcher(matcher) 130 return gp, err 131 } 132 return gp, nil 133 } 134 135 func (gp globPattern) Concat(v interface{}) (interface{}, error) { 136 switch rhs := v.(type) { 137 case string: 138 gp.append(stringToSegments(rhs)...) 139 return gp, nil 140 case globPattern: 141 // We know rhs contains exactly one segment. 142 gp.append(rhs.Segments[0]) 143 gp.Flags |= rhs.Flags 144 gp.Buts = append(gp.Buts, rhs.Buts...) 145 // This handles illegal cases such as `**[type:regular]x*[type:directory]`. 146 if gp.TypeCb != nil && rhs.TypeCb != nil { 147 return nil, ErrMultipleTypeModifiers 148 } 149 if rhs.TypeCb != nil { 150 gp.TypeCb = rhs.TypeCb 151 } 152 return gp, nil 153 } 154 155 return nil, vals.ErrConcatNotImplemented 156 } 157 158 func (gp globPattern) RConcat(v interface{}) (interface{}, error) { 159 switch lhs := v.(type) { 160 case string: 161 segs := stringToSegments(lhs) 162 // We know gp contains exactly one segment. 163 segs = append(segs, gp.Segments[0]) 164 return globPattern{Pattern: glob.Pattern{Segments: segs}, Flags: gp.Flags, 165 Buts: gp.Buts, TypeCb: gp.TypeCb}, nil 166 } 167 168 return nil, vals.ErrConcatNotImplemented 169 } 170 171 func (gp *globPattern) lastWildSeg() (glob.Wild, error) { 172 if len(gp.Segments) == 0 { 173 return glob.Wild{}, ErrBadglobPattern 174 } 175 if !glob.IsWild(gp.Segments[len(gp.Segments)-1]) { 176 return glob.Wild{}, ErrMustFollowWildcard 177 } 178 return gp.Segments[len(gp.Segments)-1].(glob.Wild), nil 179 } 180 181 func (gp *globPattern) addMatcher(matcher func(rune) bool) error { 182 lastSeg, err := gp.lastWildSeg() 183 if err != nil { 184 return err 185 } 186 gp.Segments[len(gp.Segments)-1] = glob.Wild{ 187 Type: lastSeg.Type, MatchHidden: lastSeg.MatchHidden, 188 Matchers: append(lastSeg.Matchers, matcher), 189 } 190 return nil 191 } 192 193 func (gp *globPattern) append(segs ...glob.Segment) { 194 gp.Segments = append(gp.Segments, segs...) 195 } 196 197 func wildcardToSegment(s string) (glob.Segment, error) { 198 switch s { 199 case "*": 200 return glob.Wild{Type: glob.Star, MatchHidden: false, Matchers: nil}, nil 201 case "**": 202 return glob.Wild{Type: glob.StarStar, MatchHidden: false, Matchers: nil}, nil 203 case "?": 204 return glob.Wild{Type: glob.Question, MatchHidden: false, Matchers: nil}, nil 205 default: 206 return nil, fmt.Errorf("bad wildcard: %q", s) 207 } 208 } 209 210 func stringToSegments(s string) []glob.Segment { 211 segs := []glob.Segment{} 212 for i := 0; i < len(s); { 213 j := i 214 for ; j < len(s) && s[j] != '/'; j++ { 215 } 216 if j > i { 217 segs = append(segs, glob.Literal{Data: s[i:j]}) 218 } 219 if j < len(s) { 220 for ; j < len(s) && s[j] == '/'; j++ { 221 } 222 segs = append(segs, glob.Slash{}) 223 i = j 224 } else { 225 break 226 } 227 } 228 return segs 229 } 230 231 func doGlob(gp globPattern, abort <-chan struct{}) ([]interface{}, error) { 232 but := make(map[string]struct{}) 233 for _, s := range gp.Buts { 234 but[s] = struct{}{} 235 } 236 237 vs := make([]interface{}, 0) 238 if !gp.Glob(func(pathInfo glob.PathInfo) bool { 239 select { 240 case <-abort: 241 logger.Println("glob aborted") 242 return false 243 default: 244 } 245 246 if _, ignore := but[pathInfo.Path]; ignore { 247 return true 248 } 249 250 if gp.TypeCb == nil || gp.TypeCb(pathInfo.Info.Mode()) { 251 vs = append(vs, pathInfo.Path) 252 } 253 return true 254 }) { 255 return nil, ErrInterrupted 256 } 257 if len(vs) == 0 && !gp.Flags.Has(noMatchOK) { 258 return nil, ErrWildcardNoMatch 259 } 260 return vs, nil 261 }