mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00: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:
		
				
					committed by
					
						
						Bjørn Erik Pedersen
					
				
			
			
				
	
			
			
			
						parent
						
							6d2281c8ea
						
					
				
				
					commit
					b2e3748a4e
				
			@@ -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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user