multivalued requirement parser more whitespace tolerant

pull/6/head
Meir Fischer 2014-11-23 21:55:34 -05:00
parent 162e4983b9
commit 763e48bc6f
2 changed files with 27 additions and 16 deletions

View File

@ -19,6 +19,7 @@ package labels
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"regexp"
"sort" "sort"
"strings" "strings"
@ -303,9 +304,11 @@ func (lsel *LabelSelector) String() (string, error) {
// The input will cause an error if it does not follow this form: // The input will cause an error if it does not follow this form:
// //
// <selector-syntax> ::= <requirement> | <requirement> "," <selector-syntax> // <selector-syntax> ::= <requirement> | <requirement> "," <selector-syntax>
// <requirement> ::= KEY <set-restriction> // <requirement> ::= WHITESPACE_OPT KEY <set-restriction>
// <set-restriction> ::= "" | <inclusion-exclusion> <value-set> // <set-restriction> ::= "" | <inclusion-exclusion> <value-set>
// <inclusion-exclusion> ::= " in " | " not in " // <inclusion-exclusion> ::= <inclusion> | <exclusion>
// <exclusion> ::= WHITESPACE "not" <inclusion>
// <inclusion> ::= WHITESPACE "in" WHITESPACE
// <value-set> ::= "(" <values> ")" // <value-set> ::= "(" <values> ")"
// <values> ::= VALUE | VALUE "," <values> // <values> ::= VALUE | VALUE "," <values>
// //
@ -313,6 +316,10 @@ func (lsel *LabelSelector) String() (string, error) {
// [^, ]+ // [^, ]+
// VALUE is a sequence of zero or more characters that does not contain ',', ' ' or ')' // VALUE is a sequence of zero or more characters that does not contain ',', ' ' or ')'
// [^, )]* // [^, )]*
// WHITESPACE_OPT is a sequence of zero or more whitespace characters
// \s*
// WHITESPACE is a sequence of one or more whitespace characters
// \s+
// //
// Example of valid syntax: // Example of valid syntax:
// "x in (foo,,baz),y,z not in ()" // "x in (foo,,baz),y,z not in ()"
@ -338,9 +345,15 @@ func Parse(selector string) (SetBasedSelector, error) {
waitOp waitOp
inVals inVals
) )
const inPre = "in ("
const notInPre = "not in ("
const pos = "position %d:%s" const pos = "position %d:%s"
inRegex, errIn := regexp.Compile("^\\s*in\\s+\\(")
if errIn != nil {
return nil, errIn
}
notInRegex, errNotIn := regexp.Compile("^\\s*not\\s+in\\s+\\(")
if errNotIn != nil {
return nil, errNotIn
}
state := startReq state := startReq
strStart := 0 strStart := 0
@ -350,8 +363,7 @@ func Parse(selector string) (SetBasedSelector, error) {
switch selector[i] { switch selector[i] {
case ',': case ',':
return nil, fmt.Errorf("a requirement can't be empty. "+pos, i, selector) return nil, fmt.Errorf("a requirement can't be empty. "+pos, i, selector)
case ' ': case ' ', '\t', '\n', '\f', '\r':
return nil, fmt.Errorf("white space not allowed before key. "+pos, i, selector)
default: default:
state = inKey state = inKey
strStart = i strStart = i
@ -365,17 +377,17 @@ func Parse(selector string) (SetBasedSelector, error) {
} else { } else {
items = append(items, *req) items = append(items, *req)
} }
case ' ': case ' ', '\t', '\n', '\f', '\r':
state = waitOp state = waitOp
key = selector[strStart:i] key = selector[strStart:i]
} }
case waitOp: case waitOp:
if len(selector)-i >= len(inPre) && selector[i:len(inPre)+i] == inPre { if loc := inRegex.FindStringIndex(selector[i:]); loc != nil {
op = In op = In
i += len(inPre) - 1 i += loc[1] - loc[0] - 1
} else if len(selector)-i >= len(notInPre) && selector[i:len(notInPre)+i] == notInPre { } else if loc = notInRegex.FindStringIndex(selector[i:]); loc != nil {
op = NotIn op = NotIn
i += len(notInPre) - 1 i += loc[1] - loc[0] - 1
} else { } else {
return nil, fmt.Errorf("expected \" in (\"/\" not in (\" after key. "+pos, i, selector) return nil, fmt.Errorf("expected \" in (\"/\" not in (\" after key. "+pos, i, selector)
} }

View File

@ -309,16 +309,16 @@ func TestSetSelectorParser(t *testing.T) {
Valid bool Valid bool
}{ }{
{"", &LabelSelector{Requirements: nil}, true, true}, {"", &LabelSelector{Requirements: nil}, true, true},
{"x", &LabelSelector{Requirements: []Requirement{ {"\rx", &LabelSelector{Requirements: []Requirement{
getRequirement("x", Exists, nil, t), getRequirement("x", Exists, nil, t),
}}, true, true}, }}, true, true},
{"foo in (abc)", &LabelSelector{Requirements: []Requirement{ {"foo in (abc)", &LabelSelector{Requirements: []Requirement{
getRequirement("foo", In, util.NewStringSet("abc"), t), getRequirement("foo", In, util.NewStringSet("abc"), t),
}}, true, true}, }}, true, true},
{"x not in (abc)", &LabelSelector{Requirements: []Requirement{ {"x not\n\tin (abc)", &LabelSelector{Requirements: []Requirement{
getRequirement("x", NotIn, util.NewStringSet("abc"), t), getRequirement("x", NotIn, util.NewStringSet("abc"), t),
}}, true, true}, }}, true, true},
{"x not in (abc,def)", &LabelSelector{Requirements: []Requirement{ {"x not in \t (abc,def)", &LabelSelector{Requirements: []Requirement{
getRequirement("x", NotIn, util.NewStringSet("abc", "def"), t), getRequirement("x", NotIn, util.NewStringSet("abc", "def"), t),
}}, true, true}, }}, true, true},
{"x in (abc,def)", &LabelSelector{Requirements: []Requirement{ {"x in (abc,def)", &LabelSelector{Requirements: []Requirement{
@ -342,7 +342,6 @@ func TestSetSelectorParser(t *testing.T) {
}}, false, true}, }}, false, true},
{"x,,y", nil, true, false}, {"x,,y", nil, true, false},
{",x,y", nil, true, false}, {",x,y", nil, true, false},
{"x, y", nil, true, false},
{"x nott in (y)", nil, true, false}, {"x nott in (y)", nil, true, false},
{"x not in ( )", nil, true, false}, {"x not in ( )", nil, true, false},
{"x not in (, a)", nil, true, false}, {"x not in (, a)", nil, true, false},