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