github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/resources/page/pages_sort.go (about)

     1  // Copyright 2019 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 page
    15  
    16  import (
    17  	"sort"
    18  
    19  	"github.com/gohugoio/hugo/common/collections"
    20  
    21  	"github.com/gohugoio/hugo/resources/resource"
    22  
    23  	"github.com/gohugoio/hugo/compare"
    24  	"github.com/spf13/cast"
    25  )
    26  
    27  var spc = newPageCache()
    28  
    29  /*
    30   * Implementation of a custom sorter for Pages
    31   */
    32  
    33  // A pageSorter implements the sort interface for Pages
    34  type pageSorter struct {
    35  	pages Pages
    36  	by    pageBy
    37  }
    38  
    39  // pageBy is a closure used in the Sort.Less method.
    40  type pageBy func(p1, p2 Page) bool
    41  
    42  func getOrdinals(p1, p2 Page) (int, int) {
    43  	p1o, ok1 := p1.(collections.Order)
    44  	if !ok1 {
    45  		return -1, -1
    46  	}
    47  	p2o, ok2 := p2.(collections.Order)
    48  	if !ok2 {
    49  		return -1, -1
    50  	}
    51  
    52  	return p1o.Ordinal(), p2o.Ordinal()
    53  }
    54  
    55  // Sort stable sorts the pages given the receiver's sort order.
    56  func (by pageBy) Sort(pages Pages) {
    57  	ps := &pageSorter{
    58  		pages: pages,
    59  		by:    by, // The Sort method's receiver is the function (closure) that defines the sort order.
    60  	}
    61  	sort.Stable(ps)
    62  }
    63  
    64  var (
    65  
    66  	// DefaultPageSort is the default sort func for pages in Hugo:
    67  	// Order by Ordinal, Weight, Date, LinkTitle and then full file path.
    68  	DefaultPageSort = func(p1, p2 Page) bool {
    69  		o1, o2 := getOrdinals(p1, p2)
    70  		if o1 != o2 && o1 != -1 && o2 != -1 {
    71  			return o1 < o2
    72  		}
    73  		if p1.Weight() == p2.Weight() {
    74  			if p1.Date().Unix() == p2.Date().Unix() {
    75  				c := compare.Strings(p1.LinkTitle(), p2.LinkTitle())
    76  				if c == 0 {
    77  					if p1.File().IsZero() || p2.File().IsZero() {
    78  						return p1.File().IsZero()
    79  					}
    80  					return compare.LessStrings(p1.File().Filename(), p2.File().Filename())
    81  				}
    82  				return c < 0
    83  			}
    84  			return p1.Date().Unix() > p2.Date().Unix()
    85  		}
    86  
    87  		if p2.Weight() == 0 {
    88  			return true
    89  		}
    90  
    91  		if p1.Weight() == 0 {
    92  			return false
    93  		}
    94  
    95  		return p1.Weight() < p2.Weight()
    96  	}
    97  
    98  	lessPageLanguage = func(p1, p2 Page) bool {
    99  		if p1.Language().Weight == p2.Language().Weight {
   100  			if p1.Date().Unix() == p2.Date().Unix() {
   101  				c := compare.Strings(p1.LinkTitle(), p2.LinkTitle())
   102  				if c == 0 {
   103  					if !p1.File().IsZero() && !p2.File().IsZero() {
   104  						return compare.LessStrings(p1.File().Filename(), p2.File().Filename())
   105  					}
   106  				}
   107  				return c < 0
   108  			}
   109  			return p1.Date().Unix() > p2.Date().Unix()
   110  		}
   111  
   112  		if p2.Language().Weight == 0 {
   113  			return true
   114  		}
   115  
   116  		if p1.Language().Weight == 0 {
   117  			return false
   118  		}
   119  
   120  		return p1.Language().Weight < p2.Language().Weight
   121  	}
   122  
   123  	lessPageTitle = func(p1, p2 Page) bool {
   124  		return compare.LessStrings(p1.Title(), p2.Title())
   125  	}
   126  
   127  	lessPageLinkTitle = func(p1, p2 Page) bool {
   128  		return compare.LessStrings(p1.LinkTitle(), p2.LinkTitle())
   129  	}
   130  
   131  	lessPageDate = func(p1, p2 Page) bool {
   132  		return p1.Date().Unix() < p2.Date().Unix()
   133  	}
   134  
   135  	lessPagePubDate = func(p1, p2 Page) bool {
   136  		return p1.PublishDate().Unix() < p2.PublishDate().Unix()
   137  	}
   138  )
   139  
   140  func (ps *pageSorter) Len() int      { return len(ps.pages) }
   141  func (ps *pageSorter) Swap(i, j int) { ps.pages[i], ps.pages[j] = ps.pages[j], ps.pages[i] }
   142  
   143  // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
   144  func (ps *pageSorter) Less(i, j int) bool { return ps.by(ps.pages[i], ps.pages[j]) }
   145  
   146  // Limit limits the number of pages returned to n.
   147  func (p Pages) Limit(n int) Pages {
   148  	if len(p) > n {
   149  		return p[0:n]
   150  	}
   151  	return p
   152  }
   153  
   154  // ByWeight sorts the Pages by weight and returns a copy.
   155  //
   156  // Adjacent invocations on the same receiver will return a cached result.
   157  //
   158  // This may safely be executed  in parallel.
   159  func (p Pages) ByWeight() Pages {
   160  	const key = "pageSort.ByWeight"
   161  	pages, _ := spc.get(key, pageBy(DefaultPageSort).Sort, p)
   162  	return pages
   163  }
   164  
   165  // SortByDefault sorts pages by the default sort.
   166  func SortByDefault(pages Pages) {
   167  	pageBy(DefaultPageSort).Sort(pages)
   168  }
   169  
   170  // ByTitle sorts the Pages by title and returns a copy.
   171  //
   172  // Adjacent invocations on the same receiver will return a cached result.
   173  //
   174  // This may safely be executed  in parallel.
   175  func (p Pages) ByTitle() Pages {
   176  	const key = "pageSort.ByTitle"
   177  
   178  	pages, _ := spc.get(key, pageBy(lessPageTitle).Sort, p)
   179  	return pages
   180  }
   181  
   182  // ByLinkTitle sorts the Pages by link title and returns a copy.
   183  //
   184  // Adjacent invocations on the same receiver will return a cached result.
   185  //
   186  // This may safely be executed  in parallel.
   187  func (p Pages) ByLinkTitle() Pages {
   188  	const key = "pageSort.ByLinkTitle"
   189  
   190  	pages, _ := spc.get(key, pageBy(lessPageLinkTitle).Sort, p)
   191  
   192  	return pages
   193  }
   194  
   195  // ByDate sorts the Pages by date and returns a copy.
   196  //
   197  // Adjacent invocations on the same receiver will return a cached result.
   198  //
   199  // This may safely be executed  in parallel.
   200  func (p Pages) ByDate() Pages {
   201  	const key = "pageSort.ByDate"
   202  
   203  	pages, _ := spc.get(key, pageBy(lessPageDate).Sort, p)
   204  
   205  	return pages
   206  }
   207  
   208  // ByPublishDate sorts the Pages by publish date and returns a copy.
   209  //
   210  // Adjacent invocations on the same receiver will return a cached result.
   211  //
   212  // This may safely be executed  in parallel.
   213  func (p Pages) ByPublishDate() Pages {
   214  	const key = "pageSort.ByPublishDate"
   215  
   216  	pages, _ := spc.get(key, pageBy(lessPagePubDate).Sort, p)
   217  
   218  	return pages
   219  }
   220  
   221  // ByExpiryDate sorts the Pages by publish date and returns a copy.
   222  //
   223  // Adjacent invocations on the same receiver will return a cached result.
   224  //
   225  // This may safely be executed  in parallel.
   226  func (p Pages) ByExpiryDate() Pages {
   227  	const key = "pageSort.ByExpiryDate"
   228  
   229  	expDate := func(p1, p2 Page) bool {
   230  		return p1.ExpiryDate().Unix() < p2.ExpiryDate().Unix()
   231  	}
   232  
   233  	pages, _ := spc.get(key, pageBy(expDate).Sort, p)
   234  
   235  	return pages
   236  }
   237  
   238  // ByLastmod sorts the Pages by the last modification date and returns a copy.
   239  //
   240  // Adjacent invocations on the same receiver will return a cached result.
   241  //
   242  // This may safely be executed  in parallel.
   243  func (p Pages) ByLastmod() Pages {
   244  	const key = "pageSort.ByLastmod"
   245  
   246  	date := func(p1, p2 Page) bool {
   247  		return p1.Lastmod().Unix() < p2.Lastmod().Unix()
   248  	}
   249  
   250  	pages, _ := spc.get(key, pageBy(date).Sort, p)
   251  
   252  	return pages
   253  }
   254  
   255  // ByLength sorts the Pages by length and returns a copy.
   256  //
   257  // Adjacent invocations on the same receiver will return a cached result.
   258  //
   259  // This may safely be executed  in parallel.
   260  func (p Pages) ByLength() Pages {
   261  	const key = "pageSort.ByLength"
   262  
   263  	length := func(p1, p2 Page) bool {
   264  		p1l, ok1 := p1.(resource.LengthProvider)
   265  		p2l, ok2 := p2.(resource.LengthProvider)
   266  
   267  		if !ok1 {
   268  			return true
   269  		}
   270  
   271  		if !ok2 {
   272  			return false
   273  		}
   274  
   275  		return p1l.Len() < p2l.Len()
   276  	}
   277  
   278  	pages, _ := spc.get(key, pageBy(length).Sort, p)
   279  
   280  	return pages
   281  }
   282  
   283  // ByLanguage sorts the Pages by the language's Weight.
   284  //
   285  // Adjacent invocations on the same receiver will return a cached result.
   286  //
   287  // This may safely be executed  in parallel.
   288  func (p Pages) ByLanguage() Pages {
   289  	const key = "pageSort.ByLanguage"
   290  
   291  	pages, _ := spc.get(key, pageBy(lessPageLanguage).Sort, p)
   292  
   293  	return pages
   294  }
   295  
   296  // SortByLanguage sorts the pages by language.
   297  func SortByLanguage(pages Pages) {
   298  	pageBy(lessPageLanguage).Sort(pages)
   299  }
   300  
   301  // Reverse reverses the order in Pages and returns a copy.
   302  //
   303  // Adjacent invocations on the same receiver will return a cached result.
   304  //
   305  // This may safely be executed  in parallel.
   306  func (p Pages) Reverse() Pages {
   307  	const key = "pageSort.Reverse"
   308  
   309  	reverseFunc := func(pages Pages) {
   310  		for i, j := 0, len(pages)-1; i < j; i, j = i+1, j-1 {
   311  			pages[i], pages[j] = pages[j], pages[i]
   312  		}
   313  	}
   314  
   315  	pages, _ := spc.get(key, reverseFunc, p)
   316  
   317  	return pages
   318  }
   319  
   320  // ByParam sorts the pages according to the given page Params key.
   321  //
   322  // Adjacent invocations on the same receiver with the same paramsKey will return a cached result.
   323  //
   324  // This may safely be executed  in parallel.
   325  func (p Pages) ByParam(paramsKey interface{}) Pages {
   326  	paramsKeyStr := cast.ToString(paramsKey)
   327  	key := "pageSort.ByParam." + paramsKeyStr
   328  
   329  	paramsKeyComparator := func(p1, p2 Page) bool {
   330  		v1, _ := p1.Param(paramsKeyStr)
   331  		v2, _ := p2.Param(paramsKeyStr)
   332  
   333  		if v1 == nil {
   334  			return false
   335  		}
   336  
   337  		if v2 == nil {
   338  			return true
   339  		}
   340  
   341  		isNumeric := func(v interface{}) bool {
   342  			switch v.(type) {
   343  			case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, float32, float64:
   344  				return true
   345  			default:
   346  				return false
   347  			}
   348  		}
   349  
   350  		if isNumeric(v1) && isNumeric(v2) {
   351  			return cast.ToFloat64(v1) < cast.ToFloat64(v2)
   352  		}
   353  
   354  		s1 := cast.ToString(v1)
   355  		s2 := cast.ToString(v2)
   356  
   357  		return compare.LessStrings(s1, s2)
   358  	}
   359  
   360  	pages, _ := spc.get(key, pageBy(paramsKeyComparator).Sort, p)
   361  
   362  	return pages
   363  }