| // 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 |
| } |