-
Notifications
You must be signed in to change notification settings - Fork 279
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix data race and optimize performance (#183)
- Loading branch information
1 parent
38f9eac
commit e0f0d41
Showing
5 changed files
with
147 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,51 @@ | ||
package internal | ||
|
||
import ( | ||
"bytes" | ||
"strings" | ||
"sync" | ||
gotemplate "text/template" | ||
) | ||
|
||
// Template stores the template for a string. | ||
type Template struct { | ||
Src string | ||
Template *gotemplate.Template | ||
ParseErr *error | ||
Src string | ||
LeftDelim string | ||
RightDelim string | ||
|
||
parseOnce sync.Once | ||
parsedTemplate *gotemplate.Template | ||
parseError error | ||
} | ||
|
||
func (t *Template) Parse(leftDelim, rightDelim string, funcs gotemplate.FuncMap) error { | ||
if t.ParseErr == nil { | ||
if strings.Contains(t.Src, leftDelim) { | ||
gt, err := gotemplate.New("").Funcs(funcs).Delims(leftDelim, rightDelim).Parse(t.Src) | ||
t.Template = gt | ||
t.ParseErr = &err | ||
} else { | ||
t.ParseErr = new(error) | ||
} | ||
func (t *Template) Execute(funcs gotemplate.FuncMap, data interface{}) (string, error) { | ||
leftDelim := t.LeftDelim | ||
if leftDelim == "" { | ||
leftDelim = "{{" | ||
} | ||
if !strings.Contains(t.Src, leftDelim) { | ||
// Fast path to avoid parsing a template that has no actions. | ||
return t.Src, nil | ||
} | ||
|
||
var gt *gotemplate.Template | ||
var err error | ||
if funcs == nil { | ||
t.parseOnce.Do(func() { | ||
// If funcs is nil, then we only need to parse this template once. | ||
t.parsedTemplate, t.parseError = gotemplate.New("").Delims(t.LeftDelim, t.RightDelim).Parse(t.Src) | ||
}) | ||
gt, err = t.parsedTemplate, t.parseError | ||
} else { | ||
gt, err = gotemplate.New("").Delims(t.LeftDelim, t.RightDelim).Funcs(funcs).Parse(t.Src) | ||
} | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
var buf bytes.Buffer | ||
if err := gt.Execute(&buf, data); err != nil { | ||
return "", err | ||
} | ||
return *t.ParseErr | ||
return buf.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,77 @@ | ||
package internal | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"testing" | ||
"text/template" | ||
) | ||
|
||
func TestParse(t *testing.T) { | ||
tmpl := &Template{Src: "hello"} | ||
if err := tmpl.Parse("", "", nil); err != nil { | ||
t.Fatal(err) | ||
} | ||
if tmpl.ParseErr == nil { | ||
t.Fatal("expected non-nil parse error") | ||
} | ||
if tmpl.Template == nil { | ||
t.Fatal("expected non-nil template") | ||
func TestExecute(t *testing.T) { | ||
tests := []struct { | ||
template *Template | ||
funcs template.FuncMap | ||
data interface{} | ||
result string | ||
err string | ||
noallocs bool | ||
}{ | ||
{ | ||
template: &Template{ | ||
Src: "hello", | ||
}, | ||
result: "hello", | ||
noallocs: true, | ||
}, | ||
{ | ||
template: &Template{ | ||
Src: "hello {{.Noun}}", | ||
}, | ||
data: map[string]string{ | ||
"Noun": "world", | ||
}, | ||
result: "hello world", | ||
}, | ||
{ | ||
template: &Template{ | ||
Src: "hello {{world}}", | ||
}, | ||
funcs: template.FuncMap{ | ||
"world": func() string { | ||
return "world" | ||
}, | ||
}, | ||
result: "hello world", | ||
}, | ||
{ | ||
template: &Template{ | ||
Src: "hello {{", | ||
}, | ||
err: "template: :1: unexpected unclosed action in command", | ||
noallocs: true, | ||
}, | ||
} | ||
} | ||
|
||
func TestParseError(t *testing.T) { | ||
expectedErr := fmt.Errorf("expected error") | ||
tmpl := &Template{ParseErr: &expectedErr} | ||
if err := tmpl.Parse("", "", nil); err != expectedErr { | ||
t.Fatalf("expected %#v; got %#v", expectedErr, err) | ||
for _, test := range tests { | ||
t.Run(test.template.Src, func(t *testing.T) { | ||
result, err := test.template.Execute(test.funcs, test.data) | ||
if actual := str(err); actual != test.err { | ||
t.Errorf("expected err %q; got %q", test.err, actual) | ||
} | ||
if result != test.result { | ||
t.Errorf("expected result %q; got %q", test.result, result) | ||
} | ||
allocs := testing.AllocsPerRun(10, func() { | ||
_, _ = test.template.Execute(test.funcs, test.data) | ||
}) | ||
if test.noallocs && allocs > 0 { | ||
t.Errorf("expected no allocations; got %f", allocs) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestParseWithFunc(t *testing.T) { | ||
tmpl := &Template{Src: "{{foo}}"} | ||
funcs := template.FuncMap{ | ||
"foo": func() string { | ||
return "bar" | ||
}, | ||
} | ||
if err := tmpl.Parse("", "", funcs); err != nil { | ||
t.Fatal(err) | ||
} | ||
if tmpl.ParseErr == nil { | ||
t.Fatal("expected non-nil parse error") | ||
} | ||
if tmpl.Template == nil { | ||
t.Fatal("expected non-nil template") | ||
} | ||
var buf bytes.Buffer | ||
if tmpl.Template.Execute(&buf, nil) != nil { | ||
t.Fatal("expected nil template execute error") | ||
} | ||
if buf.String() != "bar" { | ||
t.Fatalf("expected bar; got %s", buf.String()) | ||
func str(err error) string { | ||
if err == nil { | ||
return "" | ||
} | ||
return err.Error() | ||
} |