diff --git a/docs/content/templates/list.md b/docs/content/templates/list.md index c4c0bc29b..d2b58c49c 100644 --- a/docs/content/templates/list.md +++ b/docs/content/templates/list.md @@ -178,6 +178,15 @@ your list templates: {{ end }} +### Order by PublishDate + + {{ range .Data.Pages.ByPublishDate }} +
  • + {{ .Title }} +
    {{ .PublishDate.Format "Mon, Jan 2, 2006" }}
    +
  • + {{ 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: {{ end }} +### Grouping by Page publish date + + {{ range .Data.Pages.GroupByPublishDate "2006-01" }} +

    {{ .Key }}

    + + {{ end }} + +### Grouping by Page param + + {{ range .Data.Pages.GroupByParam "param_key" }} +

    {{ .Key }}

    + + {{ end }} + +### Grouping by Page param in date format + + {{ range .Data.Pages.GroupByParamDate "param_key" "2006-01" }} +

    {{ .Key }}

    + + {{ end }} + ### Reversing Key Order The ordering of the groups is performed by keys in alpha-numeric order (A–Z, diff --git a/hugolib/pageGroup.go b/hugolib/pageGroup.go index f859c2e50..e30e9654e 100644 --- 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,24 +89,47 @@ 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)) - } - tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv)) + 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)) } } @@ -112,25 +141,72 @@ 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) GroupByParam(key string, order ...string) (PagesGroup, error) { if len(p) < 1 { 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")) { 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...) +} diff --git a/hugolib/pageSort.go b/hugolib/pageSort.go index c621740b2..dbbdc4c20 100644 --- 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) diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 044cd062b..fe2bed585 100644 --- 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(`+++