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 }