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