hugolib: Enhance .Param to permit arbitrarily nested parameter references

The Param method currently assumes that its argument is a single,
distinct, top-level key to look up in the Params map. This enhances the
Param method; it will now also attempt to see if the key can be
interpreted as a nested chain of keys to look up in Params.

Fixes #2598
This commit is contained in:
John Feminella 2017-02-19 02:50:08 -05:00 committed by Bjørn Erik Pedersen
parent 6d2281c8ea
commit b2e3748a4e
6 changed files with 142 additions and 11 deletions

View file

@ -238,6 +238,7 @@ your list templates:
{{ end }}
### Order by Parameter
Order based on the specified frontmatter parameter. Pages without that
parameter will use the site's `.Site.Params` default. If the parameter is not
found at all in some entries, those entries will appear together at the end
@ -249,6 +250,13 @@ The below example sorts a list of posts by their rating.
<!-- ... -->
{{ end }}
If the frontmatter field of interest is nested beneath another field, you can
also get it:
{{ range (.Date.Pages.ByParam "author.last_name") }}
<!-- ... -->
{{ end }}
### Reverse Order
Can be applied to any of the above. Using Date for an example.

View file

@ -103,10 +103,55 @@ which would render
**See also:** [Archetypes]({{% ref "content/archetypes.md" %}}) for consistency of `Params` across pieces of content.
### Param method
In Hugo you can declare params both for the site and the individual page. A common use case is to have a general value for the site and a more specific value for some of the pages (i.e. an image).
In Hugo you can declare params both for the site and the individual page. A
common use case is to have a general value for the site and a more specific
value for some of the pages (i.e. a header image):
```
$.Param "image"
{{ $.Param "header_image" }}
```
The `.Param` method provides a way to resolve a single value whether it's
in a page parameter or a site parameter.
When frontmatter contains nested fields, like:
```
---
author:
given_name: John
family_name: Feminella
display_name: John Feminella
---
```
then `.Param` can access them by concatenating the field names together with a
dot:
```
{{ $.Param "author.display_name" }}
```
If your frontmatter contains a top-level key that is ambiguous with a nested
key, as in the following case,
```
---
favorites.flavor: vanilla
favorites:
flavor: chocolate
---
```
then the top-level key will be preferred. In the previous example, this
```
{{ $.Param "favorites.flavor" }}
```
will print `vanilla`, not `chocolate`.
### Taxonomy Terms Page Variables
[Taxonomy Terms](/templates/terms/) pages are of the type `Page` and have the following additional variables. These are available in `layouts/_defaults/terms.html` for example.

View file

@ -314,13 +314,64 @@ func (p *Page) Param(key interface{}) (interface{}, error) {
if err != nil {
return nil, err
}
keyStr = strings.ToLower(keyStr)
result, _ := p.traverseDirect(keyStr)
if result != nil {
return result, nil
}
keySegments := strings.Split(keyStr, ".")
if len(keySegments) == 1 {
return nil, nil
}
return p.traverseNested(keySegments)
}
func (p *Page) traverseDirect(key string) (interface{}, error) {
keyStr := strings.ToLower(key)
if val, ok := p.Params[keyStr]; ok {
return val, nil
}
return p.Site.Params[keyStr], nil
}
func (p *Page) traverseNested(keySegments []string) (interface{}, error) {
result := traverse(keySegments, p.Params)
if result != nil {
return result, nil
}
result = traverse(keySegments, p.Site.Params)
if result != nil {
return result, nil
}
// Didn't find anything, but also no problems.
return nil, nil
}
func traverse(keys []string, m map[string]interface{}) interface{} {
// Shift first element off.
firstKey, rest := keys[0], keys[1:]
result := m[firstKey]
// No point in continuing here.
if result == nil {
return result
}
if len(rest) == 0 {
// That was the last key.
return result
} else {
// That was not the last key.
return traverse(rest, cast.ToStringMap(result))
}
}
func (p *Page) Author() Author {
authors := p.Authors()

View file

@ -14,9 +14,8 @@
package hugolib
import (
"sort"
"github.com/spf13/cast"
"sort"
)
var spc = newPageCache()

View file

@ -20,7 +20,6 @@ import (
"testing"
"time"
"github.com/spf13/cast"
"github.com/stretchr/testify/assert"
)
@ -121,11 +120,11 @@ func TestPageSortReverse(t *testing.T) {
func TestPageSortByParam(t *testing.T) {
t.Parallel()
var k interface{} = "arbitrary"
var k interface{} = "arbitrarily.nested"
s := newTestSite(t)
unsorted := createSortTestPages(s, 10)
delete(unsorted[9].Params, cast.ToString(k))
delete(unsorted[9].Params, "arbitrarily")
firstSetValue, _ := unsorted[0].Param(k)
secondSetValue, _ := unsorted[1].Param(k)
@ -137,7 +136,7 @@ func TestPageSortByParam(t *testing.T) {
assert.Equal(t, "xyz92", lastSetValue)
assert.Equal(t, nil, unsetValue)
sorted := unsorted.ByParam("arbitrary")
sorted := unsorted.ByParam("arbitrarily.nested")
firstSetSortedValue, _ := sorted[0].Param(k)
secondSetSortedValue, _ := sorted[1].Param(k)
lastSetSortedValue, _ := sorted[8].Param(k)
@ -182,7 +181,9 @@ func createSortTestPages(s *Site, num int) Pages {
for i := 0; i < num; i++ {
p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))
p.Params = map[string]interface{}{
"arbitrary": "xyz" + fmt.Sprintf("%v", 100-i),
"arbitrarily": map[string]interface{}{
"nested": ("xyz" + fmt.Sprintf("%v", 100-i)),
},
}
w := 5

View file

@ -1336,7 +1336,7 @@ some content
func TestPageParams(t *testing.T) {
t.Parallel()
s := newTestSite(t)
want := map[string]interface{}{
wantedMap := map[string]interface{}{
"tags": []string{"hugo", "web"},
// Issue #2752
"social": []interface{}{
@ -1348,8 +1348,35 @@ func TestPageParams(t *testing.T) {
for i, c := range pagesParamsTemplate {
p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
require.NoError(t, err, "err during parse", "#%d", i)
assert.Equal(t, want, p.Params, "#%d", i)
for key, _ := range wantedMap {
assert.Equal(t, wantedMap[key], p.Params[key], "#%d", key)
}
}
}
func TestTraverse(t *testing.T) {
exampleParams := `---
rating: "5 stars"
tags:
- hugo
- web
social:
twitter: "@jxxf"
facebook: "https://example.com"
---`
t.Parallel()
s := newTestSite(t)
p, _ := s.NewPageFrom(strings.NewReader(exampleParams), "content/post/params.md")
fmt.Println("%v", p.Params)
topLevelKeyValue, _ := p.Param("rating")
assert.Equal(t, "5 stars", topLevelKeyValue)
nestedStringKeyValue, _ := p.Param("social.twitter")
assert.Equal(t, "@jxxf", nestedStringKeyValue)
nonexistentKeyValue, _ := p.Param("doesn't.exist")
assert.Nil(t, nonexistentKeyValue)
}
func TestPageSimpleMethods(t *testing.T) {