mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-29 07:00:31 +03:00
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:
parent
6d2281c8ea
commit
b2e3748a4e
6 changed files with 142 additions and 11 deletions
|
@ -238,6 +238,7 @@ your list templates:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
### Order by Parameter
|
### Order by Parameter
|
||||||
|
|
||||||
Order based on the specified frontmatter parameter. Pages without that
|
Order based on the specified frontmatter parameter. Pages without that
|
||||||
parameter will use the site's `.Site.Params` default. If the parameter is not
|
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
|
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 }}
|
{{ 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
|
### Reverse Order
|
||||||
Can be applied to any of the above. Using Date for an example.
|
Can be applied to any of the above. Using Date for an example.
|
||||||
|
|
||||||
|
|
|
@ -103,10 +103,55 @@ which would render
|
||||||
**See also:** [Archetypes]({{% ref "content/archetypes.md" %}}) for consistency of `Params` across pieces of content.
|
**See also:** [Archetypes]({{% ref "content/archetypes.md" %}}) for consistency of `Params` across pieces of content.
|
||||||
|
|
||||||
### Param method
|
### 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 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.
|
[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.
|
||||||
|
|
|
@ -314,13 +314,64 @@ func (p *Page) Param(key interface{}) (interface{}, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyStr = strings.ToLower(keyStr)
|
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 {
|
if val, ok := p.Params[keyStr]; ok {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.Site.Params[keyStr], 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 {
|
func (p *Page) Author() Author {
|
||||||
authors := p.Authors()
|
authors := p.Authors()
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,8 @@
|
||||||
package hugolib
|
package hugolib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var spc = newPageCache()
|
var spc = newPageCache()
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -121,11 +120,11 @@ func TestPageSortReverse(t *testing.T) {
|
||||||
|
|
||||||
func TestPageSortByParam(t *testing.T) {
|
func TestPageSortByParam(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var k interface{} = "arbitrary"
|
var k interface{} = "arbitrarily.nested"
|
||||||
s := newTestSite(t)
|
s := newTestSite(t)
|
||||||
|
|
||||||
unsorted := createSortTestPages(s, 10)
|
unsorted := createSortTestPages(s, 10)
|
||||||
delete(unsorted[9].Params, cast.ToString(k))
|
delete(unsorted[9].Params, "arbitrarily")
|
||||||
|
|
||||||
firstSetValue, _ := unsorted[0].Param(k)
|
firstSetValue, _ := unsorted[0].Param(k)
|
||||||
secondSetValue, _ := unsorted[1].Param(k)
|
secondSetValue, _ := unsorted[1].Param(k)
|
||||||
|
@ -137,7 +136,7 @@ func TestPageSortByParam(t *testing.T) {
|
||||||
assert.Equal(t, "xyz92", lastSetValue)
|
assert.Equal(t, "xyz92", lastSetValue)
|
||||||
assert.Equal(t, nil, unsetValue)
|
assert.Equal(t, nil, unsetValue)
|
||||||
|
|
||||||
sorted := unsorted.ByParam("arbitrary")
|
sorted := unsorted.ByParam("arbitrarily.nested")
|
||||||
firstSetSortedValue, _ := sorted[0].Param(k)
|
firstSetSortedValue, _ := sorted[0].Param(k)
|
||||||
secondSetSortedValue, _ := sorted[1].Param(k)
|
secondSetSortedValue, _ := sorted[1].Param(k)
|
||||||
lastSetSortedValue, _ := sorted[8].Param(k)
|
lastSetSortedValue, _ := sorted[8].Param(k)
|
||||||
|
@ -182,7 +181,9 @@ func createSortTestPages(s *Site, num int) Pages {
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))
|
p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))
|
||||||
p.Params = map[string]interface{}{
|
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
|
w := 5
|
||||||
|
|
|
@ -1336,7 +1336,7 @@ some content
|
||||||
func TestPageParams(t *testing.T) {
|
func TestPageParams(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := newTestSite(t)
|
s := newTestSite(t)
|
||||||
want := map[string]interface{}{
|
wantedMap := map[string]interface{}{
|
||||||
"tags": []string{"hugo", "web"},
|
"tags": []string{"hugo", "web"},
|
||||||
// Issue #2752
|
// Issue #2752
|
||||||
"social": []interface{}{
|
"social": []interface{}{
|
||||||
|
@ -1348,10 +1348,37 @@ func TestPageParams(t *testing.T) {
|
||||||
for i, c := range pagesParamsTemplate {
|
for i, c := range pagesParamsTemplate {
|
||||||
p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
|
p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
|
||||||
require.NoError(t, err, "err during parse", "#%d", i)
|
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) {
|
func TestPageSimpleMethods(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := newTestSite(t)
|
s := newTestSite(t)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue