Skip to content

Commit

Permalink
feat: support templates in initHelp
Browse files Browse the repository at this point in the history
  • Loading branch information
majori committed Feb 12, 2024
1 parent 2c74475 commit fd369a2
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 26 deletions.
2 changes: 1 addition & 1 deletion cmd/docs/templates/_schema.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
| `description` | `string` | | Description of what the recipe does |
| `source` | `string` | | URL to source code for this recipe. |
| `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. |
| `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. |
| `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 |
| `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. |
| `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. |

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/cucumber/messages/go/v21 v21.0.1 // indirect
github.com/docker/docker v24.0.6+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/expr-lang/expr v1.16.0 h1:BQabx+PbjsL2PEQwkJ4GIn3CcuUh8flduHhJ0lHjWwE=
github.com/expr-lang/expr v1.16.0/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
Expand Down
6 changes: 5 additions & 1 deletion internal/cli/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ func runExecute(cmd *cobra.Command, opts executeOptions) error {
cmd.Printf("The following files were created:\n\n%s", tree)

if re.InitHelp != "" {
cmd.Printf("\nNext up: %s\n", re.InitHelp)
help, err := sauce.RenderInitHelp()
if err != nil {
return err
}
cmd.Printf("\nNext up: %s\n", help)
}

return nil
Expand Down
38 changes: 14 additions & 24 deletions pkg/recipe/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import (
"github.com/gofrs/uuid"
)

// TemplateContext defines the context that is passed to the template engine
type TemplateContext struct {
ID string
Recipe struct {
APIVersion string
Name string
Version string
Source string
}
Variables VariableValues
}

type RenderEngine interface {
Render(templates map[string][]byte, values map[string]interface{}) (map[string][]byte, error)
}
Expand All @@ -23,29 +35,9 @@ func (re *Recipe) Execute(engine RenderEngine, values VariableValues, id uuid.UU
sauce.Recipe = *re
sauce.Values = values
sauce.ID = id
sauce.Files = make(map[string]File, len(re.Templates))

mappedValues := make(VariableValues)
for name, value := range values {
switch value := value.(type) {
// Map table to more convenient format
case TableValue:
mappedValues[name] = value.ToMapSlice()
default:
mappedValues[name] = value
}
}

// Define the context which is available on templates
context := map[string]interface{}{
"ID": sauce.ID.String(),
"Recipe": struct{ APIVersion, Name, Version, Source string }{
re.APIVersion,
re.Name,
re.Version,
re.Source,
},
"Variables": mappedValues,
}
context := sauce.CreateTemplateContext()

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

sauce.Files = make(map[string]File, len(re.Templates))

idx := 0
for filename, content := range files {
// Skip empty files
Expand Down
42 changes: 42 additions & 0 deletions pkg/recipe/sauce.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package recipe

import (
"fmt"
"strings"
"text/template"

"github.com/fatih/structs"
"github.com/gofrs/uuid"
)

Expand Down Expand Up @@ -82,3 +85,42 @@ func (s *Sauce) Conflicts(other *Sauce) []RecipeConflict {
}
return conflicts
}

func (s *Sauce) CreateTemplateContext() map[string]interface{} {
mappedValues := make(VariableValues)
for name, value := range s.Values {
switch value := value.(type) {
// Map table to more convenient format
case TableValue:
mappedValues[name] = value.ToMapSlice()
default:
mappedValues[name] = value
}
}

return structs.Map(TemplateContext{
ID: s.ID.String(),
Recipe: struct{ APIVersion, Name, Version, Source string }{
s.Recipe.APIVersion,
s.Recipe.Name,
s.Recipe.Version,
s.Recipe.Source,
},
Variables: mappedValues,
})
}

func (s *Sauce) RenderInitHelp() (string, error) {
context := s.CreateTemplateContext()
t, err := template.New("initHelp").Parse(s.Recipe.InitHelp)
if err != nil {
return "", fmt.Errorf("failed to parse initHelp template: %w", err)
}

var buf strings.Builder
if err := t.Execute(&buf, context); err != nil {
return "", fmt.Errorf("failed to render initHelp template: %w", err)
}

return buf.String(), nil
}
58 changes: 58 additions & 0 deletions pkg/recipe/sauce_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package recipe

import (
"testing"

"github.com/gofrs/uuid"
)

func TestRenderInitHelp(t *testing.T) {
scenarios := []struct {
name string
help string
values VariableValues
expectedOutput string
expectingErr bool
}{
{
"conditional text",
"{{ if .Variables.FOO }}Foo is true{{ else }}Foo is false{{ end }}",
VariableValues{
"FOO": true,
},
"Foo is true",
false,
},
{
"invalid template",
"{{ if .Variables.NOT_FOUND }}",
VariableValues{
"FOO": true,
},
"",
true,
},
}

for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
re := NewRecipe()
re.Name = "test"
re.Version = "0.0.0"
re.InitHelp = scenario.help

sauce := NewSauce()
sauce.Recipe = *re
sauce.ID = uuid.Must(uuid.NewV4())
sauce.Values = scenario.values

if help, err := sauce.RenderInitHelp(); err != nil {
if !scenario.expectingErr {
t.Fatalf("Got error when not expected: %s", err)
}
} else if help != scenario.expectedOutput {
t.Fatalf("Expected output '%s', got '%s'", scenario.expectedOutput, help)
}
})
}
}

0 comments on commit fd369a2

Please sign in to comment.