1package pager23import (4 "math"5 "net/url"6 "strconv"78 "gno.land/p/nt/bptree/rotree/v0"9 "gno.land/p/nt/ufmt/v0"10)1112// Pager is a struct that holds the B+ 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 B+ 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 B+ 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 totalItems := p.Tree.Size()57 totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize)))5859 page := &Page{60 TotalItems: totalItems,61 TotalPages: totalPages,62 PageSize: pageSize,63 Pager: p,64 }6566 // pages without content67 if pageSize < 1 {68 return page69 }7071 // page number provided is not available72 if pageNumber < 1 {73 page.HasNext = totalPages > 074 return page75 }7677 // page number provided is outside the range of total pages78 if pageNumber > totalPages {79 page.PageNumber = pageNumber80 page.HasPrev = pageNumber > 081 return page82 }8384 startIndex := (pageNumber - 1) * pageSize85 endIndex := startIndex + pageSize86 if endIndex > totalItems {87 endIndex = totalItems88 }8990 items := []Item{}9192 if p.Reversed {93 p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool {94 items = append(items, Item{Key: key, Value: value})95 return false96 })97 } else {98 p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool {99 items = append(items, Item{Key: key, Value: value})100 return false101 })102 }103104 page.Items = items105 page.PageNumber = pageNumber106 page.HasPrev = pageNumber > 1107 page.HasNext = pageNumber < totalPages108 return page109}110111func (p *Pager) MustGetPageByPath(rawURL string) *Page {112 page, err := p.GetPageByPath(rawURL)113 if err != nil {114 panic("invalid path")115 }116 return page117}118119// GetPageByPath retrieves a page of results based on the query parameters in the URL path.120func (p *Pager) GetPageByPath(rawURL string) (*Page, error) {121 pageNumber, pageSize, err := p.ParseQuery(rawURL)122 if err != nil {123 return nil, err124 }125 return p.GetPageWithSize(pageNumber, pageSize), nil126}127128// Picker generates the Markdown UI for the page Picker129func (p *Page) Picker(path string) string {130 pageNumber := p.PageNumber131 pageNumber = max(pageNumber, 1)132133 if p.TotalPages <= 1 {134 return ""135 }136137 u, _ := url.Parse(path)138 query := u.Query()139140 // Remove existing page query parameter141 query.Del(p.Pager.PageQueryParam)142143 // Encode remaining query parameters144 baseQuery := query.Encode()145 if baseQuery != "" {146 baseQuery = "&" + baseQuery147 }148 md := ""149150 if p.HasPrev {151 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", 1, p.Pager.PageQueryParam, 1, baseQuery)152153 if p.PageNumber > 4 {154 md += "… | "155 }156157 if p.PageNumber > 3 {158 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", p.PageNumber-2, p.Pager.PageQueryParam, p.PageNumber-2, baseQuery)159 }160161 if p.PageNumber > 2 {162 md += ufmt.Sprintf("[%d](?%s=%d%s) | ", p.PageNumber-1, p.Pager.PageQueryParam, p.PageNumber-1, baseQuery)163 }164 }165166 if p.PageNumber > 0 && p.PageNumber <= p.TotalPages {167 md += ufmt.Sprintf("**%d**", p.PageNumber)168 } else {169 md += ufmt.Sprintf("_%d_", p.PageNumber)170 }171172 if p.HasNext {173 if p.PageNumber < p.TotalPages-1 {174 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.PageNumber+1, p.Pager.PageQueryParam, p.PageNumber+1, baseQuery)175 }176177 if p.PageNumber < p.TotalPages-2 {178 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.PageNumber+2, p.Pager.PageQueryParam, p.PageNumber+2, baseQuery)179 }180181 if p.PageNumber < p.TotalPages-3 {182 md += " | …"183 }184185 md += ufmt.Sprintf(" | [%d](?%s=%d%s)", p.TotalPages, p.Pager.PageQueryParam, p.TotalPages, baseQuery)186 }187188 return md189}190191// ParseQuery parses the URL to extract the page number and page size.192func (p *Pager) ParseQuery(rawURL string) (int, int, error) {193 u, err := url.Parse(rawURL)194 if err != nil {195 return 1, p.DefaultPageSize, err196 }197198 query := u.Query()199 pageNumber := 1200 pageSize := p.DefaultPageSize201202 if p.PageQueryParam != "" {203 if pageStr := query.Get(p.PageQueryParam); pageStr != "" {204 pageNumber, err = strconv.Atoi(pageStr)205 if err != nil || pageNumber < 1 {206 pageNumber = 1207 }208 }209 }210211 if p.SizeQueryParam != "" {212 if sizeStr := query.Get(p.SizeQueryParam); sizeStr != "" {213 pageSize, err = strconv.Atoi(sizeStr)214 if err != nil || pageSize < 1 {215 pageSize = p.DefaultPageSize216 }217 }218 }219220 return pageNumber, pageSize, nil221}222223func max(a, b int) int {224 if a > b {225 return a226 }227 return b228}229Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.