Skip to content

Commit

Permalink
Fix #13. Idiomatic update (#14)
Browse files Browse the repository at this point in the history
* Support io.Reader/io.Writer in template. Add sync.Mutex for concurrency

* Minor cleanup for map creation and imports

* Bump to v0.4.0
  • Loading branch information
cam-stitt committed Aug 9, 2019
1 parent 2bf57a5 commit 41505f6
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 48 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ docs/_build
.idea
_book
node_modules
rise
rise
.vscode
4 changes: 2 additions & 2 deletions cmd/rise.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra"
)

const version = "v0.2.0"
const version = "v0.4.0"

var inputs string
var outputs string
Expand All @@ -33,7 +33,7 @@ var RootCmd = &cobra.Command{
if inputs == "" {
log.Fatal("Must have an input")
}
err := Run(inputs, outputs, configFiles, extraVars)
err := process(inputs, outputs, configFiles, extraVars)
if err != nil {
log.Fatal(err)
}
Expand Down
37 changes: 18 additions & 19 deletions cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,52 @@ package cmd

import (
"io"
"io/ioutil"
"os"


"github.com/openpixel/rise/internal/config"
"github.com/openpixel/rise/internal/template"
)

// Run accepts an input, output and config files and performs interpolation.
// If the output is empty, it writes to stdout
func Run(inputFile, outputFile string, configFiles []string, extraVars []string) error {
contents, err := ioutil.ReadFile(inputFile)
func prepareTemplate(configFiles []string, extraVars []string) (*template.Template, error) {
extras, err := config.LoadExtras(extraVars)
if err != nil {
return err
return nil, err
}

extras, err := config.LoadExtras(extraVars)
configResult, err := config.LoadConfigFiles(configFiles, extras)
if err != nil {
return err
return nil, err
}
return template.NewTemplate(configResult)
}

configResult, err := config.LoadConfigFiles(configFiles, extras)
// process accepts an input, output and config files and performs interpolation.
// If the output is empty, it writes to stdout
func process(inputFile, outputFile string, configFiles []string, extraVars []string) error {
tmpl, err := prepareTemplate(configFiles, extraVars)
if err != nil {
return err
}

t, err := template.NewTemplate(configResult)
f, err := os.Open(inputFile)
if err != nil {
return err
}

result, err := t.Render(string(contents))
res, err := tmpl.Render(f)
if err != nil {
return err
}

var out io.Writer
if outputFile != "" {
err = ioutil.WriteFile(outputFile, []byte(result.Value.(string)), 0644)
out, err = os.Create(outputFile)
if err != nil {
return err
}
} else {
_, err = io.WriteString(os.Stdout, result.Value.(string))
if err != nil {
return err
}
out = os.Stdout
}

return nil
_, err = io.Copy(out, res)
return err
}
27 changes: 24 additions & 3 deletions cmd/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ package cmd

import "testing"

func TestRun(t *testing.T) {
testCases := []struct {
desc string
input string
output string
}{}

for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {})
}
}

func BenchmarkRun_Simple(b *testing.B) {
input := "./examples/basic.txt"
output := "./examples/basic_output.txt"
configs := []string{"./examples/basic.hcl"}
extras := []string{}
for i := 0; i < b.N; i++ {
Run(input, output, configs, extras)
err := process(input, output, configs, extras)
if err != nil {
b.Error(err)
}
}
}

Expand All @@ -18,7 +33,10 @@ func BenchmarkRun_Complex(b *testing.B) {
configs := []string{"./examples/vars.hcl", "./examples/vars2.hcl"}
extras := []string{}
for i := 0; i < b.N; i++ {
Run(input, output, configs, extras)
err := process(input, output, configs, extras)
if err != nil {
b.Error(err)
}
}
}

Expand All @@ -28,6 +46,9 @@ func BenchmarkRun_Extras(b *testing.B) {
configs := []string{"./examples/vars.hcl", "./examples/vars2.hcl"}
extras := []string{`{"i": "value of i"}`}
for i := 0; i < b.N; i++ {
Run(input, output, configs, extras)
err := process(input, output, configs, extras)
if err != nil {
b.Error(err)
}
}
}
9 changes: 4 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ type Result struct {
}

