File Coverage

blib/lib/Catmandu/Paged.pm
Criterion Covered Total %
statement 81 92 88.0
branch 49 68 72.0
condition 5 9 55.5
subroutine 17 18 94.4
pod 9 10 90.0
total 161 197 81.7


line stmt bran cond sub pod time code
1             package Catmandu::Paged;
2              
3 19     19   122109 use Catmandu::Sane;
  19         181  
  19         308  
4              
5             our $VERSION = '1.2020';
6              
7 19     19   155 use Moo::Role;
  19         42  
  19         249  
8 19     19   8158 use namespace::clean;
  19         58  
  19         136  
9              
10             requires 'start';
11             requires 'limit';
12             requires 'total';
13             requires 'maximum_offset';
14              
15             has max_pages_in_spread => (is => 'rw', lazy => 1, default => sub {5});
16              
17             # function _do_pagination copied from Data::SpreadPagination,
18             # decrease dependencies for Catmandu
19              
20             sub _ceil {
21 62     62   147 my $x = shift;
22 62         273 return int($x + 0.9999999);
23             }
24              
25             sub _floor {
26 4     4   6 my $x = shift;
27 4         11 return int $x;
28             }
29              
30             sub _round {
31 6     6   8 my $x = shift;
32 6         13 return int($x + 0.5);
33             }
34              
35             sub _capped_total {
36 36     36   48 my $self = $_[0];
37 36         64 my $total = $self->total;
38 36 100       113 if (my $max_offset = $self->maximum_offset) {
39 4 50       19 $total = $max_offset + 1 if $total > $max_offset;
40             }
41 36         158 $total;
42             }
43              
44             sub _do_pagination {
45 4     4   8 my $self = shift;
46 4         8 my $total = $self->_capped_total;
47 4         9 my $per_page = $self->limit;
48 4         14 my $current_page = $self->page;
49 4         99 my $max_pages = $self->max_pages_in_spread;
50              
51             # qNsizes
52 4         9 my @q_size = ();
53 4         6 my ($add_pages, $adj);
54              
55             # step 2
56 4         11 my $total_pages = _ceil($total / $per_page);
57 4 100       12 my $visible_pages
58             = $max_pages < ($total_pages - 1) ? $max_pages : $total_pages - 1;
59 4 100       10 if ($total_pages - 1 <= $max_pages) {
60 2         6 @q_size = ($current_page - 1, 0, 0, $total_pages - $current_page);
61             }
62             else {
63 2         6 @q_size = (
64             _floor($visible_pages / 4),
65             _round($visible_pages / 4),
66             _ceil($visible_pages / 4),
67             _round(($visible_pages - _round($visible_pages / 4)) / 3)
68             );
69 2 100       8 if ($current_page - $q_size[0] < 1) {
    50          
    0          
    0          
70 1         4 $add_pages = $q_size[0] + $q_size[1] - $current_page + 1;
71 1         4 @q_size = (
72             $current_page - 1,
73             0,
74             $q_size[2] + _ceil($add_pages / 2),
75             $q_size[3] + _floor($add_pages / 2)
76             );
77             }
78             elsif (
79             $current_page - $q_size[1] - _ceil($q_size[1] / 3) <= $q_size[0])
80             {
81 1         4 $adj = _ceil((3 * ($current_page - $q_size[0] - 1)) / 4);
82 1         3 $add_pages = $q_size[1] - $adj;
83 1         3 @q_size = (
84             $q_size[0], $adj,
85             $q_size[2] + _ceil($add_pages / 2),
86             $q_size[3] + _floor($add_pages / 2)
87             );
88             }
89             elsif ($current_page + $q_size[3] >= $total_pages) {
90 0         0 $add_pages
91             = $q_size[2] + $q_size[3] - $total_pages + $current_page;
92 0         0 @q_size = (
93             $q_size[0] + _floor($add_pages / 2),
94             $q_size[1] + _ceil($add_pages / 2),
95             0, $total_pages - $current_page
96             );
97             }
98             elsif ($current_page + $q_size[2] >= $total_pages - $q_size[3]) {
99 0         0 $adj = _ceil(
100             (3 * ($total_pages - $current_page - $q_size[3])) / 4);
101 0         0 $add_pages = $q_size[2] - $adj;
102 0         0 @q_size = (
103             $q_size[0] + _floor($add_pages / 2),
104             $q_size[1] + _ceil($add_pages / 2),
105             $adj, $q_size[3]
106             );
107             }
108             }
109              
110             # step 3 (PROFIT)
111             $self->{PAGE_RANGES} = [
112 4 100       28 $q_size[0] == 0 ? undef : [1, $q_size[0]],
    50          
    100          
    50          
113             $q_size[1] == 0 ? undef
114             : [$current_page - $q_size[1], $current_page - 1],
115             $q_size[2] == 0 ? undef
116             : [$current_page + 1, $current_page + $q_size[2]],
117             $q_size[3] == 0 ? undef
118             : [$total_pages - $q_size[3] + 1, $total_pages],
119             ];
120              
121             }
122              
123             sub first_page {
124 5     5 1 21444 my $self = shift;
125              
126 5 100       19 return if $self->limit < 1;
127              
128 4         38 return 1;
129             }
130              
131             sub last_page {
132 33     33 1 2627 my $self = shift;
133              
134 33 100       63 return if $self->limit < 1;
135              
136 32         120 my $last = $self->_capped_total / $self->limit;
137 32         107 return _ceil($last);
138             }
139              
140             sub page {
141 42     42 1 1150 my $self = shift;
142              
143 42 100       80 return if $self->limit < 1;
144              
145 40 100       165 ($self->start == 0) && (return 1);
146              
147 20         74 my $page = _ceil(($self->start + 1) / $self->limit);
148 20 50       42 ($page < $self->last_page) ? (return $page) : (return $self->last_page);
149             }
150              
151             sub previous_page {
152 5     5 1 16 my $self = shift;
153              
154 5 100       11 return if $self->limit < 1;
155              
156 4 100       21 ($self->page > 1) ? (return $self->page - 1) : (return undef);
157             }
158              
159             sub next_page {
160 5     5 1 13 my $self = shift;
161              
162 5 100       12 return if $self->limit < 1;
163              
164 4 50       19 ($self->page < $self->last_page)
165             ? (return $self->page + 1)
166             : (return undef);
167             }
168              
169             sub first_on_page {
170 5     5 1 3151 my $self = shift;
171              
172 5 100 66     15 return 0 if $self->limit < 1 || $self->total == 0;
173              
174 4         40 return (($self->page - 1) * $self->limit) + 1;
175             }
176              
177             sub last_on_page {
178 5     5 0 2939 my $self = shift;
179              
180 5 100       18 return 0 if $self->limit < 1;
181              
182 4 50       21 ($self->page == $self->last_page)
183             ? (return $self->_capped_total)
184             : (return ($self->page * $self->limit));
185             }
186              
187             sub page_size {
188 5     5 1 11 my $self = shift;
189 5         10 return $self->limit;
190             }
191              
192             sub page_ranges {
193 0     0 1 0 my $self = shift;
194              
195 0 0       0 return if $self->limit < 1;
196              
197 0         0 $self->_do_pagination;
198 0         0 return @{$self->{PAGE_RANGES}};
  0         0  
199             }
200              
201             sub pages_in_spread {
202 5     5 1 11 my $self = shift;
203              
204 5 100       11 return [] if $self->limit < 1;
205              
206 4         25 $self->_do_pagination;
207 4         9 my $ranges = $self->{PAGE_RANGES};
208 4         7 my $pages = [];
209              
210 4 100       10 if (!defined $ranges->[0]) {
211 3 50       8 push @$pages, undef if $self->page > 1;
212             }
213             else {
214 1         4 push @$pages, $ranges->[0][0] .. $ranges->[0][1];
215 1 50 33     4 push @$pages, undef
216             if defined $ranges->[1]
217             and ($ranges->[1][0] - $ranges->[0][1]) > 1;
218             }
219              
220 4 50       16 push @$pages, $ranges->[1][0] .. $ranges->[1][1] if defined $ranges->[1];
221 4         9 push @$pages, $self->page;
222 4 100       17 push @$pages, $ranges->[2][0] .. $ranges->[2][1] if defined $ranges->[2];
223              
224 4 50       11 if (!defined $ranges->[3]) {
225 0 0       0 push @$pages, undef if $self->page < $self->last_page;
226             }
227             else {
228 4 100 66     17 push @$pages, undef
229             if defined $ranges->[2]
230             and ($ranges->[3][0] - $ranges->[2][1]) > 1;
231 4         10 push @$pages, $ranges->[3][0] .. $ranges->[3][1];
232             }
233              
234 4         30 return $pages;
235             }
236              
237             1;
238              
239             __END__
240              
241             =pod
242              
243             =head1 NAME
244              
245             Catmandu::Paged - Base class for packages that need paging result sets
246              
247             =head1 SYNOPSIS
248              
249             # Create a package that needs page calculation
250             package MyPackage;
251              
252             use Moo;
253              
254             with 'Catmandu::Paged';
255              
256             sub start {
257             12; # Starting result
258             }
259              
260             sub limit {
261             10; # Number of results per page
262             }
263              
264             sub total {
265             131237128371; # Total number of results;
266             }
267              
268             package main;
269              
270             my $x = MyPackage->new;
271              
272             printf "Start page: %s\n" , $x->first_page;
273             printf "Last page: %s\n" , $x->last_page;
274             printf "Current page: %s\n" , $x->page;
275             printf "Next page: %s\n" , $x->next_page;
276              
277             =head1 DESCRIPTION
278              
279             Packages that use L<Catmandu::Paged> as base class and implement the methods
280             C<start>, C<limit> and C<total> get for free methods that can be used to do
281             page calculation.
282              
283             =head1 OVERWRITE METHODS
284              
285             =over 4
286              
287             =item start()
288              
289             Returns the index of the first item in a result page.
290              
291             =item limit()
292              
293             Returns the number of results in a page.
294              
295             =item total()
296              
297             Returns the total number of search results.
298              
299             =back
300              
301             =head1 INSTANCE METHODS
302              
303             =over 4
304              
305             =item first_page
306              
307             Returns the index the first page in a search result containing 0 or more pages.
308              
309             =item last_page
310              
311             Returns the index of the last page in a search result containing 0 or more pages.
312              
313             =item page_size
314              
315             Returns the number items on the current page.
316              
317             =item page
318              
319             Returns the current page index.
320              
321             =item previous_page
322              
323             Returns the previous page index.
324              
325             =item next_page
326              
327             Returns the next page index.
328              
329             =item first_on_page
330              
331             Returns the result index of the first result on the page.
332              
333             =item page_ranges
334              
335             =item pages_in_spread
336              
337             Returns the previous pages and next pages, depending on the current position
338             in the result set.
339              
340             =back
341              
342             =head1 SEE ALSO
343              
344             L<Catmandu::Hits>
345              
346             =cut