diff --git a/.travis.yml b/.travis.yml index 786f930..d08a5c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.8 + - 1.9 - tip install: - go get diff --git a/README.md b/README.md index 5239f94..26d587d 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,14 @@ Reuslt file `daemon.conf`: ``` +### BuiltIn Functions + +There is support for additional builtin functions that can be used. + +List with example: + + * file - `{{ file "example.txt" }}` + ## Installation ```bash diff --git a/builtin.go b/builtin.go new file mode 100644 index 0000000..761516b --- /dev/null +++ b/builtin.go @@ -0,0 +1,100 @@ +// The implementation is based of +// https://github.com/gliderlabs/sigil/blob/master/builtin/builtin.go +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + "io" +) + +type NamedReader struct { + io.Reader + Name string +} + +// Merge template.FuncMaps into a single map +// First entry win's +func Merge(ms ...template.FuncMap) template.FuncMap { + res := make(template.FuncMap) + for _, m := range ms { + srcMap: + for k, v := range m { + // Check if (k,v) was added before: + _, ok := res[k] + if ok { + continue srcMap + } + res[k] = v + } + } + return res +} + +// Extract data from the interface +func String(in interface{}) (string, string, bool) { + switch obj := in.(type) { + case string: + return obj, "", true + case NamedReader: + data, err := ioutil.ReadAll(obj) + if err != nil { + panic(err) + } + return string(data), obj.Name, true + case fmt.Stringer: + return obj.String(), "", true + default: + return "", "", false + } +} + +func LookPath(file string) (string, error) { + if strings.HasPrefix(file, "/") { + return file, nil + } + cwd, _ := os.Getwd() + search := []string{cwd} + for _, path := range search { + filepath := filepath.Join(path, file) + if _, err := os.Stat(filepath); err == nil { + return filepath, nil + } + } + return "", fmt.Errorf("Not found in path: %s", file) +} + +func builtInFunctions() template.FuncMap { + return template.FuncMap{ + "file": File, + } +} + +func read(file interface{}) ([]byte, error) { + reader, ok := file.(NamedReader) + if ok { + return ioutil.ReadAll(reader) + } + path, _, ok := String(file) + if !ok { + return []byte{}, fmt.Errorf("file must be stream or path string") + } + fp, err := LookPath(path) + if err != nil { + return []byte{}, err + } + data, err := ioutil.ReadFile(fp) + if err != nil { + return []byte{}, err + } + return data, nil +} + +func File(filename interface{}) (interface{}, error) { + str, err := read(filename) + return string(str), err +} diff --git a/template.go b/template.go index c08d5e7..e975ffc 100644 --- a/template.go +++ b/template.go @@ -15,7 +15,7 @@ type templateData struct { func createTemplate() *template.Template { tmpl := template.New("base") - tmpl.Funcs(sprig.TxtFuncMap()) + tmpl.Funcs(Merge(sprig.TxtFuncMap(), builtInFunctions())) tmpl.Option("missingkey=zero") return tmpl diff --git a/tests/template.test b/tests/template.test index 518f165..39bbd6e 100644 --- a/tests/template.test +++ b/tests/template.test @@ -136,3 +136,15 @@ Testing template with functions: $ cat test.txt bar FOO + +Testing template with reading from file: + + $ cat > test.txt < {{file "foo.txt"}} + > {{file .Arg.foo}} + > EOF + $ printf "FOO" > foo.txt + $ go-replace --mode=template -s foo -r foo.txt test.txt + $ cat test.txt + FOO + FOO