File Coverage

blib/lib/Data/Pageset/Exponential.pm
Criterion Covered Total %
statement 103 103 100.0
branch 21 22 95.4
condition n/a
subroutine 28 28 100.0
pod 9 12 75.0
total 161 165 97.5


line stmt bran cond sub pod time code
1             package Data::Pageset::Exponential;
2              
3             # ABSTRACT: Page numbering for very large page numbers
4              
5 3     3   2447 use v5.10.1;
  3         14  
6              
7 3     3   1823 use Moo;
  3         24834  
  3         14  
8              
9 3     3   4577 use List::Util 1.33 qw/ all min /;
  3         60  
  3         290  
10 3     3   1489 use PerlX::Maybe;
  3         7280  
  3         11  
11 3     3   1688 use POSIX qw/ ceil floor /;
  3         19280  
  3         21  
12 3     3   5824 use MooX::Aliases;
  3         16180  
  3         18  
13 3     3   2406 use MooX::TypeTiny;
  3         903  
  3         18  
14 3     3   32179 use Types::Common::Numeric qw/ PositiveOrZeroInt PositiveInt /;
  3         387960  
  3         44  
15 3     3   2188 use Types::Standard qw/ is_Int Int ArrayRef is_HashRef /;
  3         9  
  3         19  
16              
17              
18 3     3   5075 use asa 'Data::Page';
  3         796  
  3         21  
19              
20 3     3   1759 use namespace::autoclean;
  3         53545  
  3         15  
