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
This commit is contained in:
Gareth Watts 2020-10-22 12:14:14 -05:00 committed by Bjørn Erik Pedersen
parent fdfa4a5fe6
commit 3400aff258
4 changed files with 108 additions and 4 deletions

View file

@ -14,6 +14,7 @@
package maps package maps
import ( import (
"fmt"
"strings" "strings"
"github.com/gobwas/glob" "github.com/gobwas/glob"
@ -64,6 +65,23 @@ func ToStringMap(in interface{}) map[string]interface{} {
return m 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 { type keyRename struct {
pattern glob.Glob pattern glob.Glob
newKey string newKey string

View file

@ -75,6 +75,39 @@ func TestToLower(t *testing.T) {
} }
} }
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) { func TestRenameKeys(t *testing.T) {
c := qt.New(t) c := qt.New(t)

View file

@ -459,4 +459,58 @@ S1|p1:|p2:p2|
}) })
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|
`)
})
} }

View file

@ -342,8 +342,7 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
if p.bucket != nil { if p.bucket != nil {
// Check for any cascade define on itself. // Check for any cascade define on itself.
if cv, found := frontmatter["cascade"]; found { if cv, found := frontmatter["cascade"]; found {
switch v := cv.(type) { if v, err := maps.ToSliceStringMap(cv); err == nil {
case []map[string]interface{}:
p.bucket.cascade = make(map[page.PageMatcher]maps.Params) p.bucket.cascade = make(map[page.PageMatcher]maps.Params)
for _, vv := range v { for _, vv := range v {
@ -367,14 +366,14 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
} }
} }
default: } else {
p.bucket.cascade = map[page.PageMatcher]maps.Params{ p.bucket.cascade = map[page.PageMatcher]maps.Params{
page.PageMatcher{}: maps.ToStringMap(cv), page.PageMatcher{}: maps.ToStringMap(cv),
} }
}
} }
} }
}
} else { } else {
frontmatter = make(map[string]interface{}) frontmatter = make(map[string]interface{})
} }