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