github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/types/label_filter.go (about) 1 package types 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 ) 8 9 var DEBUG_LABEL_FILTER_PARSING = false 10 11 type LabelFilter func([]string) bool 12 13 func matchLabelAction(label string) LabelFilter { 14 expected := strings.ToLower(label) 15 return func(labels []string) bool { 16 for i := range labels { 17 if strings.ToLower(labels[i]) == expected { 18 return true 19 } 20 } 21 return false 22 } 23 } 24 25 func matchLabelRegexAction(regex *regexp.Regexp) LabelFilter { 26 return func(labels []string) bool { 27 for i := range labels { 28 if regex.MatchString(labels[i]) { 29 return true 30 } 31 } 32 return false 33 } 34 } 35 36 func notAction(filter LabelFilter) LabelFilter { 37 return func(labels []string) bool { return !filter(labels) } 38 } 39 40 func andAction(a, b LabelFilter) LabelFilter { 41 return func(labels []string) bool { return a(labels) && b(labels) } 42 } 43 44 func orAction(a, b LabelFilter) LabelFilter { 45 return func(labels []string) bool { return a(labels) || b(labels) } 46 } 47 48 type lfToken uint 49 50 const ( 51 lfTokenInvalid lfToken = iota 52 53 lfTokenRoot 54 lfTokenOpenGroup 55 lfTokenCloseGroup 56 lfTokenNot 57 lfTokenAnd 58 lfTokenOr 59 lfTokenRegexp 60 lfTokenLabel 61 lfTokenEOF 62 ) 63 64 func (l lfToken) Precedence() int { 65 switch l { 66 case lfTokenRoot, lfTokenOpenGroup: 67 return 0 68 case lfTokenOr: 69 return 1 70 case lfTokenAnd: 71 return 2 72 case lfTokenNot: 73 return 3 74 } 75 return -1 76 } 77 78 func (l lfToken) String() string { 79 switch l { 80 case lfTokenRoot: 81 return "ROOT" 82 case lfTokenOpenGroup: 83 return "(" 84 case lfTokenCloseGroup: 85 return ")" 86 case lfTokenNot: 87 return "!" 88 case lfTokenAnd: 89 return "&&" 90 case lfTokenOr: 91 return "||" 92 case lfTokenRegexp: 93 return "/regexp/" 94 case lfTokenLabel: 95 return "label" 96 case lfTokenEOF: 97 return "EOF" 98 } 99 return "INVALID" 100 } 101 102 type treeNode struct { 103 token lfToken 104 location int 105 value string 106 107 parent *treeNode 108 leftNode *treeNode 109 rightNode *treeNode 110 } 111 112 func (tn *treeNode) setRightNode(node *treeNode) { 113 tn.rightNode = node 114 node.parent = tn 115 } 116 117 func (tn *treeNode) setLeftNode(node *treeNode) { 118 tn.leftNode = node 119 node.parent = tn 120 } 121 122 func (tn *treeNode) firstAncestorWithPrecedenceLEQ(precedence int) *treeNode { 123 if tn.token.Precedence() <= precedence { 124 return tn 125 } 126 return tn.parent.firstAncestorWithPrecedenceLEQ(precedence) 127 } 128 129 func (tn *treeNode) firstUnmatchedOpenNode() *treeNode { 130 if tn.token == lfTokenOpenGroup { 131 return tn 132 } 133 if tn.parent == nil { 134 return nil 135 } 136 return tn.parent.firstUnmatchedOpenNode() 137 } 138 139 func (tn *treeNode) constructLabelFilter(input string) (LabelFilter, error) { 140 switch tn.token { 141 case lfTokenOpenGroup: 142 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, "Mismatched '(' - could not find matching ')'.") 143 case lfTokenLabel: 144 return matchLabelAction(tn.value), nil 145 case lfTokenRegexp: 146 re, err := regexp.Compile(tn.value) 147 if err != nil { 148 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("RegExp compilation error: %s", err)) 149 } 150 return matchLabelRegexAction(re), nil 151 } 152 153 if tn.rightNode == nil { 154 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, -1, "Unexpected EOF.") 155 } 156 rightLF, err := tn.rightNode.constructLabelFilter(input) 157 if err != nil { 158 return nil, err 159 } 160 161 switch tn.token { 162 case lfTokenRoot, lfTokenCloseGroup: 163 return rightLF, nil 164 case lfTokenNot: 165 return notAction(rightLF), nil 166 } 167 168 if tn.leftNode == nil { 169 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Malformed tree - '%s' is missing left operand.", tn.token)) 170 } 171 leftLF, err := tn.leftNode.constructLabelFilter(input) 172 if err != nil { 173 return nil, err 174 } 175 176 switch tn.token { 177 case lfTokenAnd: 178 return andAction(leftLF, rightLF), nil 179 case lfTokenOr: 180 return orAction(leftLF, rightLF), nil 181 } 182 183 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Invalid token '%s'.", tn.token)) 184 } 185 186 func (tn *treeNode) tokenString() string { 187 out := fmt.Sprintf("<%s", tn.token) 188 if tn.value != "" { 189 out += " | " + tn.value 190 } 191 out += ">" 192 return out 193 } 194 195 func (tn *treeNode) toString(indent int) string { 196 out := tn.tokenString() + "\n" 197 if tn.leftNode != nil { 198 out += fmt.Sprintf("%s |_(L)_%s", strings.Repeat(" ", indent), tn.leftNode.toString(indent+1)) 199 } 200 if tn.rightNode != nil { 201 out += fmt.Sprintf("%s |_(R)_%s", strings.Repeat(" ", indent), tn.rightNode.toString(indent+1)) 202 } 203 return out 204 } 205 206 func tokenize(input string) func() (*treeNode, error) { 207 runes, i := []rune(input), 0 208 209 peekIs := func(r rune) bool { 210 if i+1 < len(runes) { 211 return runes[i+1] == r 212 } 213 return false 214 } 215 216 consumeUntil := func(cutset string) (string, int) { 217 j := i 218 for ; j < len(runes); j++ { 219 if strings.IndexRune(cutset, runes[j]) >= 0 { 220 break 221 } 222 } 223 return string(runes[i:j]), j - i 224 } 225 226 return func() (*treeNode, error) { 227 for i < len(runes) && runes[i] == ' ' { 228 i += 1 229 } 230 231 if i >= len(runes) { 232 return &treeNode{token: lfTokenEOF}, nil 233 } 234 235 node := &treeNode{location: i} 236 switch runes[i] { 237 case '&': 238 if !peekIs('&') { 239 return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Invalid token '&'. Did you mean '&&'?") 240 } 241 i += 2 242 node.token = lfTokenAnd 243 case '|': 244 if !peekIs('|') { 245 return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Invalid token '|'. Did you mean '||'?") 246 } 247 i += 2 248 node.token = lfTokenOr 249 case '!': 250 i += 1 251 node.token = lfTokenNot 252 case ',': 253 i += 1 254 node.token = lfTokenOr 255 case '(': 256 i += 1 257 node.token = lfTokenOpenGroup 258 case ')': 259 i += 1 260 node.token = lfTokenCloseGroup 261 case '/': 262 i += 1 263 value, n := consumeUntil("/") 264 i += n + 1 265 node.token, node.value = lfTokenRegexp, value 266 default: 267 value, n := consumeUntil("&|!,()/") 268 i += n 269 node.token, node.value = lfTokenLabel, strings.TrimSpace(value) 270 } 271 return node, nil 272 } 273 } 274 275 func ParseLabelFilter(input string) (LabelFilter, error) { 276 if DEBUG_LABEL_FILTER_PARSING { 277 fmt.Println("\n==============") 278 fmt.Println("Input: ", input) 279 fmt.Print("Tokens: ") 280 } 281 nextToken := tokenize(input) 282 283 root := &treeNode{token: lfTokenRoot} 284 current := root 285 LOOP: 286 for { 287 node, err := nextToken() 288 if err != nil { 289 return nil, err 290 } 291 292 if DEBUG_LABEL_FILTER_PARSING { 293 fmt.Print(node.tokenString() + " ") 294 } 295 296 switch node.token { 297 case lfTokenEOF: 298 break LOOP 299 case lfTokenLabel, lfTokenRegexp: 300 if current.rightNode != nil { 301 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Found two adjacent labels. You need an operator between them.") 302 } 303 current.setRightNode(node) 304 case lfTokenNot, lfTokenOpenGroup: 305 if current.rightNode != nil { 306 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Invalid token '%s'.", node.token)) 307 } 308 current.setRightNode(node) 309 current = node 310 case lfTokenAnd, lfTokenOr: 311 if current.rightNode == nil { 312 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Operator '%s' missing left hand operand.", node.token)) 313 } 314 nodeToStealFrom := current.firstAncestorWithPrecedenceLEQ(node.token.Precedence()) 315 node.setLeftNode(nodeToStealFrom.rightNode) 316 nodeToStealFrom.setRightNode(node) 317 current = node 318 case lfTokenCloseGroup: 319 firstUnmatchedOpenNode := current.firstUnmatchedOpenNode() 320 if firstUnmatchedOpenNode == nil { 321 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Mismatched ')' - could not find matching '('.") 322 } 323 if firstUnmatchedOpenNode == current && current.rightNode == nil { 324 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Found empty '()' group.") 325 } 326 firstUnmatchedOpenNode.token = lfTokenCloseGroup //signify the group is now closed 327 current = firstUnmatchedOpenNode.parent 328 default: 329 return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Unknown token '%s'.", node.token)) 330 } 331 } 332 if DEBUG_LABEL_FILTER_PARSING { 333 fmt.Printf("\n Tree:\n%s", root.toString(0)) 334 } 335 return root.constructLabelFilter(input) 336 } 337 338 func ValidateAndCleanupLabel(label string, cl CodeLocation) (string, error) { 339 out := strings.TrimSpace(label) 340 if out == "" { 341 return "", GinkgoErrors.InvalidEmptyLabel(cl) 342 } 343 if strings.ContainsAny(out, "&|!,()/") { 344 return "", GinkgoErrors.InvalidLabel(label, cl) 345 } 346 return out, nil 347 }