func LoadExtras(args []string) (map[string]ast.Variable, error) {
extras := map[string]ast.Variable{}
extras := make(map[string]ast.Variable)
for _, extra := range args {
extraResult := map[string]interface{}{}
extraResult := make(map[string]interface{})

err := json.Unmarshal([]byte(extra), &extraResult)
if err != nil {
Expand Down Expand Up @@ -87,11 +87,10 @@ func LoadConfigFiles(configFiles []string, extras map[string]ast.Variable) (*Res
vars[k] = v
}

result := &Result{
return &Result{
Variables: vars,
Templates: templates,
}
return result, nil
}, nil
}

func prepareVariables(vars map[string]ast.Variable, config *config) error {
Expand Down
29 changes: 21 additions & 8 deletions internal/template/template.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package template

import (
"bytes"
"io"
"strings"
"sync"

"github.com/hashicorp/hil"
"github.com/hashicorp/hil/ast"

"github.com/openpixel/rise/internal/config"
"github.com/openpixel/rise/internal/interpolation"
)

// Template is a container for holding onto the ast Variables
type Template struct {
mtx sync.Mutex
vars map[string]ast.Variable
templates map[string]ast.Variable
}
Expand All @@ -26,12 +31,14 @@ func NewTemplate(configResult *config.Result) (*Template, error) {
func (t *Template) buildConfig() *hil.EvalConfig {
vars := make(map[string]ast.Variable)

t.mtx.Lock()
for k, v := range t.vars {
vars[k] = v
}
for k, v := range t.templates {
vars[k] = v
}
t.mtx.Unlock()

return &hil.EvalConfig{
GlobalScope: &ast.BasicScope{
Expand All @@ -42,14 +49,17 @@ func (t *Template) buildConfig() *hil.EvalConfig {
}

// Render will parse the provided text and interpolate the known variables/functions
func (t *Template) Render(text string) (hil.EvaluationResult, error) {
var resultErr error

func (t *Template) Render(input io.Reader) (io.Reader, error) {
config := t.buildConfig()

tree, err := hil.Parse(text)
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(input)
if err != nil {
return nil, err
}
tree, err := hil.Parse(buf.String())
if err != nil {
return hil.InvalidResult, err
return nil, err
}

vf := visitorFn{
Expand All @@ -58,20 +68,21 @@ func (t *Template) Render(text string) (hil.EvaluationResult, error) {
}
tree = tree.Accept(vf.fn)
if vf.resultErr != nil {
return hil.InvalidResult, resultErr
return nil, vf.resultErr
}

result, err := hil.Eval(tree, config)
if err != nil {
return hil.InvalidResult, err
return nil, err
}

return result, nil
return bytes.NewBufferString(result.Value.(string)), nil
}

type visitorFn struct {
resultErr error
config *hil.EvalConfig
mtx sync.Mutex
templates map[string]ast.Variable
}

Expand Down Expand Up @@ -115,10 +126,12 @@ func (vf visitorFn) processVariable(va *ast.VariableAccess) ast.Node {
}

func (vf visitorFn) processTemplateNode(original ast.Node, name string) (replacement ast.Node, err error) {
vf.mtx.Lock()
template, ok := vf.templates[name]
if !ok {
return original, nil
}
vf.mtx.Unlock()

switch template.Type {
case ast.TypeString:
Expand Down
29 changes: 19 additions & 10 deletions internal/template/template_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package template

import (
"bytes"
"io"
"testing"

"github.com/hashicorp/hil/ast"

"github.com/openpixel/rise/internal/config"
)

func stringFromReader(reader io.Reader) string {
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
return buf.String()
}

func TestTemplate_Render(t *testing.T) {
t.Run("Test render with variable passed", func(t *testing.T) {
vars := map[string]ast.Variable{
Expand All @@ -29,13 +38,13 @@ func TestTemplate_Render(t *testing.T) {
t.Fatalf("Unexpected err: %s", err)
}

result, err := tmpl.Render(`${has(var.foo, "bar")} ${var.foo["bar"]}`)
result, err := tmpl.Render(bytes.NewBufferString(`${has(var.foo, "bar")} ${var.foo["bar"]}`))
if err != nil {
t.Fatalf("Unexpected err: %s", err)
}

if result.Value.(string) != "true Bar" {
t.Fatalf("Unexpected result: Expected %s, got %s", "true", result.Value.(string))
if stringFromReader(result) != "true Bar" {
t.Fatalf("Unexpected result: Expected %s, got %s", "true", stringFromReader(result))
}
})

Expand All @@ -55,13 +64,13 @@ func TestTemplate_Render(t *testing.T) {
t.Fatalf("Unexpected err: %s", err)
}

result, err := tmpl.Render(`${tmpl.foo}`)
result, err := tmpl.Render(bytes.NewBufferString(`${tmpl.foo}`))
if err != nil {
t.Fatalf("Unexpected err: %s", err)
}

if result.Value.(string) != "This is a template foo" {
t.Fatalf("Unexpected result: Expected %s, got %s", "This is a template foo", result.Value.(string))
if stringFromReader(result) != "This is a template foo" {
t.Fatalf("Unexpected result: Expected %s, got %s", "This is a template foo", stringFromReader(result))
}
})

Expand All @@ -74,13 +83,13 @@ func TestTemplate_Render(t *testing.T) {
t.Fatalf("Unexpected err: %s", err)
}

result, err := tmpl.Render(`${lower("FOO")}`)
result, err := tmpl.Render(bytes.NewBufferString(`${lower("FOO")}`))
if err != nil {
t.Fatalf("Unexpected err: %s", err)
}

if result.Value.(string) != "foo" {
t.Fatalf("Unexpected result: Expected %s, got %s", "foo", result.Value.(string))
if stringFromReader(result) != "foo" {
t.Fatalf("Unexpected result: Expected %s, got %s", "foo", stringFromReader(result))
}
})

Expand All @@ -92,7 +101,7 @@ func TestTemplate_Render(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected err: %s", err)
}
_, err = tmpl.Render(`${tmpl.foo}`)
_, err = tmpl.Render(bytes.NewBufferString(`${tmpl.foo}`))
if err == nil {
t.Fatal("unexpected nil err")
}
Expand Down

0 comments on commit 41505f6

Please sign in to comment.