From 5c7c0a2406b0a325d5aaab513689c4dc6914084b Mon Sep 17 00:00:00 2001 From: krako Date: Thu, 13 Jan 2022 00:59:44 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + .goreleaser.yml | 27 ++++++ LICENSE | 19 ++++ README.md | 62 +++++++++++++ cli.go | 70 ++++++++++++++ context.go | 99 ++++++++++++++++++++ func.go | 178 ++++++++++++++++++++++++++++++++++++ go.mod | 21 +++++ go.sum | 41 +++++++++ inputs/_paths.txt.tmpl | 30 ++++++ inputs/file with spaces.txt | 5 + inputs/sample.txt.tmpl | 42 +++++++++ main.go | 33 +++++++ stores/data.json | 20 ++++ stores/data.yaml | 21 +++++ template.go | 66 +++++++++++++ 16 files changed, 736 insertions(+) create mode 100644 .gitignore create mode 100644 .goreleaser.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cli.go create mode 100644 context.go create mode 100644 func.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 inputs/_paths.txt.tmpl create mode 100644 inputs/file with spaces.txt create mode 100644 inputs/sample.txt.tmpl create mode 100644 main.go create mode 100644 stores/data.json create mode 100644 stores/data.yaml create mode 100644 template.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df51db1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist/ +tmpl diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..9abc54e --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,27 @@ +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 +archives: + - format: zip +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a0cfc0f --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 Krakozaure + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbd2521 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# tmpl + +`tmpl` allows to apply variables from JSON/TOML/YAML files, +environment variables or CLI arguments to template files using Golang +`text/template` and functions from the Sprig project. + +## Project status + +This project is in a very early stage. Things might break ! + +## Usage + +For simplicity, everything is output on `stdout`. To write the result in a +file, use shell redirection. + +If a variable is not found and the strict mode is disabled (default disabled), +each missing value will be replaced with an empty string. + +- Print help/usage message. + +```sh +# $ tmpl +# $ tmpl -h +$ tmpl --help +USAGE: tmpl [OPTIONS] INPUT + +INPUT is a template file or '-' for stdin + +OPTIONS: + -e load variables from environment (default true) + -f value + load variables from JSON/TOML/YAML files (format: file path) + -s exit on any error during template processing (default false) + -v value + use one or more variables from the command line (format: name=value) +``` + +- `stdin` and environment variables. + +```sh +$ echo "Editor = {{ .Env.EDITOR }}, Shell = {{ .Env.SHELL }}" | tmpl - +Editor = nvim, Shell = /bin/bash +``` + +- `stdin` and CLI variables. + +```sh +$ echo "Hello, {{ .foo }} !" | tmpl -v foo=bar - +Hello, bar ! +``` + +- Sample from this repository + +Output is not pasted here because of its length. + +```sh +$ tmpl -f ./stores/data.yaml ./inputs/sample.txt.tmpl +``` + +## Configuration + +No configuration needed. Everything is done on the command line. diff --git a/cli.go b/cli.go new file mode 100644 index 0000000..2252e6d --- /dev/null +++ b/cli.go @@ -0,0 +1,70 @@ +package main + +import ( + "flag" + "fmt" + "os" +) + +// Allow multiple string values for one flag +type stringsArray []string + +func (s *stringsArray) String() string { + return fmt.Sprintf("%v", *s) +} + +func (s *stringsArray) Set(value string) error { + *s = append(*s, value) + return nil +} + +// Flags +var ( + VarsList stringsArray + FilesList stringsArray + UseEnv bool + Strict bool +) + +func initFlags() { + + flag.BoolVar( + &UseEnv, + "e", + true, + "load variables from environment", + ) + flag.Var( + &FilesList, + "f", + "load variables from JSON/TOML/YAML files (format: file path)", + ) + flag.Var( + &VarsList, + "v", + "use one or more variables from the command line (format: name=value)", + ) + + flag.BoolVar( + &Strict, + "s", + false, + "exit on any error during template processing (default false)", + ) + + flag.Usage = func() { + fmt.Fprintf( + os.Stderr, + `USAGE: %s [OPTIONS] INPUT + +INPUT is a template file or '-' for stdin + +OPTIONS: +`, + os.Args[0], + ) + flag.PrintDefaults() + } + + flag.Parse() +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..6f9d6e5 --- /dev/null +++ b/context.go @@ -0,0 +1,99 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" + "gopkg.in/yaml.v2" +) + +var ctx = make(map[string]interface{}) + +func loadContext(input string) { + ctx["__includeDir__"] = getIncludeDir(input) + + if UseEnv { + ctx["Env"] = getEnvVariables() + } + + for _, file := range FilesList { + for k, v := range getFileVariables(file) { + ctx[k] = v + } + } + + for k, v := range getCliVariables() { + ctx[k] = v + } +} + +func getIncludeDir(input string) string { + // If input is stdin, + // template paths are relative to the current working directory + // else relative to the input directory + if input == "-" { + cwd, err := os.Getwd() + if err != nil { + return "." + } else { + return cwd + } + } else { + return filepath.Dir(input) + } +} + +func getEnvVariables() map[string]string { + vars := make(map[string]string) + for _, env := range os.Environ() { + kv := strings.SplitN(env, "=", 2) + vars[kv[0]] = kv[1] + } + return vars +} + +func getFileVariables(file string) map[string]interface{} { + vars := make(map[string]interface{}) + + bytes, err := ioutil.ReadFile(file) + if err != nil { + fmt.Fprint(os.Stderr, fmt.Errorf("unable to read file\n%v\n", err)) + return vars + } + + if strings.HasSuffix(file, ".json") { + err = json.Unmarshal(bytes, &vars) + } else if strings.HasSuffix(file, ".toml") { + err = toml.Unmarshal(bytes, &vars) + } else if strings.HasSuffix(file, ".yaml") || strings.HasSuffix(file, ".yml") { + err = yaml.Unmarshal(bytes, &vars) + } else { + err = fmt.Errorf("bad file type: %s", file) + } + if err != nil { + fmt.Fprint(os.Stderr, fmt.Errorf("unable to load data\n%v\n", err)) + } + return vars +} + +func getCliVariables() map[string]string { + vars := make(map[string]string) + for _, pair := range VarsList { + kv := strings.SplitN(pair, "=", 2) + + v := kv[1] + if strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"") { + v = v[1 : len(v)-1] + } else if strings.HasPrefix(v, "'") && strings.HasSuffix(v, "'") { + v = v[1 : len(v)-1] + } + + vars[kv[0]] = v + } + return vars +} diff --git a/func.go b/func.go new file mode 100644 index 0000000..df43804 --- /dev/null +++ b/func.go @@ -0,0 +1,178 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "text/template" + + "github.com/BurntSushi/toml" + "github.com/Masterminds/sprig" + "gopkg.in/yaml.v2" +) + +func getFuncMap() template.FuncMap { + f := sprig.GenericFuncMap() + + f["include"] = includeTemplate + + f["toBool"] = toBool + f["toToml"] = toToml + f["toYaml"] = toYaml + + f["absPath"] = absPath + f["isFile"] = isFile + f["isDir"] = isDir + f["fileExists"] = fileExists + f["fileMode"] = fileMode + f["fileSize"] = fileSize + f["fileMTime"] = fileMTime + f["fileRead"] = fileRead + + return f +} + +func includeTemplate(input string) string { + if !filepath.IsAbs(input) { + srcFileDir, ok := ctx["__includeDir__"].(string) + if ok { + input = filepath.Join(srcFileDir, input) + } + } + + outputString, err := templateExecute(input) + if err != nil { + if Strict { + panic(fmt.Errorf("unable to render included template\n%v\n", err)) + } + return "" + } + return outputString +} + +func toBool(value string) bool { + // 0/1, f/t, F/T, FALSE/TRUE, False/True, false/true + result, err := strconv.ParseBool(value) + if err != nil { + if Strict { + panic(err.Error()) + } + return false + } + return result +} + +func toYaml(v interface{}) string { + data, err := yaml.Marshal(v) + if err != nil { + if Strict { + panic(err.Error()) + } + return "" + } + return string(data) +} + +func toToml(v interface{}) string { + buf := bytes.NewBuffer(nil) + enc := toml.NewEncoder(buf) + err := enc.Encode(v) + if err != nil { + if Strict { + panic(err.Error()) + } + return "" + } + return buf.String() +} + +func absPath(file string) string { + new_file, err := filepath.Abs(file) + if err != nil { + if Strict { + panic(err.Error()) + } + return "" + } + return new_file +} + +func isDir(path string) bool { + info, err := os.Stat(path) + if os.IsNotExist(err) { + if Strict { + panic(err.Error()) + } + return false + } + return info.IsDir() +} + +func isFile(path string) bool { + info, err := os.Stat(path) + if os.IsNotExist(err) { + if Strict { + panic(err.Error()) + } + return false + } + return info.Mode().IsRegular() +} + +func fileExists(path string) bool { + _, err := os.Stat(path) + if os.IsNotExist(err) { + if Strict { + panic(err.Error()) + } + return false + } + return true +} + +func fileMode(path string) string { + info, err := os.Stat(path) + if os.IsNotExist(err) { + if Strict { + panic(err.Error()) + } + return "" + } + return info.Mode().String() +} + +func fileSize(file string) int64 { + info, err := os.Stat(file) + if err != nil { + if Strict { + panic(err.Error()) + } + return 0 + } + return info.Size() +} + +func fileMTime(file string) string { + info, err := os.Stat(file) + if err != nil { + if Strict { + panic(err.Error()) + } + return "" + } + return info.ModTime().String() +} + +func fileRead(file string) string { + data, err := ioutil.ReadFile(file) + if err != nil { + if Strict { + panic(err.Error()) + } + return "" + } + return string(data) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8bdd01b --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/krakozaure/tmpl + +go 1.17 + +require ( + github.com/BurntSushi/toml v1.0.0 + github.com/Masterminds/sprig v2.22.0+incompatible + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/stretchr/testify v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0a96885 --- /dev/null +++ b/go.sum @@ -0,0 +1,41 @@ +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/inputs/_paths.txt.tmpl b/inputs/_paths.txt.tmpl new file mode 100644 index 0000000..ff570ff --- /dev/null +++ b/inputs/_paths.txt.tmpl @@ -0,0 +1,30 @@ +#### Files/directories + +{{ with $x := expandenv "$HOME/.local/scripts" -}} +scripts: {{ $x }} +len(scripts): {{ $x | len }} +{{ end }} + +------------------------------------------------------- + +{{ range $p := .test_paths -}} +path: {{ $p }} +absPath: {{ absPath $p }} +isDir: {{ isDir $p }} +isFile: {{ isFile $p }} +fileExists: {{ fileExists $p }} +fileMode: {{ fileMode $p }} +fileSize: {{ fileSize $p }} +fileMTime: {{ fileMTime $p }} +--- +{{ end }} + +------------------------------------------------------- + +{{ with $x := "./inputs/file with spaces.txt" -}} +Content of "{{ $x }}" + +```raw +{{ fileRead $x -}} +``` +{{- end }} diff --git a/inputs/file with spaces.txt b/inputs/file with spaces.txt new file mode 100644 index 0000000..517d8ec --- /dev/null +++ b/inputs/file with spaces.txt @@ -0,0 +1,5 @@ +abc +def +ghi +jkl +mnopqrstuvwxyz diff --git a/inputs/sample.txt.tmpl b/inputs/sample.txt.tmpl new file mode 100644 index 0000000..ed62113 --- /dev/null +++ b/inputs/sample.txt.tmpl @@ -0,0 +1,42 @@ +### Environment variables + +HOME: {{ .Env.HOME }} +USER: {{ .Env.USER }} +EDITOR: {{ .Env.EDITOR }} + +### Nested data + +key1: {{ .map.key1 }} +key2: {{ .map.key2 }} +key3: {{ .map.key3 }} +missing: {{ .map.missing }} + +### Booleans from string + +0: {{ "0" | toBool }}, 1: {{ "1" | toBool }} +f: {{ "f" | toBool }}, t: {{ "t" | toBool }} +F: {{ "F" | toBool }}, T: {{ "T" | toBool }} +false: {{ "false" | toBool }}, true: {{ "true" | toBool }} +False: {{ "False" | toBool }}, True: {{ "True" | toBool }} +FALSE: {{ "FALSE" | toBool }}, TRUE: {{ "TRUE" | toBool }} + +### Data formats + +`.key` to YAML +{{ .key | toYaml }} + +`.key.sub_key[2]` to JSON +{{ index .key.sub_key 2 | toJson }} + +### Unsorted + +{{ with $t := "The quick brown fox jumps over the lazy dog." -}} +Print "{{ $t }}" in 5 columns wrapped lines. +{{ wrapWith 5 "\n" "The quick brown fox jumps over the lazy dog." }} +{{- end }} + +uuidv4: {{ uuidv4 }} + +### Included templates + +{{ include "_paths.txt.tmpl" -}} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3b499ae --- /dev/null +++ b/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" +) + +func main() { + + initFlags() + if flag.NArg() == 0 { + flag.Usage() + os.Exit(0) + } + if flag.NArg() > 1 { + panic("Only one input should be given") + } + + input := flag.Arg(0) + if input != "-" { + inputAbs, err := filepath.Abs(input) + if err != nil { + input = inputAbs + } + } + + err := templateRun(input) + if err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + } +} diff --git a/stores/data.json b/stores/data.json new file mode 100644 index 0000000..c7f850a --- /dev/null +++ b/stores/data.json @@ -0,0 +1,20 @@ +{ + "empty_sequence": [], + "sequence": ["a", "b", "c"], + "map": { + "key1": "value1", + "key2": "value2", + "key3": "value3" + }, + "key": { + "sub_key": [ + "abc", + "def", + { + "bar": "joe", + "foo": "bar" + } + ] + }, + "test_paths": [".", "./stores", "./main.go", "./missing_file"] +} diff --git a/stores/data.yaml b/stores/data.yaml new file mode 100644 index 0000000..2004774 --- /dev/null +++ b/stores/data.yaml @@ -0,0 +1,21 @@ +empty_sequence: [] + +sequence: ["a", "b", "c"] + +map: + key1: value1 + key2: value2 + key3: value3 + +key: + sub_key: + - abc + - def + - bar: joe + foo: bar + +test_paths: + - . + - ./stores + - ./main.go + - ./missing_file diff --git a/template.go b/template.go new file mode 100644 index 0000000..05e2fec --- /dev/null +++ b/template.go @@ -0,0 +1,66 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "text/template" +) + +func templateRun(input string) error { + outputString, err := templateExecute(input) + if err != nil { + return err + } + fmt.Print(outputString) + return nil +} + +func templateExecute(input string) (string, error) { + var ( + err error + outputBytes bytes.Buffer + outputString string + ) + + inputBytes, err := readInput(input) + if err != nil { + return "", fmt.Errorf("unable to read input %v\n%v\n", input, err) + } + + tmpl := template.New(input) + tmpl.Funcs(getFuncMap()) + + tmpl, err = tmpl.Parse(string(inputBytes)) + if err != nil { + return "", fmt.Errorf("unable to parse input\n%v\n", err) + } + + if len(ctx) == 0 { + loadContext(input) + } + + err = tmpl.Execute(&outputBytes, ctx) + if err != nil { + return "", fmt.Errorf("unable to render template\n%v\n", err) + } + + outputString = outputBytes.String() + outputString = strings.ReplaceAll(outputString, "", "") + return outputString, nil +} + +func readInput(input string) ([]byte, error) { + var ( + err error + inputBytes []byte + ) + if input == "-" { + inputBytes, err = ioutil.ReadAll(os.Stdin) + } else { + inputBytes, err = ioutil.ReadFile(input) + } + return inputBytes, err +}