package filters import ( "fmt" "io" "github.com/containerd/containerd/errdefs" "github.com/pkg/errors" ) /* Parse the strings into a filter that may be used with an adaptor. The filter is made up of zero or more selectors. The format is a comma separated list of expressions, in the form of `<fieldpath><op><value>`, known as selectors. All selectors must match the target object for the filter to be true. We define the operators "==" for equality, "!=" for not equal and "~=" for a regular expression. If the operator and value are not present, the matcher will test for the presence of a value, as defined by the target object. The formal grammar is as follows: selectors := selector ("," selector)* selector := fieldpath (operator value) fieldpath := field ('.' field)* field := quoted | [A-Za-z] [A-Za-z0-9_]+ operator := "==" | "!=" | "~=" value := quoted | [^\s,]+ quoted := <go string syntax> */ func Parse(s string) (Filter, error) { // special case empty to match all if s == "" { return Always, nil } p := parser{input: s} return p.parse() } // ParseAll parses each filter in ss and returns a filter that will return true // if any filter matches the expression. // // If no filters are provided, the filter will match anything. func ParseAll(ss ...string) (Filter, error) { if len(ss) == 0 { return Always, nil } var fs []Filter for _, s := range ss { f, err := Parse(s) if err != nil { return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error()) } fs = append(fs, f) } return Any(fs), nil } type parser struct { input string scanner scanner } func (p *parser) parse() (Filter, error) { p.scanner.init(p.input) ss, err := p.selectors() if err != nil { return nil, errors.Wrap(err, "filters") } return ss, nil } func (p *parser) selectors() (Filter, error) { s, err := p.selector() if err != nil { return nil, err } ss := All{s} loop: for { tok := p.scanner.peek() switch tok { case ',': pos, tok, _ := p.scanner.scan() if tok != tokenSeparator { return nil, p.mkerr(pos, "expected a separator") } s, err := p.selector() if err != nil { return nil, err } ss = append(ss, s) case tokenEOF: break loop default: return nil, p.mkerr(p.scanner.ppos, "unexpected input: %v", string(tok)) } } return ss, nil } func (p *parser) selector() (selector, error) { fieldpath, err := p.fieldpath() if err != nil { return selector{}, err } switch p.scanner.peek() { case ',', tokenSeparator, tokenEOF: return selector{ fieldpath: fieldpath, operator: operatorPresent, }, nil } op, err := p.operator() if err != nil { return selector{}, err } var allowAltQuotes bool if op == operatorMatches { allowAltQuotes = true } value, err := p.value(allowAltQuotes) if err != nil { if err == io.EOF { return selector{}, io.ErrUnexpectedEOF } return selector{}, err } return selector{ fieldpath: fieldpath, value: value, operator: op, }, nil } func (p *parser) fieldpath() ([]string, error) { f, err := p.field() if err != nil { return nil, err } fs := []string{f} loop: for { tok := p.scanner.peek() // lookahead to consume field separator switch tok { case '.': pos, tok, _ := p.scanner.scan() // consume separator if tok != tokenSeparator { return nil, p.mkerr(pos, "expected a field separator (`.`)") } f, err := p.field() if err != nil { return nil, err } fs = append(fs, f) default: // let the layer above handle the other bad cases. break loop } } return fs, nil } func (p *parser) field() (string, error) { pos, tok, s := p.scanner.scan() switch tok { case tokenField: return s, nil case tokenQuoted: return p.unquote(pos, s, false) } return "", p.mkerr(pos, "expected field or quoted") } func (p *parser) operator() (operator, error) { pos, tok, s := p.scanner.scan() switch tok { case tokenOperator: switch s { case "==": return operatorEqual, nil case "!=": return operatorNotEqual, nil case "~=": return operatorMatches, nil default: return 0, p.mkerr(pos, "unsupported operator %q", s) } } return 0, p.mkerr(pos, `expected an operator ("=="|"!="|"~=")`) } func (p *parser) value(allowAltQuotes bool) (string, error) { pos, tok, s := p.scanner.scan() switch tok { case tokenValue, tokenField: return s, nil case tokenQuoted: return p.unquote(pos, s, allowAltQuotes) } return "", p.mkerr(pos, "expected value or quoted") } func (p *parser) unquote(pos int, s string, allowAlts bool) (string, error) { if !allowAlts && s[0] != '\'' && s[0] != '"' { return "", p.mkerr(pos, "invalid quote encountered") } uq, err := unquote(s) if err != nil { return "", p.mkerr(pos, "unquoting failed: %v", err) } return uq, nil } type parseError struct { input string pos int msg string } func (pe parseError) Error() string { if pe.pos < len(pe.input) { before := pe.input[:pe.pos] location := pe.input[pe.pos : pe.pos+1] // need to handle end after := pe.input[pe.pos+1:] return fmt.Sprintf("[%s >|%s|< %s]: %v", before, location, after, pe.msg) } return fmt.Sprintf("[%s]: %v", pe.input, pe.msg) } func (p *parser) mkerr(pos int, format string, args ...interface{}) error { return errors.Wrap(parseError{ input: p.input, pos: pos, msg: fmt.Sprintf(format, args...), }, "parse error") }