File Coverage

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