-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
includes_excludes.go
183 lines (150 loc) · 5.14 KB
/
includes_excludes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
Copyright 2017 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package collections
import (
"strings"
"github.com/gobwas/glob"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
)
type globStringSet struct {
sets.String
}
func newGlobStringSet() globStringSet {
return globStringSet{sets.NewString()}
}
func (gss globStringSet) match(match string) bool {
for _, item := range gss.List() {
g, err := glob.Compile(item)
if err != nil {
return false
}
if g.Match(match) {
return true
}
}
return false
}
// IncludesExcludes is a type that manages lists of included
// and excluded items. The logic implemented is that everything
// in the included list except those items in the excluded list
// should be included. '*' in the includes list means "include
// everything", but it is not valid in the exclude list.
type IncludesExcludes struct {
includes globStringSet
excludes globStringSet
}
func NewIncludesExcludes() *IncludesExcludes {
return &IncludesExcludes{
includes: newGlobStringSet(),
excludes: newGlobStringSet(),
}
}
// Includes adds items to the includes list. '*' is a wildcard
// value meaning "include everything".
func (ie *IncludesExcludes) Includes(includes ...string) *IncludesExcludes {
ie.includes.Insert(includes...)
return ie
}
// GetIncludes returns the items in the includes list
func (ie *IncludesExcludes) GetIncludes() []string {
return ie.includes.List()
}
// Excludes adds items to the excludes list
func (ie *IncludesExcludes) Excludes(excludes ...string) *IncludesExcludes {
ie.excludes.Insert(excludes...)
return ie
}
// GetExcludes returns the items in the excludes list
func (ie *IncludesExcludes) GetExcludes() []string {
return ie.excludes.List()
}
// ShouldInclude returns whether the specified item should be
// included or not. Everything in the includes list except those
// items in the excludes list should be included.
func (ie *IncludesExcludes) ShouldInclude(s string) bool {
if ie.excludes.match(s) {
return false
}
// len=0 means include everything
return ie.includes.Len() == 0 || ie.includes.Has("*") || ie.includes.match(s)
}
// IncludesString returns a string containing all of the includes, separated by commas, or * if the
// list is empty.
func (ie *IncludesExcludes) IncludesString() string {
return asString(ie.GetIncludes(), "*")
}
// ExcludesString returns a string containing all of the excludes, separated by commas, or <none> if the
// list is empty.
func (ie *IncludesExcludes) ExcludesString() string {
return asString(ie.GetExcludes(), "<none>")
}
func asString(in []string, empty string) string {
if len(in) == 0 {
return empty
}
return strings.Join(in, ", ")
}
// IncludeEverything returns true if the includes list is empty or '*'
// and the excludes list is empty, or false otherwise.
func (ie *IncludesExcludes) IncludeEverything() bool {
return ie.excludes.Len() == 0 && (ie.includes.Len() == 0 || (ie.includes.Len() == 1 && ie.includes.Has("*")))
}
// ValidateIncludesExcludes checks provided lists of included and excluded
// items to ensure they are a valid set of IncludesExcludes data.
func ValidateIncludesExcludes(includesList, excludesList []string) []error {
// TODO we should not allow an IncludesExcludes object to be created that
// does not meet these criteria. Do a more significant refactoring to embed
// this logic in object creation/modification.
var errs []error
includes := sets.NewString(includesList...)
excludes := sets.NewString(excludesList...)
if includes.Len() > 1 && includes.Has("*") {
errs = append(errs, errors.New("includes list must either contain '*' only, or a non-empty list of items"))
}
if excludes.Has("*") {
errs = append(errs, errors.New("excludes list cannot contain '*'"))
}
for _, itm := range excludes.List() {
if includes.Has(itm) {
errs = append(errs, errors.Errorf("excludes list cannot contain an item in the includes list: %v", itm))
}
}
return errs
}
// GenerateIncludesExcludes constructs an IncludesExcludes struct by taking the provided
// include/exclude slices, applying the specified mapping function to each item in them,
// and adding the output of the function to the new struct. If the mapping function returns
// an empty string for an item, it is omitted from the result.
func GenerateIncludesExcludes(includes, excludes []string, mapFunc func(string) string) *IncludesExcludes {
res := NewIncludesExcludes()
for _, item := range includes {
if item == "*" {
res.Includes(item)
continue
}
key := mapFunc(item)
if key == "" {
continue
}
res.Includes(key)
}
for _, item := range excludes {
key := mapFunc(item)
if key == "" {
continue
}
res.Excludes(key)
}
return res
}