shithub: hugo

Download patch

ref: d382502d6dfa1c066545e215ba83e2e0a9d2c8d7
parent: 2e95ec6844bf65a25485bdc8e2638e45788f2dcf
author: Bjørn Erik Pedersen <[email protected]>
date: Fri Feb 9 04:21:46 EST 2018

tpl/transform: Add template func for TOML/JSON/YAML docs examples conversion

Usage:

```html
{{ "title = \"Hello World\"" | transform.Remarshal "json" | safeHTML }}
```

Fixes #4389

--- a/helpers/general.go
+++ b/helpers/general.go
@@ -465,3 +465,9 @@
 
 	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))
+}
--- a/tpl/transform/init.go
+++ b/tpl/transform/init.go
@@ -88,6 +88,13 @@
 			},
 		)
 
+		ns.AddMethodMapping(ctx.Remarshal,
+			nil,
+			[][2]string{
+				{`{{ "title = \"Hello World\"" | transform.Remarshal "json" | safeHTML }}`, "{\n   \"title\": \"Hello World\"\n}\n"},
+			},
+		)
+
 		return ns
 
 	}
--- /dev/null
+++ b/tpl/transform/remarshal.go
@@ -1,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")
+
+}
--- /dev/null
+++ b/tpl/transform/remarshal_test.go
@@ -1,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)
+	}
+}