hugolib: Enhance `.Param` to permit arbitrarily nested parameter references - hugo - [fork] hugo port for 9front
HTML git clone git@git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
DIR commit b2e3748a4e148a9624b9906bd8f34a238a54429c
DIR parent 6d2281c8ead70ac07122027c989807c0aa1a7722
HTML Author: John Feminella <jxf+github@jxf.me>
Date: Sun, 19 Feb 2017 02:50:08 -0500
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
Diffstat:
M docs/content/templates/list.md | 8 ++++++++
M docs/content/templates/variables.md | 49 +++++++++++++++++++++++++++++--
M hugolib/page.go | 51 +++++++++++++++++++++++++++++++
M hugolib/pageSort.go | 3 +--
M hugolib/pageSort_test.go | 11 ++++++-----
M hugolib/page_test.go | 31 +++++++++++++++++++++++++++++--
6 files changed, 142 insertions(+), 11 deletions(-)
---
DIR diff --git a/docs/content/templates/list.md b/docs/content/templates/list.md
@@ -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.
DIR diff --git a/docs/content/templates/variables.md b/docs/content/templates/variables.md
@@ -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 "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 "image"
+{{ $.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.
DIR diff --git a/hugolib/page.go b/hugolib/page.go
@@ -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()
DIR diff --git a/hugolib/pageSort.go b/hugolib/pageSort.go
@@ -14,9 +14,8 @@
package hugolib
import (
- "sort"
-
"github.com/spf13/cast"
+ "sort"
)
var spc = newPageCache()
DIR diff --git a/hugolib/pageSort_test.go b/hugolib/pageSort_test.go
@@ -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
DIR diff --git a/hugolib/page_test.go b/hugolib/page_test.go
@@ -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,10 +1348,37 @@ 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) {
t.Parallel()
s := newTestSite(t)