1package pager23import (4 "math"5 "net/url"6 "strconv"78 "gno.land/p/nt/avl/rotree/v0"9 "gno.land/p/nt/ufmt/v0"10)1112// Pager is a struct that holds the AVL tree and pagination parameters.13type Pager struct {14 Tree rotree.IReadOnlyTree15 PageQueryParam string16 SizeQueryParam string17 DefaultPageSize int18 Reversed bool19}2021// Page represents a single page of results.22type Page struct {23 Items []Item24 PageNumber int25 PageSize int26 TotalItems int27 TotalPages int28 HasPrev bool29 HasNext bool30 Pager *Pager // Reference to the parent Pager31}3233// Item represents a key-value pair in the AVL tree.34type Item struct {35 Key string36 Value any37}3839// NewPager creates a new Pager with default values.40func NewPager(tree rotree.IReadOnlyTree, defaultPageSize int, reversed bool) *Pager {41 return &Pager{42 Tree: tree,43 PageQueryParam: "page",44 SizeQueryParam: "size",45 DefaultPageSize: defaultPageSize,46 Reversed: reversed,47 }48}4950// GetPage retrieves a page of results from the AVL tree.51func (p *Pager) GetPage(pageNumber int) *Page {52 return p.GetPageWithSize(pageNumber, p.DefaultPageSize)53}5455func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page {56 if pageSize <= 0 {57 panic("GetPageWithSize: invalid page size")58 }5960 totalItems := p.Tree.Size()61 totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize)))6263 page := &Page{64 TotalItems: totalItems,65 TotalPages: totalPages,66 PageSize: pageSize,67 Pager: p,68 }6970 // page number provided is not available71 if pageNumber < 1 {72 page.HasNext = totalPages > 073 return page74 }7576 // page number provided is outside the range of total pages77 if pageNumber > totalPages {78 page.PageNumber = pageNumber79 page.HasPrev = pageNumber > 080 return page81 }8283 startIndex := (pageNumber - 1) * pageSize84 endIndex := startIndex + pageSize85 if endIndex > totalItems {86 endIndex = totalItems87 }8889 items := []Item{}9091 if p.Reversed {92 p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool {93 items = append(items, Item{Key: key, Value: value})94 return false95 })96 } else {97 p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool {98 items = append(items, Item{Key: key, Value: value})99 return false100 })101 }102103 page.Items = items104 page.PageNumber = pageNumber105 page.HasPrev = pageNumber > 1106 page.HasNext = pageNumber < totalPages107 return page108}109110func (p *Pager) MustGetPageByPath(rawURL string) *Page {111 page, err := p.GetPageByPath(rawURL)112 if err != nil {113 panic("invalid path")114 }115 return page116}117118// GetPageByPath retrieves a page of results based on the query parameters in the URL path.119func (p *Pager) GetPageByPath(rawURL string) (*Page, error) {120 pageNumber, pageSize, err := p.ParseQuery(rawURL)121 if err != nil {122 return nil, err123 }124 return p.GetPageWithSize(pageNumber, pageSize), nil125}126127// Picker generates the Markdown UI for the page Picker128func (p *Page) Picker(path string) string {129 pageNumber := p.PageNumber130 pageNumber = max(pageNumber, 1)131132 if p.TotalPages <= 1 {133 return ""134 }135136 u, _ := url.Parse(path)137 query := u.Query()138139 // Remove existing page query parameter140 query.Del(p.Pager.PageQueryParam)141142 // Encode remaining query parameters143 baseQuery := query.Encode()144 if baseQuery != "" {145 baseQuery = "&" + baseQuery146 }147 md := ""148149 if p.HasPrev {150 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", 1, p.Pager.PageQueryParam, 1, baseQuery)151152 if p.PageNumber > 4 {153 md += "… | "154 }155156 if p.PageNumber > 3 {157 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", p.PageNumber-2, p.Pager.PageQueryParam, p.PageNumber-2, baseQuery)158 }159160 if p.PageNumber > 2 {161 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", p.PageNumber-1, p.Pager.PageQueryParam, p.PageNumber-1, baseQuery)162 }163 }164165 if p.PageNumber > 0 && p.PageNumber <= p.TotalPages {166 md += ufmt.Sprintf("**%d**", p.PageNumber)167 } else {168 md += ufmt.Sprintf("_%d_", p.PageNumber)169 }170171 if p.HasNext {172 if p.PageNumber < p.TotalPages-1 {173 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.PageNumber+1, p.Pager.PageQueryParam, p.PageNumber+1, baseQuery)174 }175176 if p.PageNumber < p.TotalPages-2 {177 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.PageNumber+2, p.Pager.PageQueryParam, p.PageNumber+2, baseQuery)178 }179180 if p.PageNumber < p.TotalPages-3 {181 md += " | …"182 }183184 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.TotalPages, p.Pager.PageQueryParam, p.TotalPages, baseQuery)185 }186187 return md188}189190// ParseQuery parses the URL to extract the page number and page size.191func (p *Pager) ParseQuery(rawURL string) (int, int, error) {192 u, err := url.Parse(rawURL)193 if err != nil {194 return 1, p.DefaultPageSize, err195 }196197 query := u.Query()198 pageNumber := 1199 pageSize := p.DefaultPageSize200201 if p.PageQueryParam != "" {202 if pageStr := query.Get(p.PageQueryParam); pageStr != "" {203 pageNumber, err = strconv.Atoi(pageStr)204 if err != nil || pageNumber < 1 {205 pageNumber = 1206 }207 }208 }209210 if p.SizeQueryParam != "" {211 if sizeStr := query.Get(p.SizeQueryParam); sizeStr != "" {212 pageSize, err = strconv.Atoi(sizeStr)213 if err != nil || pageSize < 1 {214 pageSize = p.DefaultPageSize215 }216 }217 }218219 return pageNumber, pageSize, nil220}221222func max(a, b int) int {223 if a > b {224 return a225 }226 return b227}228Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.