URI: 
       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)