Skip to content

Commit a73c7d9

Browse files
authored
feat: implement yaml functions based on helm implementation (#36)
## Description We continue to backport features, bug fixes, and more from Sprig to Sprout. This time, I'm focusing on the YAML functions part, used by Helm and intended for use by others as well. ## Changes - Add `fromYaml` function to convert yaml string into a go map - Add `toYaml` function to convert any to a yaml string - Add `mustFromYaml` function to convert and return error to the template system when occurs - Add `mustToYaml` function to convert and return error to the template system when occurs ## Fixes Masterminds/sprig#358 Masterminds/sprig#360 ## Checklist - [x] I have read the **CONTRIBUTING.md** document. - [x] My code follows the code style of this project. - [x] I have added tests to cover my changes. - [x] All new and existing tests passed. - [ ] I have updated the documentation accordingly. - [x] This change requires a change to the documentation on the website. ## Additional Information The documentation for the encoding part of the library will be released soon after this pull request on https://sprout.atom.codes Initially implemented by @blakepettersson on sprig
1 parent c6f4883 commit a73c7d9

File tree

5 files changed

+137
-9
lines changed

5 files changed

+137
-9
lines changed

Diff for: encoding_functions.go

+90-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"encoding/base64"
77
"encoding/json"
88
"strings"
9+
10+
"sigs.k8s.io/yaml"
911
)
1012

