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
This commit is contained in:
Tatsushi Demachi 2014-10-18 00:10:19 +09:00 committed by spf13
parent d013edb7f8
commit 5e28606b84
4 changed files with 308 additions and 17 deletions

View file

@ -178,6 +178,15 @@ your list templates:
</li> </li>
{{ end }} {{ 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 ### Order by Length
{{ range .Data.Pages.ByLength }} {{ range .Data.Pages.ByLength }}
@ -219,7 +228,7 @@ Can be applied to any of the above. Using Date for an example.
## Grouping Content ## Grouping Content
Hugo provides some grouping functions for list pages. You can use them to 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 Here are a variety of different ways you can group the content items in
your list templates: your list templates:
@ -252,6 +261,48 @@ your list templates:
</ul> </ul>
{{ end }} {{ 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 ### Reversing Key Order
The ordering of the groups is performed by keys in alpha-numeric order (AZ, The ordering of the groups is performed by keys in alpha-numeric order (AZ,

View file

@ -18,6 +18,7 @@ import (
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
"time"
) )
type PageGroup struct { type PageGroup struct {
@ -72,6 +73,11 @@ func (p PagesGroup) Reverse() PagesGroup {
return p return p
} }
var (
errorType = reflect.TypeOf((*error)(nil)).Elem()
pagePtrType = reflect.TypeOf((*Page)(nil))
)
func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) { func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
if len(p) < 1 { if len(p) < 1 {
return nil, nil return nil, nil
@ -83,24 +89,47 @@ func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
direction = "desc" direction = "desc"
} }
ppt := reflect.TypeOf(&Page{}) // *hugolib.Page var ft interface{}
ft, ok := pagePtrType.Elem().FieldByName(key)
ft, ok := ppt.Elem().FieldByName(key)
if !ok { 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 { for _, e := range p {
ppv := reflect.ValueOf(e) ppv := reflect.ValueOf(e)
fv := ppv.Elem().FieldByName(key) var fv reflect.Value
if !fv.IsNil() { switch ft.(type) {
if !tmp.MapIndex(fv).IsValid() { case reflect.StructField:
tmp.SetMapIndex(fv, reflect.MakeSlice(reflect.SliceOf(ppt), 0, 0)) fv = ppv.Elem().FieldByName(key)
} case reflect.Method:
tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv)) 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))
} }
} }
@ -112,25 +141,72 @@ func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
return r, nil return r, nil
} }
func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) { func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) {
if len(p) < 1 { if len(p) < 1 {
return nil, nil return nil, nil
} }
sp := p.ByDate() 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
}
}
}
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) {
r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)})
}
return r, nil
}
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 := sorter(p)
if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) { if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) {
sp = sp.Reverse() sp = sp.Reverse()
} }
date := sp[0].Date.Format(format) date := formatter(sp[0])
var r []PageGroup var r []PageGroup
r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)}) r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)})
r[0].Pages = append(r[0].Pages, sp[0]) r[0].Pages = append(r[0].Pages, sp[0])
i := 0 i := 0
for _, e := range sp[1:] { for _, e := range sp[1:] {
date = e.Date.Format(format) date = formatter(e)
if r[i].Key.(string) != date { if r[i].Key.(string) != date {
r = append(r, PageGroup{Key: date}) r = append(r, PageGroup{Key: date})
i++ i++
@ -139,3 +215,46 @@ func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
} }
return r, nil 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...)
}

View file

@ -96,6 +96,15 @@ func (p Pages) ByDate() Pages {
return p 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 { func (p Pages) ByLength() Pages {
length := func(p1, p2 *Page) bool { length := func(p1, p2 *Page) bool {
return len(p1.Content) < len(p2.Content) return len(p1.Content) < len(p2.Content)

View file

@ -404,12 +404,16 @@ func TestAbsUrlify(t *testing.T) {
var WEIGHTED_PAGE_1 = []byte(`+++ var WEIGHTED_PAGE_1 = []byte(`+++
weight = "2" weight = "2"
title = "One" title = "One"
my_param = "foo"
my_date = 1979-05-27T07:32:00Z
+++ +++
Front Matter with Ordered Pages`) Front Matter with Ordered Pages`)
var WEIGHTED_PAGE_2 = []byte(`+++ var WEIGHTED_PAGE_2 = []byte(`+++
weight = "6" weight = "6"
title = "Two" title = "Two"
publishdate = "2012-03-05"
my_param = "foo"
+++ +++
Front Matter with Ordered Pages 2`) Front Matter with Ordered Pages 2`)
@ -417,6 +421,10 @@ var WEIGHTED_PAGE_3 = []byte(`+++
weight = "4" weight = "4"
title = "Three" title = "Three"
date = "2012-04-06" 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`) Front Matter with Ordered Pages 3`)
@ -424,6 +432,9 @@ var WEIGHTED_PAGE_4 = []byte(`+++
weight = "4" weight = "4"
title = "Four" title = "Four"
date = "2012-01-01" 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`) 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) 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() bylength := s.Pages.ByLength()
if bylength[0].Title != "One" { if bylength[0].Title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].Title) 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)) 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") bydate, err := s.Pages.GroupByDate("2006-01", "asc")
if err != nil { if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err) t.Fatalf("Unable to make PageGroup array: %s", err)
@ -553,6 +595,76 @@ func TestGroupedPages(t *testing.T) {
if len(bydate[0].Pages) != 2 { 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)) 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(`+++ var PAGE_WITH_WEIGHTED_TAXONOMIES_2 = []byte(`+++