github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/hugolib/pageGroup.go (about)

     1  // Copyright 2015 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugolib
    15  
    16  import (
    17  	"errors"
    18  	"reflect"
    19  	"sort"
    20  	"strings"
    21  	"time"
    22  )
    23  
    24  // PageGroup represents a group of pages, grouped by the key.
    25  // The key is typically a year or similar.
    26  type PageGroup struct {
    27  	Key interface{}
    28  	Pages
    29  }
    30  
    31  type mapKeyValues []reflect.Value
    32  
    33  func (v mapKeyValues) Len() int      { return len(v) }
    34  func (v mapKeyValues) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
    35  
    36  type mapKeyByInt struct{ mapKeyValues }
    37  
    38  func (s mapKeyByInt) Less(i, j int) bool { return s.mapKeyValues[i].Int() < s.mapKeyValues[j].Int() }
    39  
    40  type mapKeyByStr struct{ mapKeyValues }
    41  
    42  func (s mapKeyByStr) Less(i, j int) bool {
    43  	return s.mapKeyValues[i].String() < s.mapKeyValues[j].String()
    44  }
    45  
    46  func sortKeys(v []reflect.Value, order string) []reflect.Value {
    47  	if len(v) <= 1 {
    48  		return v
    49  	}
    50  
    51  	switch v[0].Kind() {
    52  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    53  		if order == "desc" {
    54  			sort.Sort(sort.Reverse(mapKeyByInt{v}))
    55  		} else {
    56  			sort.Sort(mapKeyByInt{v})
    57  		}
    58  	case reflect.String:
    59  		if order == "desc" {
    60  			sort.Sort(sort.Reverse(mapKeyByStr{v}))
    61  		} else {
    62  			sort.Sort(mapKeyByStr{v})
    63  		}
    64  	}
    65  	return v
    66  }
    67  
    68  // PagesGroup represents a list of page groups.
    69  // This is what you get when doing page grouping in the templates.
    70  type PagesGroup []PageGroup
    71  
    72  // Reverse reverses the order of this list of page groups.
    73  func (p PagesGroup) Reverse() PagesGroup {
    74  	for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 {
    75  		p[i], p[j] = p[j], p[i]
    76  	}
    77  
    78  	return p
    79  }
    80  
    81  var (
    82  	errorType   = reflect.TypeOf((*error)(nil)).Elem()
    83  	pagePtrType = reflect.TypeOf((*Page)(nil))
    84  )
    85  
    86  // GroupBy groups by the value in the given field or method name and with the given order.
    87  // Valid values for order is asc, desc, rev and reverse.
    88  func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
    89  	if len(p) < 1 {
    90  		return nil, nil
    91  	}
    92  
    93  	direction := "asc"
    94  
    95  	if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") {
    96  		direction = "desc"
    97  	}
    98  
    99  	var ft interface{}
   100  	m, ok := pagePtrType.MethodByName(key)
   101  	if ok {
   102  		if m.Type.NumIn() != 1 || m.Type.NumOut() == 0 || m.Type.NumOut() > 2 {
   103  			return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
   104  		}
   105  		if m.Type.NumOut() == 1 && m.Type.Out(0).Implements(errorType) {
   106  			return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
   107  		}
   108  		if m.Type.NumOut() == 2 && !m.Type.Out(1).Implements(errorType) {
   109  			return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
   110  		}
   111  		ft = m
   112  	} else {
   113  		ft, ok = pagePtrType.Elem().FieldByName(key)
   114  		if !ok {
   115  			return nil, errors.New(key + " is neither a field nor a method of Page")
   116  		}
   117  	}
   118  
   119  	var tmp reflect.Value
   120  	switch e := ft.(type) {
   121  	case reflect.StructField:
   122  		tmp = reflect.MakeMap(reflect.MapOf(e.Type, reflect.SliceOf(pagePtrType)))
   123  	case reflect.Method:
   124  		tmp = reflect.MakeMap(reflect.MapOf(e.Type.Out(0), reflect.SliceOf(pagePtrType)))
   125  	}
   126  
   127  	for _, e := range p {
   128  		ppv := reflect.ValueOf(e)
   129  		var fv reflect.Value
   130  		switch ft.(type) {
   131  		case reflect.StructField:
   132  			fv = ppv.Elem().FieldByName(key)
   133  		case reflect.Method:
   134  			fv = ppv.MethodByName(key).Call([]reflect.Value{})[0]
   135  		}
   136  		if !fv.IsValid() {
   137  			continue
   138  		}
   139  		if !tmp.MapIndex(fv).IsValid() {
   140  			tmp.SetMapIndex(fv, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0))
   141  		}
   142  		tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv))
   143  	}
   144  
   145  	sortedKeys := sortKeys(tmp.MapKeys(), direction)
   146  	r := make([]PageGroup, len(sortedKeys))
   147  	for i, k := range sortedKeys {
   148  		r[i] = PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)}
   149  	}
   150  
   151  	return r, nil
   152  }
   153  
   154  // GroupByParam groups by the given page parameter key's value and with the given order.
   155  // Valid values for order is asc, desc, rev and reverse.
   156  func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) {
   157  	if len(p) < 1 {
   158  		return nil, nil
   159  	}
   160  
   161  	direction := "asc"
   162  
   163  	if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") {
   164  		direction = "desc"
   165  	}
   166  
   167  	var tmp reflect.Value
   168  	var keyt reflect.Type
   169  	for _, e := range p {
   170  		param := e.getParamToLower(key)
   171  		if param != nil {
   172  			if _, ok := param.([]string); !ok {
   173  				keyt = reflect.TypeOf(param)
   174  				tmp = reflect.MakeMap(reflect.MapOf(keyt, reflect.SliceOf(pagePtrType)))
   175  				break
   176  			}
   177  		}
   178  	}
   179  	if !tmp.IsValid() {
   180  		return nil, errors.New("There is no such a param")
   181  	}
   182  
   183  	for _, e := range p {
   184  		param := e.getParam(key, false)
   185  		if param == nil || reflect.TypeOf(param) != keyt {
   186  			continue
   187  		}
   188  		v := reflect.ValueOf(param)
   189  		if !tmp.MapIndex(v).IsValid() {
   190  			tmp.SetMapIndex(v, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0))
   191  		}
   192  		tmp.SetMapIndex(v, reflect.Append(tmp.MapIndex(v), reflect.ValueOf(e)))
   193  	}
   194  
   195  	var r []PageGroup
   196  	for _, k := range sortKeys(tmp.MapKeys(), direction) {
   197  		r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)})
   198  	}
   199  
   200  	return r, nil
   201  }
   202  
   203  func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p *Page) string, order ...string) (PagesGroup, error) {
   204  	if len(p) < 1 {
   205  		return nil, nil
   206  	}
   207  
   208  	sp := sorter(p)
   209  
   210  	if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) {
   211  		sp = sp.Reverse()
   212  	}
   213  
   214  	date := formatter(sp[0])
   215  	var r []PageGroup
   216  	r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)})
   217  	r[0].Pages = append(r[0].Pages, sp[0])
   218  
   219  	i := 0
   220  	for _, e := range sp[1:] {
   221  		date = formatter(e)
   222  		if r[i].Key.(string) != date {
   223  			r = append(r, PageGroup{Key: date})
   224  			i++
   225  		}
   226  		r[i].Pages = append(r[i].Pages, e)
   227  	}
   228  	return r, nil
   229  }
   230  
   231  // GroupByDate groups by the given page's Date value in
   232  // the given format and with the given order.
   233  // Valid values for order is asc, desc, rev and reverse.
   234  // For valid format strings, see https://golang.org/pkg/time/#Time.Format
   235  func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
   236  	sorter := func(p Pages) Pages {
   237  		return p.ByDate()
   238  	}
   239  	formatter := func(p *Page) string {
   240  		return p.Date.Format(format)
   241  	}
   242  	return p.groupByDateField(sorter, formatter, order...)
   243  }
   244  
   245  // GroupByPublishDate groups by the given page's PublishDate value in
   246  // the given format and with the given order.
   247  // Valid values for order is asc, desc, rev and reverse.
   248  // For valid format strings, see https://golang.org/pkg/time/#Time.Format
   249  func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, error) {
   250  	sorter := func(p Pages) Pages {
   251  		return p.ByPublishDate()
   252  	}
   253  	formatter := func(p *Page) string {
   254  		return p.PublishDate.Format(format)
   255  	}
   256  	return p.groupByDateField(sorter, formatter, order...)
   257  }
   258  
   259  // GroupByExpiryDate groups by the given page's ExpireDate value in
   260  // the given format and with the given order.
   261  // Valid values for order is asc, desc, rev and reverse.
   262  // For valid format strings, see https://golang.org/pkg/time/#Time.Format
   263  func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, error) {
   264  	sorter := func(p Pages) Pages {
   265  		return p.ByExpiryDate()
   266  	}
   267  	formatter := func(p *Page) string {
   268  		return p.ExpiryDate.Format(format)
   269  	}
   270  	return p.groupByDateField(sorter, formatter, order...)
   271  }
   272  
   273  // GroupByParamDate groups by a date set as a param on the page in
   274  // the given format and with the given order.
   275  // Valid values for order is asc, desc, rev and reverse.
   276  // For valid format strings, see https://golang.org/pkg/time/#Time.Format
   277  func (p Pages) GroupByParamDate(key string, format string, order ...string) (PagesGroup, error) {
   278  	sorter := func(p Pages) Pages {
   279  		var r Pages
   280  		for _, e := range p {
   281  			param := e.getParamToLower(key)
   282  			if param != nil {
   283  				if _, ok := param.(time.Time); ok {
   284  					r = append(r, e)
   285  				}
   286  			}
   287  		}
   288  		pdate := func(p1, p2 *Page) bool {
   289  			return p1.getParamToLower(key).(time.Time).Unix() < p2.getParamToLower(key).(time.Time).Unix()
   290  		}
   291  		pageBy(pdate).Sort(r)
   292  		return r
   293  	}
   294  	formatter := func(p *Page) string {
   295  		return p.getParamToLower(key).(time.Time).Format(format)
   296  	}
   297  	return p.groupByDateField(sorter, formatter, order...)
   298  }