diff --git a/go.mod b/go.mod index ae7a943..4c12d84 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,26 @@ module github.com/dirtydriver/templateer-cli go 1.25.4 require ( - github.com/spf13/cobra v1.8.1 - sigs.k8s.io/yaml v1.4.0 + github.com/spf13/cobra v1.10.2 + sigs.k8s.io/yaml v1.6.0 ) require ( - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/spf13/cast v1.7.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/crypto v0.47.0 // indirect ) require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.10 // indirect ) diff --git a/go.sum b/go.sum index dcd60b0..2706e4f 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,17 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -37,15 +42,29 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= @@ -54,3 +73,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/templater/templater.go b/templater/templater.go index 57ad468..c4483c1 100644 --- a/templater/templater.go +++ b/templater/templater.go @@ -25,7 +25,25 @@ func collectPlaceholders(node parse.Node, placeholders map[string]struct{}) { } case *parse.ActionNode: collectPlaceholders(n.Pipe, placeholders) + case *parse.IfNode: + collectPlaceholders(n.Pipe, placeholders) + collectPlaceholders(n.List, placeholders) + collectPlaceholders(n.ElseList, placeholders) + case *parse.RangeNode: + collectPlaceholders(n.Pipe, placeholders) + collectPlaceholders(n.List, placeholders) + collectPlaceholders(n.ElseList, placeholders) + case *parse.WithNode: + collectPlaceholders(n.Pipe, placeholders) + collectPlaceholders(n.List, placeholders) + collectPlaceholders(n.ElseList, placeholders) + case *parse.TemplateNode: + // {{ template "name" .Pipe }} + collectPlaceholders(n.Pipe, placeholders) case *parse.PipeNode: + for _, decl := range n.Decl { + collectPlaceholders(decl, placeholders) + } for _, cmd := range n.Cmds { collectPlaceholders(cmd, placeholders) } @@ -33,6 +51,15 @@ func collectPlaceholders(node parse.Node, placeholders map[string]struct{}) { for _, arg := range n.Args { collectPlaceholders(arg, placeholders) } + case *parse.VariableNode: + // VariableNode represents variables like {{ $name }} or declarations like {{ $name := ... }}. + // We record the variable name so callers can see which template variables are in use. + // Note: Ident typically contains a single entry like "$name". + if len(n.Ident) == 0 { + return + } + placeholder := strings.Join(n.Ident, ".") + placeholders[placeholder] = struct{}{} case *parse.FieldNode: // FieldNode represents expressions like {{ .GroupID }} // The Ident slice contains the field names, which we join with dots. @@ -42,7 +69,7 @@ func collectPlaceholders(node parse.Node, placeholders map[string]struct{}) { // ChainNode represents chained accesses like {{ .User.Name }} var base string if fieldNode, ok := n.Node.(*parse.FieldNode); ok { - base = "." + strings.Join(fieldNode.Ident, ".") + base = strings.Join(fieldNode.Ident, ".") } else { // Fallback: use the node's string representation. base = fmt.Sprintf("%v", n.Node) @@ -50,7 +77,7 @@ func collectPlaceholders(node parse.Node, placeholders map[string]struct{}) { if len(n.Field) > 0 { base += "." + strings.Join(n.Field, ".") } - placeholders[base] = struct{}{} + placeholders[strings.TrimPrefix(base, ".")] = struct{}{} } } @@ -70,7 +97,7 @@ func CollectParameters(tempFiles []string) ([]string, error) { go func(file string) { defer wg.Done() - tmpl, err := template.ParseFiles(file) + tmpl, err := template.New(filepath.Base(file)).Funcs(sprig.TxtFuncMap()).ParseFiles(file) if err != nil { errChan <- err return diff --git a/templater/templater_test.go b/templater/templater_test.go index 4de312f..3df255b 100644 --- a/templater/templater_test.go +++ b/templater/templater_test.go @@ -142,40 +142,40 @@ func TestRenderTemplate(t *testing.T) { // TestRenderTemplateWithSprigFunctions tests template rendering with Sprig functions. func TestRenderTemplateWithSprigFunctions(t *testing.T) { tests := []struct { - name string + name string templateContent string - params map[string]interface{} - expected string + params map[string]interface{} + expected string }{ { - name: "Upper function", + name: "Upper function", templateContent: "{{.name | upper}}", - params: map[string]interface{}{"name": "john"}, - expected: "JOHN", + params: map[string]interface{}{"name": "john"}, + expected: "JOHN", }, { - name: "Title function", + name: "Title function", templateContent: "{{.name | title}}", - params: map[string]interface{}{"name": "john doe"}, - expected: "John Doe", + params: map[string]interface{}{"name": "john doe"}, + expected: "John Doe", }, { - name: "Default function", + name: "Default function", templateContent: "{{.name | default \"Anonymous\"}}", - params: map[string]interface{}{}, - expected: "Anonymous", + params: map[string]interface{}{}, + expected: "Anonymous", }, { - name: "Date formatting", + name: "Date formatting", templateContent: "{{now | date \"2006-01-02\"}}", - params: map[string]interface{}{}, - expected: "", // We'll check this separately since date changes + params: map[string]interface{}{}, + expected: "", // We'll check this separately since date changes }, { - name: "String operations", + name: "String operations", templateContent: "{{.text | trim | repeat 2}}", - params: map[string]interface{}{"text": " hello "}, - expected: "hellohello", + params: map[string]interface{}{"text": " hello "}, + expected: "hellohello", }, } @@ -218,28 +218,28 @@ func TestRenderTemplateWithSprigFunctions(t *testing.T) { // TestRenderTemplateErrors tests error handling in RenderTemplate function. func TestRenderTemplateErrors(t *testing.T) { tests := []struct { - name string + name string templateContent string - params map[string]interface{} - expectError bool + params map[string]interface{} + expectError bool }{ { - name: "Invalid template syntax", - templateContent: "{{ .Name }", // Missing closing bracket - params: map[string]interface{}{"Name": "John"}, - expectError: true, + name: "Invalid template syntax", + templateContent: "{{ .Name }", // Missing closing bracket + params: map[string]interface{}{"Name": "John"}, + expectError: true, }, { - name: "Invalid function call", + name: "Invalid function call", templateContent: "{{ .Name | nonExistentFunction }}", - params: map[string]interface{}{"Name": "John"}, - expectError: true, + params: map[string]interface{}{"Name": "John"}, + expectError: true, }, { - name: "Execution error", - templateContent: "{{ index .Items 0 }}", // Accessing non-existent slice - params: map[string]interface{}{"Name": "John"}, - expectError: true, + name: "Execution error", + templateContent: "{{ index .Items 0 }}", // Accessing non-existent slice + params: map[string]interface{}{"Name": "John"}, + expectError: true, }, } @@ -262,7 +262,7 @@ func TestRenderTemplateErrors(t *testing.T) { // Render the template _, err = RenderTemplate(tmpFile.Name(), tt.params) - + if tt.expectError && err == nil { t.Errorf("expected error, got nil") } else if !tt.expectError && err != nil { @@ -347,3 +347,275 @@ func TestWriteTemplate_Error(t *testing.T) { t.Errorf("Expected error when writing to a directory, got nil") } } + +// TestCollectParametersFromIfNode tests that placeholders inside {{ if }} blocks are collected. +func TestCollectParametersFromIfNode(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "if_template.tmpl") + content := `{{ if .Condition }} + Value: {{ .TrueValue }} + {{ else }} + Value: {{ .FalseValue }} + {{ end }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"Condition", "TrueValue", "FalseValue"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} + +// TestCollectParametersFromRangeNode tests that placeholders inside {{ range }} blocks are collected. +func TestCollectParametersFromRangeNode(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "range_template.tmpl") + content := `{{ range .Items }} + Item: {{ .Name }} + {{ else }} + No items: {{ .EmptyMessage }} + {{ end }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"Items", "Name", "EmptyMessage"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} + +// TestCollectParametersFromWithNode tests that placeholders inside {{ with }} blocks are collected. +func TestCollectParametersFromWithNode(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "with_template.tmpl") + content := `{{ with .User }} + Name: {{ .Name }} + Email: {{ .Email }} + {{ else }} + No user: {{ .DefaultMessage }} + {{ end }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"User", "Name", "Email", "DefaultMessage"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} + +// TestCollectParametersFromPipelineWithSprig tests that placeholders in pipelines with Sprig functions are collected. +func TestCollectParametersFromPipelineWithSprig(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "pipeline_template.tmpl") + content := `Name: {{ .Name | upper }} +Project: {{ printf "%s-%s" .ProjectName .Version | lower }} +Default: {{ .MissingValue | default "fallback" }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"Name", "ProjectName", "Version", "MissingValue"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} + +// TestCollectParametersFromNestedFields tests that nested field access (ChainNode) is collected correctly. +func TestCollectParametersFromNestedFields(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "nested_template.tmpl") + content := `User: {{ .User.Profile.Name }} +Config: {{ .App.Settings.Port }} +Deep: {{ .Level1.Level2.Level3.Value }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"User.Profile.Name", "App.Settings.Port", "Level1.Level2.Level3.Value"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} + +// TestCollectParametersFromVariableDeclarations tests that pipeline variable declarations are handled. +func TestCollectParametersFromVariableDeclarations(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "variable_template.tmpl") + content := `{{ $name := .UserName }} +{{ $email := .UserEmail }} +Name: {{ $name }} +Email: {{ $email }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + // Should collect both the source fields and the variable names + // Note: Variables are prefixed with $ + expectedFields := []string{"UserName", "UserEmail"} + for _, expected := range expectedFields { + found := false + for _, param := range params { + if param == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected to find parameter %q in %v", expected, params) + } + } +} + +// TestCollectParametersComplexTemplate tests a complex template combining multiple features. +func TestCollectParametersComplexTemplate(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "complex_template.tmpl") + // This mimics the structure of .teamcity/ci.properties.tmpl + content := `# Project Configuration +vcs.project.key={{ .bitbucketProjectName }} +project.name={{ .bitbucketRepositoryName }} +sonar.portfolio.name={{ printf "%s-%s" .bitbucketProjectName .bitbucketRepositoryName | lower }} + +# Conditional Configuration +{{ if .enableDocker }} +docker.repository={{ .dockerRepository | lower }} +{{ end }} + +# Nested Configuration +java.build.image={{ .java.image }} +app.version={{ .app.version.number }}` + + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expectedParams := []string{ + "bitbucketProjectName", + "bitbucketRepositoryName", + "enableDocker", + "dockerRepository", + "java.image", + "app.version.number", + } + + for _, expected := range expectedParams { + found := false + for _, param := range params { + if param == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected to find parameter %q in %v", expected, params) + } + } +} + +// TestCollectParametersTemplateNode tests that {{ template }} invocations are handled. +func TestCollectParametersTemplateNode(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "template_invoke.tmpl") + content := `{{ template "header" .HeaderData }} +{{ template "body" .BodyContent }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"HeaderData", "BodyContent"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} + +// TestCollectParametersMultipleSprigFunctions tests templates using various Sprig functions. +func TestCollectParametersMultipleSprigFunctions(t *testing.T) { + tempDir := t.TempDir() + + file := filepath.Join(tempDir, "sprig_functions.tmpl") + content := `Upper: {{ .text | upper }} +Lower: {{ .text | lower }} +Title: {{ .text | title }} +Trim: {{ .text | trim }} +Default: {{ .missing | default "fallback" }} +Repeat: {{ .text | repeat 3 }} +Replace: {{ .text | replace "old" "new" }}` + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + params, err := CollectParameters([]string{file}) + if err != nil { + t.Fatalf("CollectParameters returned error: %v", err) + } + + expected := []string{"text", "missing"} + sort.Strings(params) + sort.Strings(expected) + if !reflect.DeepEqual(params, expected) { + t.Errorf("Expected parameters %v, got %v", expected, params) + } +} diff --git a/utils/utils_test.go b/utils/utils_test.go index f26b58d..e400f68 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -90,7 +90,7 @@ func TestCheckMissingKeys(t *testing.T) { t.Run("nested keys present", func(t *testing.T) { m := map[string]interface{}{ "project": map[string]interface{}{ - "name": "test", + "name": "test", "version": "1.0.0", }, "maven": map[string]interface{}{ @@ -214,7 +214,7 @@ func TestHasNestedKey(t *testing.T) { expected: false, }, { - name: "empty path", + name: "empty path", m: map[string]interface{}{}, path: []string{}, expected: false,