Skip to content

Commit

Permalink
Add additional template functions
Browse files Browse the repository at this point in the history
closest, coalesce, first, groupByKeys and dir
  • Loading branch information
jwilder committed Nov 26, 2014
1 parent 3abd5c0 commit 53af4c6
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 1 deletion.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion docker-gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ func (c *Context) Env() map[string]string {
env[parts[0]] = parts[1]
}
return env

}

func (c *ConfigFile) filterWatches() ConfigFile {
Expand Down
68 changes: 68 additions & 0 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

This comment has been minimized.

Copy link
@md5

md5 Nov 26, 2014

Contributor

FYI, I think I had a working polymorphic version of keys on a branch at some point. I'll try to dig it up.

This comment has been minimized.

Copy link
@md5

md5 Nov 26, 2014

Contributor

I just looked and I had a non-polymorphic version working for map[string]string. However, after using the reflect package in last (as you've now used it in first), I don't think that creating a polymorphic version of keys using the reflect package would be too hard.

This comment has been minimized.

Copy link
@jwilder

jwilder Nov 26, 2014

Author Collaborator

I initially tried to get a keys(map[string]interface{}) working but got blocked on type casting from within a template. If you had that working that would be really useful.

This comment has been minimized.

Copy link
@md5

md5 Nov 27, 2014

Contributor

See #39

func contains(item map[string]string, key string) bool {
if _, ok := item[key]; ok {
return true
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''")
}
}

0 comments on commit 53af4c6

Please sign in to comment.