|  | // Package paramtools provides Params and ParamSet. | 
|  | package paramtools | 
|  |  | 
|  | import ( | 
|  | "encoding/json" | 
|  | "sort" | 
|  | "strings" | 
|  |  | 
|  | "go.skia.org/infra/go/sets" | 
|  | "go.skia.org/infra/go/skerr" | 
|  | "go.skia.org/infra/go/util" | 
|  | ) | 
|  |  | 
|  | // Params is a set of key,value pairs. | 
|  | type Params map[string]string | 
|  |  | 
|  | // ParamSet is a set of keys and the possible values that the keys could have. I.e. | 
|  | // the []string should contain no duplicates. | 
|  | type ParamSet map[string][]string | 
|  |  | 
|  | // ReadOnlyParamSet is a ParamSet that doesn't allow offer any mutating methods. | 
|  | // | 
|  | // Note that you can still modify the map, but hopefully the name along with the | 
|  | // removal of the mutating methods will catch most of the mis-uses. | 
|  | type ReadOnlyParamSet map[string][]string | 
|  |  | 
|  | // NewParams returns the parsed structured key (see query) as Params. | 
|  | // | 
|  | // It presumes a valid key, i.e. something that passed query.IsValid. | 
|  | func NewParams(key string) Params { | 
|  | ret := Params{} | 
|  | parts := strings.Split(key, ",") | 
|  | parts = parts[1 : len(parts)-1] | 
|  | for _, s := range parts { | 
|  | pair := strings.SplitN(s, "=", 2) | 
|  | ret[pair[0]] = pair[1] | 
|  | } | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // Add adds each set of Params in order to this Params. | 
|  | // | 
|  | // Values in p will be overwritten. | 
|  | func (p Params) Add(b ...Params) { | 
|  | for _, oneMap := range b { | 
|  | for k, v := range oneMap { | 
|  | p[k] = v | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Copy returns a copy of the Params. | 
|  | func (p Params) Copy() Params { | 
|  | ret := make(Params, len(p)) | 
|  | for k, v := range p { | 
|  | ret[k] = v | 
|  | } | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // Equal returns true if this Params equals a. | 
|  | func (p Params) Equal(a Params) bool { | 
|  | if len(a) != len(p) { | 
|  | return false | 
|  | } | 
|  | // Since they are the same size we only need to check from one side, i.e. | 
|  | // compare a's values to p's values. | 
|  | for k, v := range a { | 
|  | if bv, ok := p[k]; !ok || bv != v { | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | // Keys returns the keys of the Params. | 
|  | func (p Params) Keys() []string { | 
|  | ret := make([]string, 0, len(p)) | 
|  | for v := range p { | 
|  | ret = append(ret, v) | 
|  | } | 
|  |  | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // NewParamSet returns a new ParamSet initialized with the given maps of parameters. | 
|  | func NewParamSet(ps ...Params) ParamSet { | 
|  | ret := ParamSet{} | 
|  | for _, onePS := range ps { | 
|  | ret.AddParams(onePS) | 
|  | } | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // NewReadOnlyParamSet returns a new ReadOnlyParamSet initialized with the given | 
|  | // maps of parameters. | 
|  | func NewReadOnlyParamSet(ps ...Params) ReadOnlyParamSet { | 
|  | return ReadOnlyParamSet(NewParamSet(ps...)) | 
|  | } | 
|  |  | 
|  | // AddParams adds the Params to this ParamSet. | 
|  | func (p ParamSet) AddParams(ps Params) { | 
|  | for k, v := range ps { | 
|  | // You might be tempted to replace this with | 
|  | // sort.SearchStrings(), but that's actually slower for short | 
|  | // slices. The breakpoint seems to around 50, and since most | 
|  | // of our ParamSet lists are short that ends up being slower. | 
|  | params := p[k] | 
|  | if !util.In(v, params) { | 
|  | p[k] = append(params, v) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // AddParamsFromKey is the same as calling | 
|  | // | 
|  | //	paramset.AddParams(NewParams(key)) | 
|  | // | 
|  | // but without creating the intermediate Params. | 
|  | // | 
|  | // It presumes a valid key, i.e. something that passed query.ValidateKey. | 
|  | func (p ParamSet) AddParamsFromKey(key string) { | 
|  | parts := strings.Split(key, ",") | 
|  | parts = parts[1 : len(parts)-1] | 
|  | for _, s := range parts { | 
|  | pair := strings.SplitN(s, "=", 2) | 
|  | params := p[pair[0]] | 
|  | if !util.In(pair[1], params) { | 
|  | p[pair[0]] = append(params, pair[1]) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // AddParamSet adds the ParamSet or ReadOnlyParamSet to this ParamSet. | 
|  | func (p ParamSet) AddParamSet(ps map[string][]string) { | 
|  | for k, arr := range ps { | 
|  | if _, ok := p[k]; !ok { | 
|  | p[k] = append([]string{}, arr...) | 
|  | } else { | 
|  | for _, v := range arr { | 
|  | if !util.In(v, p[k]) { | 
|  | p[k] = append(p[k], v) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Equal returns true if the given Paramset contain exactly the same keys and associated | 
|  | // values as this one. Side Effect: both ParamSets will be normalized after this call (their | 
|  | // values will be sorted) if they have the same number of keys. | 
|  | func (p ParamSet) Equal(right map[string][]string) bool { | 
|  | if len(p) != len(right) { | 
|  | return false | 
|  | } | 
|  | p.Normalize() | 
|  | ParamSet(right).Normalize() | 
|  | for k, leftValues := range p { | 
|  | rightValues, ok := right[k] | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  | // Due to normalize, we expect leftValues and rightValues to be in sorted order | 
|  | if len(leftValues) != len(rightValues) { | 
|  | return false | 
|  | } | 
|  | for i := range leftValues { | 
|  | if leftValues[i] != rightValues[i] { | 
|  | return false | 
|  | } | 
|  | } | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | // Keys returns the keys of the ReadOnlyParamSet. | 
|  | func (p ReadOnlyParamSet) Keys() []string { | 
|  | ret := make([]string, 0, len(p)) | 
|  | for v := range p { | 
|  | ret = append(ret, v) | 
|  | } | 
|  |  | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // ToString returns a json string representation of the paramset. | 
|  | func (p ReadOnlyParamSet) ToString() (string, error) { | 
|  | b, err := json.Marshal(p) | 
|  | if err != nil { | 
|  | return "", err | 
|  | } | 
|  |  | 
|  | return string(b), nil | 
|  | } | 
|  |  | 
|  | // FromString creates a new paramset object from json string. | 
|  | func FromString(jsonStr string) (ParamSet, error) { | 
|  | var ps ParamSet | 
|  | err := json.Unmarshal([]byte(jsonStr), &ps) | 
|  | return ps, err | 
|  | } | 
|  |  | 
|  | // Keys returns the keys of the ParamSet. | 
|  | func (p ParamSet) Keys() []string { | 
|  | return ReadOnlyParamSet(p).Keys() | 
|  | } | 
|  |  | 
|  | // Copy returns a copy of the ParamSet. | 
|  | func (p ParamSet) Copy() ParamSet { | 
|  | ret := ParamSet{} | 
|  | for k, v := range p { | 
|  | newV := make([]string, len(v), len(v)) | 
|  | copy(newV, v) | 
|  | ret[k] = newV | 
|  | } | 
|  |  | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // FrozenCopy returns a copy of the ParamSet as a ReadOnlyParamSet. | 
|  | func (p ParamSet) FrozenCopy() ReadOnlyParamSet { | 
|  | return ReadOnlyParamSet(p.Copy()) | 
|  | } | 
|  |  | 
|  | // Freeze returns the ReadOnlyParamSet version of the ParamSet. | 
|  | // | 
|  | // It is up to the caller to make sure the original ParamSet is not modified, or | 
|  | // call FrozenCopy() instead. | 
|  | func (p ParamSet) Freeze() ReadOnlyParamSet { | 
|  | return ReadOnlyParamSet(p) | 
|  | } | 
|  |  | 
|  | // Normalize all the values by sorting them. | 
|  | func (p ParamSet) Normalize() { | 
|  | for _, arr := range p { | 
|  | sort.Strings(arr) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Matches returns true if the params in 'p' match the sets given in 'right'. | 
|  | // For every key in 'p' there has to be a matching key in 'right' and the | 
|  | // intersection of their values must be not empty. | 
|  | func (p ReadOnlyParamSet) Matches(right ReadOnlyParamSet) bool { | 
|  | for key, vals := range p { | 
|  | rightVals, ok := right[key] | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  |  | 
|  | found := false | 
|  | for _, targetVal := range vals { | 
|  | if util.In(targetVal, rightVals) { | 
|  | found = true | 
|  | break | 
|  | } | 
|  | } | 
|  | if !found { | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | // Matches returns true if the params in 'p' match the sets given in 'right'. | 
|  | // For every key in 'p' there has to be a matching key in 'right' and the | 
|  | // intersection of their values must be not empty. | 
|  | func (p ParamSet) Matches(right ParamSet) bool { | 
|  | return ReadOnlyParamSet(p).Matches(ReadOnlyParamSet(right)) | 
|  | } | 
|  |  | 
|  | // CartesianProduct returns a channel of Params that represent the Cartesian | 
|  | // Product of all the values for the given keys. | 
|  | func (p ReadOnlyParamSet) CartesianProduct(keys []string) (<-chan Params, error) { | 
|  | ret := make(chan Params) | 
|  | counts := make([]int, len(keys)) | 
|  | for i, key := range keys { | 
|  | counts[i] = len(p[key]) | 
|  | } | 
|  | cpChan, err := sets.CartesianProduct(counts) | 
|  | if err != nil { | 
|  | close(ret) | 
|  | return nil, skerr.Wrapf(err, "can not make cartesian product") | 
|  | } | 
|  |  | 
|  | go func() { | 
|  | for indices := range cpChan { | 
|  | v := Params{} | 
|  | for i, n := range indices { | 
|  | v[keys[i]] = p[keys[i]][n] | 
|  | } | 
|  | ret <- v | 
|  | } | 
|  | close(ret) | 
|  | }() | 
|  |  | 
|  | return ret, nil | 
|  | } | 
|  |  | 
|  | // CartesianProduct returns a channel of Params that represent the Cartesian | 
|  | // Product of all the values for the given keys. | 
|  | func (p ParamSet) CartesianProduct(keys []string) (<-chan Params, error) { | 
|  | return ReadOnlyParamSet(p).CartesianProduct(keys) | 
|  | } | 
|  |  | 
|  | // MatchesParams returns true if the params in 'p' match the values given in | 
|  | // 'right'. For every key in 'p' there has to be a matching key in 'right' and | 
|  | // the intersection of their values must be not empty. | 
|  | func (p ReadOnlyParamSet) MatchesParams(right Params) bool { | 
|  | for key, vals := range p { | 
|  | rightVal, ok := right[key] | 
|  | if !ok { | 
|  | return false | 
|  | } | 
|  |  | 
|  | found := false | 
|  | for _, targetVal := range vals { | 
|  | if targetVal == rightVal { | 
|  | found = true | 
|  | break | 
|  | } | 
|  | } | 
|  | if !found { | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | // MatchesParams returns true if the params in 'p' match the values given in | 
|  | // 'right'. For every key in 'p' there has to be a matching key in 'right' and | 
|  | // the intersection of their values must be not empty. | 
|  | func (p ParamSet) MatchesParams(right Params) bool { | 
|  | return ReadOnlyParamSet(p).MatchesParams(right) | 
|  | } | 
|  |  | 
|  | // Size returns the total number of values in the ReadOnlyParamSet. | 
|  | func (p ReadOnlyParamSet) Size() int { | 
|  | var ret int | 
|  | for _, vals := range p { | 
|  | ret += len(vals) | 
|  | } | 
|  | return ret | 
|  | } | 
|  |  | 
|  | // Size returns the total number of values in the ParamSet. | 
|  | func (p ParamSet) Size() int { | 
|  | return ReadOnlyParamSet(p).Size() | 
|  | } | 
|  |  | 
|  | // ParamMatcher is a list of Paramsets that can be matched against. The primary | 
|  | // purpose is to match against a set of rules, e.g. ignore rules. | 
|  | type ParamMatcher []ParamSet | 
|  |  | 
|  | // MatchAny returns true if the given ParamSet matches any of the rules in the matcher. | 
|  | func (p ParamMatcher) MatchAny(params ParamSet) bool { | 
|  | for _, oneRule := range p { | 
|  | if oneRule.Matches(params) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // MatchAnyParams returns true if the given Params matches any of the rules in the matcher. | 
|  | func (p ParamMatcher) MatchAnyParams(params Params) bool { | 
|  | for _, oneRule := range p { | 
|  | if oneRule.MatchesParams(params) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } |