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 }