github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/resources/page/pagination.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  	"errors"
    18  	"fmt"
    19  	"html/template"
    20  	"math"
    21  	"reflect"
    22  
    23  	"github.com/gohugoio/hugo/config"
    24  
    25  	"github.com/spf13/cast"
    26  )
    27  
    28  // PaginatorProvider provides two ways to create a page paginator.
    29  type PaginatorProvider interface {
    30  	// Paginator creates a paginator with the default page set.
    31  	Paginator(options ...any) (*Pager, error)
    32  	// Paginate creates a paginator with the given page set in pages.
    33  	Paginate(pages any, options ...any) (*Pager, error)
    34  }
    35  
    36  // Pager represents one of the elements in a paginator.
    37  // The number, starting on 1, represents its place.
    38  type Pager struct {
    39  	number int
    40  	*Paginator
    41  }
    42  
    43  func (p Pager) String() string {
    44  	return fmt.Sprintf("Pager %d", p.number)
    45  }
    46  
    47  type paginatedElement interface {
    48  	Len() int
    49  }
    50  
    51  type pagers []*Pager
    52  
    53  var (
    54  	paginatorEmptyPages      Pages
    55  	paginatorEmptyPageGroups PagesGroup
    56  )
    57  
    58  type Paginator struct {
    59  	paginatedElements []paginatedElement
    60  	pagers
    61  	paginationURLFactory
    62  	total int
    63  	size  int
    64  }
    65  
    66  type paginationURLFactory func(int) string
    67  
    68  // PageNumber returns the current page's number in the pager sequence.
    69  func (p *Pager) PageNumber() int {
    70  	return p.number
    71  }
    72  
    73  // URL returns the URL to the current page.
    74  func (p *Pager) URL() template.HTML {
    75  	return template.HTML(p.paginationURLFactory(p.PageNumber()))
    76  }
    77  
    78  // Pages returns the Pages on this page.
    79  // Note: If this return a non-empty result, then PageGroups() will return empty.
    80  func (p *Pager) Pages() Pages {
    81  	if len(p.paginatedElements) == 0 {
    82  		return paginatorEmptyPages
    83  	}
    84  
    85  	if pages, ok := p.element().(Pages); ok {
    86  		return pages
    87  	}
    88  
    89  	return paginatorEmptyPages
    90  }
    91  
    92  // PageGroups return Page groups for this page.
    93  // Note: If this return non-empty result, then Pages() will return empty.
    94  func (p *Pager) PageGroups() PagesGroup {
    95  	if len(p.paginatedElements) == 0 {
    96  		return paginatorEmptyPageGroups
    97  	}
    98  
    99  	if groups, ok := p.element().(PagesGroup); ok {
   100  		return groups
   101  	}
   102  
   103  	return paginatorEmptyPageGroups
   104  }
   105  
   106  func (p *Pager) element() paginatedElement {
   107  	if len(p.paginatedElements) == 0 {
   108  		return paginatorEmptyPages
   109  	}
   110  	return p.paginatedElements[p.PageNumber()-1]
   111  }
   112  
   113  // page returns the Page with the given index
   114  func (p *Pager) page(index int) (Page, error) {
   115  	if pages, ok := p.element().(Pages); ok {
   116  		if pages != nil && len(pages) > index {
   117  			return pages[index], nil
   118  		}
   119  		return nil, nil
   120  	}
   121  
   122  	// must be PagesGroup
   123  	// this construction looks clumsy, but ...
   124  	// ... it is the difference between 99.5% and 100% test coverage :-)
   125  	groups := p.element().(PagesGroup)
   126  
   127  	i := 0
   128  	for _, v := range groups {
   129  		for _, page := range v.Pages {
   130  			if i == index {
   131  				return page, nil
   132  			}
   133  			i++
   134  		}
   135  	}
   136  	return nil, nil
   137  }
   138  
   139  // NumberOfElements gets the number of elements on this page.
   140  func (p *Pager) NumberOfElements() int {
   141  	return p.element().Len()
   142  }
   143  
   144  // HasPrev tests whether there are page(s) before the current.
   145  func (p *Pager) HasPrev() bool {
   146  	return p.PageNumber() > 1
   147  }
   148  
   149  // Prev returns the pager for the previous page.
   150  func (p *Pager) Prev() *Pager {
   151  	if !p.HasPrev() {
   152  		return nil
   153  	}
   154  	return p.pagers[p.PageNumber()-2]
   155  }
   156  
   157  // HasNext tests whether there are page(s) after the current.
   158  func (p *Pager) HasNext() bool {
   159  	return p.PageNumber() < len(p.paginatedElements)
   160  }
   161  
   162  // Next returns the pager for the next page.
   163  func (p *Pager) Next() *Pager {
   164  	if !p.HasNext() {
   165  		return nil
   166  	}
   167  	return p.pagers[p.PageNumber()]
   168  }
   169  
   170  // First returns the pager for the first page.
   171  func (p *Pager) First() *Pager {
   172  	return p.pagers[0]
   173  }
   174  
   175  // Last returns the pager for the last page.
   176  func (p *Pager) Last() *Pager {
   177  	return p.pagers[len(p.pagers)-1]
   178  }
   179  
   180  // Pagers returns a list of pagers that can be used to build a pagination menu.
   181  func (p *Paginator) Pagers() pagers {
   182  	return p.pagers
   183  }
   184  
   185  // PageSize returns the size of each paginator page.
   186  func (p *Paginator) PageSize() int {
   187  	return p.size
   188  }
   189  
   190  // TotalPages returns the number of pages in the paginator.
   191  func (p *Paginator) TotalPages() int {
   192  	return len(p.paginatedElements)
   193  }
   194  
   195  // TotalNumberOfElements returns the number of elements on all pages in this paginator.
   196  func (p *Paginator) TotalNumberOfElements() int {
   197  	return p.total
   198  }
   199  
   200  func splitPages(pages Pages, size int) []paginatedElement {
   201  	var split []paginatedElement
   202  	for low, j := 0, len(pages); low < j; low += size {
   203  		high := int(math.Min(float64(low+size), float64(len(pages))))
   204  		split = append(split, pages[low:high])
   205  	}
   206  
   207  	return split
   208  }
   209  
   210  func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement {
   211  	type keyPage struct {
   212  		key  any
   213  		page Page
   214  	}
   215  
   216  	var (
   217  		split     []paginatedElement
   218  		flattened []keyPage
   219  	)
   220  
   221  	for _, g := range pageGroups {
   222  		for _, p := range g.Pages {
   223  			flattened = append(flattened, keyPage{g.Key, p})
   224  		}
   225  	}
   226  
   227  	numPages := len(flattened)
   228  
   229  	for low, j := 0, numPages; low < j; low += size {
   230  		high := int(math.Min(float64(low+size), float64(numPages)))
   231  
   232  		var (
   233  			pg         PagesGroup
   234  			key        any
   235  			groupIndex = -1
   236  		)
   237  
   238  		for k := low; k < high; k++ {
   239  			kp := flattened[k]
   240  			if key == nil || key != kp.key {
   241  				key = kp.key
   242  				pg = append(pg, PageGroup{Key: key})
   243  				groupIndex++
   244  			}
   245  			pg[groupIndex].Pages = append(pg[groupIndex].Pages, kp.page)
   246  		}
   247  		split = append(split, pg)
   248  	}
   249  
   250  	return split
   251  }
   252  
   253  func ResolvePagerSize(conf config.AllProvider, options ...any) (int, error) {
   254  	if len(options) == 0 {
   255  		return conf.Paginate(), nil
   256  	}
   257  
   258  	if len(options) > 1 {
   259  		return -1, errors.New("too many arguments, 'pager size' is currently the only option")
   260  	}
   261  
   262  	pas, err := cast.ToIntE(options[0])
   263  
   264  	if err != nil || pas <= 0 {
   265  		return -1, errors.New(("'pager size' must be a positive integer"))
   266  	}
   267  
   268  	return pas, nil
   269  }
   270  
   271  func Paginate(td TargetPathDescriptor, seq any, pagerSize int) (*Paginator, error) {
   272  	if pagerSize <= 0 {
   273  		return nil, errors.New("'paginate' configuration setting must be positive to paginate")
   274  	}
   275  
   276  	urlFactory := newPaginationURLFactory(td)
   277  
   278  	var paginator *Paginator
   279  
   280  	groups, ok, err := ToPagesGroup(seq)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	if ok {
   285  		paginator, _ = newPaginatorFromPageGroups(groups, pagerSize, urlFactory)
   286  	} else {
   287  		pages, err := ToPages(seq)
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  		paginator, _ = newPaginatorFromPages(pages, pagerSize, urlFactory)
   292  	}
   293  
   294  	return paginator, nil
   295  }
   296  
   297  // probablyEqual checks page lists for probable equality.
   298  // It may return false positives.
   299  // The motivation behind this is to avoid potential costly reflect.DeepEqual
   300  // when "probably" is good enough.
   301  func probablyEqualPageLists(a1 any, a2 any) bool {
   302  	if a1 == nil || a2 == nil {
   303  		return a1 == a2
   304  	}
   305  
   306  	t1 := reflect.TypeOf(a1)
   307  	t2 := reflect.TypeOf(a2)
   308  
   309  	if t1 != t2 {
   310  		return false
   311  	}
   312  
   313  	if g1, ok := a1.(PagesGroup); ok {
   314  		g2 := a2.(PagesGroup)
   315  		if len(g1) != len(g2) {
   316  			return false
   317  		}
   318  		if len(g1) == 0 {
   319  			return true
   320  		}
   321  		if g1.Len() != g2.Len() {
   322  			return false
   323  		}
   324  
   325  		return g1[0].Pages[0] == g2[0].Pages[0]
   326  	}
   327  
   328  	p1, err1 := ToPages(a1)
   329  	p2, err2 := ToPages(a2)
   330  
   331  	// probably the same wrong type
   332  	if err1 != nil && err2 != nil {
   333  		return true
   334  	}
   335  
   336  	if len(p1) != len(p2) {
   337  		return false
   338  	}
   339  
   340  	if len(p1) == 0 {
   341  		return true
   342  	}
   343  
   344  	return p1[0] == p2[0]
   345  }
   346  
   347  func newPaginatorFromPages(pages Pages, size int, urlFactory paginationURLFactory) (*Paginator, error) {
   348  	if size <= 0 {
   349  		return nil, errors.New("Paginator size must be positive")
   350  	}
   351  
   352  	split := splitPages(pages, size)
   353  
   354  	return newPaginator(split, len(pages), size, urlFactory)
   355  }
   356  
   357  func newPaginatorFromPageGroups(pageGroups PagesGroup, size int, urlFactory paginationURLFactory) (*Paginator, error) {
   358  	if size <= 0 {
   359  		return nil, errors.New("Paginator size must be positive")
   360  	}
   361  
   362  	split := splitPageGroups(pageGroups, size)
   363  
   364  	return newPaginator(split, pageGroups.Len(), size, urlFactory)
   365  }
   366  
   367  func newPaginator(elements []paginatedElement, total, size int, urlFactory paginationURLFactory) (*Paginator, error) {
   368  	p := &Paginator{total: total, paginatedElements: elements, size: size, paginationURLFactory: urlFactory}
   369  
   370  	var ps pagers
   371  
   372  	if len(elements) > 0 {
   373  		ps = make(pagers, len(elements))
   374  		for i := range p.paginatedElements {
   375  			ps[i] = &Pager{number: (i + 1), Paginator: p}
   376  		}
   377  	} else {
   378  		ps = make(pagers, 1)
   379  		ps[0] = &Pager{number: 1, Paginator: p}
   380  	}
   381  
   382  	p.pagers = ps
   383  
   384  	return p, nil
   385  }
   386  
   387  func newPaginationURLFactory(d TargetPathDescriptor) paginationURLFactory {
   388  	return func(pageNumber int) string {
   389  		pathDescriptor := d
   390  		var rel string
   391  		if pageNumber > 1 {
   392  			rel = fmt.Sprintf("/%s/%d/", d.PathSpec.Cfg.PaginatePath(), pageNumber)
   393  			pathDescriptor.Addends = rel
   394  		}
   395  
   396  		return CreateTargetPaths(pathDescriptor).RelPermalink(d.PathSpec)
   397  	}
   398  }