Skip to content

Commit

Permalink
builtin: add 'marshalYAML' and 'unmarshalYAML' builtins
Browse files Browse the repository at this point in the history
The 'marshalYAML' and 'unmarshalYAML' builtins encode and decode YAML
values.

For example, to decode a file called "receipt.yaml" in the template's
directory, containing:

    ---
    receipt:     Oz-Ware Purchase Invoice
    date:        2012-08-06
    customer:
      first_name:   Dorothy
      family_name:  Gale

use the following Scriggo code:

    {% var v map[string]any %}
    {{ unmarshalYAML(render "receipt.yaml", &v) }}
    {{ v.customer.first_name }} {{ v.customer.family_name }}
  • Loading branch information
gazerro committed Jul 7, 2022
1 parent 8774f32 commit 46e3dfe
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 0 deletions.
72 changes: 72 additions & 0 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@
// "hex": builtin.Hex,
// "marshalJSON": builtin.MarshalJSON,
// "marshalJSONIndent": builtin.MarshalJSONIndent,
// "marshalYAML": builtin.MarshalYAML,
// "md5": builtin.Md5,
// "unmarshalJSON": builtin.UnmarshalJSON,
// "unmarshalYAML": builtin.UnmarshalYAML,
//
// // html
// "htmlEscape": builtin.HtmlEscape,
Expand Down Expand Up @@ -158,6 +160,8 @@ import (
"github.com/open2b/scriggo"
"github.com/open2b/scriggo/internal/thirdparties"
"github.com/open2b/scriggo/native"

"gopkg.in/yaml.v3"
)

// Abbreviate abbreviates s to almost n runes. If s is longer than n runes,
Expand Down Expand Up @@ -412,6 +416,27 @@ func MarshalJSONIndent(v interface{}, prefix, indent string) (native.JSON, error
return native.JSON(b), nil
}

// MarshalYAML returns the YAML encoding of v.
//
// See https://pkg.go.dev/gopkg.in/yaml.v3#Marshal for details.
func MarshalYAML(v interface{}) (_ string, err error) {
defer func() {
msg := recover()
if msg != nil {
if s, ok := msg.(string); ok {
err = errors.New("marshalYAML: " + s)
} else {
err = errors.New("marshalYAML: unknown error")
}
}
}()
b, err := yaml.Marshal(v)
if err != nil {
return "", errors.New("marshalYAML: " + err.Error())
}
return string(b), nil
}

// Max returns the larger of x or y.
func Max(x, y int) int {
if x < y {
Expand Down Expand Up @@ -872,6 +897,53 @@ func UnmarshalJSON(data string, v interface{}) error {
return nil
}

// UnmarshalYAML parses the YAML-encoded data and stores the result in a new
// value pointed to by v. If v is nil or not a pointer, UnmarshalYAML returns
// an error.
//
// Unlike yaml.Unmarshal of the yaml package, UnmarshalYAML does not accept a
// map value, use a pointer to a map value instead. UnmarshalYAM does not
// change the value pointed to by v but instantiates a new value and then
// replaces the value pointed to by v, if no errors occur.
//
// See https://pkg.go.dev/gopkg.in/yaml.v3#Unmarshal for details.
func UnmarshalYAML(data string, v interface{}) (err error) {
if v == nil {
return errors.New("unmarshalYAML: cannot unmarshal into nil")
}
rv := reflect.ValueOf(v)
rt := rv.Type()
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("unmarshalYAML: cannot unmarshal into non-pointer value of type " + rt.String())
}
if rv.IsZero() {
return errors.New("unmarshalYAML: cannot unmarshal into a nil pointer of type " + rt.String())
}
vp := reflect.New(rt.Elem())
defer func() {
msg := recover()
if msg != nil {
if s, ok := msg.(string); ok {
err = errors.New("unmarshalYAML: " + s)
} else {
err = errors.New("unmarshalYAML: unknown error")
}
}
}()
err = yaml.Unmarshal([]byte(data), vp.Interface())
if err != nil {
if e, ok := err.(*yaml.TypeError); ok {
if len(e.Errors) == 0 {
return errors.New("unmarshalYAML: unknown error")
}
return errors.New("unmarshalYAML: " + e.Errors[0])
}
return errors.New("unmarshalYAML: " + err.Error())
}
rv.Elem().Set(vp.Elem())
return nil
}

// isSeparator reports whether the rune could mark a word boundary.
// TODO: update when package unicode captures more of the properties.
func isSeparator(r rune) bool {
Expand Down
14 changes: 14 additions & 0 deletions builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ var tests = []struct {
{(func() string { s, _ := MarshalJSONIndent(5, " ", "\t"); return string(s) })(), "5"},
{(func() string { s, _ := MarshalJSONIndent([]string{"red", "green"}, "\t", " "); return string(s) })(), "[\n\t \"red\",\n\t \"green\"\n\t]"},

// marshalYAML
{(func() string { s, _ := MarshalYAML(nil); return s })(), "null\n"},
{(func() string { s, _ := MarshalYAML(5); return s })(), "5\n"},
{(func() string { s, _ := MarshalYAML([]string{"red", "green"}); return s })(), "- red\n- green\n"},

// max
{spf("%d", Max(0, 0)), "0"},
{spf("%d", Max(5, 0)), "5"},
Expand Down Expand Up @@ -308,6 +313,15 @@ var tests = []struct {
{spf("%v", UnmarshalJSON("", []int{})), "unmarshalJSON: cannot unmarshal into non-pointer value of type []int"},
{spf("%v", UnmarshalJSON("5", &[]int{})), "unmarshalJSON: cannot unmarshal number into value of type []int"},

// unmarshalYAML
{spf("%#v", (func() interface{} { var v map[string]interface{}; _ = UnmarshalYAML("", &v); return v })()), "map[string]interface {}(nil)"},
{spf("%#v", (func() interface{} { var v map[string]interface{}; _ = UnmarshalYAML(`{"a":"b"}`, &v); return v })()), `map[string]interface {}{"a":"b"}`},
{spf("%#v", (func() interface{} { var v []int; _ = UnmarshalYAML("- 1\n- 2\n- 3\n", &v); return v })()), "[]int{1, 2, 3}"},
{spf("%v", UnmarshalYAML("", nil)), "unmarshalYAML: cannot unmarshal into nil"},
{spf("%v", UnmarshalYAML("", (*int)(nil))), "unmarshalYAML: cannot unmarshal into a nil pointer of type *int"},
{spf("%v", UnmarshalYAML("", []int{})), "unmarshalYAML: cannot unmarshal into non-pointer value of type []int"},
{spf("%v", UnmarshalYAML("5", &[]int{})), "unmarshalYAML: line 1: cannot unmarshal !!int `5` into []int"},

// unsafeconv
{string(toHTML("<a>")), "<a>"},
{string(toCSS("#AAA")), "#AAA"},
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/yuin/goldmark v1.4.12
golang.org/x/mod v0.5.1
golang.org/x/tools v0.1.9
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 46e3dfe

Please sign in to comment.