Skip to content

add groupByLabel function #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
* *`groupBy $containers $fieldPath`*: Groups an array of `RuntimeContainer` instances based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value, which must be a string. Returns a map from the value of the field path expression to an array of containers having that value. Containers that do not have a value for the field path in question are omitted.
* *`groupByKeys $containers $fieldPath`*: Returns the same as `groupBy` but only returns the keys of the map.
* *`groupByMulti $containers $fieldPath $sep`*: Like `groupBy`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. A container whose `$fieldPath` value contains a list of strings will show up in the map output under each of those strings.
* *`groupByLabel $containers $label`*: Returns the same as `groupBy` but grouping by the given label's value.
* *`hasPrefix $prefix $string`*: Returns whether `$prefix` is a prefix of `$string`.
* *`hasSuffix $suffix $string`*: Returns whether `$suffix` is a suffix of `$string`.
* *`intersect $slice1 $slice2`*: Returns the strings that exist in both string slices.
Expand Down
37 changes: 32 additions & 5 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func getArrayValues(funcName string, entries interface{}) (*reflect.Value, error
}

// Generalized groupBy function
func generalizedGroupBy(funcName string, entries interface{}, key string, addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
func generalizedGroupBy(funcName string, entries interface{}, getValue func(interface{}) (interface{}, error), addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
entriesVal, err := getArrayValues(funcName, entries)

if err != nil {
Expand All @@ -61,16 +61,26 @@ func generalizedGroupBy(funcName string, entries interface{}, key string, addEnt
groups := make(map[string][]interface{})
for i := 0; i < entriesVal.Len(); i++ {
v := reflect.Indirect(entriesVal.Index(i)).Interface()
value := deepGet(v, key)
value, err := getValue(v)
if err != nil {
return nil, err
}
if value != nil {
addEntry(groups, value, v)
}
}
return groups, nil
}

func generalizedGroupByKey(funcName string, entries interface{}, key string, addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
getKey := func(v interface{}) (interface{}, error) {
return deepGet(v, key), nil
}
return generalizedGroupBy(funcName, entries, getKey, addEntry)
}

func groupByMulti(entries interface{}, key, sep string) (map[string][]interface{}, error) {
return generalizedGroupBy("groupByMulti", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
return generalizedGroupByKey("groupByMulti", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
items := strings.Split(value.(string), sep)
for _, item := range items {
groups[item] = append(groups[item], v)
Expand All @@ -80,14 +90,14 @@ func groupByMulti(entries interface{}, key, sep string) (map[string][]interface{

// groupBy groups a generic array or slice by the path property key
func groupBy(entries interface{}, key string) (map[string][]interface{}, error) {
return generalizedGroupBy("groupBy", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
return generalizedGroupByKey("groupBy", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
}

// groupByKeys is the same as groupBy but only returns a list of keys
func groupByKeys(entries interface{}, key string) ([]string, error) {
keys, err := generalizedGroupBy("groupByKeys", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
keys, err := generalizedGroupByKey("groupByKeys", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})

Expand All @@ -102,6 +112,22 @@ func groupByKeys(entries interface{}, key string) ([]string, error) {
return ret, nil
}

// groupByLabel is the same as groupBy but over a given label
func groupByLabel(entries interface{}, label string) (map[string][]interface{}, error) {
getLabel := func(v interface{}) (interface{}, error) {
if container, ok := v.(RuntimeContainer); ok {
if value, ok := container.Labels[label]; ok {
return value, nil
}
return nil, nil
}
return nil, fmt.Errorf("Must pass an array or slice of RuntimeContainer to 'groupByLabel'; received %v", v)
}
return generalizedGroupBy("groupByLabel", entries, getLabel, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
}

// Generalized where function
func generalizedWhere(funcName string, entries interface{}, key string, test func(interface{}) bool) (interface{}, error) {
entriesVal, err := getArrayValues(funcName, entries)
Expand Down Expand Up @@ -396,6 +422,7 @@ func newTemplate(name string) *template.Template {
"groupBy": groupBy,
"groupByKeys": groupByKeys,
"groupByMulti": groupByMulti,
"groupByLabel": groupByLabel,
"hasPrefix": hasPrefix,
"hasSuffix": hasSuffix,
"json": marshalJson,
Expand Down
55 changes: 55 additions & 0 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,61 @@ func TestGroupByAfterWhere(t *testing.T) {
}
}

func TestGroupByLabel(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "one",
},
ID: "1",
},
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "two",
},
ID: "2",
},
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "one",
},
ID: "3",
},
&RuntimeContainer{
ID: "4",
},
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "",
},
ID: "5",
},
}

groups, err := groupByLabel(containers, "com.docker.compose.project")
if err != nil {
t.FailNow()
}

if len(groups) != 3 {
t.Fail()
}

if len(groups["one"]) != 2 {
t.Fail()
}
if len(groups[""]) != 1 {
t.Fail()
}

if len(groups["two"]) != 1 {
t.FailNow()
}
if groups["two"][0].(RuntimeContainer).ID != "2" {
t.Fail()
}
}

func TestGroupByMulti(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Expand Down