summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--helpers/general.go6
-rw-r--r--tpl/transform/init.go7
-rw-r--r--tpl/transform/remarshal.go98
-rw-r--r--tpl/transform/remarshal_test.go154
4 files changed, 265 insertions, 0 deletions
diff --git a/helpers/general.go b/helpers/general.go
index 3d4396440..da05548f4 100644
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -465,3 +465,9 @@ func DiffStringSlices(slice1 []string, slice2 []string) []string {
return diffStr
}
+
+// DiffString splits the strings into fields and runs it into DiffStringSlices.
+// Useful for tests.
+func DiffStrings(s1, s2 string) []string {
+ return DiffStringSlices(strings.Fields(s1), strings.Fields(s2))
+}
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)
+ }
+}