diff --git a/README.md b/README.md index 31e987f6..10f4b7b7 100644 --- a/README.md +++ b/README.md @@ -105,9 +105,14 @@ Within those templates, the object emitted by docker-gen will have [this structu #### Functions +* *`closest $array $value`: Returns the longest matching substring in `$array` that matches `$value` +* *`coalesce ...`*: Returns the first non-nil argument. * *`contains $map $key`*: Returns `true` if `$map` contains `$key`. Takes maps from `string` to `string`. +* *`dir $path`: Returns an array of filenames in the specified `$path`. * *`exists $path`*: Returns `true` if `$path` refers to an existing file or directory. Takes a string. +* *`first $array`*: Returns the first value of an array or nil if the arry is nil or empty. * *`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. * *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split) * *`replace $string $old $new $count`*: Replaces up to `$count` occurences of `$old` with `$new` in `$string`. Alias for [`strings.Replace`](http://golang.org/pkg/strings/#Replace) diff --git a/docker-gen.go b/docker-gen.go index 300d1a3e..ebb66261 100644 --- a/docker-gen.go +++ b/docker-gen.go @@ -93,7 +93,6 @@ func (c *Context) Env() map[string]string { env[parts[0]] = parts[1] } return env - } func (c *ConfigFile) filterWatches() ConfigFile { diff --git a/template.go b/template.go index 4fa9bc46..0c8f593d 100644 --- a/template.go +++ b/template.go @@ -43,6 +43,7 @@ func groupByMulti(entries []*RuntimeContainer, key, sep string) map[string][]*Ru return groups } +// groupBy groups a list of *RuntimeContainers by the path property key func groupBy(entries []*RuntimeContainer, key string) map[string][]*RuntimeContainer { groups := make(map[string][]*RuntimeContainer) for _, v := range entries { @@ -54,6 +55,16 @@ func groupBy(entries []*RuntimeContainer, key string) map[string][]*RuntimeConta return groups } +// groupByKeys is the same as groupBy but only returns a list of keys +func groupByKeys(entries []*RuntimeContainer, key string) []string { + groups := groupBy(entries, key) + ret := []string{} + for k, _ := range groups { + ret = append(ret, k) + } + return ret +} + func contains(item map[string]string, key string) bool { if _, ok := item[key]; ok { return true @@ -91,24 +102,81 @@ func marshalJson(input interface{}) (string, error) { return strings.TrimSuffix(buf.String(), "\n"), nil } +// arrayFirst returns first item in the array or nil if the +// input is nil or empty +func arrayFirst(input interface{}) interface{} { + if input == nil { + return nil + } + + arr := reflect.ValueOf(input) + + if arr.Len() == 0 { + return nil + } + + return arr.Index(0).Interface() +} + +// arrayLast returns last item in the array func arrayLast(input interface{}) interface{} { arr := reflect.ValueOf(input) return arr.Index(arr.Len() - 1).Interface() } +// arrayClosest find the longest matching substring in values +// that matches input +func arrayClosest(values []string, input string) string { + best := "" + for _, v := range values { + if strings.Contains(input, v) && len(v) > len(best) { + best = v + } + } + return best +} + +// dirList returns a list of files in the specified path +func dirList(path string) ([]string, error) { + names := []string{} + files, err := ioutil.ReadDir(path) + if err != nil { + return names, err + } + for _, f := range files { + names = append(names, f.Name()) + } + return names, nil +} + +// coalesce returns the first non nil argument +func coalesce(input ...interface{}) interface{} { + for _, v := range input { + if v != nil { + return v + } + } + return nil +} + func generateFile(config Config, containers Context) bool { templatePath := config.Template tmpl, err := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{ + "closest": arrayClosest, + "coalesce": coalesce, "contains": contains, "exists": exists, + "first": arrayFirst, "groupBy": groupBy, "groupByMulti": groupByMulti, + "groupByKeys": groupByKeys, "split": strings.Split, "replace": strings.Replace, "dict": dict, "sha1": hashSha1, "json": marshalJson, "last": arrayLast, + "dir": dirList, }).ParseFiles(templatePath) if err != nil { log.Fatalf("unable to parse template: %s", err) diff --git a/template_test.go b/template_test.go index 9f62381c..1fe40bb3 100644 --- a/template_test.go +++ b/template_test.go @@ -183,3 +183,21 @@ func TestJson(t *testing.T) { t.Fatal("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded)) } } + +func TestArrayClosestExact(t *testing.T) { + if arrayClosest([]string{"foo.bar.com", "bar.com"}, "foo.bar.com") != "foo.bar.com" { + t.Fatal("Expected foo.bar.com") + } +} + +func TestArrayClosestSubstring(t *testing.T) { + if arrayClosest([]string{"foo.fo.com", "bar.com"}, "foo.bar.com") != "bar.com" { + t.Fatal("Expected bar.com") + } +} + +func TestArrayClosestNoMatch(t *testing.T) { + if arrayClosest([]string{"foo.fo.com", "bip.com"}, "foo.bar.com") != "" { + t.Fatal("Expected ''") + } +}