Skip to content

Commit 0442a40

Browse files
askaltpsergee
authored andcommitted
templates: add built-in functions to the engine
This patch adds several useful built-in functions to the template engine, which can be used in templates. * `port` generates a port * `atoi` converts string to int * `replicasets` generates names for replicasets and instances Part of #489
1 parent c1b1ad3 commit 0442a40

File tree

4 files changed

+146
-2
lines changed

4 files changed

+146
-2
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package engines
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// genState describes template generation state.
8+
type genState struct {
9+
port int
10+
}
11+
12+
// newGenState creates genState.
13+
func newGenState() *genState {
14+
return &genState{port: 3301}
15+
}
16+
17+
// genPort generates port.
18+
func (state *genState) genPort() int {
19+
ret := state.port
20+
state.port++
21+
return ret
22+
}
23+
24+
// replicaset describes generated replicaset information.
25+
type replicaset struct {
26+
Name string
27+
InstNames []string
28+
}
29+
30+
const (
31+
maxReplicasetsNumber = 1000
32+
maxReplicasetSize = 32
33+
)
34+
35+
// genReplicasets generates replicasets bunch.
36+
func genReplicasets(
37+
baseName string,
38+
replicasetsNumber int,
39+
replicasetSize int) ([]replicaset, error) {
40+
if replicasetsNumber <= 0 || replicasetsNumber > maxReplicasetsNumber {
41+
return nil, fmt.Errorf("replicasetsNumber must be in [%d, %d]", 0, maxReplicasetsNumber)
42+
}
43+
if replicasetSize <= 0 || replicasetSize > maxReplicasetSize {
44+
return nil, fmt.Errorf("replicasetSize must be in [%d, %d]", 0, maxReplicasetSize)
45+
}
46+
47+
replicasets := make([]replicaset, replicasetsNumber)
48+
for i := range replicasets {
49+
replicasetName := fmt.Sprintf("%s-%03d", baseName, i+1)
50+
instNames := make([]string, replicasetSize)
51+
for j := range instNames {
52+
if replicasetSize <= 26 {
53+
instNames[j] = fmt.Sprintf("%s-%c", replicasetName, 'a'+byte(j))
54+
} else {
55+
instNames[j] = fmt.Sprintf("%s-%03d", replicasetName, j+1)
56+
}
57+
}
58+
replicasets[i].Name = replicasetName
59+
replicasets[i].InstNames = instNames
60+
}
61+
return replicasets, nil
62+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package engines
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestGenPort(t *testing.T) {
11+
state := newGenState()
12+
require.Equal(t, state.genPort(), 3301)
13+
require.Equal(t, state.genPort(), 3302)
14+
}
15+
16+
func TestGenReplicasets(t *testing.T) {
17+
replicasets, err := genReplicasets("name", 4, 3)
18+
require.NoError(t, err)
19+
assert.Equal(t, replicasets, []replicaset{
20+
{
21+
Name: "name-001",
22+
InstNames: []string{"name-001-a", "name-001-b", "name-001-c"},
23+
},
24+
{
25+
Name: "name-002",
26+
InstNames: []string{"name-002-a", "name-002-b", "name-002-c"},
27+
},
28+
{
29+
Name: "name-003",
30+
InstNames: []string{"name-003-a", "name-003-b", "name-003-c"},
31+
},
32+
{
33+
Name: "name-004",
34+
InstNames: []string{"name-004-a", "name-004-b", "name-004-c"},
35+
},
36+
})
37+
38+
replicasets, err = genReplicasets("name", 1, 27)
39+
require.NoError(t, err)
40+
assert.Equal(t, replicasets[0].InstNames[26], "name-001-027")
41+
42+
_, err = genReplicasets("name", -1, 0)
43+
require.Error(t, err)
44+
}

cli/templates/internal/engines/go_text_engine.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,25 @@ import (
44
"bytes"
55
"fmt"
66
"os"
7+
"path"
8+
"strconv"
79
"text/template"
810
)
911

1012
type GoTextEngine struct {
1113
}
1214

15+
// makeTemplate creates template for rendering.
16+
func makeTemplate(name string) *template.Template {
17+
state := newGenState()
18+
funcMap := template.FuncMap{
19+
"port": state.genPort,
20+
"replicasets": genReplicasets,
21+
"atoi": strconv.Atoi,
22+
}
23+
return template.New(name).Funcs(funcMap)
24+
}
25+
1326
// RenderFile renders srcPath template to dstPath using go text/template engine.
1427
func (GoTextEngine) RenderFile(srcPath string, dstPath string, data interface{}) error {
1528
stat, err := os.Stat(srcPath)
@@ -18,7 +31,12 @@ func (GoTextEngine) RenderFile(srcPath string, dstPath string, data interface{})
1831
}
1932
originFileMode := stat.Mode()
2033

21-
parsedTemplate, err := template.ParseFiles(srcPath)
34+
content, err := os.ReadFile(srcPath)
35+
if err != nil {
36+
return fmt.Errorf("error reading file %s: %s", srcPath, err)
37+
}
38+
39+
parsedTemplate, err := makeTemplate(path.Base(srcPath)).Parse(string(content))
2240
if err != nil {
2341
return fmt.Errorf("error parsing %s: %s", srcPath, err)
2442
}
@@ -41,7 +59,7 @@ func (GoTextEngine) RenderFile(srcPath string, dstPath string, data interface{})
4159

4260
// RenderText renders in text using go tex/template engine.
4361
func (GoTextEngine) RenderText(in string, data interface{}) (string, error) {
44-
parsedTemplate, err := template.New("file").Parse(in)
62+
parsedTemplate, err := makeTemplate("file").Parse(in)
4563
if err != nil {
4664
return "", fmt.Errorf("failed to parse %s: %s", in, err)
4765
}

cli/templates/internal/engines/go_text_engine_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,24 @@ func TestTextRendering(t *testing.T) {
8585
_, err = engine.RenderText(templateText, data)
8686
require.EqualError(t, err, "template execution failed: template: file:1:2: "+
8787
"executing \"file\" at <.hello>: map has no entry for key \"hello\"")
88+
89+
// Test builtin functions.
90+
templateText = "{{port}}"
91+
expectedText = "3301"
92+
actualText, err = engine.RenderText(templateText, nil)
93+
require.NoError(t, err)
94+
assert.Equal(t, expectedText, actualText)
95+
96+
templateText = `{{range replicasets "name" 1 1}}` +
97+
"Hi, {{.Name}}! Your instances: {{ range .InstNames }}{{.}}{{end}}{{end}}"
98+
expectedText = "Hi, name-001! Your instances: name-001-a"
99+
actualText, err = engine.RenderText(templateText, nil)
100+
require.NoError(t, err)
101+
assert.Equal(t, expectedText, actualText)
102+
103+
templateText = `{{atoi "5"}} apples`
104+
expectedText = "5 apples"
105+
actualText, err = engine.RenderText(templateText, nil)
106+
require.NoError(t, err)
107+
assert.Equal(t, expectedText, actualText)
88108
}

0 commit comments

Comments
 (0)