| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package matcher |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "fmt" |
| 8 | "strings" |
| 9 | ) |
| 10 | |
| 11 | type ( |
| 12 | simplePatternTerm struct { |
| 13 | matcher Matcher |
| 14 | positive bool |
| 15 | } |
| 16 | |
| 17 | // simplePatternsMatcher patterns. |
| 18 | simplePatternsMatcher []simplePatternTerm |
| 19 | ) |
| 20 | |
| 21 | // NewSimplePatternsMatcher creates new simple patterns. It returns error in case one of patterns has bad syntax. |
| 22 | func NewSimplePatternsMatcher(expr string) (Matcher, error) { |
| 23 | ps := simplePatternsMatcher{} |
| 24 | |
| 25 | for pattern := range strings.FieldsSeq(expr) { |
| 26 | positive := true |
| 27 | if strings.HasPrefix(pattern, "!") { |
| 28 | positive = false |
| 29 | pattern = strings.TrimPrefix(pattern, "!") |
| 30 | } |
| 31 | if err := ps.add(pattern, positive); err != nil { |
| 32 | return nil, err |
| 33 | } |
| 34 | } |
| 35 | if len(ps) == 0 { |
| 36 | return FALSE(), nil |
| 37 | } |
| 38 | return ps, nil |
| 39 | } |
| 40 | |
| 41 | // NewSimplePatternListMatcher creates a simple-patterns matcher from a pre-split |
| 42 | // list of glob patterns. Use it when individual patterns may contain whitespace. |
| 43 | func NewSimplePatternListMatcher(patterns []string) (Matcher, error) { |
| 44 | ps := simplePatternsMatcher{} |
| 45 | hasPositive := false |
| 46 | |
| 47 | for _, pattern := range patterns { |
| 48 | pattern = strings.TrimSpace(pattern) |
| 49 | if pattern == "" { |
| 50 | continue |
| 51 | } |
| 52 | negative := strings.HasPrefix(pattern, "!") |
| 53 | if negative { |
| 54 | pattern = strings.TrimSpace(strings.TrimPrefix(pattern, "!")) |
| 55 | } |
| 56 | if pattern == "" { |
| 57 | return nil, errors.New("invalid empty negative pattern") |
| 58 | } |
| 59 | hasPositive = hasPositive || !negative |
| 60 | if err := ps.add(pattern, !negative); err != nil { |
| 61 | return nil, fmt.Errorf("invalid pattern: %w", err) |
| 62 | } |
| 63 | } |
| 64 | if len(ps) == 0 { |
| 65 | return FALSE(), nil |
| 66 | } |
| 67 | if !hasPositive { |
| 68 | return nil, errors.New("must include at least one positive pattern") |
| 69 | } |
| 70 | return ps, nil |
| 71 | } |
| 72 | |
| 73 | func (m *simplePatternsMatcher) add(term string, positive bool) error { |
| 74 | p := simplePatternTerm{positive: positive} |
| 75 | matcher, err := NewGlobMatcher(term) |
| 76 | if err != nil { |
| 77 | return err |
| 78 | } |
| 79 | |
| 80 | p.matcher = matcher |
| 81 | *m = append(*m, p) |
| 82 | |
| 83 | return nil |
| 84 | } |
| 85 | |
| 86 | func (m simplePatternsMatcher) Match(b []byte) bool { |
| 87 | return m.MatchString(string(b)) |
| 88 | } |
| 89 | |
| 90 | // MatchString matches. |
| 91 | func (m simplePatternsMatcher) MatchString(line string) bool { |
| 92 | for _, p := range m { |
| 93 | if p.matcher.MatchString(line) { |
| 94 | return p.positive |
| 95 | } |
| 96 | } |
| 97 | return false |
| 98 | } |