shithub: hugo

Download patch

ref: 3400aff2588cbf9dd4629c05537d16b019d0fdf5
parent: fdfa4a5fe62232f65f1dd8d6fe0c500374228788
author: Gareth Watts <gareth@omnipotent.net>
date: Thu Oct 22 08:14:14 EDT 2020

Allow cascade _target to work with non toml fm

The TOML lib unmarshals slices of string maps to []map[string]interface{}
whereas YAML and JSON decode to []interface{}

The existing tests only check for TOML working correctly, and _target
with cascade did not work at all for frontmatter defined in other formats.

Add a function to normalize those slices

Fixes #7874

--- a/common/maps/maps.go
+++ b/common/maps/maps.go
@@ -14,6 +14,7 @@
 package maps
 
 import (
+	"fmt"
 	"strings"
 
 	"github.com/gobwas/glob"
@@ -62,6 +63,23 @@
 func ToStringMap(in interface{}) map[string]interface{} {
 	m, _ := ToStringMapE(in)
 	return m
+}
+
+func ToSliceStringMap(in interface{}) ([]map[string]interface{}, error) {
+	switch v := in.(type) {
+	case []map[string]interface{}:
+		return v, nil
+	case []interface{}:
+		var s []map[string]interface{}
+		for _, entry := range v {
+			if vv, ok := entry.(map[string]interface{}); ok {
+				s = append(s, vv)
+			}
+		}
+		return s, nil
+	default:
+		return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
+	}
 }
 
 type keyRename struct {
--- a/common/maps/maps_test.go
+++ b/common/maps/maps_test.go
@@ -75,6 +75,39 @@
 	}
 }
 
+func TestToSliceStringMap(t *testing.T) {
+	c := qt.New(t)
+
+	tests := []struct {
+		input    interface{}
+		expected []map[string]interface{}
+	}{
+		{
+			input: []map[string]interface{}{
+				{"abc": 123},
+			},
+			expected: []map[string]interface{}{
+				{"abc": 123},
+			},
+		}, {
+			input: []interface{}{
+				map[string]interface{}{
+					"def": 456,
+				},
+			},
+			expected: []map[string]interface{}{
+				{"def": 456},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		v, err := ToSliceStringMap(test.input)
+		c.Assert(err, qt.IsNil)
+		c.Assert(v, qt.DeepEquals, test.expected)
+	}
+}
+
 func TestRenameKeys(t *testing.T) {
 	c := qt.New(t)
 
--- a/hugolib/cascade_test.go
+++ b/hugolib/cascade_test.go
@@ -459,4 +459,58 @@
 
 	})
 
+	c.Run("slice with yaml _target", func(c *qt.C) {
+		b := newBuilder(c)
+
+		b.WithContent("_index.md", `---
+title: "Home"
+cascade:
+- p1: p1
+  _target:
+    path: "**p1**"
+- p2: p2
+  _target:
+    kind: "section"
+---
+`)
+
+		b.Build(BuildCfg{})
+
+		b.AssertFileContent("public/index.html", `
+P1|p1:p1|p2:|
+S1|p1:|p2:p2|
+`)
+
+	})
+
+	c.Run("slice with json _target", func(c *qt.C) {
+		b := newBuilder(c)
+
+		b.WithContent("_index.md", `{
+"title": "Home",
+"cascade": [
+  {
+    "p1": "p1",
+	"_target": {
+	  "path": "**p1**"
+    }
+  },{
+    "p2": "p2",
+	"_target": {
+      "kind": "section"
+    }
+  }
+]
+}
+`)
+
+		b.Build(BuildCfg{})
+
+		b.AssertFileContent("public/index.html", `
+		P1|p1:p1|p2:|
+		S1|p1:|p2:p2|
+		`)
+
+	})
+
 }
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -342,8 +342,7 @@
 		if p.bucket != nil {
 			// Check for any cascade define on itself.
 			if cv, found := frontmatter["cascade"]; found {
-				switch v := cv.(type) {
-				case []map[string]interface{}:
+				if v, err := maps.ToSliceStringMap(cv); err == nil {
 					p.bucket.cascade = make(map[page.PageMatcher]maps.Params)
 
 					for _, vv := range v {
@@ -367,12 +366,12 @@
 						}
 
 					}
-				default:
+				} else {
 					p.bucket.cascade = map[page.PageMatcher]maps.Params{
 						page.PageMatcher{}: maps.ToStringMap(cv),
 					}
-				}
 
+				}
 			}
 		}
 	} else {