github.com/jmooring/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 }