summaryrefslogtreecommitdiffstats
path: root/tpl/encoding
diff options
context:
space:
mode:
Diffstat (limited to 'tpl/encoding')
-rw-r--r--tpl/encoding/encoding.go116
-rw-r--r--tpl/encoding/encoding_test.go121
-rw-r--r--tpl/encoding/init.go59
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)
+}