File Coverage

blib/lib/Regex/Range/Number.pm
Criterion Covered Total %
statement 114 130 87.6
branch 46 70 65.7
condition 23 39 58.9
subroutine 9 9 100.0
pod 0 3 0.0
total 192 251 76.4


line stmt bran cond sub pod time code
1             package Regex::Range::Number;
2              
3 5     5   409114 use 5.006;
  5         14  
4 5     5   20 use strict;
  5         7  
  5         132  
5 5     5   32 use warnings;
  5         16  
  5         225  
6 5     5   1905 use Array::Merge::Unique qw/unique_array/;
  5         77251  
  5         30  
7 5     5   411 use base qw/Import::Export/;
  5         7  
  5         5942  
8             our $VERSION = '0.08';
9             our (%helper, %cache);
10             BEGIN {
11             %helper = (
12             zip => sub {
13             [ map {
14 33         126408 [substr( $_[0], $_ , 1 ), substr($_[1], $_, 1)]
  96         204  
15             } 0 .. (length($_[0]) - 1) ]
16             },
17             compare => sub {
18 3 100       33 $_[0] > $_[1] ? 1 : $_[1] > $_[0] ? -1 : 0;
    100          
19             },
20             push => sub {
21 34         71 unique_array($_[0], $_[1]);
22             },
23             contains => sub {
24 28         476 my (%u);
25 28 50 33     54 grep { !$u{$_->{$_[1]}} && do { $u{$_->{$_[1]}} = 1 } && $_ } @{ $_[0] };
  2         9  
  2         10  
  28         40  
26 28         66 $u{$_[2]};
27             },
28             nines => sub {
29 48         134311 substr($_[0], 0, (0 - $_[1])) . ('9' x $_[1]);
30             },
31             zeros => sub {
32 16         38 $_[0] - $_[0] % 10 ^ $_[1];
33             },
34             quantifier => sub {
35 34 50       63 my ($s, $st) = ($_[0]->[0], ($_[0]->[1] ? (',' . $_[0]->[1]) : ''));
36 34 100 100     102 return '' if (!$s || $s == 1);
37 12         22 return '{' . $s . $st . '}';
38             },
39             character => sub {
40 17 50       67 sprintf '[%s%s%s]', $_[0], (($_[1] - $_[0]) == 1 ? '' : '-'), $_[1];
41             },
42             padding => sub {
43 18         106 $_[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       53 $_[0] < $_[1] ? $_[0] : $_[1];
54             },
55             max => sub {
56 17 50       52 $_[0] < $_[1] ? $_[1] : $_[0];
57             },
58             capture => sub {
59 8         21 sprintf "(%s)", $_[0];
60             },
61             sift => sub {
62 9         27 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         39 my ($m, $mx, $n, $z, $s) = (($_[0] + 0), ($_[1] + 0), 1, 1, [($_[1] + 0)]);
66 13         28 my $st = $helper{nines}($m, $n);
67 13   66     58 while ($m <= $st && $st <= $mx) {
68 31         50 $s = $helper{push}($s, $st);
69 31         814 $n += 1;
70 31         45 $st = $helper{nines}($m, $n);
71             }
72 13         25 $st = $helper{zeros}($mx + 1, $z) - 1;
73 13   66     59 while ($m < $st && $st <= $mx) {
74 2         5 $s = $helper{push}($s, $st);
75 2         39 $z += 1;
76 2         4 $st = $helper{zeros}($mx + 1, $z) - 1;
77             }
78 13         17 return [sort { $a <=> $b } @{ $s }];
  45         90  
  13         42  
79             },
80             pattern => sub {
81 34         65 my ($s, $st) = @_;
82             return {
83 34 100       81 pattern => $s,
84             digits => []
85             } if ($s == $st);
86 32         48 my ($z, $p, $d) = ($helper{zip}($s, $st), '', 0);
87 32         54 for my $n (@{$z}) {
  32         45  
88             ($n->[0] == $n->[1])
89 32         42 ? do { $p .= $n->[0] }
90             : ($n->[0] != 0 || $n->[1] != 9)
91 17         17 ? do {$p .= $helper{character}(@{$n})}
  17         26  
92 94 100 66     203 : do { $d += 1 };
  45 100       50  
    100          
93             }
94 32 100       65 $p .= '[0-9]' if ($d);
95 32         105 return { pattern => $p, digits => [$d] };
96             },
97             split => sub {
98 11         24 my ($m, $mx, $tok) = @_;
99 11         28 my ($r, $t, $s, $p) = ($helper{ranges}($m, $mx), [], $m);
100 11         15 for my $rr (@{$r}) {
  11         19  
101 34         50 my $o = $helper{pattern}($s, $rr);
102 34         39 my $zeros = '';
103 34 50 66     126 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       53 $zeros = $helper{padz}($rr, $tok) if $tok->{isPadded};
111 34         55 $o->{string} = $zeros . $o->{pattern} . $helper{quantifier}($o->{digits});
112 34         36 push @{$t}, $o;
  34         45  
113 34         37 $s = $rr + 1;
114 34         42 $p = $o;
115             }
116 11         41 return $t;
117             },
118             filter => sub {
119 27         41 my ($arr, $c, $p, $i, $o) = @_;
120 27         35 my @r = ();
121 27         26 foreach my $tok ( @{ $arr }) {
  27         36  
122 26         35 my $e = $tok->{string};
123 26 50 33     50 if (!$i && !$helper{contains}($c, 'string', $e)) {
    0 0        
124 26         44 push @r, $p . $e;
125             }
126             elsif ($i && $helper{contains}($c, 'string', $e)) {
127 0         0 push @r, $p . $e;
128             }
129             }
130 27         82 return @r;
131             }
132 5     5   2680 );
133             }
134              
135             our %EX = (
136             number_range => [qw/all/],
137             '%helper' => [qw/all/]
138             );
139              
140 2     2 0 257575 sub new { bless {}, $_[0] }
141              
142             sub helpers {
143 2     2 0 36 return %helper;
144             }
145              
146             sub number_range {
147 28 100 100 28 0 8359 (ref( $_[0] ) || "") eq 'Regex::Range::Number' and shift @_;
148 28         74 my ($start, $max, $options) = @_;
149              
150 28 100       52 if (ref $start eq 'ARRAY') {
151 5 100       15 $max = {} unless ref $max eq 'HASH';
152             map {
153             return $max->{capture}
154 5 100       22 ? sprintf('(%s)', $_)
155             : $_
156             } join '|',
157 15 100       56 map { number_range($_->[0], $_->[1], $max->{individual} ? {capture => 1, %{$max}} : ()) }
  6         15  
158 15         32 grep { ref $_ eq 'ARRAY' }
159 5         8 @{$start};
  5         11  
160             }
161              
162 23 50 33     47 return $start if (not defined $max || $start == $max);
163              
164 23   100     51 $options ||= {};
165 23   100     56 my $capture = $options->{capture} || '';
166            
167 23         40 my $key = sprintf('%s:%s=%s', $start, $max, $capture);
168 23 100       48 return $cache{$key}->{result} if $cache{$key};
169              
170 17         33 my ($a, $b) = ($helper{min}($start, $max), $helper{max}($start, $max));
171              
172 17 100       34 if ( ($b - $a) == 1 ) {
173 8         13 my $result = $start . '|' . $max;
174 8 100       26 $result = $helper{capture}($result) if ($options->{capture});
175 8         29 $cache{$key} = { result => $result };
176 8         24 return $result;
177             }
178              
179             my $tok = {
180             min => $a,
181             max => $b,
182             positives => [],
183             negatives => [],
184 9 50 33     31 ($helper{padding}($a) || $helper{padding}($b) ? (
185             isPadded => 1,
186             maxLen => length $max
187             ) : ())
188             };
189              
190 9 50       20 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       22 $tok->{positives} = $helper{split}($a, $b, $tok, $options) if ($b >= 0);
197 9         18 $tok->{result} = $helper{sift}($tok, $options);
198 9 100       21 $tok->{result} = $helper{capture}($tok->{result}) if $capture;
199              
200 9         14 $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.07
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