/vendor/github.com/docker/engine-api/types/filters/parse.go
https://gitlab.com/admin-github-cloud/quayctl · Go · 257 lines · 175 code · 36 blank · 46 comment · 52 complexity · f891a05fe97dbf074c8108aa19922eb0 MD5 · raw file
- // Package filters provides helper function to parse and handle command line
- // filter, used for example in docker ps or docker images commands.
- package filters
- import (
- "encoding/json"
- "errors"
- "fmt"
- "regexp"
- "strings"
- )
- // Args stores filter arguments as map key:{map key: bool}.
- // It contains a aggregation of the map of arguments (which are in the form
- // of -f 'key=value') based on the key, and store values for the same key
- // in an map with string keys and boolean values.
- // e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu'
- // the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}}
- type Args struct {
- fields map[string]map[string]bool
- }
- // NewArgs initializes a new Args struct.
- func NewArgs() Args {
- return Args{fields: map[string]map[string]bool{}}
- }
- // ParseFlag parses the argument to the filter flag. Like
- //
- // `docker ps -f 'created=today' -f 'image.name=ubuntu*'`
- //
- // If prev map is provided, then it is appended to, and returned. By default a new
- // map is created.
- func ParseFlag(arg string, prev Args) (Args, error) {
- filters := prev
- if len(arg) == 0 {
- return filters, nil
- }
- if !strings.Contains(arg, "=") {
- return filters, ErrBadFormat
- }
- f := strings.SplitN(arg, "=", 2)
- name := strings.ToLower(strings.TrimSpace(f[0]))
- value := strings.TrimSpace(f[1])
- filters.Add(name, value)
- return filters, nil
- }
- // ErrBadFormat is an error returned in case of bad format for a filter.
- var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
- // ToParam packs the Args into an string for easy transport from client to server.
- func ToParam(a Args) (string, error) {
- // this way we don't URL encode {}, just empty space
- if a.Len() == 0 {
- return "", nil
- }
- buf, err := json.Marshal(a.fields)
- if err != nil {
- return "", err
- }
- return string(buf), nil
- }
- // FromParam unpacks the filter Args.
- func FromParam(p string) (Args, error) {
- if len(p) == 0 {
- return NewArgs(), nil
- }
- r := strings.NewReader(p)
- d := json.NewDecoder(r)
- m := map[string]map[string]bool{}
- if err := d.Decode(&m); err != nil {
- r.Seek(0, 0)
- // Allow parsing old arguments in slice format.
- // Because other libraries might be sending them in this format.
- deprecated := map[string][]string{}
- if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
- m = deprecatedArgs(deprecated)
- } else {
- return NewArgs(), err
- }
- }
- return Args{m}, nil
- }
- // Get returns the list of values associates with a field.
- // It returns a slice of strings to keep backwards compatibility with old code.
- func (filters Args) Get(field string) []string {
- values := filters.fields[field]
- if values == nil {
- return make([]string, 0)
- }
- slice := make([]string, 0, len(values))
- for key := range values {
- slice = append(slice, key)
- }
- return slice
- }
- // Add adds a new value to a filter field.
- func (filters Args) Add(name, value string) {
- if _, ok := filters.fields[name]; ok {
- filters.fields[name][value] = true
- } else {
- filters.fields[name] = map[string]bool{value: true}
- }
- }
- // Del removes a value from a filter field.
- func (filters Args) Del(name, value string) {
- if _, ok := filters.fields[name]; ok {
- delete(filters.fields[name], value)
- }
- }
- // Len returns the number of fields in the arguments.
- func (filters Args) Len() int {
- return len(filters.fields)
- }
- // MatchKVList returns true if the values for the specified field matches the ones
- // from the sources.
- // e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
- // field is 'label' and sources are {'label1': '1', 'label2': '2'}
- // it returns true.
- func (filters Args) MatchKVList(field string, sources map[string]string) bool {
- fieldValues := filters.fields[field]
- //do not filter if there is no filter set or cannot determine filter
- if len(fieldValues) == 0 {
- return true
- }
- if sources == nil || len(sources) == 0 {
- return false
- }
- for name2match := range fieldValues {
- testKV := strings.SplitN(name2match, "=", 2)
- v, ok := sources[testKV[0]]
- if !ok {
- return false
- }
- if len(testKV) == 2 && testKV[1] != v {
- return false
- }
- }
- return true
- }
- // Match returns true if the values for the specified field matches the source string
- // e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
- // field is 'image.name' and source is 'ubuntu'
- // it returns true.
- func (filters Args) Match(field, source string) bool {
- if filters.ExactMatch(field, source) {
- return true
- }
- fieldValues := filters.fields[field]
- for name2match := range fieldValues {
- match, err := regexp.MatchString(name2match, source)
- if err != nil {
- continue
- }
- if match {
- return true
- }
- }
- return false
- }
- // ExactMatch returns true if the source matches exactly one of the filters.
- func (filters Args) ExactMatch(field, source string) bool {
- fieldValues, ok := filters.fields[field]
- //do not filter if there is no filter set or cannot determine filter
- if !ok || len(fieldValues) == 0 {
- return true
- }
- // try to march full name value to avoid O(N) regular expression matching
- if fieldValues[source] {
- return true
- }
- return false
- }
- // FuzzyMatch returns true if the source matches exactly one of the filters,
- // or the source has one of the filters as a prefix.
- func (filters Args) FuzzyMatch(field, source string) bool {
- if filters.ExactMatch(field, source) {
- return true
- }
- fieldValues := filters.fields[field]
- for prefix := range fieldValues {
- if strings.HasPrefix(source, prefix) {
- return true
- }
- }
- return false
- }
- // Include returns true if the name of the field to filter is in the filters.
- func (filters Args) Include(field string) bool {
- _, ok := filters.fields[field]
- return ok
- }
- // Validate ensures that all the fields in the filter are valid.
- // It returns an error as soon as it finds an invalid field.
- func (filters Args) Validate(accepted map[string]bool) error {
- for name := range filters.fields {
- if !accepted[name] {
- return fmt.Errorf("Invalid filter '%s'", name)
- }
- }
- return nil
- }
- // WalkValues iterates over the list of filtered values for a field.
- // It stops the iteration if it finds an error and it returns that error.
- func (filters Args) WalkValues(field string, op func(value string) error) error {
- if _, ok := filters.fields[field]; !ok {
- return nil
- }
- for v := range filters.fields[field] {
- if err := op(v); err != nil {
- return err
- }
- }
- return nil
- }
- func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
- m := map[string]map[string]bool{}
- for k, v := range d {
- values := map[string]bool{}
- for _, vv := range v {
- values[vv] = true
- }
- m[k] = values
- }
- return m
- }