1113
// Base64Encode encodes a string into its Base64 representation.
@@ -117,7 +119,7 @@ func (fh *FunctionHandler) FromJson(v string) any {
117119
//
118120
// Example:
119121
//
120-
// jsonStr := fh.ToJson(map[string]interface{}{"name": "John", "age": 30})
122+
// jsonStr := fh.ToJson(map[string]any{"name": "John", "age": 30})
121123
// fmt.Println(jsonStr) // Output: {"age":30,"name":"John"}
122124
func (fh *FunctionHandler) ToJson(v any) string {
123125
output, _ := fh.MustToJson(v)
@@ -136,7 +138,7 @@ func (fh *FunctionHandler) ToJson(v any) string {
136138
//
137139
// Example:
138140
//
139-
// prettyJson := fh.ToPrettyJson(map[string]interface{}{"name": "John", "age": 30})
141+
// prettyJson := fh.ToPrettyJson(map[string]any{"name": "John", "age": 30})
140142
// fmt.Println(prettyJson) // Output: {
141143
// // "age": 30,
142144
// // "name": "John"
@@ -158,13 +160,54 @@ func (fh *FunctionHandler) ToPrettyJson(v any) string {
158160
//
159161
// Example:
160162
//
161-
// rawJson := fh.ToRawJson(map[string]interface{}{"content": "<div>Hello World!</div>"})
163+
// rawJson := fh.ToRawJson(map[string]any{"content": "<div>Hello World!</div>"})
162164
// fmt.Println(rawJson) // Output: {"content":"<div>Hello World!</div>"}
163165
func (fh *FunctionHandler) ToRawJson(v any) string {
164166
output, _ := fh.MustToRawJson(v)
165167
return output
166168
}
167169

170+
// FromYAML deserializes a YAML string into a Go map.
171+
//
172+
// Parameters:
173+
//
174+
// str string - the YAML string to deserialize.
175+
//
176+
// Returns:
177+
//
178+
// any - a map representing the YAML data. Returns nil if deserialization fails.
179+
//
180+
// Example:
181+
//
182+
// {{ "name: John Doe\nage: 30" | fromYAML }} // Output: map[name:John Doe age:30]
183+
func (fh *FunctionHandler) FromYAML(str string) any {
184+
var m = make(map[string]any)
185+
186+
if err := yaml.Unmarshal([]byte(str), &m); err != nil {
187+
return nil
188+
}
189+
190+
return m
191+
}
192+
193+
// ToYAML serializes a Go data structure to a YAML string.
194+
//
195+
// Parameters:
196+
//
197+
// v any - the data structure to serialize.
198+
//
199+
// Returns:
200+
//
201+
// string - the YAML string representation of the data structure.
202+
//
203+
// Example:
204+
//
205+
// {{ {"name": "John Doe", "age": 30} | toYAML }} // Output: "name: John Doe\nage: 30\n"
206+
func (fh *FunctionHandler) ToYAML(v any) string {
207+
result, _ := fh.MustToYAML(v)
208+
return result
209+
}
210+
168211
// MustFromJson decodes a JSON string into a Go data structure, returning an
169212
// error if decoding fails.
170213
//
@@ -257,3 +300,47 @@ func (fh *FunctionHandler) MustToRawJson(v any) (string, error) {
257300
}
258301
return strings.TrimSuffix(buf.String(), "\n"), nil
259302
}
303+
304+
// MustFromYaml deserializes a YAML string into a Go data structure, returning
305+
// the result along with any error that occurs.
306+
//
307+
// Parameters:
308+
//
309+
// v string - the YAML string to deserialize.
310+
//
311+
// Returns:
312+
//
313+
// any - the Go data structure representing the deserialized YAML content.
314+
// error - an error if the YAML content cannot be deserialized.
315+
//
316+
// Example:
317+
//
318+
// {{ "name: John Doe\nage: 30" | mustFromYaml }} // Output: map[name:John Doe age:30], nil
319+
func (fh *FunctionHandler) MustFromYAML(v string) (any, error) {
320+
var output any
321+
err := yaml.Unmarshal([]byte(v), &output)
322+
return output, err
323+
}
324+
325+
// MustToYAML serializes a Go data structure to a YAML string and returns any error that occurs during the serialization.
326+
//
327+
// Parameters:
328+
//
329+
// v any - the data structure to serialize.
330+
//
331+
// Returns:
332+
//
333+
// string - the YAML string representation of the data structure.
334+
// error - error if the serialization fails.
335+
//
336+
// Example:
337+
//
338+
// {{ {"name": "John Doe", "age": 30} | mustToYAML }} // Output: "name: John Doe\nage: 30\n", nil
339+
func (fh *FunctionHandler) MustToYAML(v any) (string, error) {
340+
data, err := yaml.Marshal(v)
341+
if err != nil {
342+
return "", err
343+
}
344+
345+
return strings.TrimSuffix(string(data), "\n"), nil
346+
}

Diff for: encoding_functions_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ func TestToRawJson(t *testing.T) {
8181
runTestCases(t, tests)
8282
}
8383

84+
func TestFromYAML(t *testing.T) {
85+
var tests = testCases{
86+
{"TestEmptyInput", `{{ "" | fromYaml }}`, "map[]", nil},
87+
{"TestVariableInput", `{{ .V | fromYaml }}`, "map[bar:map[baz:1] foo:55]", map[string]any{"V": "foo: 55\nbar:\n baz: 1\n"}},
88+
{"TestAccessField", `{{ (.V | fromYaml).foo }}`, "55", map[string]any{"V": "foo: 55"}},
89+
{"TestInvalidInput", `{{ .V | fromYaml }}`, "<no value>", map[string]any{"V": "foo: :: baz"}},
90+
}
91+
92+
runTestCases(t, tests)
93+
}
94+
95+
func TestToYAML(t *testing.T) {
96+
var tests = testCases{
97+
{"TestEmptyInput", `{{ "" | toYaml }}`, "\"\"", nil},
98+
{"TestVariableInput", `{{ .V | toYaml }}`, "bar: baz\nfoo: 55", map[string]any{"V": map[string]any{"foo": 55, "bar": "baz"}}},
99+
}
100+
101+
runTestCases(t, tests)
102+
}
103+
84104
func TestMustFromJson(t *testing.T) {
85105
var tests = mustTestCases{
86106
{testCase{"TestEmptyInput", `{{ "" | mustFromJson }}`, "", nil}, "unexpected end"},
@@ -120,3 +140,23 @@ func TestMustToRawJson(t *testing.T) {
120140

121141
runMustTestCases(t, tests)
122142
}
143+
144+
func TestMustFromYAML(t *testing.T) {
145+
var tests = mustTestCases{
146+
{testCase{"TestEmptyInput", `{{ "foo: :: baz" | mustFromYaml }}`, "", nil}, "error converting YAML to JSON"},
147+
{testCase{"TestVariableInput", `{{ .V | mustFromYaml }}`, "map[bar:map[baz:1] foo:55]", map[string]any{"V": "foo: 55\nbar:\n baz: 1\n"}}, ""},
148+
{testCase{"TestInvalidInput", `{{ .V | mustFromYaml }}`, "", map[string]any{"V": ":"}}, "did not find expected key"},
149+
}
150+
151+
runMustTestCases(t, tests)
152+
}
153+
154+
func TestMustToYAML(t *testing.T) {
155+
var tests = mustTestCases{
156+
{testCase{"TestEmptyInput", `{{ "" | mustToYaml }}`, "\"\"", nil}, ""},
157+
{testCase{"TestVariableInput", `{{ .V | mustToYaml }}`, "bar: baz\nfoo: 55", map[string]any{"V": map[string]any{"foo": 55, "bar": "baz"}}}, ""},
158+
{testCase{"TestInvalidInput", `{{ .V | mustToYaml }}`, "", map[string]any{"V": make(chan int)}}, "json: unsupported type: chan int"},
159+
}
160+
161+
runMustTestCases(t, tests)
162+
}

Diff for: go.mod

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ require (
66
dario.cat/mergo v1.0.0
77
github.com/Masterminds/semver/v3 v3.2.1
88
github.com/google/uuid v1.6.0
9-
github.com/huandu/xstrings v1.4.0
109
github.com/mitchellh/copystructure v1.2.0
11-
github.com/shopspring/decimal v1.3.1
1210
github.com/spf13/cast v1.6.0
1311
github.com/stretchr/testify v1.9.0
1412
golang.org/x/crypto v0.21.0
1513
golang.org/x/text v0.14.0
14+
sigs.k8s.io/yaml v1.4.0
1615
)
1716

1817
require (

Diff for: go.sum

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
1010
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1111
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1212
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
13-
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
14-
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
1513
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
1614
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
1715
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -24,8 +22,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
2422
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2523
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
2624
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
27-
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
28-
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
2925
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
3026
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
3127
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
@@ -38,3 +34,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
3834
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3935
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4036
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
37+
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
38+
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

Diff for: sprout.go

+4
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,14 @@ func FuncMap(opts ...FunctionHandlerOption) template.FuncMap {
193193
fnHandler.funcMap["toJson"] = fnHandler.ToJson
194194
fnHandler.funcMap["toPrettyJson"] = fnHandler.ToPrettyJson
195195
fnHandler.funcMap["toRawJson"] = fnHandler.ToRawJson
196+
fnHandler.funcMap["fromYaml"] = fnHandler.FromYAML
197+
fnHandler.funcMap["toYaml"] = fnHandler.ToYAML
196198
fnHandler.funcMap["mustFromJson"] = fnHandler.MustFromJson
197199
fnHandler.funcMap["mustToJson"] = fnHandler.MustToJson
198200
fnHandler.funcMap["mustToPrettyJson"] = fnHandler.MustToPrettyJson
199201
fnHandler.funcMap["mustToRawJson"] = fnHandler.MustToRawJson
202+
fnHandler.funcMap["mustFromYaml"] = fnHandler.MustFromYAML
203+
fnHandler.funcMap["mustToYaml"] = fnHandler.MustToYAML
200204
fnHandler.funcMap["ternary"] = fnHandler.Ternary
201205
fnHandler.funcMap["deepCopy"] = fnHandler.DeepCopy
202206
fnHandler.funcMap["mustDeepCopy"] = fnHandler.MustDeepCopy

0 commit comments

Comments
 (0)