├── .gitignore ├── LICENSE ├── README.md ├── cmd └── templatectl │ └── main.go ├── go.mod └── internal └── template ├── template.go └── template_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/templatectl/templatectl 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fatih Arslan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # templatectl 2 | Simple templating CLI 3 | 4 | ## Install 5 | 6 | ``` 7 | GO111MODULE=on go get github.com/fatih/templatectl/cmd/templatectl@latest 8 | ``` 9 | 10 | ## Usage 11 | 12 | 13 | ```sh 14 | # By default templatectl prints to stdout 15 | echo 'This is {{ env "ENV_FOO" }}' > input.tmpl 16 | export ENV_FOO="foo" 17 | $ templatectl --input input.tmpl 18 | This is foo 19 | 20 | # Or output to a file 21 | $ templatectl --input input.tmpl --output templated.txt 22 | $ cat templated.txt 23 | This is foo 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /cmd/templatectl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | 11 | "github.com/fatih/templatectl/internal/template" 12 | ) 13 | 14 | func main() { 15 | if err := realMain(); err != nil { 16 | log.Fatalln(err) 17 | } 18 | } 19 | 20 | func realMain() error { 21 | input := flag.String("input", "", "File path to process") 22 | output := flag.String("output", "", "Path to save the processed template file(optional)") 23 | flag.Parse() 24 | 25 | if *input == "" { 26 | return errors.New("usage: templatectl --input file.tmpl --output exported.txt") 27 | } 28 | 29 | t := template.NewTemplate() 30 | buf, err := t.ExecuteFile(*input) 31 | if err != nil { 32 | return fmt.Errorf("error executing file %q template: %s", *input, err) 33 | } 34 | 35 | if *output != "" { 36 | info, err := os.Stat(*input) 37 | if err != nil { 38 | return fmt.Errorf("error retrieving file info %q: %s", *output, err) 39 | } 40 | 41 | err = ioutil.WriteFile(*output, []byte(buf), info.Mode()) 42 | if err != nil { 43 | return fmt.Errorf("error saving processed file %q: %s", *output, err) 44 | } 45 | } else { 46 | fmt.Print(buf) 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fatih/templatectl 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /internal/template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "text/template" 9 | ) 10 | 11 | // NewTemplate returns a new template instance 12 | func NewTemplate() *Template { 13 | return &Template{} 14 | } 15 | 16 | // Template is responsible for producing files from the given files 17 | type Template struct{} 18 | 19 | // ExecuteFile executes the given template file and returns the final result 20 | func (t *Template) ExecuteFile(file string) (string, error) { 21 | f, err := ioutil.ReadFile(file) 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | return t.Execute(string(f)) 27 | } 28 | 29 | // Execute executes the given template and returns the final result 30 | func (t *Template) Execute(in string) (string, error) { 31 | funcMap := template.FuncMap{ 32 | "env": func(key string) (string, error) { 33 | val, found := os.LookupEnv(key) 34 | if !found { 35 | return "", fmt.Errorf("environment variable %q is not defined", key) 36 | } 37 | return val, nil 38 | }, 39 | } 40 | 41 | tmpl, err := template.New("file"). 42 | Funcs(funcMap). 43 | Parse(string(in)) 44 | if err != nil { 45 | return "", err 46 | } 47 | 48 | var buf bytes.Buffer 49 | err = tmpl.Execute(&buf, nil) 50 | if err != nil { 51 | return "", err 52 | } 53 | 54 | return buf.String(), nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/template/template_test.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestTemplate_Execute(t *testing.T) { 9 | test := []struct { 10 | name string 11 | in string 12 | out string 13 | envs map[string]string 14 | valid bool 15 | }{ 16 | { 17 | name: "template with environment variable", 18 | envs: map[string]string{ 19 | "SOME_ENV_VARIABLE_FOO": "foo", 20 | }, 21 | in: `This is: {{ env "SOME_ENV_VARIABLE_FOO" }}`, 22 | out: `This is: foo`, 23 | valid: true, 24 | }, 25 | { 26 | name: "template with no environment variable defined", 27 | in: `This is: {{ env "SOME_ENV_VARIABLE_FOO" }}`, 28 | }, 29 | } 30 | 31 | for _, ts := range test { 32 | t.Run(ts.name, func(t *testing.T) { 33 | for key, val := range ts.envs { 34 | if err := os.Setenv(key, val); err != nil { 35 | t.Fatal(err) 36 | } 37 | defer os.Unsetenv(key) 38 | } 39 | 40 | tmpl := NewTemplate() 41 | out, err := tmpl.Execute(ts.in) 42 | valid := err == nil 43 | if ts.valid != valid { 44 | t.Fatalf("test case validity should be: %v but got: %v", ts.valid, valid) 45 | } 46 | 47 | if !ts.valid { 48 | return 49 | } 50 | 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | if ts.out != out { 56 | t.Errorf("test case: %+v \n===== WANT =====\n\n%+v\n===== GOT =====\n\n%+v", 57 | ts.name, ts.out, out) 58 | } 59 | }) 60 | } 61 | 62 | } 63 | --------------------------------------------------------------------------------