File Coverage

blib/lib/Regexp/NumRange.pm
Criterion Covered Total %
statement 110 110 100.0
branch 59 66 89.3
condition 9 12 75.0
subroutine 8 8 100.0
pod 2 2 100.0
total 188 198 94.9


line stmt bran cond sub pod time code
1             package Regexp::NumRange;
2              
3 4     4   112431 use 5.006;
  4         15  
  4         168  
4 4     4   23 use strict;
  4         7  
  4         138  
5 4     4   20 use warnings;
  4         13  
  4         108  
6 4     4   21 use Carp;
  4         8  
  4         558  
7 4     4   3646 use POSIX qw( ceil );
  4         30133  
  4         29  
8              
9 4     4   4923 use base 'Exporter';
  4         7  
  4         4300  
10             our @EXPORT_OK = qw( rx_range rx_max );
11              
12             =head1 NAME
13              
14             Regexp::NumRange - Create Regular Expressions for numeric ranges
15              
16             =head1 VERSION
17              
18             Version 0.03
19              
20             =cut
21              
22             our $VERSION = '0.03';
23              
24             =head1 SYNOPSIS
25              
26             B is a package for generating regular expression strings. These strings can be used in a regular expression to correctly match numeric strings within only a specified range.
27              
28             Example Usage:
29              
30             use Test::More;
31             use Regexp::NumRange qw/ rx_max /;
32              
33             my $rx = rx_max(255);
34              
35             like '100', qr/^$rx$/, '100 is less than 255';
36             unlike '256', qr/^$rx$/, '256 is greater tha 255';
37              
38             =head1 EXPORT
39              
40             Exports Available:
41              
42             use Regexp::NumRange qw/ rx_max rx_range /;
43              
44             =head1 SUBROUTINES/METHODS
45              
46             =head2 rx_range
47              
48             Create a regex string between two arbitrary integers.
49              
50             use Test::More;
51             use Regexp::NumRange qw/ rx_range /;
52              
53             my $string = rx_range(256, 1024);
54             my $rx = qr/^$string$/;
55              
56             ok "10" !~ $rx;
57             ok "300" =~ $rx;
58             ok "2000" !~ $rx;
59              
60             =cut
61              
62             sub rx_range {
63 18     18 1 117941 my ( $s, $e ) = @_;
64 18         38 $s = int($s);
65 18         39 $e = int($e);
66 18 100       83 ( $s, $e ) = ( $e, $s ) if $e < $s;
67 18 100       74 return rx_max($e) if $s == 0;
68              
69 16         90 my @ds = split //, "$s";
70 16         71 my @de = split //, "$e";
71              
72 16         33 my $maxd = scalar @de;
73 16         35 my $mind = scalar @ds;
74 16         68 my $diff = $maxd - $mind;
75              
76 16         34 my $rx = '(';
77              
78             # after last significant digit
79 16         47 my @l = @de;
80 16         44 my $a = 0;
81 16 100 100     104 if ( $diff || $de[0] - $ds[0] >= 1 ) {
82 14         46 while ( scalar(@l) >= 2 ) {
83 32         72 my $d = pop @l;
84 32 100       87 my $ld = ( $a == 0 ) ? $d : $d - 1;
85 32 100       141 next if $ld < 0;
86 25         181 $rx .= join( '', @l );
87 25         57 $rx .= "[0-$ld]";
88 25 100       67 $rx .= "[0-9]" if $a >= 1;
89 25 100       70 $rx .= "{$a}" if $a > 1;
90 25         39 $rx .= '|';
91 25         145 $a++;
92             }
93             }
94              
95             # counting up to common digits
96 16 100       53 if ($diff) {
    100          
97 11         26 my $min = $ds[0] + 1;
98 11 50       31 if ( $min <= 9 ) {
99 11         15 my $n = $mind - 1;
100 11         32 $rx .= "[$min-9]";
101 11 100       40 $rx .= "[0-9]{$n}" if $n >= 1;
102 11         25 $rx .= '|';
103             }
104             }
105             elsif ( $de[0] - $ds[0] > 1 ) {
106              
107             # betwixt same digit
108 2         7 my $n = $mind - 1;
109 2         4 my $d1 = $ds[0] + 1;
110 2         4 my $d2 = $de[0] - 1;
111 2         7 $rx .= "[$d1-$d2]";
112 2 50       10 $rx .= "[0-9]{$n}" if $n >= 1;
113 2         5 $rx .= '|';
114             }
115              
116             # lowest digit
117             {
118 16         27 my $m = $mind - 2;
  16         31  
119 16         160 my $l = $ds[-1];
120 16 100 66     95 my $md = ( $ds[0] == $de[0] && !$diff ) ? $de[-1] : 9;
121 16         62 $rx .= join( '', @ds[ 0 .. $m ] );
122 16         45 $rx .= "[$l-$md]";
123 16         35 $rx .= '|';
124             }
125              
126             # full middle digit ranges
127 16         25 my $om = -1;
128 16         46 while ( $diff > 1 ) {
129 6         11 my $m = $maxd - $diff + 1;
130 6 100       24 my $r = ( $m == $maxd - 1 ) ? $de[0] - 1 : 9;
131 6         7 $diff--;
132 6 100       18 if ( $r <= 0 ) {
133 1         2 $r = 9;
134 1         2 $m--;
135             }
136 6 50       25 $rx .= "[1-$r]" if $r >= 1;
137 6         8 $rx .= '[0-9]';
138 6 50       20 $rx .= "{$m}" if $r > 1;
139 6         7 $rx .= '|';
140 6         18 $om = $m;
141             }
142 16 100       43 if ( $diff == 1 ) {
143 11         19 my $m = $maxd - 1;
144 11         21 my $r = $de[0] - 1;
145 11 100       28 if ( $m == $om ) {
146 3         5 $r = 9;
147 3         6 $m = $mind;
148             }
149 11 100       27 if ( $r >= 1 ) {
150 6         16 $rx .= "[1-$r]";
151 6 50       28 $rx .= "[0-9]" if $m >= 1;
152 6 100       26 $rx .= "{$m}" if $m > 1;
153 6         11 $rx .= '|';
154             }
155 11         19 $m--;
156             }
157              
158 16         113 $rx =~ s/\|$//;
159 16         26 $rx .= ')';
160 16         93 return $rx;
161             }
162              
163             =head2 rx_max
164              
165             Create a regex string between 0 and an arbitrary integer.
166              
167             my $rx_string = rx_max(1024); # create a string matching numbers between 0 and 1024
168             is $rx_string, '(102[0-4]|10[0-1][0-9]|0?[0-9]{1,3})';
169              
170             =cut
171              
172             sub rx_max {
173 68     68 1 2591 my ($max) = @_;
174 68         94 $max = int($max);
175 68 100       170 return "[0-$max]" if $max <= 9;
176 63         88 my $rx = '(';
177 63         248 my @digits = split //, "$max";
178 63         93 my $after = 0;
179 63         154 while ( scalar(@digits) ) {
180 178         236 $after++;
181 178         253 my $d = pop @digits;
182 178 100       407 my $ld = ( $after == 1 ) ? $d : $d - 1;
183 178 100       329 my $first = scalar(@digits) ? 0 : 1;
184 178 50 66     499 next if $ld < 0 && $after > 1 && !$first;
      66        
185 165         293 $rx .= join( '', @digits );
186 165 100       340 $rx .= ( $ld < 1 ) ? '0' : "[0-$ld]";
187 165 100       287 $rx .= $first ? '?' : '';
188 165 100       369 $rx .= "[0-9]" if $after > 1;
189 165 50       361 $rx .= $first ? '{1,' : '{' if $after > 2;
    100          
190 165 100       323 $rx .= ( $after - 1 ) . '}' if $after > 2;
191 165 100       670 $rx .= '|' unless $first;
192             }
193 63         292 return $rx . ')';
194             }
195              
196             1;
197              
198             __END__