Add sort and grouping functions for publish date and param of Page `GroupBy` is modified to allow it to receive a method name argument for example `Type` as its first argument. It is only allowed to call with a method which takes no arguments and returns a result or a pair of a result and an error. - hugo - [fork] hugo port for 9front
 (HTM) git clone git@git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 5e28606b84ef230914d0e63aebe3925364ee3966
 (DIR) parent d013edb7f838b739db72530e06eb47721baec7b8
 (HTM) Author: Tatsushi Demachi <tdemachi@gmail.com>
       Date:   Sat, 18 Oct 2014 00:10:19 +0900
       
       Add sort and grouping functions for publish date and param of Page
       `GroupBy` is modified to allow it to receive a method name argument for
       example `Type` as its first argument. It is only allowed to call with
       a method which takes no arguments and returns a result or a pair of
       a result and an error.
       
       The functions discussed at #443 are also added
       
       - `ByPublishDate`: Order contents by `PublishDate` front matter variable
       - `GroupByPublishDate(format, order)`: Group contents by `PublishDate`
         front matter variable formatted in string like `GroupByDate`
       - `GroupByParam(key, order)`: Group contents by `Param` front matter
         variable specified by `key` argument
       - `GroupByParamDate(key, format, order)`: Group contents by `Param`
         front matter variable specified by `key` argument and formatted in
         string like `GroupByDate`. It's effective against `time.Time` type
         front matter variable
       
       Diffstat:
         M docs/content/templates/list.md      |      53 ++++++++++++++++++++++++++++++-
         M hugolib/pageGroup.go                |     149 +++++++++++++++++++++++++++----
         M hugolib/pageSort.go                 |       9 +++++++++
         M hugolib/site_test.go                |     112 +++++++++++++++++++++++++++++++
       
       4 files changed, 307 insertions(+), 16 deletions(-)
       ---
 (DIR) diff --git a/docs/content/templates/list.md b/docs/content/templates/list.md
       @@ -178,6 +178,15 @@ your list templates:
            </li>
            {{ end }}
        
       +### Order by PublishDate
       +
       +    {{ range .Data.Pages.ByPublishDate }}
       +    <li>
       +    <a href="{{ .Permalink }}">{{ .Title }}</a>
       +    <div class="meta">{{ .PublishDate.Format "Mon, Jan 2, 2006" }}</div>
       +    </li>
       +    {{ end }}
       +
        ### Order by Length
        
            {{ range .Data.Pages.ByLength }}
       @@ -219,7 +228,7 @@ Can be applied to any of the above. Using Date for an example.
        ## Grouping Content
        
        Hugo provides some grouping functions for list pages. You can use them to
       -group pages by Section, Date etc.
       +group pages by Section, Type, Date etc.
        
        Here are a variety of different ways you can group the content items in
        your list templates:
       @@ -252,6 +261,48 @@ your list templates:
            </ul>
            {{ end }}
        
       +### Grouping by Page publish date
       +
       +    {{ range .Data.Pages.GroupByPublishDate "2006-01" }}
       +    <h3>{{ .Key }}</h3>
       +    <ul>
       +        {{ range .Pages }}
       +        <li>
       +        <a href="{{ .Permalink }}">{{ .Title }}</a>
       +        <div class="meta">{{ .PublishDate.Format "Mon, Jan 2, 2006" }}</div>
       +        </li>
       +        {{ end }}
       +    </ul>
       +    {{ end }}
       +
       +### Grouping by Page param
       +
       +    {{ range .Data.Pages.GroupByParam "param_key" }}
       +    <h3>{{ .Key }}</h3>
       +    <ul>
       +        {{ range .Pages }}
       +        <li>
       +        <a href="{{ .Permalink }}">{{ .Title }}</a>
       +        <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
       +        </li>
       +        {{ end }}
       +    </ul>
       +    {{ end }}
       +
       +### Grouping by Page param in date format
       +
       +    {{ range .Data.Pages.GroupByParamDate "param_key" "2006-01" }}
       +    <h3>{{ .Key }}</h3>
       +    <ul>
       +        {{ range .Pages }}
       +        <li>
       +        <a href="{{ .Permalink }}">{{ .Title }}</a>
       +        <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
       +        </li>
       +        {{ end }}
       +    </ul>
       +    {{ end }}
       +
        ### Reversing Key Order
        
        The ordering of the groups is performed by keys in alpha-numeric order (A–Z,
 (DIR) diff --git a/hugolib/pageGroup.go b/hugolib/pageGroup.go
       @@ -18,6 +18,7 @@ import (
                "reflect"
                "sort"
                "strings"
       +        "time"
        )
        
        type PageGroup struct {
       @@ -72,6 +73,11 @@ func (p PagesGroup) Reverse() PagesGroup {
                return p
        }
        
       +var (
       +        errorType   = reflect.TypeOf((*error)(nil)).Elem()
       +        pagePtrType = reflect.TypeOf((*Page)(nil))
       +)
       +
        func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
                if len(p) < 1 {
                        return nil, nil
       @@ -83,26 +89,96 @@ func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
                        direction = "desc"
                }
        
       -        ppt := reflect.TypeOf(&Page{}) // *hugolib.Page
       -
       -        ft, ok := ppt.Elem().FieldByName(key)
       -
       +        var ft interface{}
       +        ft, ok := pagePtrType.Elem().FieldByName(key)
                if !ok {
       -                return nil, errors.New("No such field in Page struct")
       +                m, ok := pagePtrType.MethodByName(key)
       +                if !ok {
       +                        return nil, errors.New(key + " is neither a field nor a method of Page")
       +                }
       +                if m.Type.NumIn() != 1 || m.Type.NumOut() == 0 || m.Type.NumOut() > 2 {
       +                        return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
       +                }
       +                if m.Type.NumOut() == 1 && m.Type.Out(0).Implements(errorType) {
       +                        return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
       +                }
       +                if m.Type.NumOut() == 2 && !m.Type.Out(1).Implements(errorType) {
       +                        return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
       +                }
       +                ft = m
                }
        
       -        tmp := reflect.MakeMap(reflect.MapOf(ft.Type, reflect.SliceOf(ppt)))
       +        var tmp reflect.Value
       +        switch e := ft.(type) {
       +        case reflect.StructField:
       +                tmp = reflect.MakeMap(reflect.MapOf(e.Type, reflect.SliceOf(pagePtrType)))
       +        case reflect.Method:
       +                tmp = reflect.MakeMap(reflect.MapOf(e.Type.Out(0), reflect.SliceOf(pagePtrType)))
       +        }
        
                for _, e := range p {
                        ppv := reflect.ValueOf(e)
       -                fv := ppv.Elem().FieldByName(key)
       -                if !fv.IsNil() {
       -                        if !tmp.MapIndex(fv).IsValid() {
       -                                tmp.SetMapIndex(fv, reflect.MakeSlice(reflect.SliceOf(ppt), 0, 0))
       +                var fv reflect.Value
       +                switch ft.(type) {
       +                case reflect.StructField:
       +                        fv = ppv.Elem().FieldByName(key)
       +                case reflect.Method:
       +                        fv = ppv.MethodByName(key).Call([]reflect.Value{})[0]
       +                }
       +                if !fv.IsValid() {
       +                        continue
       +                }
       +                if !tmp.MapIndex(fv).IsValid() {
       +                        tmp.SetMapIndex(fv, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0))
       +                }
       +        }
       +
       +        var r []PageGroup
       +        for _, k := range sortKeys(tmp.MapKeys(), direction) {
       +                r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)})
       +        }
       +
       +        return r, nil
       +}
       +
       +func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) {
       +        if len(p) < 1 {
       +                return nil, nil
       +        }
       +
       +        direction := "asc"
       +
       +        if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") {
       +                direction = "desc"
       +        }
       +
       +        var tmp reflect.Value
       +        var keyt reflect.Type
       +        for _, e := range p {
       +                param := e.GetParam(key)
       +                if param != nil {
       +                        if _, ok := param.([]string); !ok {
       +                                keyt = reflect.TypeOf(param)
       +                                tmp = reflect.MakeMap(reflect.MapOf(keyt, reflect.SliceOf(pagePtrType)))
       +                                break
                                }
       -                        tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv))
                        }
                }
       +        if !tmp.IsValid() {
       +                return nil, errors.New("There is no such a param")
       +        }
       +
       +        for _, e := range p {
       +                param := e.GetParam(key)
       +                if param == nil || reflect.TypeOf(param) != keyt {
       +                        continue
       +                }
       +                v := reflect.ValueOf(param)
       +                if !tmp.MapIndex(v).IsValid() {
       +                        tmp.SetMapIndex(v, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0))
       +                }
       +                tmp.SetMapIndex(v, reflect.Append(tmp.MapIndex(v), reflect.ValueOf(e)))
       +        }
        
                var r []PageGroup
                for _, k := range sortKeys(tmp.MapKeys(), direction) {
       @@ -112,25 +188,25 @@ func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
                return r, nil
        }
        
       -func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
       +func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p *Page) string, order ...string) (PagesGroup, error) {
                if len(p) < 1 {
                        return nil, nil
                }
        
       -        sp := p.ByDate()
       +        sp := sorter(p)
        
                if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) {
                        sp = sp.Reverse()
                }
        
       -        date := sp[0].Date.Format(format)
       +        date := formatter(sp[0])
                var r []PageGroup
                r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)})
                r[0].Pages = append(r[0].Pages, sp[0])
        
                i := 0
                for _, e := range sp[1:] {
       -                date = e.Date.Format(format)
       +                date = formatter(e)
                        if r[i].Key.(string) != date {
                                r = append(r, PageGroup{Key: date})
                                i++
       @@ -139,3 +215,46 @@ func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
                }
                return r, nil
        }
       +
       +func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
       +        sorter := func(p Pages) Pages {
       +                return p.ByDate()
       +        }
       +        formatter := func(p *Page) string {
       +                return p.Date.Format(format)
       +        }
       +        return p.groupByDateField(sorter, formatter, order...)
       +}
       +
       +func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, error) {
       +        sorter := func(p Pages) Pages {
       +                return p.ByPublishDate()
       +        }
       +        formatter := func(p *Page) string {
       +                return p.PublishDate.Format(format)
       +        }
       +        return p.groupByDateField(sorter, formatter, order...)
       +}
       +
       +func (p Pages) GroupByParamDate(key string, format string, order ...string) (PagesGroup, error) {
       +        sorter := func(p Pages) Pages {
       +                var r Pages
       +                for _, e := range p {
       +                        param := e.GetParam(key)
       +                        if param != nil {
       +                                if _, ok := param.(time.Time); ok {
       +                                        r = append(r, e)
       +                                }
       +                        }
       +                }
       +                pdate := func(p1, p2 *Page) bool {
       +                        return p1.GetParam(key).(time.Time).Unix() < p2.GetParam(key).(time.Time).Unix()
       +                }
       +                PageBy(pdate).Sort(r)
       +                return r
       +        }
       +        formatter := func(p *Page) string {
       +                return p.GetParam(key).(time.Time).Format(format)
       +        }
       +        return p.groupByDateField(sorter, formatter, order...)
       +}
 (DIR) diff --git a/hugolib/pageSort.go b/hugolib/pageSort.go
       @@ -96,6 +96,15 @@ func (p Pages) ByDate() Pages {
                return p
        }
        
       +func (p Pages) ByPublishDate() Pages {
       +        pubDate := func(p1, p2 *Page) bool {
       +                return p1.PublishDate.Unix() < p2.PublishDate.Unix()
       +        }
       +
       +        PageBy(pubDate).Sort(p)
       +        return p
       +}
       +
        func (p Pages) ByLength() Pages {
                length := func(p1, p2 *Page) bool {
                        return len(p1.Content) < len(p2.Content)
 (DIR) diff --git a/hugolib/site_test.go b/hugolib/site_test.go
       @@ -404,12 +404,16 @@ func TestAbsUrlify(t *testing.T) {
        var WEIGHTED_PAGE_1 = []byte(`+++
        weight = "2"
        title = "One"
       +my_param = "foo"
       +my_date = 1979-05-27T07:32:00Z
        +++
        Front Matter with Ordered Pages`)
        
        var WEIGHTED_PAGE_2 = []byte(`+++
        weight = "6"
        title = "Two"
       +publishdate = "2012-03-05"
       +my_param = "foo"
        +++
        Front Matter with Ordered Pages 2`)
        
       @@ -417,6 +421,10 @@ var WEIGHTED_PAGE_3 = []byte(`+++
        weight = "4"
        title = "Three"
        date = "2012-04-06"
       +publishdate = "2012-04-06"
       +my_param = "bar"
       +only_one = "yes"
       +my_date = 2010-05-27T07:32:00Z
        +++
        Front Matter with Ordered Pages 3`)
        
       @@ -424,6 +432,9 @@ var WEIGHTED_PAGE_4 = []byte(`+++
        weight = "4"
        title = "Four"
        date = "2012-01-01"
       +publishdate = "2012-01-01"
       +my_param = "baz"
       +my_date = 2010-05-27T07:32:00Z
        +++
        Front Matter with Ordered Pages 4. This is longer content`)
        
       @@ -472,6 +483,17 @@ func TestOrderedPages(t *testing.T) {
                        t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rev[0].Title)
                }
        
       +        bypubdate := s.Pages.ByPublishDate()
       +
       +        if bypubdate[0].Title != "One" {
       +                t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bypubdate[0].Title)
       +        }
       +
       +        rbypubdate := bypubdate.Reverse()
       +        if rbypubdate[0].Title != "Three" {
       +                t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rbypubdate[0].Title)
       +        }
       +
                bylength := s.Pages.ByLength()
                if bylength[0].Title != "One" {
                        t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].Title)
       @@ -534,6 +556,26 @@ func TestGroupedPages(t *testing.T) {
                        t.Errorf("PageGroup has unexpected number of pages. Third group should have '%d' pages, got '%d' pages", 2, len(rbysection[2].Pages))
                }
        
       +        bytype, err := s.Pages.GroupBy("Type", "asc")
       +        if err != nil {
       +                t.Fatalf("Unable to make PageGroup array: %s", err)
       +        }
       +        if bytype[0].Key != "sect1" {
       +                t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "sect1", bytype[0].Key)
       +        }
       +        if bytype[1].Key != "sect2" {
       +                t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "sect2", bytype[1].Key)
       +        }
       +        if bytype[2].Key != "sect3" {
       +                t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect3", bytype[2].Key)
       +        }
       +        if bytype[2].Pages[0].Title != "Four" {
       +                t.Errorf("PageGroup has an unexpected page. Third group's data should have '%s', got '%s'", "Four", bytype[0].Pages[0].Title)
       +        }
       +        if len(bytype[0].Pages) != 2 {
       +                t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(bytype[2].Pages))
       +        }
       +
                bydate, err := s.Pages.GroupByDate("2006-01", "asc")
                if err != nil {
                        t.Fatalf("Unable to make PageGroup array: %s", err)
       @@ -553,6 +595,76 @@ func TestGroupedPages(t *testing.T) {
                if len(bydate[0].Pages) != 2 {
                        t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(bydate[2].Pages))
                }
       +
       +        bypubdate, err := s.Pages.GroupByPublishDate("2006")
       +        if err != nil {
       +                t.Fatalf("Unable to make PageGroup array: %s", err)
       +        }
       +        if bypubdate[0].Key != "2012" {
       +                t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "2012", bypubdate[0].Key)
       +        }
       +        if bypubdate[1].Key != "0001" {
       +                t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "0001", bypubdate[1].Key)
       +        }
       +        if bypubdate[0].Pages[0].Title != "Three" {
       +                t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", bypubdate[0].Pages[0].Title)
       +        }
       +        if len(bypubdate[0].Pages) != 3 {
       +                t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 3, len(bypubdate[0].Pages))
       +        }
       +
       +        byparam, err := s.Pages.GroupByParam("my_param", "desc")
       +        if err != nil {
       +                t.Fatalf("Unable to make PageGroup array: %s", err)
       +        }
       +        if byparam[0].Key != "foo" {
       +                t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "foo", byparam[0].Key)
       +        }
       +        if byparam[1].Key != "baz" {
       +                t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "baz", byparam[1].Key)
       +        }
       +        if byparam[2].Key != "bar" {
       +                t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "bar", byparam[2].Key)
       +        }
       +        if byparam[2].Pages[0].Title != "Three" {
       +                t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", byparam[2].Pages[0].Title)
       +        }
       +        if len(byparam[0].Pages) != 2 {
       +                t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byparam[0].Pages))
       +        }
       +
       +        _, err = s.Pages.GroupByParam("not_exist")
       +        if err == nil {
       +                t.Errorf("GroupByParam didn't return an expected error")
       +        }
       +
       +        byOnlyOneParam, err := s.Pages.GroupByParam("only_one")
       +        if err != nil {
       +                t.Fatalf("Unable to make PageGroup array: %s", err)
       +        }
       +        if len(byOnlyOneParam) != 1 {
       +                t.Errorf("PageGroup array has unexpected elements. Group length should be '%d', got '%d'", 1, len(byOnlyOneParam))
       +        }
       +        if byOnlyOneParam[0].Key != "yes" {
       +                t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "yes", byOnlyOneParam[0].Key)
       +        }
       +
       +        byParamDate, err := s.Pages.GroupByParamDate("my_date", "2006-01")
       +        if err != nil {
       +                t.Fatalf("Unable to make PageGroup array: %s", err)
       +        }
       +        if byParamDate[0].Key != "2010-05" {
       +                t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "2010-05", byParamDate[0].Key)
       +        }
       +        if byParamDate[1].Key != "1979-05" {
       +                t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "1979-05", byParamDate[1].Key)
       +        }
       +        if byParamDate[1].Pages[0].Title != "One" {
       +                t.Errorf("PageGroup has an unexpected page. Second group's pages should have '%s', got '%s'", "One", byParamDate[1].Pages[0].Title)
       +        }
       +        if len(byParamDate[0].Pages) != 2 {
       +                t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byParamDate[2].Pages))
       +        }
        }
        
        var PAGE_WITH_WEIGHTED_TAXONOMIES_2 = []byte(`+++