File Coverage

blib/lib/Regex/Range/Number.pm
Criterion Covered Total %
statement 111 130 85.3
branch 39 68 57.3
condition 20 37 54.0
subroutine 9 9 100.0
pod 0 3 0.0
total 179 247 72.4


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