ref: b2e3748a4e148a9624b9906bd8f34a238a54429c
parent: 6d2281c8ead70ac07122027c989807c0aa1a7722
author: John Feminella <[email protected]>
date: Sat Feb 18 21:50:08 EST 2017
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
--- a/docs/content/templates/list.md
+++ b/docs/content/templates/list.md
@@ -238,6 +238,7 @@
{{ 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
@@ -246,6 +247,13 @@
The below example sorts a list of posts by their rating.
{{ range (.Data.Pages.ByParam "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 }}
--- a/docs/content/templates/variables.md
+++ b/docs/content/templates/variables.md
@@ -103,10 +103,55 @@
**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.
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -314,11 +314,62 @@
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 {
--- a/hugolib/pageSort.go
+++ b/hugolib/pageSort.go
@@ -14,9 +14,8 @@
package hugolib
import (
- "sort"
-
"github.com/spf13/cast"
+ "sort"
)
var spc = newPageCache()
--- a/hugolib/pageSort_test.go
+++ b/hugolib/pageSort_test.go
@@ -20,7 +20,6 @@
"testing"
"time"
- "github.com/spf13/cast"
"github.com/stretchr/testify/assert"
)
@@ -121,11 +120,11 @@
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 @@
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 @@
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
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -1336,7 +1336,7 @@
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 @@
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) {