21              
22             # RECOMMEND PREREQ: Type::Tiny::XS
23             # RECOMMEND PREREQ: Ref::Util::XS
24              
25             our $VERSION = 'v0.3.2';
26              
27              
28             has total_entries => (
29             is => 'rw',
30             isa => PositiveOrZeroInt,
31             default => 0,
32             );
33              
34              
35             has entries_per_page => (
36             is => 'rw',
37             isa => PositiveInt,
38             default => 10,
39             );
40              
41              
42             has first_page => (
43             is => 'ro',
44             isa => Int,
45             default => 1,
46             );
47              
48              
49             has current_page => (
50             is => 'rw',
51             isa => Int,
52             lazy => 1,
53             default => \&first_page,
54             coerce => sub { floor( $_[0] // 0 ) },
55             );
56              
57              
58             has exponent_base => (
59             is => 'ro',
60             isa => PositiveInt,
61             default => 10,
62             );
63              
64              
65             has exponent_max => (
66             is => 'ro',
67             isa => PositiveInt,
68             default => 3,
69             );
70              
71              
72             has pages_per_exponent => (
73             is => 'ro',
74             isa => PositiveInt,
75             default => 3,
76             );
77              
78              
79             has pages_per_set => (
80             is => 'lazy',
81             isa => PositiveInt,
82             alias => 'max_pages_per_set',
83             builder => sub {
84 1     1   963 my ($self) = @_;
85 3     3   2584 use integer;
  3         47  
  3         21  
86 1         7 my $n = $self->pages_per_exponent * ( $self->exponent_max + 1 );
87 1         18 return ($n - 1) * 2 + 1;
88             },
89             );
90              
91              
92             has series => (
93             is => 'lazy',
94             isa => ArrayRef [Int],
95             builder => sub {
96 1     1   13 my ($self) = @_;
97              
98 3     3   359 use integer;
  3         40  
  3         12  
99              
100 1         2 my @series;
101              
102 1         6 my $n = $self->exponent_base;
103 1         4 my $m = $self->exponent_max;
104              
105 1         2 my $j = 0;
106 1         5 while ( $j <= $m ) {
107              
108 4         7 my $i = $n**$j;
109 4         8 my $a = $i;
110 4         10 my $p = $self->pages_per_exponent;
111              
112 4         10 while ( $p-- ) {
113 12         18 push @series, $a - 1;
114 12         26 $a += $i;
115             }
116              
117 4         8 $j++;
118              
119             }
120              
121 1         5 my @prevs = map { -$_ } reverse @series[1..$#series];
  11         21  
122              
123              
124 1         22 return [@prevs, @series];
125             },
126             );
127              
128             around current_page => sub {
129             my $next = shift;
130             my $self = shift;
131              
132             # N.B. unlike Data::Page, setting a value outside the first_page
133             # or last_page will not return that value.
134              
135             my $page = $self->$next(@_);
136              
137             return $self->first_page if $page < $self->first_page;
138              
139             return $self->last_page if $page > $self->last_page;
140              
141             return $page;
142             };
143              
144              
145             sub entries_on_this_page {
146 71     71 1 39657 my ($self) = @_;
147              
148 71 100       1685 if ( $self->total_entries ) {
149 68         529 return $self->last - $self->first + 1;
150             }
151             else {
152 3         34 return 0;
153             }
154             }
155              
156              
157             sub last_page {
158 1558     1558 1 75302 my ($self) = @_;
159 1558 100       23971 return $self->total_entries
160             ? ceil( $self->total_entries / $self->entries_per_page )
161             : $self->first_page;
162             }
163              
164              
165             sub first {
166 386     386 1 38560 my ($self) = @_;
167 386 100       6396 if ( $self->total_entries ) {
168 380         8196 return ( $self->current_page - 1 ) * $self->entries_per_page + 1;
169             }
170             else {
171 6         58 return 0;
172             }
173             }
174              
175              
176             sub last {
177 211     211 1 37470 my ($self) = @_;
178 211 100       4807 if ( $self->current_page == $self->last_page ) {
179 121         2607 return $self->total_entries;
180             }
181             else {
182 90         2182 return $self->current_page * $self->entries_per_page;
183             }
184             }
185              
186              
187             sub previous_page {
188 71     71 1 38517 my ($self) = @_;
189 71         1788 my $page = $self->current_page;
190              
191 71 100       413 return $page > $self->first_page
192             ? $page - 1
193             : undef;
194             }
195              
196              
197             sub next_page {
198 71     71 1 148 my ($self) = @_;
199 71         1785 my $page = $self->current_page;
200              
201 71 100       159 return $page < $self->last_page
202             ? $page + 1
203             : undef;
204             }
205              
206              
207             sub splice {
208 72     72 0 610 my ( $self, $items ) = @_;
209              
210 72         200 my $last = min( $self->last, scalar(@$items) );
211              
212             return $last
213 72 100       757 ? @{$items}[ $self->first - 1 .. $last - 1 ]
  68         750  
214             : ();
215             }
216              
217              
218             sub skipped {
219 71     71 0 38483 my ($self) = @_;
220 71 100       1648 return $self->total_entries
221             ? $self->first - 1
222             : 0;
223             }
224              
225             # Ideally, we'd use a trigger instead, but Moo does not pass the old
226             # value to a trigger.
227              
228             around entries_per_page => sub {
229             my $next = shift;
230             my $self = shift;
231              
232             if (@_) {
233              
234             my $value = shift;
235              
236             my $first = $self->first;
237              
238             $self->$next($value);
239              
240             $self->current_page( $self->first_page + $first / $value );
241              
242             return $value;
243             }
244             else {
245              
246             return $self->$next;
247              
248             }
249             };
250              
251              
252             sub pages_in_set {
253 2     2 1 698 my ($self) = @_;
254              
255 3     3   2698 use integer;
  3         9  
  3         10  
256              
257 2         8 my $first = $self->first_page;
258 2         5 my $last = $self->last_page;
259 2         94 my $page = $self->current_page;
260              
261             return [
262 46 100       134 grep { $first <= $_ && $_ <= $last }
263 2         6 map { $page + $_ } @{ $self->series }
  46         82  
  2         31  
264             ];
265             }
266              
267              
268             sub previous_set {
269 2     2 1 6 my ($self) = @_;
270              
271 2         53 my $page = $self->current_page - (2 * $self->pages_per_exponent) - 1;
272 2 100       1166 return $page < $self->first_page
273             ? undef
274             : $page;
275             }
276              
277              
278             sub next_set {
279 2     2 1 8 my ($self) = @_;
280              
281 2         53 my $page = $self->current_page + (2 * $self->pages_per_exponent) - 1;
282 2 50       6 return $page > $self->last_page
283             ? undef
284             : $page;
285             }
286              
287              
288             sub change_entries_per_page {
289 70     70 0 36892 my ($self, $value) = @_;
290              
291 70         1870 $self->entries_per_page($value);
292              
293 70         1267 return $self->current_page;
294             }
295              
296              
297             sub BUILDARGS {
298             my ( $class, @args ) = @_;
299              
300             if (@args == 1 && is_HashRef(@args)) {
301             return $args[0];
302             }
303              
304             if ( @args && ( @args <= 3 ) && all { is_Int($_) } @args ) {
305              
306             return {
307             total_entries => $args[0],
308             maybe entries_per_page => $args[1],
309             maybe current_page => $args[2],
310             };
311              
312             }
313              
314             return {@args};
315             }
316              
317              
318             1;
319              
320             __END__
321              
322             =pod
323              
324             =encoding UTF-8
325              
326             =head1 NAME
327              
328             Data::Pageset::Exponential - Page numbering for very large page numbers
329              
330             =head1 VERSION
331              
332             version v0.3.2
333              
334             =head1 SYNOPSIS
335              
336             my $pager = Data::Pageset::Exponential->new(
337             total_entries => $total_entries,
338             entries_per_page => $per_page,
339             );
340              
341             $pager->current_page( 1 );
342              
343             my $pages = $pager->pages_in_set;
344              
345             # Returns
346             # [ 1, 2, 3, 10, 20, 30, 100, 200, 300, 1000, 2000, 3000 ]
347              
348             =head1 DESCRIPTION
349              
350             This is a pager designed for paging through resultsets that contain
351             hundreds if not thousands of pages.
352              
353             The interface is similar to L<Data::Pageset> with sliding pagesets.
354              
355             =head1 ATTRIBUTES
356              
357             =head2 C<total_entries>
358              
359             This is the total number of entries.
360              
361             It is a read/write attribute.
362              
363             =head2 C<entries_per_page>
364              
365             This is the total number of entries per page. It defaults to C<10>.
366              
367             It is a read/write attribute.
368              
369             =head2 C<first_page>
370              
371             This returns the first page. It defaults to C<1>.
372              
373             =head2 C<current_page>
374              
375             This is the current page number. It defaults to the L</first_page>.
376              
377             It is a read/write attribute.
378              
379             =head2 C<exponent_base>
380              
381             This is the base exponent for page sets. It defaults to C<10>.
382              
383             =head2 C<exponent_max>
384              
385             This is the maximum exponent for page sets. It defaults to C<3>, for
386             pages in the thousands.
387              
388             It should not be greater than
389              
390             ceil( log( $total_pages ) / log(10) )
391              
392             however, larger numbers will increase the size of L</pages_in_set>.
393              
394             =head2 C<pages_per_exponent>
395              
396             This is the number of pages per exponent. It defaults to C<3>.
397              
398             =head2 C<pages_per_set>
399              
400             This is the maximum number of pages in L</pages_in_set>. It defaults
401             to
402              
403             1 + 2 * ( $pages_per_exponent * ( $exponent_max + 1 ) - 1 )
404              
405             which for the default values is 23.
406              
407             This should be an odd number.
408              
409             This was renamed from L</max_pages_per_set> in v0.3.0.
410              
411             =head2 C<max_pages_per_set>
412              
413             This is a deprecated alias for L</pages_per_set>.
414              
415             =head1 METHODS
416              
417             =head2 C<entries_on_this_page>
418              
419             Returns the number of entries on the page.
420              
421             =head2 C<last_page>
422              
423             Returns the number of the last page.
424              
425             =head2 C<first>
426              
427             Returns the index of the first entry on the L</current_page>.
428              
429             =head2 C<last>
430              
431             Returns the index of the last entry on the L</current_page>.
432              
433             =head2 C<previous_page>
434              
435             Returns the number of the previous page.
436              
437             =head2 C<next_page>
438              
439             Returns the number of the next page.
440              
441             =head2 C<pages_in_set>
442              
443             Returns an array reference of pages in the page set.
444              
445             =head2 C<previous_set>
446              
447             This returns the first page number of the previous page set, for the
448             first exponent.
449              
450             It is added for compatability with L<Data::Pageset>.
451              
452             =head2 C<next_set>
453              
454             This returns the first page number of the next page set, for the first
455             exponent.
456              
457             It is added for compatability with L<Data::Pageset>.
458              
459             =for Pod::Coverage isa
460              
461             =for Pod::Coverage series
462              
463             =for Pod::Coverage splice
464              
465             =for Pod::Coverage skipped
466              
467             =for Pod::Coverage change_entries_per_page
468              
469             =for Pod::Coverage BUILDARGS
470              
471             =head1 KNOWN ISSUES
472              
473             =head2 Differences with Data::Page
474              
475             This module is intended as a drop-in replacement for L<Data::Page>.
476             However, it is based on a complete rewrite of L<Data::Page> using
477             L<Moo>, rather than extending it. Because of that, it needs to fake
478             C<@ISA>. This may break some applications.
479              
480             Otherwise, it has the following differences:
481              
482             =over
483              
484             =item *
485              
486             The attributes have type constraints. Invalid data may throw a fatal
487             error instead of being ignored.
488              
489             =item *
490              
491             Setting the L</current_page> to a value outside the L</first_page> or
492             L</last_page> will return the first or last page, instead of that
493             value.
494              
495             =back
496              
497             =head2 Differences with Data::Pageset
498              
499             This module can behave like L<Data::Pageset> in C<slide> mode if the
500             exponent is set to C<1>:
501              
502             my $pager = Data::Pageset::Exponential->new(
503             exponent_max => 1,
504             pages_per_exponent => 10,
505             pages_per_set => 10,
506             );
507              
508             =head1 SEE ALSO
509              
510             =over
511              
512             =item *
513              
514             L<Data::Page>
515              
516             =item *
517              
518             L<Data::Pageset>
519              
520             =back
521              
522             =head1 SOURCE
523              
524             The development version is on github at L<https://github.com/robrwo/Data-Pageset-Exponential>
525             and may be cloned from L<git://github.com/robrwo/Data-Pageset-Exponential.git>
526              
527             =head1 BUGS
528              
529             Please report any bugs or feature requests on the bugtracker website
530             L<https://github.com/robrwo/Data-Pageset-Exponential/issues>
531              
532             When submitting a bug or request, please include a test-file or a
533             patch to an existing test-file that illustrates the bug or desired
534             feature.
535              
536             =head1 AUTHOR
537              
538             Robert Rothenberg <rrwo@cpan.org>
539              
540             Test code was adapted from L<Data::Page> to ensure compatability.
541              
542             =head1 COPYRIGHT AND LICENSE
543              
544             This software is Copyright (c) 2018-2021 by Robert Rothenberg.
545              
546             This is free software, licensed under:
547              
548             The Artistic License 2.0 (GPL Compatible)
549              
550             =cut