diff --git a/engine/pongo.go b/engine/pongo.go new file mode 100644 index 0000000..8d6e154 --- /dev/null +++ b/engine/pongo.go @@ -0,0 +1,40 @@ +package engine + +import ( + "os" + "strings" + + "github.com/flosch/pongo2" + log "github.com/golang/glog" +) + +type PongoTemplar struct { + Source string +} + +func (templar *PongoTemplar) GenerateTemplate() (string, error) { + context := pongo2.Context{} + + tmpl, err := pongo2.FromString(templar.Source) + if err != nil { + return "", err + } + + for _, val := range os.Environ() { + parts := strings.SplitN(val, "=", 2) + key, value := parts[0], parts[1] + + context[key] = value + } + + if log.V(3) { + log.Info("Using context %v", context) + } + + out, err := tmpl.Execute(context) + if err != nil { + return "", err + } + + return out, nil +} diff --git a/engine/templar.go b/engine/templar.go new file mode 100644 index 0000000..4c4fb61 --- /dev/null +++ b/engine/templar.go @@ -0,0 +1,5 @@ +package engine + +type Templar interface { + GenerateTemplate() (string, error) +} diff --git a/template.go b/engine/template.go similarity index 71% rename from template.go rename to engine/template.go index b89df99..eb42a4a 100644 --- a/template.go +++ b/engine/template.go @@ -1,26 +1,46 @@ -package main +package engine import ( "bytes" "errors" + "flag" "fmt" - "github.com/Masterminds/sprig" "os" "strings" "text/template" + + "github.com/Masterminds/sprig" ) type TextTemplar struct { - source string - name string - delimLeft string - delimRight string + Source string + Name string } type OptionalString struct { ptr *string } +var ( + delimLeft string + delimRight string +) + +func init() { + flag.StringVar( + &delimLeft, + "delim-left", + "", + "(text/template only) Override default left delimiter {{.", + ) + flag.StringVar( + &delimRight, + "delim-right", + "", + "(text/template only) Override default right delimiter }}.", + ) +} + func (s OptionalString) String() string { if s.ptr == nil { return "" @@ -46,7 +66,7 @@ func Require(arg interface{}) (string, error) { } } - return "", fmt.Errorf("Requires: unsupported type '%T'!", v) + return "", fmt.Errorf("Requires: unsupported type '%T'!", arg) } func EnvAll() (map[string]string, error) { @@ -65,16 +85,13 @@ var funcMap = template.FuncMap{ "envall": EnvAll, } -func (templar *TextTemplar) generateTemplate() (string, error) { - var t *template.Template - var err error - - t, err = template.New(templar.name). - Delims(templar.delimLeft, templar.delimRight). +func (templar *TextTemplar) GenerateTemplate() (string, error) { + t, err := template.New(templar.Name). + Delims(delimLeft, delimRight). Option("missingkey=error"). Funcs(funcMap). Funcs(sprig.TxtFuncMap()). - Parse(templar.source) + Parse(templar.Source) if err != nil { return "", err diff --git a/glide.yaml b/glide.yaml index ee67828..a65fb43 100644 --- a/glide.yaml +++ b/glide.yaml @@ -4,3 +4,6 @@ import: version: v1 - package: github.com/Masterminds/sprig version: 2.8.0 +- package: github.com/flosch/pongo2 + version: v3 +- package: github.com/golang/glog diff --git a/goenvtemplator.go b/goenvtemplator.go index 19c7570..b6a04b0 100644 --- a/goenvtemplator.go +++ b/goenvtemplator.go @@ -4,42 +4,49 @@ import ( "errors" "flag" "fmt" - "github.com/joho/godotenv" "io/ioutil" - "log" "os" "os/exec" "path/filepath" "strings" "syscall" + + log "github.com/golang/glog" + "github.com/joho/godotenv" + + "github.com/seznam/goenvtemplator/engine" ) -type Templar interface { - generateTemplate() (string, error) -} +var ( + buildVersion string = "Build version was not specified." +) + +type templatesPaths []templatePath -type templatePaths struct { +// to parse slice of strings from flags we need to use custom type +type envFiles []string + +type templatePath struct { source string destination string } -func (t templatePaths) String() string { +func (t templatePath) String() string { return fmt.Sprintf("{source: '%s', destination: '%s'}", t.source, t.destination) } -type templatesPaths []templatePaths - func (ts *templatesPaths) Set(value string) error { - var t templatePaths - parts := strings.Split(value, ":") - if len(parts) == 2 { - t.source = strings.TrimSpace(parts[0]) - t.destination = strings.TrimSpace(parts[1]) - } else { + parts := strings.SplitN(value, ":", 2) + if len(parts) < 2 { return errors.New("Option has invalid format!") } - *ts = append(*ts, t) + + *ts = append(*ts, templatePath{ + source: strings.TrimSpace(parts[0]), + destination: strings.TrimSpace(parts[1]), + }) + return nil } @@ -47,9 +54,6 @@ func (ts *templatesPaths) String() string { return fmt.Sprintf("%v", *ts) } -// to parse slice of strings from flags we need to use custom type -type envFiles []string - func (ef *envFiles) Set(value string) error { *ef = append(*ef, value) return nil @@ -61,8 +65,7 @@ func (ef *envFiles) String() string { func writeFile(destinationPath string, data string) error { if !filepath.IsAbs(destinationPath) { - log.Fatalf("Destination path '%s' is not absolute!", destinationPath) - return errors.New("absolute path error") + return fmt.Errorf("absolute path error: %s", destinationPath) } if err := ioutil.WriteFile(destinationPath, []byte(data), 0664); err != nil { @@ -72,10 +75,9 @@ func writeFile(destinationPath string, data string) error { return nil } -func readSource(templatePath string) (string, error) { +func readFile(templatePath string) (string, error) { if !filepath.IsAbs(templatePath) { - log.Fatalf("Template path '%s' is not absolute!", templatePath) - return "", errors.New("absolute path error") + return "", fmt.Errorf("absolute path error: %s", templatePath) } var slice []byte @@ -88,37 +90,46 @@ func readSource(templatePath string) (string, error) { } -func generateTemplates( - ts templatesPaths, debug bool, delimLeft string, delimRight string, engine string) error { +func generateTemplates(ts templatesPaths, engineName string) error { for _, t := range ts { - if v > 0 { - log.Printf("generating %s -> %s", t.source, t.destination) + if log.V(1) { + log.Info("generating %s -> %s", t.source, t.destination) } - var templar Templar + var templar engine.Templar - source, err := readSource(t.source) + source, err := readFile(t.source) if err != nil { return err } templateName := filepath.Base(t.source) - switch engine { - default: - templar = &TextTemplar{ - source: source, - name: templateName, - delimLeft: delimLeft, - delimRight: delimRight, + switch engineName { + case "pongo": + templar = &engine.PongoTemplar{ + Source: source, } + case "text/template": + templar = &engine.TextTemplar{ + Source: source, + Name: templateName, + } + } + + if log.V(3) { + log.Info("Templating %s", templateName) } - render, err := templar.generateTemplate() + render, err := templar.GenerateTemplate() if err != nil { return err } + if log.V(3) { + log.Info("Generated template %s", render) + } + if err = writeFile(t.destination, render); err != nil { return err } @@ -127,49 +138,41 @@ func generateTemplates( return nil } -var ( - v int - buildVersion string = "Build version was not specified." -) - func main() { var tmpls templatesPaths - flag.Var(&tmpls, "template", "Template (/template:/dest). Can be passed multiple times.") - var debugTemplates bool - flag.BoolVar(&debugTemplates, "debug-templates", false, "Print processed templates to stdout.") var doExec bool - flag.BoolVar(&doExec, "exec", false, "Activates exec by command. First non-flag arguments is the command, the rest are it's arguments.") var printVersion bool - flag.BoolVar(&printVersion, "version", false, "Prints version.") var envFileList envFiles + var engine string + + flag.Var(&tmpls, "template", "Template (/template:/dest). Can be passed multiple times.") + flag.BoolVar(&doExec, "exec", false, "Activates exec by command. First non-flag arguments is the command, the rest are it's arguments.") + flag.BoolVar(&printVersion, "version", false, "Prints version.") flag.Var(&envFileList, "env-file", "Additional file with environment variables. Can be passed multiple times.") - var delimLeft string - flag.StringVar(&delimLeft, "delim-left", "", "Override default left delimiter {{.") - var delimRight string - flag.StringVar(&delimRight, "delim-right", "", "Override default right delimiter }}.") - flag.IntVar(&v, "v", 0, "Verbosity level.") + flag.StringVar( + &engine, "engine", "text/template", + "Override default text/template [supports: text/template, pongo]", + ) flag.Parse() - // if no env-file was passed, godotenv.Load loads .env file by default, we want to disable this if len(envFileList) > 0 { - err := godotenv.Load(envFileList...) - if err != nil { + if err := godotenv.Load(envFileList...); err != nil { log.Fatal(err) } } if printVersion { - log.Printf("Version: %s", buildVersion) + log.Info("Version: %s", buildVersion) os.Exit(0) } - if v > 0 { - log.Print("Generating templates") + if log.V(1) { + log.Info("Generating templates") } - if err := generateTemplates(tmpls, debugTemplates, delimLeft, delimRight, "default"); err != nil { - panic(err) + if err := generateTemplates(tmpls, engine); err != nil { + log.Fatal(err) } if doExec { diff --git a/template_test.go b/template_test.go index 2881c72..c86c6dd 100644 --- a/template_test.go +++ b/template_test.go @@ -18,6 +18,7 @@ func TestGenerateTemplate(t *testing.T) { err error leftDelim string rightDelim string + engine string }{ {in: `K={{ env "GOENVTEMPLATOR_DEFINED_VAR" }}`, want: `K=foo`}, {in: `K={{ env "GOENVTEMPLATOR_DEFINED_FILE_VAR" }}`, want: `K=bar`}, @@ -33,6 +34,8 @@ func TestGenerateTemplate(t *testing.T) { {in: ``, want: ``}, {in: `K={{env "GOENVTEMPLATOR_DEFINED_VAR"}}`, want: `K={{env "GOENVTEMPLATOR_DEFINED_VAR"}}`, err: nil, leftDelim: "[[", rightDelim: "]]"}, {in: `K=[[env "GOENVTEMPLATOR_DEFINED_VAR"]]`, want: `K=foo`, err: nil, leftDelim: "[[", rightDelim: "]]"}, + {in: `K={{ GOENVTEMPLATOR_DEFINED_VAR }}`, want: `K=foo`, engine: `pongo`}, + {in: `K={{ FOO|default:"bar" }}`, want: `K=bar`, engine: `pongo`}, } os.Setenv("GOENVTEMPLATOR_DEFINED_VAR", "foo") @@ -43,7 +46,23 @@ func TestGenerateTemplate(t *testing.T) { } for _, testcase := range tests { - got, gotErr := generateTemplate(testcase.in, templateName, testcase.leftDelim, testcase.rightDelim) + var templar Templar + + switch testcase.engine { + case "pongo": + templar = &PongoTemplar{ + Source: testcase.in, + } + default: + templar = &TextTemplar{ + Source: testcase.in, + Name: templateName, + DelimLeft: testcase.leftDelim, + DelimRight: testcase.rightDelim, + } + } + + got, gotErr := templar.generateTemplate() if testcase.contains != "" { if !strings.Contains(got, testcase.contains) {