summaryrefslogtreecommitdiffstats
path: root/tpl
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2018-02-09 09:21:46 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2018-02-10 12:36:31 +0100
commitd382502d6dfa1c066545e215ba83e2e0a9d2c8d7 (patch)
tree66b88972782856043b1b1f6fb22cdf272cb3ddde /tpl
parent2e95ec6844bf65a25485bdc8e2638e45788f2dcf (diff)
tpl/transform: Add template func for TOML/JSON/YAML docs examples conversion
Usage: ```html {{ "title = \"Hello World\"" | transform.Remarshal "json" | safeHTML }} ``` Fixes #4389
Diffstat (limited to 'tpl')
-rw-r--r--tpl/transform/init.go7
-rw-r--r--tpl/transform/remarshal.go98
-rw-r--r--tpl/transform/remarshal_test.go154
3 files changed, 259 insertions, 0 deletions
diff --git a/tpl/transform/init.go b/tpl/transform/init.go
index c0e9b2d5d..86951c253 100644
--- a/tpl/transform/init.go
+++ b/tpl/transform/init.go
@@ -88,6 +88,13 @@ func init() {
},
)
+ ns.AddMethodMapping(ctx.Remarshal,
+ nil,
+ [][2]string{
+ {`{{ "title = \"Hello World\"" | transform.Remarshal "json" | safeHTML }}`, "{\n \"title\": \"Hello World\"\n}\n"},
+ },
+ )
+
return ns
}
diff --git a/tpl/transform/remarshal.go b/tpl/transform/remarshal.go
new file mode 100644
index 000000000..490def5f3
--- /dev/null
+++ b/tpl/transform/remarshal.go
@@ -0,0 +1,98 @@
+package transform
+
+import (
+ "bytes"
+ "errors"
+ "strings"
+
+ "github.com/gohugoio/hugo/parser"
+ "github.com/spf13/cast"
+)
+
+// Remarshal is used in the Hugo documentation to convert configuration
+// examples from YAML to JSON, TOML (and possibly the other way around).
+// The is primarily a helper for the Hugo docs site.
+// It is not a general purpose YAML to TOML converter etc., and may
+// change without notice if it serves a purpose in the docs.
+// Format is one of json, yaml or toml.
+func (ns *Namespace) Remarshal(format string, data interface{}) (string, error) {
+ from, err := cast.ToStringE(data)
+ if err != nil {
+ return "", err
+ }
+
+ from = strings.TrimSpace(from)
+ format = strings.TrimSpace(strings.ToLower(format))
+
+ if from == "" {
+ return "", nil
+ }
+
+ mark, err := toFormatMark(format)
+ if err != nil {
+ return "", err
+ }
+
+ fromFormat, err := detectFormat(from)
+ if err != nil {
+ return "", err
+ }
+
+ var metaHandler func(d []byte) (map[string]interface{}, error)
+
+ switch fromFormat {
+ case "yaml":
+ metaHandler = parser.HandleYAMLMetaData
+ case "toml":
+ metaHandler = parser.HandleTOMLMetaData
+ case "json":
+ metaHandler = parser.HandleJSONMetaData
+ }
+
+ meta, err := metaHandler([]byte(from))
+ if err != nil {
+ return "", err
+ }
+
+ var result bytes.Buffer
+ if err := parser.InterfaceToConfig(meta, mark, &result); err != nil {
+ return "", err
+ }
+
+ return result.String(), nil
+}
+
+func toFormatMark(format string) (rune, error) {
+ // TODO(bep) the parser package needs a cleaning.
+ switch format {
+ case "yaml":
+ return rune(parser.YAMLLead[0]), nil
+ case "toml":
+ return rune(parser.TOMLLead[0]), nil
+ case "json":
+ return rune(parser.JSONLead[0]), nil
+ }
+
+ return 0, errors.New("failed to detect target data serialization format")
+}
+
+func detectFormat(data string) (string, error) {
+ jsonIdx := strings.Index(data, "{")
+ yamlIdx := strings.Index(data, ":")
+ tomlIdx := strings.Index(data, "=")
+
+ if jsonIdx != -1 && (yamlIdx == -1 || jsonIdx < yamlIdx) && (tomlIdx == -1 || jsonIdx < tomlIdx) {
+ return "json", nil
+ }
+
+ if yamlIdx != -1 && (tomlIdx == -1 || yamlIdx < tomlIdx) {
+ return "yaml", nil
+ }
+
+ if tomlIdx != -1 {
+ return "toml", nil
+ }
+
+ return "", errors.New("failed to detect data serialization format")
+
+}
diff --git a/tpl/transform/remarshal_test.go b/tpl/transform/remarshal_test.go
new file mode 100644
index 000000000..78980e62c
--- /dev/null
+++ b/tpl/transform/remarshal_test.go
@@ -0,0 +1,154 @@
+// Copyright 2018 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 transform
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRemarshal(t *testing.T) {
+ t.Parallel()
+
+ ns := New(newDeps(viper.New()))
+ assert := require.New(t)
+
+ tomlExample := `title = "Test Metadata"
+
+[[resources]]
+ src = "**image-4.png"
+ title = "The Fourth Image!"
+ [resources.params]
+ byline = "picasso"
+
+[[resources]]
+ name = "my-cool-image-:counter"
+ src = "**.png"
+ title = "TOML: The Image #:counter"
+ [resources.params]
+ byline = "bep"
+`
+
+ yamlExample := `resources:
+- params:
+ byline: picasso
+ src: '**image-4.png'
+ title: The Fourth Image!
+- name: my-cool-image-:counter
+ params:
+ byline: bep
+ src: '**.png'
+ title: 'TOML: The Image #:counter'
+title: Test Metadata
+`
+
+ jsonExample := `{
+ "resources": [
+ {
+ "params": {
+ "byline": "picasso"
+ },
+ "src": "**image-4.png",
+ "title": "The Fourth Image!"
+ },
+ {
+ "name": "my-cool-image-:counter",
+ "params": {
+ "byline": "bep"
+ },
+ "src": "**.png",
+ "title": "TOML: The Image #:counter"
+ }
+ ],
+ "title": "Test Metadata"
+}
+`
+
+ variants := []struct {
+ format string
+ data string
+ }{
+ {"yaml", yamlExample},
+ {"json", jsonExample},
+ {"toml", tomlExample},
+ {"TOML", tomlExample},
+ {"Toml", tomlExample},
+ {" TOML ", tomlExample},
+ }
+
+ for _, v1 := range variants {
+ for _, v2 := range variants {
+ // Both from and to may be the same here, but that is fine.
+ fromTo := fmt.Sprintf("%s => %s", v2.format, v1.format)
+
+ converted, err := ns.Remarshal(v1.format, v2.data)
+ assert.NoError(err, fromTo)
+ diff := helpers.DiffStrings(v1.data, converted)
+ if len(diff) > 0 {
+ t.Errorf("[%s] Expected \n%v\ngot\n%v\ndiff:\n%v", fromTo, v1.data, converted, diff)
+ }
+
+ }
+ }
+
+}
+
+func TestTestRemarshalError(t *testing.T) {
+ t.Parallel()
+
+ ns := New(newDeps(viper.New()))
+ assert := require.New(t)
+
+ _, err := ns.Remarshal("asdf", "asdf")
+ assert.Error(err)
+
+ _, err = ns.Remarshal("json", "asdf")
+ assert.Error(err)
+
+}
+
+func TestRemarshalDetectFormat(t *testing.T) {
+ t.Parallel()
+ assert := require.New(t)
+
+ for i, test := range []struct {
+ data string
+ expect interface{}
+ }{
+ {`foo = "bar"`, "toml"},
+ {` foo = "bar"`, "toml"},
+ {`foo="bar"`, "toml"},
+ {`foo: "bar"`, "yaml"},
+ {`foo:"bar"`, "yaml"},
+ {`{ "foo": "bar"`, "json"},
+ {`asdfasdf`, false},
+ {``, false},
+ } {
+ errMsg := fmt.Sprintf("[%d] %s", i, test.data)
+
+ result, err := detectFormat(test.data)
+
+ if b, ok := test.expect.(bool); ok && !b {
+ assert.Error(err, errMsg)
+ continue
+ }
+
+ assert.NoError(err, errMsg)
+ assert.Equal(test.expect, result, errMsg)
+ }
+}