1// Package pager provides pagination functionality through a generic pager implementation.2//3// Example usage:4//5// import (6// "strconv"7// "strings"8//9// "gno.land/p/jeronimoalbi/pager/v0"10// )11//12// func Render(path string) string {13// // Define the items to paginate14// items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}15//16// // Create a pager that paginates 4 items at a time17// p, err := pager.New(path, len(items), pager.WithPageSize(4))18// if err != nil {19// panic(err)20// }21//22// // Render items for the current page23// var output strings.Builder24// p.Iterate(func(i int) bool {25// output.WriteString("- " + strconv.Itoa(items[i]) + "\n")26// return false27// })28//29// // Render page picker30// if p.HasPages() {31// output.WriteString("\n" + pager.Picker(p))32// }33//34// return output.String()35// }36package pager3738import (39 "errors"40 "math"41 "net/url"42 "strconv"43 "strings"44)4546var ErrInvalidPageNumber = errors.New("invalid page number")4748// PagerIterFn defines a callback to iterate page items.49type PagerIterFn func(index int) (stop bool)5051// New creates a new pager.52func New(rawURL string, totalItems int, options ...PagerOption) (Pager, error) {53 u, err := url.Parse(rawURL)54 if err != nil {55 return Pager{}, err56 }5758 p := Pager{59 query: u.RawQuery,60 pageQueryParam: DefaultPageQueryParam,61 pageSize: DefaultPageSize,62 page: 1,63 totalItems: totalItems,64 }65 for _, apply := range options {66 apply(&p)67 }6869 p.pageCount = int(math.Ceil(float64(p.totalItems) / float64(p.pageSize)))7071 rawPage := u.Query().Get(p.pageQueryParam)72 if rawPage != "" {73 p.page, _ = strconv.Atoi(rawPage)74 if p.page == 0 || p.page > p.pageCount {75 return Pager{}, ErrInvalidPageNumber76 }77 }7879 return p, nil80}8182// MustNew creates a new pager or panics if there is an error.83func MustNew(rawURL string, totalItems int, options ...PagerOption) Pager {84 p, err := New(rawURL, totalItems, options...)85 if err != nil {86 panic(err)87 }88 return p89}9091// Pager allows paging items.92type Pager struct {93 query, pageQueryParam string94 pageSize, page, pageCount, totalItems int95}9697// TotalItems returns the total number of items to paginate.98func (p Pager) TotalItems() int {99 return p.totalItems100}101102// PageSize returns the size of each page.103func (p Pager) PageSize() int {104 return p.pageSize105}106107// Page returns the current page number.108func (p Pager) Page() int {109 return p.page110}111112// PageCount returns the number pages.113func (p Pager) PageCount() int {114 return p.pageCount115}116117// Offset returns the index of the first page item.118func (p Pager) Offset() int {119 return (p.page - 1) * p.pageSize120}121122// HasPages checks if pager has more than one page.123func (p Pager) HasPages() bool {124 return p.pageCount > 1125}126127// GetPageURI returns the URI for a page.128// An empty string is returned when page doesn't exist.129func (p Pager) GetPageURI(page int) string {130 if page < 1 || page > p.PageCount() {131 return ""132 }133134 values, _ := url.ParseQuery(p.query)135 values.Set(p.pageQueryParam, strconv.Itoa(page))136 return "?" + values.Encode()137}138139// PrevPageURI returns the URI path to the previous page.140// An empty string is returned when current page is the first page.141func (p Pager) PrevPageURI() string {142 if p.page == 1 || !p.HasPages() {143 return ""144 }145 return p.GetPageURI(p.page - 1)146}147148// NextPageURI returns the URI path to the next page.149// An empty string is returned when current page is the last page.150func (p Pager) NextPageURI() string {151 if p.page == p.pageCount {152 // Current page is the last page153 return ""154 }155 return p.GetPageURI(p.page + 1)156}157158// Iterate allows iterating page items.159func (p Pager) Iterate(fn PagerIterFn) bool {160 if p.totalItems == 0 {161 return true162 }163164 start := p.Offset()165 end := start + p.PageSize()166 if end > p.totalItems {167 end = p.totalItems168 }169170 for i := start; i < end; i++ {171 if fn(i) {172 return true173 }174 }175 return false176}177178// TODO: Support different types of pickers (ex. with clickable page numbers)179180// Picker returns a string with the pager as Markdown.181// An empty string is returned when the pager has no pages.182func Picker(p Pager) string {183 if !p.HasPages() {184 return ""185 }186187 var out strings.Builder188189 if s := p.PrevPageURI(); s != "" {190 out.WriteString("[«](" + s + ") | ")191 } else {192 out.WriteString("\\- | ")193 }194195 out.WriteString("page " + strconv.Itoa(p.Page()) + " of " + strconv.Itoa(p.PageCount()))196197 if s := p.NextPageURI(); s != "" {198 out.WriteString(" | [»](" + s + ")")199 } else {200 out.WriteString(" | \\-")201 }202203 return out.String()204}205Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.