shithub: hugo

Download patch

ref: 5e28606b84ef230914d0e63aebe3925364ee3966
parent: d013edb7f838b739db72530e06eb47721baec7b8
author: Tatsushi Demachi <[email protected]>
date: Fri Oct 17 20:10:19 EDT 2014

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

--- a/docs/content/templates/list.md
+++ b/docs/content/templates/list.md
@@ -178,6 +178,15 @@
     </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 @@
 ## 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:
@@ -241,6 +250,48 @@
 ### Grouping by Page date
 
     {{ range .Data.Pages.GroupByDate "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 }}
+
+### 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 }}
--- a/hugolib/pageGroup.go
+++ b/hugolib/pageGroup.go
@@ -18,6 +18,7 @@
 	"reflect"
 	"sort"
 	"strings"
+	"time"
 )
 
 type PageGroup struct {
@@ -72,6 +73,11 @@
 	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,27 +89,97 @@
 		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) {
 		r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)})
@@ -112,18 +188,18 @@
 	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])
@@ -130,7 +206,7 @@
 
 	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++
@@ -138,4 +214,47 @@
 		r[i].Pages = append(r[i].Pages, e)
 	}
 	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...)
 }
--- a/hugolib/pageSort.go
+++ b/hugolib/pageSort.go
@@ -96,6 +96,15 @@
 	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)
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -404,6 +404,8 @@
 var WEIGHTED_PAGE_1 = []byte(`+++
 weight = "2"
 title = "One"
+my_param = "foo"
+my_date = 1979-05-27T07:32:00Z
 +++
 Front Matter with Ordered Pages`)
 
@@ -410,6 +412,8 @@
 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 @@
 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 @@
 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 @@
 		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 @@
 		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)
@@ -552,6 +594,76 @@
 	}
 	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))
 	}
 }