Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.

Commit 41505f6

Browse files
authored
Fix #13. Idiomatic update (#14)
* Support io.Reader/io.Writer in template. Add sync.Mutex for concurrency * Minor cleanup for map creation and imports * Bump to v0.4.0
1 parent 2bf57a5 commit 41505f6

File tree

7 files changed

+90
-48
lines changed

7 files changed

+90
-48
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ docs/_build
77
.idea
88
_book
99
node_modules
10-
rise
10+
rise
11+
.vscode

cmd/rise.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
const version = "v0.2.0"
10+
const version = "v0.4.0"
1111

1212
var inputs string
1313
var outputs string
@@ -33,7 +33,7 @@ var RootCmd = &cobra.Command{
3333
if inputs == "" {
3434
log.Fatal("Must have an input")
3535
}
36-
err := Run(inputs, outputs, configFiles, extraVars)
36+
err := process(inputs, outputs, configFiles, extraVars)
3737
if err != nil {
3838
log.Fatal(err)
3939
}

cmd/runner.go

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,52 @@ package cmd
22

33
import (
44
"io"
5-
"io/ioutil"
65
"os"
76

8-
97
"github.com/openpixel/rise/internal/config"
108
"github.com/openpixel/rise/internal/template"
119
)
1210

13-
// Run accepts an input, output and config files and performs interpolation.
14-
// If the output is empty, it writes to stdout
15-
func Run(inputFile, outputFile string, configFiles []string, extraVars []string) error {
16-
contents, err := ioutil.ReadFile(inputFile)
11+
func prepareTemplate(configFiles []string, extraVars []string) (*template.Template, error) {
12+
extras, err := config.LoadExtras(extraVars)
1713
if err != nil {
18-
return err
14+
return nil, err
1915
}
20-
21-
extras, err := config.LoadExtras(extraVars)
16+
configResult, err := config.LoadConfigFiles(configFiles, extras)
2217
if err != nil {
23-
return err
18+
return nil, err
2419
}
20+
return template.NewTemplate(configResult)
21+
}
2522

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

31-
t, err := template.NewTemplate(configResult)
31+
f, err := os.Open(inputFile)
3232
if err != nil {
3333
return err
3434
}
3535

36-
result, err := t.Render(string(contents))
36+
res, err := tmpl.Render(f)
3737
if err != nil {
3838
return err
3939
}
4040

41+
var out io.Writer
4142
if outputFile != "" {
42-
err = ioutil.WriteFile(outputFile, []byte(result.Value.(string)), 0644)
43+
out, err = os.Create(outputFile)
4344
if err != nil {
4445
return err
4546
}
4647
} else {
47-
_, err = io.WriteString(os.Stdout, result.Value.(string))
48-
if err != nil {
49-
return err
50-
}
48+
out = os.Stdout
5149
}
5250

53-
return nil
51+
_, err = io.Copy(out, res)
52+
return err
5453
}

cmd/runner_test.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@ package cmd
22

33
import "testing"
44

5+
func TestRun(t *testing.T) {
6+
testCases := []struct {
7+
desc string
8+
input string
9+
output string
10+
}{}
11+
12+
for _, tC := range testCases {
13+
t.Run(tC.desc, func(t *testing.T) {})
14+
}
15+
}
16+
517
func BenchmarkRun_Simple(b *testing.B) {
618
input := "./examples/basic.txt"
719
output := "./examples/basic_output.txt"
820
configs := []string{"./examples/basic.hcl"}
921
extras := []string{}
1022
for i := 0; i < b.N; i++ {
11-
Run(input, output, configs, extras)
23+
err := process(input, output, configs, extras)
24+
if err != nil {
25+
b.Error(err)
26+
}
1227
}
1328
}
1429

@@ -18,7 +33,10 @@ func BenchmarkRun_Complex(b *testing.B) {
1833
configs := []string{"./examples/vars.hcl", "./examples/vars2.hcl"}
1934
extras := []string{}
2035
for i := 0; i < b.N; i++ {
21-
Run(input, output, configs, extras)
36+
err := process(input, output, configs, extras)
37+
if err != nil {
38+
b.Error(err)
39+
}
2240
}
2341
}
2442

@@ -28,6 +46,9 @@ func BenchmarkRun_Extras(b *testing.B) {
2846
configs := []string{"./examples/vars.hcl", "./examples/vars2.hcl"}
2947
extras := []string{`{"i": "value of i"}`}
3048
for i := 0; i < b.N; i++ {
31-
Run(input, output, configs, extras)
49+
err := process(input, output, configs, extras)
50+
if err != nil {
51+
b.Error(err)
52+
}
3253
}
3354
}

internal/config/config.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ type Result struct {
3939
}
4040

4141
func LoadExtras(args []string) (map[string]ast.Variable, error) {
42-
extras := map[string]ast.Variable{}
42+
extras := make(map[string]ast.Variable)
4343
for _, extra := range args {
44-
extraResult := map[string]interface{}{}
44+
extraResult := make(map[string]interface{})
4545

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

90-
result := &Result{
90+
return &Result{
9191
Variables: vars,
9292
Templates: templates,
93-
}
94-
return result, nil
93+
}, nil
9594
}
9695

9796
func prepareVariables(vars map[string]ast.Variable, config *config) error {

internal/template/template.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package template
22

33
import (
4+
"bytes"
5+
"io"
46
"strings"
7+
"sync"
58

69
"github.com/hashicorp/hil"
710
"github.com/hashicorp/hil/ast"
11+
812
"github.com/openpixel/rise/internal/config"
913
"github.com/openpixel/rise/internal/interpolation"
1014
)
1115

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

34+
t.mtx.Lock()
2935
for k, v := range t.vars {
3036
vars[k] = v
3137
}
3238
for k, v := range t.templates {
3339
vars[k] = v
3440
}
41+
t.mtx.Unlock()
3542

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

4451
// Render will parse the provided text and interpolate the known variables/functions
45-
func (t *Template) Render(text string) (hil.EvaluationResult, error) {
46-
var resultErr error
47-
52+
func (t *Template) Render(input io.Reader) (io.Reader, error) {
4853
config := t.buildConfig()
4954

50-
tree, err := hil.Parse(text)
55+
buf := new(bytes.Buffer)
56+
_, err := buf.ReadFrom(input)
57+
if err != nil {
58+
return nil, err
59+
}
60+
tree, err := hil.Parse(buf.String())
5161
if err != nil {
52-
return hil.InvalidResult, err
62+
return nil, err
5363
}
5464

5565
vf := visitorFn{
@@ -58,20 +68,21 @@ func (t *Template) Render(text string) (hil.EvaluationResult, error) {
5868
}
5969
tree = tree.Accept(vf.fn)
6070
if vf.resultErr != nil {
61-
return hil.InvalidResult, resultErr
71+
return nil, vf.resultErr
6272
}
6373

6474
result, err := hil.Eval(tree, config)
6575
if err != nil {
66-
return hil.InvalidResult, err
76+
return nil, err
6777
}
6878

69-
return result, nil
79+
return bytes.NewBufferString(result.Value.(string)), nil
7080
}
7181

7282
type visitorFn struct {
7383
resultErr error
7484
config *hil.EvalConfig
85+
mtx sync.Mutex
7586
templates map[string]ast.Variable
7687
}
7788

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

117128
func (vf visitorFn) processTemplateNode(original ast.Node, name string) (replacement ast.Node, err error) {
129+
vf.mtx.Lock()
118130
template, ok := vf.templates[name]
119131
if !ok {
120132
return original, nil
121133
}
134+
vf.mtx.Unlock()
122135

123136
switch template.Type {
124137
case ast.TypeString:

internal/template/template_test.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
package template
22

33
import (
4+
"bytes"
5+
"io"
46
"testing"
57

68
"github.com/hashicorp/hil/ast"
9+
710
"github.com/openpixel/rise/internal/config"
811
)
912

13+
func stringFromReader(reader io.Reader) string {
14+
buf := new(bytes.Buffer)
15+
buf.ReadFrom(reader)
16+
return buf.String()
17+
}
18+
1019
func TestTemplate_Render(t *testing.T) {
1120
t.Run("Test render with variable passed", func(t *testing.T) {
1221
vars := map[string]ast.Variable{
@@ -29,13 +38,13 @@ func TestTemplate_Render(t *testing.T) {
2938
t.Fatalf("Unexpected err: %s", err)
3039
}
3140

32-
result, err := tmpl.Render(`${has(var.foo, "bar")} ${var.foo["bar"]}`)
41+
result, err := tmpl.Render(bytes.NewBufferString(`${has(var.foo, "bar")} ${var.foo["bar"]}`))
3342
if err != nil {
3443
t.Fatalf("Unexpected err: %s", err)
3544
}
3645

37-
if result.Value.(string) != "true Bar" {
38-
t.Fatalf("Unexpected result: Expected %s, got %s", "true", result.Value.(string))
46+
if stringFromReader(result) != "true Bar" {
47+
t.Fatalf("Unexpected result: Expected %s, got %s", "true", stringFromReader(result))
3948
}
4049
})
4150

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

58-
result, err := tmpl.Render(`${tmpl.foo}`)
67+
result, err := tmpl.Render(bytes.NewBufferString(`${tmpl.foo}`))
5968
if err != nil {
6069
t.Fatalf("Unexpected err: %s", err)
6170
}
6271

63-
if result.Value.(string) != "This is a template foo" {
64-
t.Fatalf("Unexpected result: Expected %s, got %s", "This is a template foo", result.Value.(string))
72+
if stringFromReader(result) != "This is a template foo" {
73+
t.Fatalf("Unexpected result: Expected %s, got %s", "This is a template foo", stringFromReader(result))
6574
}
6675
})
6776

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

77-
result, err := tmpl.Render(`${lower("FOO")}`)
86+
result, err := tmpl.Render(bytes.NewBufferString(`${lower("FOO")}`))
7887
if err != nil {
7988
t.Fatalf("Unexpected err: %s", err)
8089
}
8190

82-
if result.Value.(string) != "foo" {
83-
t.Fatalf("Unexpected result: Expected %s, got %s", "foo", result.Value.(string))
91+
if stringFromReader(result) != "foo" {
92+
t.Fatalf("Unexpected result: Expected %s, got %s", "foo", stringFromReader(result))
8493
}
8594
})
8695

@@ -92,7 +101,7 @@ func TestTemplate_Render(t *testing.T) {
92101
if err != nil {
93102
t.Fatalf("Unexpected err: %s", err)
94103
}
95-
_, err = tmpl.Render(`${tmpl.foo}`)
104+
_, err = tmpl.Render(bytes.NewBufferString(`${tmpl.foo}`))
96105
if err == nil {
97106
t.Fatal("unexpected nil err")
98107
}

0 commit comments

Comments
 (0)