diff options
Diffstat (limited to 'tpl/encoding')
-rw-r--r-- | tpl/encoding/encoding.go | 116 | ||||
-rw-r--r-- | tpl/encoding/encoding_test.go | 121 | ||||
-rw-r--r-- | tpl/encoding/init.go | 59 |
3 files changed, 296 insertions, 0 deletions
diff --git a/tpl/encoding/encoding.go b/tpl/encoding/encoding.go new file mode 100644 index 000000000..f9102967a --- /dev/null +++ b/tpl/encoding/encoding.go @@ -0,0 +1,116 @@ +// Copyright 2020 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package encoding provides template functions for encoding content. +package encoding + +import ( + "encoding/base64" + "encoding/json" + "errors" + "html/template" + + bp "github.com/gohugoio/hugo/bufferpool" + + "github.com/gohugoio/hugo/common/maps" + "github.com/mitchellh/mapstructure" + "github.com/spf13/cast" +) + +// New returns a new instance of the encoding-namespaced template functions. +func New() *Namespace { + return &Namespace{} +} + +// Namespace provides template functions for the "encoding" namespace. +type Namespace struct{} + +// Base64Decode returns the base64 decoding of the given content. +func (ns *Namespace) Base64Decode(content any) (string, error) { + conv, err := cast.ToStringE(content) + if err != nil { + return "", err + } + + dec, err := base64.StdEncoding.DecodeString(conv) + return string(dec), err +} + +// Base64Encode returns the base64 encoding of the given content. +func (ns *Namespace) Base64Encode(content any) (string, error) { + conv, err := cast.ToStringE(content) + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString([]byte(conv)), nil +} + +// Jsonify encodes a given object to JSON. To pretty print the JSON, pass a map +// or dictionary of options as the first value in args. Supported options are +// "prefix" and "indent". Each JSON element in the output will begin on a new +// line beginning with prefix followed by one or more copies of indent according +// to the indentation nesting. +func (ns *Namespace) Jsonify(args ...any) (template.HTML, error) { + var ( + b []byte + err error + obj any + opts jsonifyOpts + ) + + switch len(args) { + case 0: + return "", nil + case 1: + obj = args[0] + case 2: + var m map[string]any + m, err = maps.ToStringMapE(args[0]) + if err != nil { + break + } + if err = mapstructure.WeakDecode(m, &opts); err != nil { + break + } + obj = args[1] + default: + err = errors.New("too many arguments to jsonify") + } + + if err != nil { + return "", err + } + + buff := bp.GetBuffer() + defer bp.PutBuffer(buff) + e := json.NewEncoder(buff) + e.SetEscapeHTML(!opts.NoHTMLEscape) + e.SetIndent(opts.Prefix, opts.Indent) + if err = e.Encode(obj); err != nil { + return "", err + } + b = buff.Bytes() + // See https://github.com/golang/go/issues/37083 + // Hugo changed from MarshalIndent/Marshal. To make the output + // the same, we need to trim the trailing newline. + b = b[:len(b)-1] + + return template.HTML(b), nil +} + +type jsonifyOpts struct { + Prefix string + Indent string + NoHTMLEscape bool +} diff --git a/tpl/encoding/encoding_test.go b/tpl/encoding/encoding_test.go new file mode 100644 index 000000000..8e6e2da48 --- /dev/null +++ b/tpl/encoding/encoding_test.go @@ -0,0 +1,121 @@ +// Copyright 2020 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "html/template" + "math" + "testing" + + qt "github.com/frankban/quicktest" +) + +type tstNoStringer struct{} + +func TestBase64Decode(t *testing.T) { + t.Parallel() + c := qt.New(t) + + ns := New() + + for _, test := range []struct { + v any + expect any + }{ + {"YWJjMTIzIT8kKiYoKSctPUB+", "abc123!?$*&()'-=@~"}, + // errors + {t, false}, + } { + + result, err := ns.Base64Decode(test.v) + + if b, ok := test.expect.(bool); ok && !b { + c.Assert(err, qt.Not(qt.IsNil)) + continue + } + + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, test.expect) + } +} + +func TestBase64Encode(t *testing.T) { + t.Parallel() + c := qt.New(t) + + ns := New() + + for _, test := range []struct { + v any + expect any + }{ + {"YWJjMTIzIT8kKiYoKSctPUB+", "WVdKak1USXpJVDhrS2lZb0tTY3RQVUIr"}, + // errors + {t, false}, + } { + + result, err := ns.Base64Encode(test.v) + + if b, ok := test.expect.(bool); ok && !b { + c.Assert(err, qt.Not(qt.IsNil)) + continue + } + + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, test.expect) + } +} + +func TestJsonify(t *testing.T) { + t.Parallel() + c := qt.New(t) + ns := New() + + for i, test := range []struct { + opts any + v any + expect any + }{ + {nil, []string{"a", "b"}, template.HTML(`["a","b"]`)}, + {map[string]string{"indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<i>\"a\",\n<i>\"b\"\n]")}, + {map[string]string{"prefix": "<p>"}, []string{"a", "b"}, template.HTML("[\n<p>\"a\",\n<p>\"b\"\n<p>]")}, + {map[string]string{"prefix": "<p>", "indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<p><i>\"a\",\n<p><i>\"b\"\n<p>]")}, + {map[string]string{"indent": "<i>"}, []string{"a", "b"}, template.HTML("[\n<i>\"a\",\n<i>\"b\"\n]")}, + {map[string]any{"noHTMLEscape": false}, []string{"<a>", "<b>"}, template.HTML("[\"\\u003ca\\u003e\",\"\\u003cb\\u003e\"]")}, + {map[string]any{"noHTMLEscape": true}, []string{"<a>", "<b>"}, template.HTML("[\"<a>\",\"<b>\"]")}, + {nil, tstNoStringer{}, template.HTML("{}")}, + {nil, nil, template.HTML("null")}, + // errors + {nil, math.NaN(), false}, + {tstNoStringer{}, []string{"a", "b"}, false}, + } { + args := []any{} + + if test.opts != nil { + args = append(args, test.opts) + } + + args = append(args, test.v) + + result, err := ns.Jsonify(args...) + + if b, ok := test.expect.(bool); ok && !b { + c.Assert(err, qt.Not(qt.IsNil), qt.Commentf("#%d", i)) + continue + } + + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, test.expect, qt.Commentf("#%d", i)) + } +} diff --git a/tpl/encoding/init.go b/tpl/encoding/init.go new file mode 100644 index 000000000..1d42b4e37 --- /dev/null +++ b/tpl/encoding/init.go @@ -0,0 +1,59 @@ +// Copyright 2020 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "github.com/gohugoio/hugo/deps" + "github.com/gohugoio/hugo/tpl/internal" +) + +const name = "encoding" + +func init() { + f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { + ctx := New() + + ns := &internal.TemplateFuncsNamespace{ + Name: name, + Context: func(args ...any) (any, error) { return ctx, nil }, + } + + ns.AddMethodMapping(ctx.Base64Decode, + []string{"base64Decode"}, + [][2]string{ + {`{{ "SGVsbG8gd29ybGQ=" | base64Decode }}`, `Hello world`}, + {`{{ 42 | base64Encode | base64Decode }}`, `42`}, + }, + ) + + ns.AddMethodMapping(ctx.Base64Encode, + []string{"base64Encode"}, + [][2]string{ + {`{{ "Hello world" | base64Encode }}`, `SGVsbG8gd29ybGQ=`}, + }, + ) + + ns.AddMethodMapping(ctx.Jsonify, + []string{"jsonify"}, + [][2]string{ + {`{{ (slice "A" "B" "C") | jsonify }}`, `["A","B","C"]`}, + {`{{ (slice "A" "B" "C") | jsonify (dict "indent" " ") }}`, "[\n \"A\",\n \"B\",\n \"C\"\n]"}, + }, + ) + + return ns + } + + internal.AddTemplateFuncsNamespace(f) +} |