File Coverage

blib/lib/Regex/Range/Number.pm
Criterion Covered Total %
statement 114 130 87.6
branch 44 68 64.7
condition 23 39 58.9
subroutine 9 9 100.0
pod 0 3 0.0
total 190 249 76.3


line stmt bran cond sub pod time code
1             package Regex::Range::Number;
2              
3 5     5   371790 use 5.006;
  5         56  
4 5     5   30 use strict;
  5         8  
  5         116  
5 5     5   22 use warnings;
  5         10  
  5         236  
6 5     5   2641 use Array::Merge::Unique qw/unique_array/;
  5         97906  
  5         48  
7 5     5   472 use base qw/Import::Export/;
  5         10  
  5         7726  
8             our $VERSION = '0.05';
9             our (%helper, %cache);
10             BEGIN {
11             %helper = (
12             zip => sub {
13             [ map {
14 33         177 [substr( $_[0], $_ , 1 ), substr($_[1], $_, 1)]
  96         296  
15             } 0 .. (length($_[0]) - 1) ]
16             },
17             compare => sub {
18 3 100       21 $_[0] > $_[1] ? 1 : $_[1] > $_[0] ? -1 : 0;
    100          
19             },
20             push => sub {
21 34         98 unique_array($_[0], $_[1]);
22             },
23             contains => sub {
24 28         606 my (%u);
25 28 50 33     42 grep { !$u{$_->{$_[1]}} && do { $u{$_->{$_[1]}} = 1 } && $_ } @{ $_[0] };
  2         8  
  2         15  
  28         59  
26 28         87 $u{$_[2]};
27             },
28             nines => sub {
29 48         341 substr($_[0], 0, (0 - $_[1])) . ('9' x $_[1]);
30             },
31             zeros => sub {
32 16         55 $_[0] - $_[0] % 10 ^ $_[1];
33             },
34             quantifier => sub {
35 34 50       100 my ($s, $st) = ($_[0]->[0], ($_[0]->[1] ? (',' . $_[0]->[1]) : ''));
36 34 100 100     151 return '' if (!$s || $s == 1);
37 12         36 return '{' . $s . $st . '}';
38             },
39             character => sub {
40 17 50       93 sprintf '[%s%s%s]', $_[0], (($_[1] - $_[0]) == 1 ? '' : '-'), $_[1];
41             },
42             padding => sub {
43 18         112 $_[0] =~ m/^-?(0+)\d/;
44             },
45             padz => sub {
46 0 0       0 if ($_[1]->{isPadded}) {
47 0         0 my $d = $_[1]->{maxLen} - length $_[0];
48 0 0       0 return ! $d ? $d == 0 ? '' : '0{' . $d . '}' : '0';
    0          
49             }
50 0         0 $_[0];
51             },
52             min => sub {
53 17 50       70 $_[0] < $_[1] ? $_[0] : $_[1];
54             },
55             max => sub {
56 17 50       56 $_[0] < $_[1] ? $_[1] : $_[0];
57             },
58             capture => sub {
59 8         30 sprintf "(%s)", $_[0];
60             },
61             sift => sub {
62 9         28 return join '|', $helper{filter}($_[0]->{negatives}, $_[0]->{positives}, '-', 0, $_[1]), $helper{filter}($_[0]->{positives}, $_[0]->{negatives}, '', 0, $_[1]), $helper{filter}($_[0]->{negatives}, $_[0]->{positives}, '-?', 1, $_[1]);
63             },
64             ranges => sub {
65 13         53 my ($m, $mx, $n, $z, $s) = (($_[0] + 0), ($_[1] + 0), 1, 1, [($_[1] + 0)]);
66 13         34 my $st = $helper{nines}($m, $n);
67 13   66     101 while ($m <= $st && $st <= $mx) {
68 31         74 $s = $helper{push}($s, $st);
69 31         1187 $n += 1;
70 31         62 $st = $helper{nines}($m, $n);
71             }
72 13         48 $st = $helper{zeros}($mx + 1, $z) - 1;
73 13   66     74 while ($m < $st && $st <= $mx) {
74 2         10 $s = $helper{push}($s, $st);
75 2         61 $z += 1;
76 2         20 $st = $helper{zeros}($mx + 1, $z) - 1;
77             }
78 13         24 return [sort { $a <=> $b } @{ $s }];
  45         132  
  13         69  
79             },
80             pattern => sub {
81 34         66 my ($s, $st) = @_;
82             return {
83 34 100       83 pattern => $s,
84             digits => []
85             } if ($s == $st);
86 32         66 my ($z, $p, $d) = ($helper{zip}($s, $st), '', 0);
87 32         54 for my $n (@{$z}) {
  32         59  
88             ($n->[0] == $n->[1])
89 32         56 ? do { $p .= $n->[0] }
90             : ($n->[0] != 0 || $n->[1] != 9)
91 17         29 ? do {$p .= $helper{character}(@{$n})}
  17         39  
92 94 100 66     290 : do { $d += 1 };
  45 100       77  
93             }
94 32 100       70 $p .= '[0-9]' if ($d);
95 32         132 return { pattern => $p, digits => [$d] };
96             },
97             split => sub {
98 11         35 my ($m, $mx, $tok) = @_;
99 11         44 my ($r, $t, $s, $p) = ($helper{ranges}($m, $mx), [], $m);
100 11         21 for my $rr (@{$r}) {
  11         31  
101 34         66 my $o = $helper{pattern}($s, $rr);
102 34         68 my $zeros = '';
103 34 50 66     193 if ( !$tok->{isPadded} && $p && $p->{pattern} eq $o->{pattern}) {
      66        
104 0 0       0 pop @{ $p->{digits} } if (scalar @{ $p->{digits} } > 1);
  0         0  
  0         0  
105 0         0 push @{ $p->{digits} }, $o->{digits};
  0         0  
106 0         0 $p->{string} = $p->{pattern} . $helper{quantifier}($p->{digits});
107 0         0 $s = $rr . 1;
108 0         0 next;
109             }
110 34 50       68 $zeros = $helper{padz}($rr, $tok) if $tok->{isPadded};
111 34         85 $o->{string} = $zeros . $o->{pattern} . $helper{quantifier}($o->{digits});
112 34         62 push @{$t}, $o;
  34         58  
113 34         52 $s = $rr + 1;
114 34         68 $p = $o;
115             }
116 11         76 return $t;
117             },
118             filter => sub {
119 27         56 my ($arr, $c, $p, $i, $o) = @_;
120 27         40 my @r = ();
121 27         36 foreach my $tok ( @{ $arr }) {
  27         49  
122 26         48 my $e = $tok->{string};
123 26 50 33     65 if (!$i && !$helper{contains}($c, 'string', $e)) {
    0 0        
124 26         77 push @r, $p . $e;
125             }
126             elsif ($i && $helper{contains}($c, 'string', $e)) {
127 0         0 push @r, $p . $e;
128             }
129             }
130 27         110 return @r;
131             }
132 5     5   3108 );
133             }
134              
135             our %EX = (
136             number_range => [qw/all/],
137             '%helper' => [qw/all/]
138             );
139              
140 2     2 0 201 sub new { bless {}, $_[0] }
141              
142             sub helpers {
143 2     2 0 47 return %helper;
144             }
145              
146             sub number_range {
147 28 100 100 28 0 8445 (ref( $_[0] ) || "") eq 'Regex::Range::Number' and shift @_;
148 28         59 my ($start, $max, $options) = @_;
149              
150 28 100       58 if (ref $start eq 'ARRAY') {
151 5 100       16 $max = {} unless ref $max eq 'HASH';
152             map {
153             return $max->{capture}
154 5 100       31 ? sprintf('(%s)', $_)
155             : $_
156             } join '|',
157 15 100       47 map { number_range($_->[0], $_->[1], $max->{individual} ? {capture => 1, %{$max}} : ()) }
  6         54  
158 15         35 grep { ref $_ eq 'ARRAY' }
159 5         10 @{$start};
  5         13  
160             }
161              
162 23 50 33     56 return $start if (not defined $max || $start == $max);
163              
164 23   100     78 $options ||= {};
165 23   100     66 my $capture = $options->{capture} || '';
166            
167 23         90 my $key = sprintf('%s:%s=%s', $start, $max, $capture);
168 23 100       69 return $cache{$key}->{result} if $cache{$key};
169              
170 17         44 my ($a, $b) = ($helper{min}($start, $max), $helper{max}($start, $max));
171              
172 17 100       45 if ( ($b - $a) == 1 ) {
173 8         23 my $result = $start . '|' . $max;
174 8 100       40 $result = $helper{capture}($result) if ($options->{capture});
175 8         30 $cache{$key} = { result => $result };
176 8         46 return $result;
177             }
178              
179             my $tok = {
180             min => $a,
181             max => $b,
182             positives => [],
183             negatives => [],
184 9 50 33     36 ($helper{padding}($a) || $helper{padding}($b) ? (
185             isPadded => 1,
186             maxLen => length $max
187             ) : ())
188             };
189              
190 9 50       33 if ( $a < 0 ) {
191 0 0       0 my $newMin = $b < 0 ? $b : 1;
192 0         0 $tok->{negatives} = $helper{split}($newMin, $a, $tok, $options);
193 0         0 $a = $tok->{a} = 0;
194             }
195              
196 9 50       34 $tok->{positives} = $helper{split}($a, $b, $tok, $options) if ($b >= 0);
197 9         23 $tok->{result} = $helper{sift}($tok, $options);
198 9 100       28 $tok->{result} = $helper{capture}($tok->{result}) if $capture;
199              
200 9         32 $cache{$key} = $tok;
201 9         33 return $tok->{result};
202             }
203              
204             =head1 NAME
205              
206             Regex::Range::Number - Generate number matching regexes
207              
208             =head1 VERSION
209              
210             Version 0.05
211              
212             =cut
213              
214             =head1 SYNOPSIS
215              
216             use Regex::Range::Number;
217              
218             my $gene = Regex::Range::Number->new();
219             my $reg = $gene->number_range(100, 1999); # 10[0-9]|1[1-9][0-9]|[2-9][0-9]{2}|1[0-9]{3}
220             1234 =~ m@$reg@;
221              
222             ...
223              
224             use Regex::Range::Number qw/number_range/;
225             my $reg = number_range(100, 1999, { capture => 1 }); # (10[0-9]|1[1-9][0-9]|[2-9][0-9]{2}|1[0-9]{3})
226             1234 =~ m?$reg?;
227              
228             my $range = number_range([[55, 56], [75, 89], [92, 100]], {capture => 1}); # (55|56|7[5-9]|8[0-9]|9[2-9]|100)'
229              
230             =cut
231              
232             =head1 AUTHOR
233              
234             LNATION, C<< >>
235              
236             =head1 BUGS
237              
238             Please report any bugs or feature requests to C, or through
239             the web interface at L. I will be notified, and then you'll
240             automatically be notified of progress on your bug as I make changes.
241              
242             =head1 SUPPORT
243              
244             You can find documentation for this module with the perldoc command.
245              
246             perldoc Regex::Range::Number
247              
248              
249             You can also look for information at:
250              
251             =over 4
252              
253             =item * RT: CPAN's request tracker (report bugs here)
254              
255             L
256              
257             =item * AnnoCPAN: Annotated CPAN documentation
258              
259             L
260              
261             =item * CPAN Ratings
262              
263             L
264              
265             =item * Search CPAN
266              
267             L
268              
269             =back
270              
271             =head1 ACKNOWLEDGEMENTS
272              
273              
274             =head1 LICENSE AND COPYRIGHT
275              
276             Copyright 2018 LNATION.
277              
278             This program is free software; you can redistribute it and/or modify it
279             under the terms of the the Artistic License (2.0). You may obtain a
280             copy of the full license at:
281              
282             L
283              
284             Any use, modification, and distribution of the Standard or Modified
285             Versions is governed by this Artistic License. By using, modifying or
286             distributing the Package, you accept this license. Do not use, modify,
287             or distribute the Package, if you do not accept this license.
288              
289             If your Modified Version has been derived from a Modified Version made
290             by someone other than you, you are nevertheless required to ensure that
291             your Modified Version complies with the requirements of this license.
292              
293             This license does not grant you the right to use any trademark, service
294             mark, tradename, or logo of the Copyright Holder.
295              
296             This license includes the non-exclusive, worldwide, free-of-charge
297             patent license to make, have made, use, offer to sell, sell, import and
298             otherwise transfer the Package with respect to any patent claims
299             licensable by the Copyright Holder that are necessarily infringed by the
300             Package. If you institute patent litigation (including a cross-claim or
301             counterclaim) against any party alleging that the Package constitutes
302             direct or contributory patent infringement, then this Artistic License
303             to you shall terminate on the date that such litigation is filed.
304              
305             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
306             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
307             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
308             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
309             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
310             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
311             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
312             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
313              
314              
315             =cut
316              
317             1; # End of Regex::Range::Number