File Coverage

blib/lib/String/Range/Expand.pm
Criterion Covered Total %
statement 77 79 97.4
branch 25 32 78.1
condition 6 6 100.0
subroutine 9 9 100.0
pod 2 2 100.0
total 119 128 92.9


line stmt bran cond sub pod time code
1             package String::Range::Expand;
2              
3             #######################
4             # LOAD MODULES
5             #######################
6 3     3   53423 use strict;
  3         7  
  3         125  
7 3     3   15 use warnings FATAL => 'all';
  3         4  
  3         127  
8 3     3   52 use Carp qw(croak carp);
  3         6  
  3         287  
9              
10             #######################
11             # VERSION
12             #######################
13             our $VERSION = '0.04';
14              
15             #######################
16             # EXPORT
17             #######################
18 3     3   15 use base qw(Exporter);
  3         14  
  3         4200  
19             our ( @EXPORT, @EXPORT_OK );
20              
21             @EXPORT = qw(expand_range);
22             @EXPORT_OK = qw(expand_range expand_expr);
23              
24             #######################
25             # PUBLIC FUNCTIONS
26             #######################
27             sub expand_range {
28 4     4 1 19 my ($range_expr) = @_;
29              
30 4         8 my @range;
31              
32             # Define a valid range
33 4         20 my $valid_range = qr{[a-zA-Z0-9\,\-\^]+}x;
34              
35             # split expression into ranges
36 4         6 my @bits;
37 4 100       76 if ( $range_expr =~ m{\[$valid_range\]}x ) {
38              
39             # This is a Range
40             # Loop thru' multiple instances (e.g. [a-c][f-i])
41 3         6 while (1) {
42              
43 8 100       82 if ( $range_expr =~ m{(\[$valid_range\])}x ) {
44 6         18 my $match = $+;
45 6         20 my $pre = substr( $range_expr, 0, $-[0] );
46 6 50       23 push @bits, ($pre) if defined $pre;
47 6         12 push @bits, $match;
48 6         18 substr( $range_expr, 0, $+[0], '' );
49             } ## end if ( $range_expr =~ m{(\[$valid_range\])}x)
50             else {
51 2         6 push @bits, $range_expr;
52 2         5 $range_expr = '';
53             } ## end else [ if ( $range_expr =~ m{(\[$valid_range\])}x)]
54 8 100       29 last unless $range_expr;
55              
56             } ## end while (1)
57             } ## end if ( $range_expr =~ m{\[$valid_range\]}x)
58             else {
59              
60             # Expression does not have any ranges to expand
61 1         3 push @range, $range_expr;
62             } ## end else [ if ( $range_expr =~ m{\[$valid_range\]}x)]
63              
64             # Expand
65 4         11 foreach my $_bit (@bits) {
66 14 100       48 if ( $_bit =~ m{^\[(.+)\]$}x ) {
67             @range
68 6         21 ? do { @range = _combine( \@range, [ _compute($1) ] ); }
69 6 50       15 : do { @range = _compute($1); };
  0         0  
70             } ## end if ( $_bit =~ m{^\[(.+)\]$}x)
71             else {
72             @range
73 5         15 ? do { @range = _combine( \@range, [$_bit] ); }
74 8 100       24 : do { push( @range, $_bit ); };
  3         8  
75             } ## end else [ if ( $_bit =~ m{^\[(.+)\]$}x)]
76             } ## end foreach my $_bit (@bits)
77              
78 4 50       27 @range = sort { lc($a) cmp lc($b) } @range if @range;
  29         45  
79 4         63 return @range;
80             } ## end sub expand_range
81              
82              
83             sub expand_expr {
84 1     1 1 13 my @range;
85 1         5 foreach my $expr ( _split_expr(@_) ) {
86 3         9 push @range, expand_range($expr);
87             }
88 1 50       8 @range = sort { lc($a) cmp lc($b) } @range if @range;
  19         27  
89 1         16 return @range;
90             } ## end sub expand_expr
91              
92             #######################
93             # INTERNAL FUNCTIONS
94             #######################
95              
96             ## _compute
97             ## This performs the actual expansion
98             sub _compute {
99 6     6   14 my ($expr) = @_;
100              
101 6         9 my @list; # Expanded values
102              
103             # Loop thru' ranges
104 6         18 foreach my $_range ( split( /,/x, $expr ) ) {
105              
106             # Type: [aa-az]. Normal Range
107 10 100       83 if ( $_range =~ m{^(\w+)\-(\w+)$}x ) { push @list, ( $1 .. $2 ); }
  6 100       38  
    50          
108              
109             # Type: [^ba-be]. Negate range
110             elsif ( $_range =~ m{^\^(\w+)\-(\w+)$}x ) {
111 2         11 foreach my $_exclude ( $1 .. $2 ) {
112 4         8 @list = grep { !/^$_exclude$/x } @list;
  14         90  
113             }
114             } ## end elsif ( $_range =~ m{^\^(\w+)\-(\w+)$}x)
115              
116             # Type: [^zz]. Negate element
117             elsif ( $_range =~ m{^\^(\w+)$}x ) {
118 2         6 @list = grep { !/^$1$/x } @list;
  8         86  
119             }
120              
121             # Type: [foo]. Individual element
122 0         0 else { push @list, $_range; }
123             } ## end foreach my $_range ( split(...))
124 6         32 return @list;
125             } ## end sub _compute
126              
127             ## _combine
128             sub _combine {
129 11     11   20 my ( $a1, $a2 ) = @_;
130              
131 11         13 my @list;
132              
133 11         17 foreach my $_a1 (@$a1) {
134 57         74 foreach my $_a2 (@$a2) {
135 81         202 push @list, join( '', $_a1, $_a2 );
136             }
137             } ## end foreach my $_a1 (@$a1)
138              
139 11         90 return @list;
140             } ## end sub _combine
141              
142             ## split string into range expressions
143             sub _split_expr {
144 1     1   4 my @args = @_;
145 1         2 my @found;
146 1         4 foreach my $arg (@args) {
147 1         14 my @parts = split( /\s*(?
148 1         6 while ( my $bit = shift @parts ) {
149 4 50       20 next unless $bit =~ m{^\S+$};
150 4 100 100     39 if ( $bit =~ m{\[} and $bit !~ m{\]} ) {
    100 100        
151 1         3 my @current = ($bit);
152 1         6 while ( my $next = shift @parts ) {
153 1         3 push @current, $next;
154 1 50       5 last if $next =~ m{\]};
155             } ## end while ( my $next = shift ...)
156 1         10 push @found, join( ',', @current );
157             } ## end if ( $bit =~ m{\[} and...{]})
158             elsif ( $bit =~ m{\]} and $bit !~ m{\[} ) {
159 1         2 my $previous = pop @found;
160 1         6 push @found, join( ',', $previous, $bit );
161             } ## end elsif ( $bit =~ m{\]} and...{[})
162             else {
163 2         10 push @found, $bit;
164             }
165             } ## end while ( my $bit = shift @parts)
166             } ## end foreach my $arg (@args)
167 1         5 return @found;
168             } ## end sub _split_expr
169              
170             #######################
171             1;
172              
173             __END__