File Coverage

blib/lib/File/GlobMapper.pm
Criterion Covered Total %
statement 140 152 92.1
branch 62 76 81.5
condition 3 12 25.0
subroutine 14 14 100.0
pod 0 4 0.0
total 219 258 84.8


line stmt bran cond sub pod time code
1             package File::GlobMapper;
2              
3 130     130   991 use strict;
  130         291  
  130         4862  
4 130     130   648 use warnings;
  130         217  
  130         5606  
5 130     130   675 use Carp;
  130         657  
  130         31518  
6              
7             our ($CSH_GLOB);
8              
9             BEGIN
10             {
11 130 50   130   955 if ($] < 5.006)
12             {
13 0         0 require File::BSDGlob; File::BSDGlob->import(':glob');
  0         0  
14 0         0 $CSH_GLOB = File::BSDGlob::GLOB_CSH();
15 0         0 *globber = \&File::BSDGlob::csh_glob;
16             }
17             else
18             {
19 130         903 require File::Glob; File::Glob->import(':glob');
  130         29668  
20 130         440 $CSH_GLOB = File::Glob::GLOB_CSH();
21             #*globber = \&File::Glob::bsd_glob;
22 130         362797 *globber = \&File::Glob::csh_glob;
23             }
24             }
25              
26             our ($Error);
27              
28             our ($VERSION, @EXPORT_OK);
29             $VERSION = '1.001';
30             @EXPORT_OK = qw( globmap );
31              
32             our $BEGIN_DELIM = "\xFF";
33             our $END_DELIM = "\xFE";
34             our $BACKSLASH_ESC = "\xFD";
35             our $HASH_ESC = "\xFC";
36             our $STAR_ESC = "\xFB";
37              
38             our ($noPreBS, $metachars, $matchMetaRE, %mapping, %wildCount);
39             $noPreBS = '(?
40             $metachars = '.*?[](){}';
41             $matchMetaRE = '[' . quotemeta($metachars) . ']';
42              
43             %mapping = (
44             '*' => '([^/]*)',
45             '?' => '([^/])',
46             '.' => '\.',
47             '[' => '([',
48             '(' => '(',
49             ')' => ')',
50             );
51              
52             %wildCount = map { $_ => 1 } qw/ * ? . { ( [ /;
53              
54             sub globmap ($$;)
55             {
56 3     3 0 8929 my $inputGlob = shift ;
57 3         7 my $outputGlob = shift ;
58              
59 3 50       22 my $obj = File::GlobMapper->new($inputGlob, $outputGlob, @_)
60             or croak "globmap: $Error" ;
61 3         9 return $obj->getFileMap();
62             }
63              
64             sub new
65             {
66 58     58 0 212901 my $class = shift ;
67 58         134 my $inputGlob = shift ;
68 58         117 my $outputGlob = shift ;
69             # TODO -- flags needs to default to whatever File::Glob does
70 58   33     307 my $flags = shift || $CSH_GLOB ;
71             #my $flags = shift ;
72              
73 58         343 $inputGlob =~ s/^\s*\<\s*//;
74 58         282 $inputGlob =~ s/\s*\>\s*$//;
75              
76 58         225 $outputGlob =~ s/^\s*\<\s*//;
77 58         244 $outputGlob =~ s/\s*\>\s*$//;
78              
79 58         619 my %object =
80             ( InputGlob => $inputGlob,
81             OutputGlob => $outputGlob,
82             GlobFlags => $flags,
83             Braces => 0,
84             WildCount => 0,
85             Pairs => [],
86             Sigil => '#',
87             );
88              
89 58   33     389 my $self = bless \%object, ref($class) || $class ;
90              
91 58 100       230 $self->_parseInputGlob()
92             or return undef ;
93              
94 33 50       121 $self->_parseOutputGlob()
95             or return undef ;
96              
97 33         3578 my @inputFiles = globber($self->{InputGlob}, $flags) ;
98              
99 33 50       224 if (GLOB_ERROR)
100             {
101 0         0 $Error = $!;
102 0         0 return undef ;
103             }
104              
105             #if (whatever)
106             {
107 33         71 my $missing = grep { ! -e $_ } @inputFiles ;
  33         87  
  80         1009  
108              
109 33 50       113 if ($missing)
110             {
111 0         0 $Error = "$missing input files do not exist";
112 0         0 return undef ;
113             }
114             }
115              
116 33         158 $self->{InputFiles} = \@inputFiles ;
117              
118 33 100       161 $self->_getFiles()
119             or return undef ;
120              
121 32         149 return $self;
122             }
123              
124             sub _retError
125             {
126 25     25   52 my $string = shift ;
127 25         60 $Error = "$string in input fileglob" ;
128 25         55 return undef ;
129             }
130              
131             sub _unmatched
132             {
133 25     25   59 my $delimeter = shift ;
134              
135 25         175 _retError("Unmatched $delimeter");
136 25         421 return undef ;
137             }
138              
139             sub _parseBit
140             {
141 7     7   15 my $self = shift ;
142              
143 7         16 my $string = shift ;
144              
145 7         13 my $out = '';
146 7         13 my $depth = 0 ;
147              
148 7         104 while ($string =~ s/(.*?)$noPreBS(,|$matchMetaRE)//)
149             {
150 9         22 $out .= quotemeta($1) ;
151 9 100       29 $out .= $mapping{$2} if defined $mapping{$2};
152              
153 9 100       28 ++ $self->{WildCount} if $wildCount{$2} ;
154              
155 9 100 33     60 if ($2 eq ',')
    100          
    100          
    100          
    100          
    50          
156             {
157 3 50       10 return _unmatched("(")
158             if $depth ;
159              
160 3         18 $out .= '|';
161             }
162             elsif ($2 eq '(')
163             {
164 1         9 ++ $depth ;
165             }
166             elsif ($2 eq ')')
167             {
168 1 50       5 return _unmatched(")")
169             if ! $depth ;
170              
171 0         0 -- $depth ;
172             }
173             elsif ($2 eq '[')
174             {
175             # TODO -- quotemeta & check no '/'
176             # TODO -- check for \] & other \ within the []
177 1 50       9 $string =~ s#(.*?\])##
178             or return _unmatched("[");
179 0         0 $out .= "$1)" ;
180             }
181             elsif ($2 eq ']')
182             {
183 1         3 return _unmatched("]");
184             }
185             elsif ($2 eq '{' || $2 eq '}')
186             {
187 0         0 return _retError("Nested {} not allowed");
188             }
189             }
190              
191 4         10 $out .= quotemeta $string;
192              
193 4 100       28 return _unmatched("(")
194             if $depth ;
195              
196 3         9 return $out ;
197             }
198              
199             sub _parseInputGlob
200             {
201 58     58   119 my $self = shift ;
202              
203 58         203 my $string = $self->{InputGlob} ;
204 58         118 my $inGlob = '';
205              
206             # Multiple concatenated *'s don't make sense
207             #$string =~ s#\*\*+#*# ;
208              
209             # TODO -- Allow space to delimit patterns?
210             #my @strings = split /\s+/, $string ;
211             #for my $str (@strings)
212 58         117 my $out = '';
213 58         102 my $depth = 0 ;
214              
215 58         1043 while ($string =~ s/(.*?)$noPreBS($matchMetaRE)//)
216             {
217 132         419 $out .= quotemeta($1) ;
218 132 100       557 $out .= $mapping{$2} if defined $mapping{$2};
219 132 100       455 ++ $self->{WildCount} if $wildCount{$2} ;
220              
221 132 100       1328 if ($2 eq '(')
    100          
    100          
    100          
    100          
    100          
222             {
223 3         103 ++ $depth ;
224             }
225             elsif ($2 eq ')')
226             {
227 18 100       96 return _unmatched(")")
228             if ! $depth ;
229              
230 2         11 -- $depth ;
231             }
232             elsif ($2 eq '[')
233             {
234             # TODO -- quotemeta & check no '/' or '(' or ')'
235             # TODO -- check for \] & other \ within the []
236 2 100       14 $string =~ s#(.*?\])##
237             or return _unmatched("[");
238 1         8 $out .= "$1)" ;
239             }
240             elsif ($2 eq ']')
241             {
242 1         5 return _unmatched("]");
243             }
244             elsif ($2 eq '}')
245             {
246 1         4 return _unmatched("}");
247             }
248             elsif ($2 eq '{')
249             {
250             # TODO -- check no '/' within the {}
251             # TODO -- check for \} & other \ within the {}
252              
253 8         16 my $tmp ;
254 8 100       78 unless ( $string =~ s/(.*?)$noPreBS\}//)
255             {
256 1         4 return _unmatched("{");
257             }
258             #$string =~ s#(.*?)\}##;
259              
260             #my $alt = join '|',
261             # map { quotemeta $_ }
262             # split "$noPreBS,", $1 ;
263 7         23 my $alt = $self->_parseBit($1);
264 7 100       44 defined $alt or return 0 ;
265 3         7 $out .= "($alt)" ;
266              
267 3         17 ++ $self->{Braces} ;
268             }
269             }
270              
271 34 100       112 return _unmatched("(")
272             if $depth ;
273              
274 33         79 $out .= quotemeta $string ;
275              
276              
277 33         348 $self->{InputGlob} =~ s/$noPreBS[\(\)]//g;
278 33         133 $self->{InputPattern} = $out ;
279              
280             #print "# INPUT '$self->{InputGlob}' => '$out'\n";
281              
282 33         123 return 1 ;
283              
284             }
285              
286             sub _parseOutputGlob
287             {
288 33     33   68 my $self = shift ;
289              
290 33         107 my $string = $self->{OutputGlob} ;
291 33         71 my $maxwild = $self->{WildCount};
292              
293 33 50       148 if ($self->{GlobFlags} & GLOB_TILDE)
294             #if (1)
295             {
296 33         82 $string =~ s{
297             ^ ~ # find a leading tilde
298             ( # save this in $1
299             [^/] # a non-slash character
300             * # repeated 0 or more times (0 means me)
301             )
302             }{
303             $1
304             ? (getpwnam($1))[7]
305             : ( $ENV{HOME} || $ENV{LOGDIR} )
306 0 0 0     0 }ex;
307              
308             }
309              
310             # max #1 must be == to max no of '*' in input
311 33         201 while ( $string =~ m/#(\d)/g )
312             {
313 34 50       168 croak "Max wild is #$maxwild, you tried #$1"
314             if $1 > $maxwild ;
315             }
316              
317 33         68 my $noPreBS = '(?
318 33         61 my $noPreESC = '(?
319              
320             # escape any use of the delimiter symbols
321             # $string =~ s/(${BEGIN_DELIM}|${END_DELIM}|${BACKSLASH_ESC})/$1$1/g;
322              
323             # escape \# and \*
324 33         85 $string =~ s/\\#/${HASH_ESC}/g;
325 33         116 $string =~ s/\\\*/${STAR_ESC}/g;
326              
327             # Transform "#3" to BEGIN_DELIM 3 END_DELIM
328 33         533 $string =~ s/${noPreESC}#(\d)/${BEGIN_DELIM}${1}${END_DELIM}/g;
329              
330 33         98 $string =~ s#\*#${BEGIN_DELIM}${END_DELIM}#g;
331              
332             # print "INPUT '$self->{InputPattern}'\n";
333             # print "OUTPUT '$self->{OutputGlob}' => '$string'\n";
334              
335 33         101 $self->{OutputPattern} = $string ;
336              
337 33         227 return 1 ;
338             }
339              
340             sub _getFiles
341             {
342 33     33   68 my $self = shift ;
343              
344 33         75 my %outInMapping = ();
345 33         67 my %inFiles = () ;
346              
347 33         438 foreach my $inFile (@{ $self->{InputFiles} })
  33         122  
348             {
349 80 100       278 next if $inFiles{$inFile} ++ ;
350              
351 79         131 my $outFile = $inFile ;
352 79         121 my @matches ;
353              
354 79         166 my $noPreESC = '(?
355              
356 79 50       1519 if (@matches = ($inFile =~ m/$self->{InputPattern}/ ))
357             {
358 79         167 $outFile = $self->{OutputPattern};
359 79         129 my $ix = 1;
360              
361             # get the filename glob
362 79         526 $outFile =~ s/${noPreESC}${BEGIN_DELIM}${END_DELIM}/$inFile/g;
363              
364             # now each of the #1, #2,...
365 79         157 for my $pattern (@matches)
366             {
367 108         1944 $outFile =~ s/${noPreESC}${BEGIN_DELIM}${ix}${END_DELIM}/$pattern/g;
368              
369 108         242 ++ $ix;
370             }
371              
372             # unescape
373 79         260 $outFile =~ s/${BEGIN_DELIM}${BEGIN_DELIM}/${BEGIN_DELIM}/g;
374 79         234 $outFile =~ s/${END_DELIM}${END_DELIM}/${END_DELIM}/g;
375 79         251 $outFile =~ s/${HASH_ESC}/#/g;
376 79         204 $outFile =~ s/${STAR_ESC}/*/g;
377              
378 79 100       206 if (defined $outInMapping{$outFile})
379             {
380 1         4 $Error = "multiple input files map to one output file";
381 1         13 return undef ;
382             }
383 78         202 $outInMapping{$outFile} = $inFile;
384 78         121 push @{ $self->{Pairs} }, [$inFile, $outFile];
  78         359  
385             }
386             }
387              
388 32         155 return 1 ;
389             }
390              
391             sub getFileMap
392             {
393 32     32 0 4932 my $self = shift ;
394              
395 32         113 return $self->{Pairs} ;
396             }
397              
398             sub getHash
399             {
400 4     4 0 6672 my $self = shift ;
401              
402 4         13 return { map { $_->[0] => $_->[1] } @{ $self->{Pairs} } } ;
  5         26  
  4         16  
403             }
404              
405             1;
406              
407             __END__