github.com/jbramsden/hugo@v0.47.1/hugolib/pagination.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  	"fmt"
    19  	"html/template"
    20  	"math"
    21  	"reflect"
    22  	"strings"
    23  
    24  	"github.com/gohugoio/hugo/config"
    25  
    26  	"github.com/spf13/cast"
    27  )
    28  
    29  // Pager represents one of the elements in a paginator.
    30  // The number, starting on 1, represents its place.
    31  type Pager struct {
    32  	number int
    33  	*paginator
    34  }
    35  
    36  func (p Pager) String() string {
    37  	return fmt.Sprintf("Pager %d", p.number)
    38  }
    39  
    40  type paginatedElement interface {
    41  	Len() int
    42  }
    43  
    44  // Len returns the number of pages in the list.
    45  func (p Pages) Len() int {
    46  	return len(p)
    47  }
    48  
    49  // Len returns the number of pages in the page group.
    50  func (psg PagesGroup) Len() int {
    51  	l := 0
    52  	for _, pg := range psg {
    53  		l += len(pg.Pages)
    54  	}
    55  	return l
    56  }
    57  
    58  type pagers []*Pager
    59  
    60  var (
    61  	paginatorEmptyPages      Pages
    62  	paginatorEmptyPageGroups PagesGroup
    63  )
    64  
    65  type paginator struct {
    66  	paginatedElements []paginatedElement
    67  	pagers
    68  	paginationURLFactory
    69  	total   int
    70  	size    int
    71  	source  interface{}
    72  	options []interface{}
    73  }
    74  
    75  type paginationURLFactory func(int) string
    76  
    77  // PageNumber returns the current page's number in the pager sequence.
    78  func (p *Pager) PageNumber() int {
    79  	return p.number
    80  }
    81  
    82  // URL returns the URL to the current page.
    83  func (p *Pager) URL() template.HTML {
    84  	return template.HTML(p.paginationURLFactory(p.PageNumber()))
    85  }
    86  
    87  // Pages returns the Pages on this page.
    88  // Note: If this return a non-empty result, then PageGroups() will return empty.
    89  func (p *Pager) Pages() Pages {
    90  	if len(p.paginatedElements) == 0 {
    91  		return paginatorEmptyPages
    92  	}
    93  
    94  	if pages, ok := p.element().(Pages); ok {
    95  		return pages
    96  	}
    97  
    98  	return paginatorEmptyPages
    99  }
   100  
   101  // PageGroups return Page groups for this page.
   102  // Note: If this return non-empty result, then Pages() will return empty.
   103  func (p *Pager) PageGroups() PagesGroup {
   104  	if len(p.paginatedElements) == 0 {
   105  		return paginatorEmptyPageGroups
   106  	}
   107  
   108  	if groups, ok := p.element().(PagesGroup); ok {
   109  		return groups
   110  	}
   111  
   112  	return paginatorEmptyPageGroups
   113  }
   114  
   115  func (p *Pager) element() paginatedElement {
   116  	if len(p.paginatedElements) == 0 {
   117  		return paginatorEmptyPages
   118  	}
   119  	return p.paginatedElements[p.PageNumber()-1]
   120  }
   121  
   122  // page returns the Page with the given index
   123  func (p *Pager) page(index int) (*Page, error) {
   124  
   125  	if pages, ok := p.element().(Pages); ok {
   126  		if pages != nil && len(pages) > index {
   127  			return pages[index], nil
   128  		}
   129  		return nil, nil
   130  	}
   131  
   132  	// must be PagesGroup
   133  	// this construction looks clumsy, but ...
   134  	// ... it is the difference between 99.5% and 100% test coverage :-)
   135  	groups := p.element().(PagesGroup)
   136  
   137  	i := 0
   138  	for _, v := range groups {
   139  		for _, page := range v.Pages {
   140  			if i == index {
   141  				return page, nil
   142  			}
   143  			i++
   144  		}
   145  	}
   146  	return nil, nil
   147  }
   148  
   149  // NumberOfElements gets the number of elements on this page.
   150  func (p *Pager) NumberOfElements() int {
   151  	return p.element().Len()
   152  }
   153  
   154  // HasPrev tests whether there are page(s) before the current.
   155  func (p *Pager) HasPrev() bool {
   156  	return p.PageNumber() > 1
   157  }
   158  
   159  // Prev returns the pager for the previous page.
   160  func (p *Pager) Prev() *Pager {
   161  	if !p.HasPrev() {
   162  		return nil
   163  	}
   164  	return p.pagers[p.PageNumber()-2]
   165  }
   166  
   167  // HasNext tests whether there are page(s) after the current.
   168  func (p *Pager) HasNext() bool {
   169  	return p.PageNumber() < len(p.paginatedElements)
   170  }
   171  
   172  // Next returns the pager for the next page.
   173  func (p *Pager) Next() *Pager {
   174  	if !p.HasNext() {
   175  		return nil
   176  	}
   177  	return p.pagers[p.PageNumber()]
   178  }
   179  
   180  // First returns the pager for the first page.
   181  func (p *Pager) First() *Pager {
   182  	return p.pagers[0]
   183  }
   184  
   185  // Last returns the pager for the last page.
   186  func (p *Pager) Last() *Pager {
   187  	return p.pagers[len(p.pagers)-1]
   188  }
   189  
   190  // Pagers returns a list of pagers that can be used to build a pagination menu.
   191  func (p *paginator) Pagers() pagers {
   192  	return p.pagers
   193  }
   194  
   195  // PageSize returns the size of each paginator page.
   196  func (p *paginator) PageSize() int {
   197  	return p.size
   198  }
   199  
   200  // TotalPages returns the number of pages in the paginator.
   201  func (p *paginator) TotalPages() int {
   202  	return len(p.paginatedElements)
   203  }
   204  
   205  // TotalNumberOfElements returns the number of elements on all pages in this paginator.
   206  func (p *paginator) TotalNumberOfElements() int {
   207  	return p.total
   208  }
   209  
   210  func splitPages(pages Pages, size int) []paginatedElement {
   211  	var split []paginatedElement
   212  	for low, j := 0, len(pages); low < j; low += size {
   213  		high := int(math.Min(float64(low+size), float64(len(pages))))
   214  		split = append(split, pages[low:high])
   215  	}
   216  
   217  	return split
   218  }
   219  
   220  func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement {
   221  
   222  	type keyPage struct {
   223  		key  interface{}
   224  		page *Page
   225  	}
   226  
   227  	var (
   228  		split     []paginatedElement
   229  		flattened []keyPage
   230  	)
   231  
   232  	for _, g := range pageGroups {
   233  		for _, p := range g.Pages {
   234  			flattened = append(flattened, keyPage{g.Key, p})
   235  		}
   236  	}
   237  
   238  	numPages := len(flattened)
   239  
   240  	for low, j := 0, numPages; low < j; low += size {
   241  		high := int(math.Min(float64(low+size), float64(numPages)))
   242  
   243  		var (
   244  			pg         PagesGroup
   245  			key        interface{}
   246  			groupIndex = -1
   247  		)
   248  
   249  		for k := low; k < high; k++ {
   250  			kp := flattened[k]
   251  			if key == nil || key != kp.key {
   252  				key = kp.key
   253  				pg = append(pg, PageGroup{Key: key})
   254  				groupIndex++
   255  			}
   256  			pg[groupIndex].Pages = append(pg[groupIndex].Pages, kp.page)
   257  		}
   258  		split = append(split, pg)
   259  	}
   260  
   261  	return split
   262  }
   263  
   264  // Paginator get this Page's main output's paginator.
   265  func (p *Page) Paginator(options ...interface{}) (*Pager, error) {
   266  	return p.mainPageOutput.Paginator(options...)
   267  }
   268  
   269  // Paginator gets this PageOutput's paginator if it's already created.
   270  // If it's not, one will be created with all pages in Data["Pages"].
   271  func (p *PageOutput) Paginator(options ...interface{}) (*Pager, error) {
   272  	if !p.IsNode() {
   273  		return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.title)
   274  	}
   275  	pagerSize, err := resolvePagerSize(p.s.Cfg, options...)
   276  
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	var initError error
   282  
   283  	p.paginatorInit.Do(func() {
   284  		if p.paginator != nil {
   285  			return
   286  		}
   287  
   288  		pathDescriptor := p.targetPathDescriptor
   289  		if p.s.owner.IsMultihost() {
   290  			pathDescriptor.LangPrefix = ""
   291  		}
   292  		pagers, err := paginatePages(pathDescriptor, p.data["Pages"], pagerSize)
   293  
   294  		if err != nil {
   295  			initError = err
   296  		}
   297  
   298  		if len(pagers) > 0 {
   299  			// the rest of the nodes will be created later
   300  			p.paginator = pagers[0]
   301  			p.paginator.source = "paginator"
   302  			p.paginator.options = options
   303  		}
   304  
   305  	})
   306  
   307  	if initError != nil {
   308  		return nil, initError
   309  	}
   310  
   311  	return p.paginator, nil
   312  }
   313  
   314  // Paginate invokes this Page's main output's Paginate method.
   315  func (p *Page) Paginate(seq interface{}, options ...interface{}) (*Pager, error) {
   316  	return p.mainPageOutput.Paginate(seq, options...)
   317  }
   318  
   319  // Paginate gets this PageOutput's paginator if it's already created.
   320  // If it's not, one will be created with the qiven sequence.
   321  // Note that repeated calls will return the same result, even if the sequence is different.
   322  func (p *PageOutput) Paginate(seq interface{}, options ...interface{}) (*Pager, error) {
   323  	if !p.IsNode() {
   324  		return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.title)
   325  	}
   326  
   327  	pagerSize, err := resolvePagerSize(p.s.Cfg, options...)
   328  
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  
   333  	var initError error
   334  
   335  	p.paginatorInit.Do(func() {
   336  		if p.paginator != nil {
   337  			return
   338  		}
   339  
   340  		pathDescriptor := p.targetPathDescriptor
   341  		if p.s.owner.IsMultihost() {
   342  			pathDescriptor.LangPrefix = ""
   343  		}
   344  		pagers, err := paginatePages(pathDescriptor, seq, pagerSize)
   345  
   346  		if err != nil {
   347  			initError = err
   348  		}
   349  
   350  		if len(pagers) > 0 {
   351  			// the rest of the nodes will be created later
   352  			p.paginator = pagers[0]
   353  			p.paginator.source = seq
   354  			p.paginator.options = options
   355  		}
   356  
   357  	})
   358  
   359  	if initError != nil {
   360  		return nil, initError
   361  	}
   362  
   363  	if p.paginator.source == "paginator" {
   364  		return nil, errors.New("a Paginator was previously built for this Node without filters; look for earlier .Paginator usage")
   365  	}
   366  
   367  	if !reflect.DeepEqual(options, p.paginator.options) || !probablyEqualPageLists(p.paginator.source, seq) {
   368  		return nil, errors.New("invoked multiple times with different arguments")
   369  	}
   370  
   371  	return p.paginator, nil
   372  }
   373  
   374  func resolvePagerSize(cfg config.Provider, options ...interface{}) (int, error) {
   375  	if len(options) == 0 {
   376  		return cfg.GetInt("paginate"), nil
   377  	}
   378  
   379  	if len(options) > 1 {
   380  		return -1, errors.New("too many arguments, 'pager size' is currently the only option")
   381  	}
   382  
   383  	pas, err := cast.ToIntE(options[0])
   384  
   385  	if err != nil || pas <= 0 {
   386  		return -1, errors.New(("'pager size' must be a positive integer"))
   387  	}
   388  
   389  	return pas, nil
   390  }
   391  
   392  func paginatePages(td targetPathDescriptor, seq interface{}, pagerSize int) (pagers, error) {
   393  
   394  	if pagerSize <= 0 {
   395  		return nil, errors.New("'paginate' configuration setting must be positive to paginate")
   396  	}
   397  
   398  	urlFactory := newPaginationURLFactory(td)
   399  
   400  	var paginator *paginator
   401  
   402  	if groups, ok := seq.(PagesGroup); ok {
   403  		paginator, _ = newPaginatorFromPageGroups(groups, pagerSize, urlFactory)
   404  	} else {
   405  		pages, err := toPages(seq)
   406  		if err != nil {
   407  			return nil, err
   408  		}
   409  		paginator, _ = newPaginatorFromPages(pages, pagerSize, urlFactory)
   410  	}
   411  
   412  	pagers := paginator.Pagers()
   413  
   414  	return pagers, nil
   415  }
   416  
   417  func toPages(seq interface{}) (Pages, error) {
   418  	if seq == nil {
   419  		return Pages{}, nil
   420  	}
   421  
   422  	switch seq.(type) {
   423  	case Pages:
   424  		return seq.(Pages), nil
   425  	case *Pages:
   426  		return *(seq.(*Pages)), nil
   427  	case WeightedPages:
   428  		return (seq.(WeightedPages)).Pages(), nil
   429  	case PageGroup:
   430  		return (seq.(PageGroup)).Pages, nil
   431  	default:
   432  		return nil, fmt.Errorf("unsupported type in paginate, got %T", seq)
   433  	}
   434  }
   435  
   436  // probablyEqual checks page lists for probable equality.
   437  // It may return false positives.
   438  // The motivation behind this is to avoid potential costly reflect.DeepEqual
   439  // when "probably" is good enough.
   440  func probablyEqualPageLists(a1 interface{}, a2 interface{}) bool {
   441  
   442  	if a1 == nil || a2 == nil {
   443  		return a1 == a2
   444  	}
   445  
   446  	t1 := reflect.TypeOf(a1)
   447  	t2 := reflect.TypeOf(a2)
   448  
   449  	if t1 != t2 {
   450  		return false
   451  	}
   452  
   453  	if g1, ok := a1.(PagesGroup); ok {
   454  		g2 := a2.(PagesGroup)
   455  		if len(g1) != len(g2) {
   456  			return false
   457  		}
   458  		if len(g1) == 0 {
   459  			return true
   460  		}
   461  		if g1.Len() != g2.Len() {
   462  			return false
   463  		}
   464  
   465  		return g1[0].Pages[0] == g2[0].Pages[0]
   466  	}
   467  
   468  	p1, err1 := toPages(a1)
   469  	p2, err2 := toPages(a2)
   470  
   471  	// probably the same wrong type
   472  	if err1 != nil && err2 != nil {
   473  		return true
   474  	}
   475  
   476  	if len(p1) != len(p2) {
   477  		return false
   478  	}
   479  
   480  	if len(p1) == 0 {
   481  		return true
   482  	}
   483  
   484  	return p1[0] == p2[0]
   485  }
   486  
   487  func newPaginatorFromPages(pages Pages, size int, urlFactory paginationURLFactory) (*paginator, error) {
   488  
   489  	if size <= 0 {
   490  		return nil, errors.New("Paginator size must be positive")
   491  	}
   492  
   493  	split := splitPages(pages, size)
   494  
   495  	return newPaginator(split, len(pages), size, urlFactory)
   496  }
   497  
   498  func newPaginatorFromPageGroups(pageGroups PagesGroup, size int, urlFactory paginationURLFactory) (*paginator, error) {
   499  
   500  	if size <= 0 {
   501  		return nil, errors.New("Paginator size must be positive")
   502  	}
   503  
   504  	split := splitPageGroups(pageGroups, size)
   505  
   506  	return newPaginator(split, pageGroups.Len(), size, urlFactory)
   507  }
   508  
   509  func newPaginator(elements []paginatedElement, total, size int, urlFactory paginationURLFactory) (*paginator, error) {
   510  	p := &paginator{total: total, paginatedElements: elements, size: size, paginationURLFactory: urlFactory}
   511  
   512  	var ps pagers
   513  
   514  	if len(elements) > 0 {
   515  		ps = make(pagers, len(elements))
   516  		for i := range p.paginatedElements {
   517  			ps[i] = &Pager{number: (i + 1), paginator: p}
   518  		}
   519  	} else {
   520  		ps = make(pagers, 1)
   521  		ps[0] = &Pager{number: 1, paginator: p}
   522  	}
   523  
   524  	p.pagers = ps
   525  
   526  	return p, nil
   527  }
   528  
   529  func newPaginationURLFactory(d targetPathDescriptor) paginationURLFactory {
   530  
   531  	return func(page int) string {
   532  		pathDescriptor := d
   533  		var rel string
   534  		if page > 1 {
   535  			rel = fmt.Sprintf("/%s/%d/", d.PathSpec.PaginatePath, page)
   536  			pathDescriptor.Addends = rel
   537  		}
   538  
   539  		targetPath := createTargetPath(pathDescriptor)
   540  		targetPath = strings.TrimSuffix(targetPath, d.Type.BaseFilename())
   541  		link := d.PathSpec.PrependBasePath(targetPath)
   542  		// Note: The targetPath is massaged with MakePathSanitized
   543  		return d.PathSpec.URLizeFilename(link)
   544  	}
   545  }