Skip to content

Commit

Permalink
Merge pull request #2 from gwynforthewyn/pipelining
Browse files Browse the repository at this point in the history
pipelining
  • Loading branch information
gwynforthewyn authored Sep 10, 2023
2 parents 2641208 + 4fac0e8 commit 9b10c5f
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 44 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ TEMPL_DIR - Defaults to ~/.config/templ. Directory that stores template git repo
Variable: value
OtherVariable: othervalue
```

`templ templatename | templ KEY=VALUE` - pipeline a render operation! You do not need a config file, you can pipe through templ itself.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ go 1.21
require (
github.com/go-git/go-git/v5 v5.8.1
github.com/sirupsen/logrus v1.9.3
golang.org/x/term v0.12.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -27,8 +29,7 @@ require (
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/tools v0.6.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand Down
34 changes: 32 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"flag"
"fmt"
"golang.org/x/term"
"io"
"os"
"path/filepath"
"runtime"
Expand All @@ -29,6 +31,29 @@ func main() {
flag.Usage = func() { fmt.Println(usage); flag.PrintDefaults(); return }
flag.Parse()

fd := os.Stdin.Fd()

if !term.IsTerminal(int(fd)) {
input, err := io.ReadAll(os.Stdin)

if err != nil {
_, file, line, _ := runtime.Caller(0)
panic(fmt.Errorf("%s:%d: %v", file, line, err))
}

variableDefinitions := flag.Args()
hydratedTemplate, err := templates.RenderFromString(string(input), variableDefinitions)

if err != nil {
_, file, line, _ := runtime.Caller(0)
panic(fmt.Errorf("%s:%d: %v", file, line, err))
}

fmt.Println(hydratedTemplate)

os.Exit(0)
}

if *list {
files, err := templatedirectories.List()

Expand All @@ -44,7 +69,7 @@ func main() {
os.Exit(0)
}

candidateTemplates := flag.Args()
templatesAndConfigFilePaths := flag.Args()

createTemplDir()

Expand Down Expand Up @@ -74,7 +99,12 @@ func main() {
}
}

templates.Render(candidateTemplates)
err := templates.Render(templatesAndConfigFilePaths)
if err != nil {
_, file, line, _ := runtime.Caller(0)
panic(fmt.Errorf("%s:%d: %v", file, line, err))
}

}

//Helper functions
Expand Down
146 changes: 110 additions & 36 deletions templates/templates.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,75 @@
package templates

import (
"bytes"
"errors"
"fmt"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"templ/configelements"
"text/template"
)

type TemplateVariableErr struct {
ErrorMessage string
}

func (t TemplateVariableErr) Error() string {
return t.ErrorMessage
}

func (t TemplateVariableErr) Is(target error) bool {
_, ok := target.(TemplateVariableErr)
return ok
}

func Render(templates []string) error {
templateFilePaths, templateVariablesFilesPaths, err := parseArgvArguments(templates)

if err != nil {
_, file, line, _ := runtime.Caller(0)
return fmt.Errorf("%s:%d: %v", file, line, err)
}

err = renderFromFiles(templateFilePaths, templateVariablesFilesPaths)

if err != nil {
_, file, line, _ := runtime.Caller(0)
return fmt.Errorf("%s:%d: %v", file, line, err)
}

return nil
}

func RenderFromString(template string, variableDefinitions []string) (hydratedtemplate string, err error) {
// If we don't receive any arguments, just pass back up the chain.
if len(variableDefinitions) == 0 {
return template, nil
}

variables, err := convertFromArrayToKeymap(variableDefinitions)

if err != nil {
return "", err
}

hydratedtemplate, err = renderFromString(template, variables)

return
}

func parseArgvArguments(templates []string) ([]string, map[string]string, error) {
// Data structures to store paths to the template files. These may optionally have an associated variables file to hydrate with.
var templateFilePaths = make([]string, 0)
var templateVariablesFilesPaths = make(map[string]string, 0)

for _, path := range templates {
// The arguments at this point either read as a name/of/template_file, or as name/of/template_file=path/to/variables.
// In the first case, I want to store the path to the template file in an array to hand in to the render command.
// In the first case, I want to store the path to the template file in an array to hand in to the renderFromFiles command.
// In the second case, we store the path to the template file in the same array, and also use that path as an
// index in a map, where the value is the variables file's path.

Expand All @@ -41,7 +90,7 @@ func Render(templates []string) error {

if err != nil {
logrus.Error(err)
return err
return nil, nil, err
} else {
logrus.Debug("Found templateFilePaths,", templateFilePaths)
}
Expand All @@ -56,18 +105,10 @@ func Render(templates []string) error {
}
}
}

err := render(templateFilePaths, templateVariablesFilesPaths)

if err != nil {
logrus.Error(err)
return err
}

return nil
return templateFilePaths, templateVariablesFilesPaths, nil
}

func render(templateFiles []string, templateVariables map[string]string) error {
func renderFromFiles(templateFiles []string, templateVariables map[string]string) error {
err := validateTemplatesExist(templateFiles)

if err != nil {
Expand All @@ -85,8 +126,8 @@ func render(templateFiles []string, templateVariables map[string]string) error {
content, err := os.ReadFile(string(templatePath))

if err != nil {
logrus.Error("Failed to read template file: ", templatePath, " with error ", err)
continue
_, file, line, _ := runtime.Caller(0)
return fmt.Errorf("%s:%d: %v", file, line, err)
}

fmt.Println(string(content))
Expand All @@ -98,45 +139,59 @@ func render(templateFiles []string, templateVariables map[string]string) error {

// Consume the template variables, which are a yaml file, into a map
// of key value pairs.
templateVariables, err := getTemplateVariables(templateVariablesFilePath)
templateVariables, err := getTemplateVariablesFromYamlFile(templateVariablesFilePath)

if err != nil {
_, file, line, _ := runtime.Caller(0)
return fmt.Errorf("%s:%d: %v", file, line, err)
}

templateContents, err := os.ReadFile(string(templatePath))
templateContents, err := os.ReadFile(templatePath)

if err != nil {
logrus.Info("Failed to read template file: ", templatePath, " with error ", err)
return err
} else {
logrus.Info("Successfully read template file: ", templatePath)
_, file, line, _ := runtime.Caller(0)
return fmt.Errorf("%s:%d: %v", file, line, err)
}

// Convert template file content to a string
templateText := string(templateContents)

// Create a new template and parse the template text
tmpl, err := template.New(string(templatePath)).Parse(templateText)
output, err := renderFromString(templateText, templateVariables)

if err != nil {
logrus.Error("Failed to parse template: ", err)
return err
} else {
logrus.Info("Successfully parsed template: ", templatePath)
_, file, line, _ := runtime.Caller(0)
return fmt.Errorf("%s:%d: %v", file, line, err)
}

// Execute the template with the data
err = tmpl.Execute(os.Stdout, templateVariables)
fmt.Println(output)
}

return nil
}

func renderFromString(templateText string, templateVariables map[string]string) (string, error) {
// Create a new template and parse the template text
tmpl, err := template.New("roflcopter").Parse(templateText)

if err != nil {
if err != nil {
log.Fatalf("Failed to execute template: %v", err)
return err
} else {
logrus.Info("Successfully executed template: ", templatePath)
_, file, line, _ := runtime.Caller(0)
return "", fmt.Errorf("%s:%d: %v", file, line, err)
}
}

return nil
// Execute the template with the data
var buffer bytes.Buffer
err = tmpl.Execute(&buffer, templateVariables)

if err != nil {
_, file, line, _ := runtime.Caller(0)
return buffer.String(), fmt.Errorf("%s:%d: %v", file, line, err)
}

return buffer.String(), nil
}

func getTemplateVariables(templateVariablesFilePath string) (map[string]string, error) {
func getTemplateVariablesFromYamlFile(templateVariablesFilePath string) (map[string]string, error) {
// Read the YAML file
yamlFile, err := os.ReadFile(templateVariablesFilePath)
if err != nil {
Expand Down Expand Up @@ -216,3 +271,22 @@ func findFilesByName(root string, names []string) ([]string, error) {

return foundFiles, nil
}

func convertFromArrayToKeymap(input []string) (map[string]string, error) {
k := make(map[string]string)

for _, arg := range input {
if !strings.Contains(arg, "=") {
_, file, line, _ := runtime.Caller(0)

message := fmt.Sprintf("%s:%d: Argument <%s> not formatted as FOO=BAR", file, line, arg)
err := TemplateVariableErr{ErrorMessage: message}
return k, err
}

s := strings.Split(arg, "=")
k[s[0]] = s[1]
}

return k, nil
}
64 changes: 64 additions & 0 deletions templates/templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package templates_test

import (
"templ/templates"
"testing"
)

func TestRenderFromStringWithEmptyString(t *testing.T) {
hydratedTemplate, err := templates.RenderFromString("", []string{})

if err != nil {
t.Errorf("%v", err)
}

if hydratedTemplate != "" {
t.Errorf("HydratedTemplate should be empty. Received <%s>", hydratedTemplate)
}
}

func TestRenderFromStringWithPlainString(t *testing.T) {
template := `
I love humans
`
hydratedTemplate, err := templates.RenderFromString(template, []string{})

if err != nil {
t.Errorf("%v", err)
}

if hydratedTemplate != template {
t.Errorf("HydratedTemplate should be contain a simple string. Received <%s>", hydratedTemplate)
}
}

func TestRenderFromStringWithTemplateContainingAVariableButNoVariables(t *testing.T) {
template := `I love {{ .SPECIES }}`

templateVariables := []string{}
hydratedTemplate, err := templates.RenderFromString(template, templateVariables)

if err != nil {
t.Errorf("%v", err)
}

if hydratedTemplate != template {
t.Errorf("Expected <%s>, received <%s>", template, hydratedTemplate)
}
}

func TestRenderFromStringWithVariablesAndDefinitions(t *testing.T) {
template := `I love {{ .SPECIES }}`

templateVariables := []string{"SPECIES=HUMAN"}
hydratedTemplate, err := templates.RenderFromString(template, templateVariables)

if err != nil {
t.Errorf("%v", err)
}

expected := "I love HUMAN"
if hydratedTemplate != expected {
t.Errorf("Expected <%s>, received <%s>", expected, hydratedTemplate)
}
}

0 comments on commit 9b10c5f

Please sign in to comment.