Skip to content

Commit df4afda

Browse files
committed
feat: support templates in initHelp
1 parent 2c74475 commit df4afda

File tree

7 files changed

+126
-26
lines changed

7 files changed

+126
-26
lines changed

cmd/docs/templates/_schema.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
| `description` | `string` | | Description of what the recipe does |
1212
| `source` | `string` | | URL to source code for this recipe. |
1313
| `templateExtension` | `string` | | File extension of files in "templates" directory which should be templated. Files not matched by this extension will be copied as-is. If left empty (the default), all files will be templated. |
14-
| `initHelp` | `string` | | A message which will be showed to an user after a succesful recipe execution. Can be used to guide the user what should be done next in the project directory. |
14+
| `initHelp` | `string` | | A message which will be showed to an user after a succesful recipe execution. Can be used to guide the user what should be done next in the project directory. Supports templating |
1515
| `ignorePatterns` | `[]string` | | Glob patterns for ignoring generated files from future recipe upgrades. Ignored files will not be regenerated even if their templates change in future versions of the recipe. |
1616
| `vars` | [`[]Variable`](#variable) | | An array of variables which can be used in templates. The user will be prompted to provide the value for the variable if not set with `--set` flag. |
1717

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ require (
3535
github.com/cucumber/messages/go/v21 v21.0.1 // indirect
3636
github.com/docker/docker v24.0.6+incompatible // indirect
3737
github.com/docker/docker-credential-helpers v0.8.0 // indirect
38+
github.com/fatih/structs v1.1.0 // indirect
3839
github.com/gogo/protobuf v1.3.2 // indirect
3940
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
4041
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
5959
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
6060
github.com/expr-lang/expr v1.16.0 h1:BQabx+PbjsL2PEQwkJ4GIn3CcuUh8flduHhJ0lHjWwE=
6161
github.com/expr-lang/expr v1.16.0/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
62+
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
63+
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
6264
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
6365
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
6466
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=

internal/cli/execute.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,11 @@ func runExecute(cmd *cobra.Command, opts executeOptions) error {
208208
cmd.Printf("The following files were created:\n\n%s", tree)
209209

210210
if re.InitHelp != "" {
211-
cmd.Printf("\nNext up: %s\n", re.InitHelp)
211+
help, err := sauce.RenderInitHelp()
212+
if err != nil {
213+
return err
214+
}
215+
cmd.Printf("\nNext up: %s\n", help)
212216
}
213217

214218
return nil

pkg/recipe/execute.go

+14-24
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ import (
99
"github.com/gofrs/uuid"
1010
)
1111

12+
// TemplateContext defines the context that is passed to the template engine
13+
type TemplateContext struct {
14+
ID string
15+
Recipe struct {
16+
APIVersion string
17+
Name string
18+
Version string
19+
Source string
20+
}
21+
Variables VariableValues
22+
}
23+
1224
type RenderEngine interface {
1325
Render(templates map[string][]byte, values map[string]interface{}) (map[string][]byte, error)
1426
}
@@ -23,29 +35,9 @@ func (re *Recipe) Execute(engine RenderEngine, values VariableValues, id uuid.UU
2335
sauce.Recipe = *re
2436
sauce.Values = values
2537
sauce.ID = id
38+
sauce.Files = make(map[string]File, len(re.Templates))
2639

27-
mappedValues := make(VariableValues)
28-
for name, value := range values {
29-
switch value := value.(type) {
30-
// Map table to more convenient format
31-
case TableValue:
32-
mappedValues[name] = value.ToMapSlice()
33-
default:
34-
mappedValues[name] = value
35-
}
36-
}
37-
38-
// Define the context which is available on templates
39-
context := map[string]interface{}{
40-
"ID": sauce.ID.String(),
41-
"Recipe": struct{ APIVersion, Name, Version, Source string }{
42-
re.APIVersion,
43-
re.Name,
44-
re.Version,
45-
re.Source,
46-
},
47-
"Variables": mappedValues,
48-
}
40+
context := sauce.CreateTemplateContext()
4941

5042
// Filter out templates we might not want to render
5143
templates := make(map[string][]byte)
@@ -66,8 +58,6 @@ func (re *Recipe) Execute(engine RenderEngine, values VariableValues, id uuid.UU
6658
// Add the plain files
6759
maps.Copy(files, plainFiles)
6860

69-
sauce.Files = make(map[string]File, len(re.Templates))
70-
7161
idx := 0
7262
for filename, content := range files {
7363
// Skip empty files

pkg/recipe/sauce.go

+42
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package recipe
22

33
import (
44
"fmt"
5+
"strings"
6+
"text/template"
57

8+
"github.com/fatih/structs"
69
"github.com/gofrs/uuid"
710
)
811

@@ -82,3 +85,42 @@ func (s *Sauce) Conflicts(other *Sauce) []RecipeConflict {
8285
}
8386
return conflicts
8487
}
88+
89+
func (s *Sauce) CreateTemplateContext() map[string]interface{} {
90+
mappedValues := make(VariableValues)
91+
for name, value := range s.Values {
92+
switch value := value.(type) {
93+
// Map table to more convenient format
94+
case TableValue:
95+
mappedValues[name] = value.ToMapSlice()
96+
default:
97+
mappedValues[name] = value
98+
}
99+
}
100+
101+
return structs.Map(TemplateContext{
102+
ID: s.ID.String(),
103+
Recipe: struct{ APIVersion, Name, Version, Source string }{
104+
s.Recipe.APIVersion,
105+
s.Recipe.Name,
106+
s.Recipe.Version,
107+
s.Recipe.Source,
108+
},
109+
Variables: mappedValues,
110+
})
111+
}
112+
113+
func (s *Sauce) RenderInitHelp() (string, error) {
114+
context := s.CreateTemplateContext()
115+
t, err := template.New("initHelp").Parse(s.Recipe.InitHelp)
116+
if err != nil {
117+
return "", fmt.Errorf("failed to parse initHelp template: %w", err)
118+
}
119+
120+
var buf strings.Builder
121+
if err := t.Execute(&buf, context); err != nil {
122+
return "", fmt.Errorf("failed to render initHelp template: %w", err)
123+
}
124+
125+
return buf.String(), nil
126+
}

pkg/recipe/sauce_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package recipe
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gofrs/uuid"
7+
)
8+
9+
func TestRenderInitHelp(t *testing.T) {
10+
scenarios := []struct {
11+
name string
12+
help string
13+
values VariableValues
14+
expectedOutput string
15+
expectingErr bool
16+
}{
17+
{
18+
"conditional text",
19+
"{{ if .Variables.FOO }}Foo is true{{ else }}Foo is false{{ end }}",
20+
VariableValues{
21+
"FOO": true,
22+
},
23+
"Foo is true",
24+
false,
25+
},
26+
{
27+
"invalid template",
28+
"{{ if .Variables.NOT_FOUND }}",
29+
VariableValues{
30+
"FOO": true,
31+
},
32+
"",
33+
true,
34+
},
35+
}
36+
37+
for _, scenario := range scenarios {
38+
t.Run(scenario.name, func(t *testing.T) {
39+
re := NewRecipe()
40+
re.Name = "test"
41+
re.Version = "0.0.0"
42+
re.InitHelp = scenario.help
43+
44+
sauce := NewSauce()
45+
sauce.Recipe = *re
46+
sauce.ID = uuid.Must(uuid.NewV4())
47+
sauce.Values = scenario.values
48+
49+
if help, err := sauce.RenderInitHelp(); err != nil {
50+
if !scenario.expectingErr {
51+
t.Fatalf("Got error when not expected: %s", err)
52+
}
53+
} else if scenario.expectingErr {
54+
t.Fatal("Was expecting error, did not get any")
55+
56+
} else if help != scenario.expectedOutput {
57+
t.Fatalf("Expected output '%s', got '%s'", scenario.expectedOutput, help)
58+
}
59+
})
60+
}
61+
}

0 commit comments

Comments
 (0)