mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05: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 }}
|
||||
|
||||
### 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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
package hugolib
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var spc = newPageCache()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue