File Coverage

blib/lib/Number/Continuation.pm
Criterion Covered Total %
statement 99 99 100.0
branch 27 28 96.4
condition 19 19 100.0
subroutine 15 15 100.0
pod 1 1 100.0
total 161 162 99.3


line stmt bran cond sub pod time code
1             package Number::Continuation;
2              
3 4     4   72121 use strict;
  4         24  
  4         107  
4 4     4   24 use warnings;
  4         9  
  4         103  
5 4     4   22 use base qw(Exporter);
  4         6  
  4         584  
6 4     4   1763 use boolean qw(true);
  4         13756  
  4         18  
7              
8 4     4   337 use Carp qw(croak);
  4         9  
  4         254  
9 4     4   2174 use Params::Validate ':all';
  4         36625  
  4         5474  
10              
11             our ($VERSION, @EXPORT_OK);
12              
13             $VERSION = '0.06';
14             @EXPORT_OK = qw(continuation);
15              
16             validation_options(
17             on_fail => sub
18             {
19             my ($error) = @_;
20             chomp $error;
21             croak $error;
22             },
23             stack_skip => 2,
24             );
25              
26             sub continuation
27             {
28 15     15 1 11104 my (@list, %opts, $set);
29              
30 15         68 _init(\$set, \%opts, \@_);
31              
32 15 100       43 if (wantarray) {
33 7         22 _construct($set, \@list);
34 7         39 return @list;
35             }
36             else {
37 8         20 return _format($set, \%opts);
38             }
39             }
40              
41             sub _init
42             {
43 15     15   31 my ($set, $opts, $args) = @_;
44              
45 15 100       64 if (ref $args->[-1] eq 'HASH') {
46 1         1 %$opts = %{$args->[-1]};
  1         5  
47 1         3 pop @$args;
48             }
49              
50 15         55 my $re_digits = qr!^\-?\d+$!;
51              
52             my $spec = sub
53             {
54 2     2   4 my ($args, $spec) = @_;
55 2         3 my @spec;
56 2         15 push @spec, $spec while $args--;
57 2         27 return @spec;
58 15         77 };
59              
60 15 100       48 if (@$args == 1) {
    100          
61             validate_pos(@$args, {
62             type => SCALAR | ARRAYREF,
63             callbacks => {
64             'valid set' => sub
65             {
66 11 100   11   103 foreach my $num (ref $_[0] ? @{$_[0]} : (split /\s+/, $_[0])) {
  2         6  
67 133 50       507 die "invalid number\n" unless $num =~ $re_digits;
68             }
69 11 100       102 $$set = ref $_[0] ? $_[0] : [ split /\s+/, $_[0] ];
70 11         49 return true;
71             }
72             },
73 11         189 });
74             }
75             elsif (@$args > 1) {
76 2         7 my %spec = (
77             type => SCALAR,
78             regex => $re_digits,
79             );
80 2         7 validate_pos(@$args, $spec->(scalar @$args, \%spec));
81 2         125 $$set = $args;
82             }
83             else {
84 2         3 $$set = [];
85             }
86              
87 15         285 my @args = %$opts;
88 15         48 validate(@args, {
89             delimiter => {
90             type => SCALAR,
91             optional => true,
92             regex => qr!^\S{2}$!,
93             },
94             range => {
95             type => SCALAR,
96             optional => true,
97             regex => qr!^\S{1,2}$!,
98             },
99             separator => {
100             type => SCALAR,
101             optional => true,
102             regex => qr!^\S$!,
103             },
104             });
105              
106 15   100     833 $opts->{delimiter} ||= '';
107 15   100     68 $opts->{range} ||= '-';
108 15   100     56 $opts->{separator} ||= ',';
109              
110 15         28 @{$opts->{delimiters}} = split //, $opts->{delimiter};
  15         79  
111 15   100     71 $opts->{delimiters}[0] ||= '';
112 15   100     102 $opts->{delimiters}[1] ||= '';
113             }
114              
115             sub _construct
116             {
117 7     7   16 my ($set, $list) = @_;
118              
119 7         19 my $prev_number = undef;
120              
121 7         16 my $entry = [];
122 7         19 foreach my $num (@$set) {
123 71 100 100     271 if (defined $prev_number
      100        
124             && !(($num - $prev_number == 1) # positive continuation
125             || ($prev_number - $num == 1) # negative continuation
126             )) {
127 28         43 push @$list, $entry;
128 28         42 $entry = [];
129             }
130 71         125 push @$entry, $num;
131 71         105 $prev_number = $num;
132             }
133 7 100       26 push @$list, $entry if @$entry;
134             }
135              
136             sub _format
137             {
138 8     8   20 my ($set, $opts) = @_;
139              
140 8         15 my $string = '';
141              
142             my $begin = sub
143             {
144 37     37   61 my ($string, $num) = @_;
145 37         62 $$string .= $opts->{delimiters}[0];
146 37         68 $$string .= $num;
147 8         30 };
148             my $range = sub
149             {
150 24     24   41 my ($string, $num) = @_;
151 24         38 $$string .= $opts->{range};
152 24         38 $$string .= $num;
153 8         21 };
154             my $end = sub
155             {
156 37     37   56 my ($string) = @_;
157 37         65 $$string .= $opts->{delimiters}[1];
158 37         67 $$string .= "$opts->{separator} ";
159 8         21 };
160              
161 8         13 my $consecutive = 0;
162 8         12 my $prev_number = undef;
163              
164 8         18 foreach my $num (@$set) {
165 80 100       130 if (!defined $prev_number) {
166 7         15 $begin->(\$string, $num);
167             }
168             else {
169 73 100 100     208 if (($num - $prev_number == 1) # positive continuation
    100          
170             || ($prev_number - $num == 1) # negative continuation
171             ) {
172 43         59 $consecutive++;
173             }
174             elsif ($consecutive) {
175 21         44 $range->(\$string, $prev_number);
176 21         43 $end->(\$string);
177 21         48 $begin->(\$string, $num);
178 21         33 $consecutive = 0;
179             }
180             else {
181 9         28 $end->(\$string);
182 9         15 $begin->(\$string, $num);
183             }
184             }
185 80         117 $prev_number = $num;
186             }
187 8 100       19 if ($consecutive) {
188 3         6 $range->(\$string, $prev_number);
189             }
190 8 100       21 if (@$set) {
191 7         13 $end->(\$string);
192             }
193              
194 8         112 $string =~ s/\Q$opts->{separator}\E $//;
195              
196 8         92 return $string;
197             }
198              
199             1;
200             __END__