File Coverage

blib/lib/Regex/Range/Number.pm
Criterion Covered Total %
statement 100 122 81.9
branch 28 60 46.6
condition 18 37 48.6
subroutine 9 9 100.0
pod 0 3 0.0
total 155 231 67.1


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