Skip to content

Commit e7eeee8

Browse files
authored
Merge pull request #382 from nginx-proxy/sort-functions
Sorting template functions
2 parents 1f294d3 + 675f804 commit e7eeee8

File tree

4 files changed

+200
-0
lines changed

4 files changed

+200
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
376376
* *`sha1 $string`*: Returns the hexadecimal representation of the SHA1 hash of `$string`.
377377
* *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split)
378378
* *`splitN $string $sep $count`*: Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`. Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN)
379+
* *`sortStringsAsc $strings`: Returns a slice of strings `$strings` sorted in ascending order.
380+
* *`sortStringsDesc $strings`: Returns a slice of strings `$strings` sorted in descending (reverse) order.
381+
* *`sortObjectsByKeysAsc $objects $fieldPath`: Returns the array `$objects`, sorted in ascending order based on the values of a field path expression `$fieldPath`.
382+
* *`sortObjectsByKeysDesc $objects $fieldPath`: Returns the array `$objects`, sorted in descending (reverse) order based on the values of a field path expression `$fieldPath`.
379383
* *`trimPrefix $prefix $string`*: If `$prefix` is a prefix of `$string`, return `$string` with `$prefix` trimmed from the beginning. Otherwise, return `$string` unchanged.
380384
* *`trimSuffix $suffix $string`*: If `$suffix` is a suffix of `$string`, return `$string` with `$suffix` trimmed from the end. Otherwise, return `$string` unchanged.
381385
* *`trim $string`*: Removes whitespace from both sides of `$string`.

internal/template/sort.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package template
2+
3+
import (
4+
"reflect"
5+
"sort"
6+
)
7+
8+
// sortStrings returns a sorted array of strings in increasing order
9+
func sortStringsAsc(values []string) []string {
10+
sort.Strings(values)
11+
return values
12+
}
13+
14+
// sortStringsDesc returns a sorted array of strings in decreasing order
15+
func sortStringsDesc(values []string) []string {
16+
sort.Sort(sort.Reverse(sort.StringSlice(values)))
17+
return values
18+
}
19+
20+
type sortable interface {
21+
sort.Interface
22+
set(string, interface{}) error
23+
get() []interface{}
24+
}
25+
26+
type sortableData struct {
27+
data []interface{}
28+
}
29+
30+
func (s sortableData) get() []interface{} {
31+
return s.data
32+
}
33+
34+
func (s sortableData) Len() int { return len(s.data) }
35+
36+
func (s sortableData) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
37+
38+
type sortableByKey struct {
39+
sortableData
40+
key string
41+
}
42+
43+
func (s *sortableByKey) set(funcName string, entries interface{}) (err error) {
44+
entriesVal, err := getArrayValues(funcName, entries)
45+
if err != nil {
46+
return
47+
}
48+
s.data = make([]interface{}, entriesVal.Len())
49+
for i := 0; i < entriesVal.Len(); i++ {
50+
s.data[i] = reflect.Indirect(entriesVal.Index(i)).Interface()
51+
}
52+
return
53+
}
54+
55+
// method required to implement sort.Interface
56+
func (s sortableByKey) Less(i, j int) bool {
57+
values := map[int]string{i: "", j: ""}
58+
for k := range values {
59+
if v := reflect.ValueOf(deepGet(s.data[k], s.key)); v.Kind() != reflect.Invalid {
60+
values[k] = v.Interface().(string)
61+
}
62+
}
63+
return values[i] < values[j]
64+
}
65+
66+
// Generalized SortBy function
67+
func generalizedSortBy(funcName string, entries interface{}, s sortable, reverse bool) (sorted []interface{}, err error) {
68+
err = s.set(funcName, entries)
69+
if err != nil {
70+
return nil, err
71+
}
72+
if reverse {
73+
sort.Stable(sort.Reverse(s))
74+
} else {
75+
sort.Stable(s)
76+
}
77+
return s.get(), nil
78+
}
79+
80+
// sortObjectsByKeysAsc returns a sorted array of objects, sorted by object's key field in ascending order
81+
func sortObjectsByKeysAsc(objs interface{}, key string) ([]interface{}, error) {
82+
s := &sortableByKey{key: key}
83+
return generalizedSortBy("sortObjsByKeys", objs, s, false)
84+
}
85+
86+
// sortObjectsByKeysDesc returns a sorted array of objects, sorted by object's key field in descending order
87+
func sortObjectsByKeysDesc(objs interface{}, key string) ([]interface{}, error) {
88+
s := &sortableByKey{key: key}
89+
return generalizedSortBy("sortObjsByKey", objs, s, true)
90+
}

internal/template/sort_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package template
2+
3+
import (
4+
"testing"
5+
6+
"github.com/nginx-proxy/docker-gen/internal/context"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestSortStringsAsc(t *testing.T) {
11+
strings := []string{"foo", "bar", "baz", "qux"}
12+
expected := []string{"bar", "baz", "foo", "qux"}
13+
assert.Equal(t, expected, sortStringsAsc(strings))
14+
}
15+
16+
func TestSortStringsDesc(t *testing.T) {
17+
strings := []string{"foo", "bar", "baz", "qux"}
18+
expected := []string{"qux", "foo", "baz", "bar"}
19+
assert.Equal(t, expected, sortStringsDesc(strings))
20+
}
21+
22+
func TestSortObjectsByKeysAsc(t *testing.T) {
23+
containers := []*context.RuntimeContainer{
24+
{
25+
Env: map[string]string{
26+
"VIRTUAL_HOST": "bar.localhost",
27+
},
28+
ID: "9",
29+
},
30+
{
31+
Env: map[string]string{
32+
"VIRTUAL_HOST": "foo.localhost",
33+
},
34+
ID: "1",
35+
},
36+
{
37+
Env: map[string]string{
38+
"VIRTUAL_HOST": "baz.localhost",
39+
},
40+
ID: "3",
41+
},
42+
{
43+
Env: map[string]string{},
44+
ID: "8",
45+
},
46+
}
47+
48+
sorted, err := sortObjectsByKeysAsc(containers, "ID")
49+
50+
assert.NoError(t, err)
51+
assert.Len(t, sorted, 4)
52+
assert.Equal(t, "foo.localhost", sorted[0].(context.RuntimeContainer).Env["VIRTUAL_HOST"])
53+
assert.Equal(t, "9", sorted[3].(context.RuntimeContainer).ID)
54+
55+
sorted, err = sortObjectsByKeysAsc(sorted, "Env.VIRTUAL_HOST")
56+
57+
assert.NoError(t, err)
58+
assert.Len(t, sorted, 4)
59+
assert.Equal(t, "foo.localhost", sorted[3].(context.RuntimeContainer).Env["VIRTUAL_HOST"])
60+
assert.Equal(t, "8", sorted[0].(context.RuntimeContainer).ID)
61+
}
62+
63+
func TestSortObjectsByKeysDesc(t *testing.T) {
64+
containers := []*context.RuntimeContainer{
65+
{
66+
Env: map[string]string{
67+
"VIRTUAL_HOST": "bar.localhost",
68+
},
69+
ID: "9",
70+
},
71+
{
72+
Env: map[string]string{
73+
"VIRTUAL_HOST": "foo.localhost",
74+
},
75+
ID: "1",
76+
},
77+
{
78+
Env: map[string]string{
79+
"VIRTUAL_HOST": "baz.localhost",
80+
},
81+
ID: "3",
82+
},
83+
{
84+
Env: map[string]string{},
85+
ID: "8",
86+
},
87+
}
88+
89+
sorted, err := sortObjectsByKeysDesc(containers, "ID")
90+
91+
assert.NoError(t, err)
92+
assert.Len(t, sorted, 4)
93+
assert.Equal(t, "bar.localhost", sorted[0].(context.RuntimeContainer).Env["VIRTUAL_HOST"])
94+
assert.Equal(t, "1", sorted[3].(context.RuntimeContainer).ID)
95+
96+
sorted, err = sortObjectsByKeysDesc(sorted, "Env.VIRTUAL_HOST")
97+
98+
assert.NoError(t, err)
99+
assert.Len(t, sorted, 4)
100+
assert.Equal(t, "", sorted[3].(context.RuntimeContainer).Env["VIRTUAL_HOST"])
101+
assert.Equal(t, "1", sorted[0].(context.RuntimeContainer).ID)
102+
}

internal/template/template.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func newTemplate(name string) *template.Template {
6767
"sha1": hashSha1,
6868
"split": strings.Split,
6969
"splitN": strings.SplitN,
70+
"sortStringsAsc": sortStringsAsc,
71+
"sortStringsDesc": sortStringsDesc,
72+
"sortObjectsByKeysAsc": sortObjectsByKeysAsc,
73+
"sortObjectsByKeysDesc": sortObjectsByKeysDesc,
7074
"trimPrefix": trimPrefix,
7175
"trimSuffix": trimSuffix,
7276
"trim": trim,

0 commit comments

Comments
 (0)