From ec6cfbaaaccbe695d3c091c45e13ea0211baacd8 Mon Sep 17 00:00:00 2001 From: sh0rez Date: Mon, 10 Aug 2020 17:50:54 +0200 Subject: [PATCH] feat(cli): support negative filters Output filtering (`-t`) now supports excluding objects, by prepending an exlamation mark (`!`) before the regular expression --- docs/docs/targets.md | 11 +++++++++ pkg/process/filter.go | 46 ++++++++++++++++++++++++++++++++++++- pkg/process/process_test.go | 9 ++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/docs/docs/targets.md b/docs/docs/targets.md index 6b8edb253..e8fdb270e 100644 --- a/docs/docs/targets.md +++ b/docs/docs/targets.md @@ -78,3 +78,14 @@ apiVersion: apps/v1 kind: Deployment # ... ``` + +## Excluding + +Sometimes it may be desirably to exclude a single object, instead of including all others. + +To do so, prepend the regular expression with an exclamation mark (`!`), like so: + +```bash +# filter out all Deployments +$ tk show . -t '!deployment/.*' +``` diff --git a/pkg/process/filter.go b/pkg/process/filter.go index 8e8d7e79b..6b2b8c5d7 100644 --- a/pkg/process/filter.go +++ b/pkg/process/filter.go @@ -9,12 +9,16 @@ import ( ) // Filter returns all elements of the list that match at least one expression +// and are not ignored func Filter(list manifest.List, exprs Matchers) manifest.List { out := make(manifest.List, 0, len(list)) for _, m := range list { if !exprs.MatchString(m.KindName()) { continue } + if exprs.IgnoreString(m.KindName()) { + continue + } out = append(out, m) } return out @@ -26,7 +30,13 @@ type Matcher interface { MatchString(string) bool } +// Ignorer is like matcher, but for explicitely ignoring resources +type Ignorer interface { + IgnoreString(string) bool +} + // Matchers is a collection of multiple expressions. +// A matcher may also implement Ignorer to explicitely ignore fields type Matchers []Matcher // MatchString returns whether at least one expression (OR) matches the string @@ -38,6 +48,18 @@ func (e Matchers) MatchString(s string) bool { return b } +func (e Matchers) IgnoreString(s string) bool { + b := false + for _, exp := range e { + i, ok := exp.(Ignorer) + if !ok { + continue + } + b = b || i.IgnoreString(s) + } + return b +} + // RegExps is a helper to construct Matchers from regular expressions func RegExps(rs []*regexp.Regexp) Matchers { xprs := make(Matchers, 0, len(rs)) @@ -50,11 +72,20 @@ func RegExps(rs []*regexp.Regexp) Matchers { func StrExps(strs ...string) (Matchers, error) { exps := make(Matchers, 0, len(strs)) for _, raw := range strs { - s := fmt.Sprintf(`(?i)^%s$`, raw) + // trim exlamation mark, not supported by regex + s := fmt.Sprintf(`(?i)^%s$`, strings.TrimPrefix(raw, "!")) + + // create regexp matcher + var exp Matcher exp, err := regexp.Compile(s) if err != nil { return nil, ErrBadExpr{err} } + + // if negative (!), invert regex behaviour + if strings.HasPrefix(raw, "!") { + exp = NegMatcher{exp: exp} + } exps = append(exps, exp) } return exps, nil @@ -76,3 +107,16 @@ type ErrBadExpr struct { func (e ErrBadExpr) Error() string { return fmt.Sprintf("%s.\nSee https://tanka.dev/output-filtering/#regular-expressions for details on regular expressions.", strings.Title(e.inner.Error())) } + +// NexMatcher is a matcher that inverts the original behaviour +type NegMatcher struct { + exp Matcher +} + +func (n NegMatcher) MatchString(s string) bool { + return true +} + +func (n NegMatcher) IgnoreString(s string) bool { + return n.exp.MatchString(s) +} diff --git a/pkg/process/process_test.go b/pkg/process/process_test.go index 5014b3805..cf0461593 100644 --- a/pkg/process/process_test.go +++ b/pkg/process/process_test.go @@ -64,6 +64,15 @@ func TestProcess(t *testing.T) { `DePlOyMeNt/GrAfAnA`, ), }, + { + name: "targets-negative", + deep: testDataDeep().Deep, + flat: manifest.List{ + testDataDeep().Flat[".app.web.frontend.nodejs.express.service"], + testDataDeep().Flat[".app.namespace"], + }, + targets: MustStrExps(`!deployment/.*`), + }, { name: "unwrap-list", deep: loadFixture("list").Deep,