github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/process/filter.go (about) 1 package process 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/grafana/tanka/pkg/kubernetes/manifest" 9 "golang.org/x/text/cases" 10 "golang.org/x/text/language" 11 ) 12 13 // Filter returns all elements of the list that match at least one expression 14 // and are not ignored 15 func Filter(list manifest.List, exprs Matchers) manifest.List { 16 out := make(manifest.List, 0, len(list)) 17 for _, m := range list { 18 if !exprs.MatchString(m.KindName()) { 19 continue 20 } 21 if exprs.IgnoreString(m.KindName()) { 22 continue 23 } 24 out = append(out, m) 25 } 26 return out 27 } 28 29 // Matcher is a single filter expression. The passed argument of Matcher is of the 30 // form `kind/name` (manifest.KindName()) 31 type Matcher interface { 32 MatchString(string) bool 33 } 34 35 // Ignorer is like matcher, but for explicitly ignoring resources 36 type Ignorer interface { 37 IgnoreString(string) bool 38 } 39 40 // Matchers is a collection of multiple expressions. 41 // A matcher may also implement Ignorer to explicitly ignore fields 42 type Matchers []Matcher 43 44 // MatchString returns whether at least one expression (OR) matches the string 45 func (e Matchers) MatchString(s string) bool { 46 b := false 47 for _, exp := range e { 48 b = b || exp.MatchString(s) 49 } 50 return b 51 } 52 53 func (e Matchers) IgnoreString(s string) bool { 54 b := false 55 for _, exp := range e { 56 i, ok := exp.(Ignorer) 57 if !ok { 58 continue 59 } 60 b = b || i.IgnoreString(s) 61 } 62 return b 63 } 64 65 // RegExps is a helper to construct Matchers from regular expressions 66 func RegExps(rs []*regexp.Regexp) Matchers { 67 xprs := make(Matchers, 0, len(rs)) 68 for _, r := range rs { 69 xprs = append(xprs, r) 70 } 71 return xprs 72 } 73 74 func StrExps(strs ...string) (Matchers, error) { 75 exps := make(Matchers, 0, len(strs)) 76 for _, raw := range strs { 77 // trim exlamation mark, not supported by regex 78 s := fmt.Sprintf(`(?i)^%s$`, strings.TrimPrefix(raw, "!")) 79 80 // create regexp matcher 81 var exp Matcher 82 exp, err := regexp.Compile(s) 83 if err != nil { 84 return nil, ErrBadExpr{err} 85 } 86 87 // if negative (!), invert regex behaviour 88 if strings.HasPrefix(raw, "!") { 89 exp = NegMatcher{exp: exp} 90 } 91 exps = append(exps, exp) 92 } 93 return exps, nil 94 } 95 96 func MustStrExps(strs ...string) Matchers { 97 exps, err := StrExps(strs...) 98 if err != nil { 99 panic(err) 100 } 101 return exps 102 } 103 104 // ErrBadExpr occurs when the regexp compiling fails 105 type ErrBadExpr struct { 106 inner error 107 } 108 109 func (e ErrBadExpr) Error() string { 110 caser := cases.Title(language.English) 111 return fmt.Sprintf("%s.\nSee https://tanka.dev/output-filtering/#regular-expressions for details on regular expressions.", caser.String(e.inner.Error())) 112 } 113 114 // NexMatcher is a matcher that inverts the original behaviour 115 type NegMatcher struct { 116 exp Matcher 117 } 118 119 func (n NegMatcher) MatchString(_ string) bool { 120 return true 121 } 122 123 func (n NegMatcher) IgnoreString(s string) bool { 124 return n.exp.MatchString(s) 125 }