File Coverage

blib/lib/File/Zglob.pm
Criterion Covered Total %
statement 100 141 70.9
branch 70 106 66.0
condition 43 75 57.3
subroutine 13 14 92.8
pod 1 7 14.2
total 227 343 66.1


line stmt bran cond sub pod time code
1             package File::Zglob;
2 4     4   111296 use strict;
  4         7  
  4         180  
3 4     4   23 use warnings 'all', FATAL => 'recursion';
  4         8  
  4         191  
4 4     4   100 use 5.008008;
  4         18  
  4         274  
5             our $VERSION = '0.11';
6 4     4   28 use base qw(Exporter);
  4         6  
  4         537  
7              
8             our @EXPORT = qw(zglob);
9              
10 4     4   22 use File::Basename;
  4         6  
  4         9002  
11              
12             our $SEPCHAR = '/';
13             our $NOCASE = $^O =~ /^(?:MSWin32|VMS|os2|dos|riscos|MacOS|darwin)$/ ? 1 : 0;
14             our $DIRFLAG = \"DIR?";
15             our $DEEPFLAG = \"**";
16             our $PARENTFLAG = \"..";
17             our $DEBUG = 0;
18             our $STRICT_LEADING_DOT = 1;
19             our $STRICT_WILDCARD_SLASH = 1;
20              
21             sub zglob {
22 19     19 1 16634 my ($pattern) = @_;
23             #dbg("FOLDING: $pattern");
24             # support ~tokuhirom/
25 19 50       59 if ($^O eq 'MSWin32') {
26 0         0 require Win32;
27 0         0 $pattern =~ s!^(\~[^$SEPCHAR]*)!Win32::GetLongPathName([glob($1)]->[0])!e;
  0         0  
28             } else {
29 19         121 $pattern =~ s!^(\~[^$SEPCHAR]*)![glob($1)]->[0]!e;
  1         21  
30             }
31 19         41 my ($node, $matcher) = glob_prepare_pattern($pattern);
32             # $node : \0 if absolute path, \1 if relative.
33              
34             #dbg("pattern: ", $node, $matcher);
35 19         67 return _rec($node, $matcher, []);
36             }
37              
38             sub dbg(@) {
39 164 50   164 0 296 return unless $DEBUG;
40 0         0 my ($pkg, $filename, $line, $sub) = caller(1);
41 0         0 my $i = 0;
42 0         0 while (caller($i++)) { 1 }
  0         0  
43 0         0 my $msg;
44 0         0 $msg .= ('-' x ($i-5));
45 0         0 $msg .= " [$sub] ";
46 0         0 for (@_) {
47 0         0 $msg .= ' ';
48 0 0       0 if (not defined $_) {
    0          
49 0         0 $msg .= '<>';
50             } elsif (ref $_) {
51 0         0 require Data::Dumper;
52 0         0 local $Data::Dumper::Terse = 1;
53 0         0 local $Data::Dumper::Indent = 0;
54 0         0 $msg .= Data::Dumper::Dumper($_);
55             } else {
56 0         0 $msg .= $_;
57             }
58             }
59 0         0 $msg .= " at $filename line $line\n";
60 0         0 print($msg);
61             }
62              
63             sub _recstar {
64 20     20   29 my ($node, $matcher) = @_;
65             #dbg("recstar: ", $node, $matcher, $seed);
66             return (
67 13         36 _rec( $node, $matcher ),
68             (
69 20         53 map { _recstar( $_, $matcher ) }
70             glob_fs_fold( $node, qr{^[^.].*$}, 1 )
71             )
72             );
73             }
74              
75             sub _rec {
76 72     72   98 my ($node, $matcher) = @_;
77             # $matcher: ArrayRef[Any]
78              
79 72         68 my ($current, @rest) = @{$matcher};
  72         133  
80 72 100 100     412 if (!defined $current) {
    100 66        
    100          
    100          
81             #dbg("FINISHED");
82 1         4 return ();
83             } elsif (ref($current) eq 'SCALAR' && $current == $DEEPFLAG) {
84             #dbg("** mode");
85 7         19 return _recstar($node, \@rest);
86             } elsif (ref($current) eq 'SCALAR' && $current == $PARENTFLAG) {
87 2 50 66     20 if (ref($node) eq 'SCALAR' && $$node eq 1) { #t
    100 66        
88 0         0 die "You cannot get a parent directory of root dir.";
89             } elsif (ref($node) eq 'SCALAR' && $$node eq 0) { #f
90 1         6 return _rec("..", \@rest);
91             } else {
92 1         8 return _rec("$node$SEPCHAR..", \@rest);
93             }
94             } elsif (@rest == 0) {
95             #dbg("file name");
96             # (folder proc seed node (car matcher) #f)
97 37         76 return glob_fs_fold($node, $current, 0);
98             } else {
99 25         54 return glob_fs_fold($node, $current, 1, \@rest);
100             }
101             }
102              
103              
104             # /^home$/ のような固定の文字列の場合に高速化をはかるための最適化予定地なので、とりあえず undef をかえしておいても問題がない
105             sub fixed_regexp_p {
106 0     0 0 0 return undef;
107 0         0 die "TBI"
108             }
109              
110             # returns arrayref of seeds.
111             sub glob_fs_fold {
112 82     82 0 126 my ($node, $regexp, $non_leaf_p, $rest) = @_;
113              
114 82         71 my $prefix = do {
115 82 100       214 if (ref $node eq 'SCALAR') {
    50          
116 19 100       53 if ($$node eq 1) { #t
    50          
117 1         2 $SEPCHAR
118             } elsif ($$node eq '0') { #f
119 18         27 '';
120             } else {
121 0         0 die "FATAL";
122             }
123             } elsif ($node !~ m{/$}) {
124 63         201 $node . '/';
125             } else {
126 0         0 $node;
127             }
128             };
129 82         177 dbg("prefix: $prefix");
130 82         135 dbg("regxp: ", $regexp);
131 82 0 33     219 if ($^O eq 'MSWin32' && ref $regexp eq 'SCALAR' && $$regexp =~ /^[a-zA-Z]\:$/) {
      33        
132 0         0 return _rec($$regexp . '/', $rest);
133             }
134 82 50 33     200 if (ref $regexp eq 'SCALAR' && $regexp == $DIRFLAG) {
135 0 0       0 if ($rest) {
136 0         0 return _rec($prefix, $rest);
137             } else {
138 0         0 return ($prefix);
139             }
140             # } elsif (my $string_portion = fixed_regexp_p($regexp)) { # /^path$/
141             # die "TBI";
142             # my $full = $prefix . $string_portion;
143             # if (-e $full && (!$non_leaf_p || -d $full)) {
144             # $proc->($full, $seed);
145             # } else {
146             # $proc;
147             # }
148             } else { # normal regexp
149             #dbg("normal regexp");
150 82         83 my $dir = do {
151 82 100 100     339 if (ref($node) eq 'SCALAR' && $$node eq 1) {
    100 66        
152 1         3 $SEPCHAR
153             } elsif (ref($node) eq 'SCALAR' && $$node eq 0) {
154 18         27 '.';
155             } else {
156 63         99 $node;
157             }
158             };
159             #dbg("dir: $dir");
160 82 50       1800 opendir my $dirh, $dir or do {
161             #dbg("cannot open dir: $dir: $!");
162 0         0 return ();
163             };
164 82         84 my @ret;
165 82         478281 while (defined(my $child = readdir($dirh))) {
166 595 100 100     2465 next if $child eq '.' or $child eq '..';
167 431         375 my $full;
168             #dbg("non-leaf: ", $non_leaf_p);
169 431 100 66     3387 if (($child =~ $regexp) && ($full = $prefix . $child) && (!$non_leaf_p || -d $full)) {
      100        
      66        
170             #dbg("matched: ", $regexp, $child, $full);
171 73 100       122 if ($rest) {
172 31         60 push @ret, _rec($full, $rest);
173             } else {
174 42         162 push @ret, $full;
175             }
176             # } else {
177             #dbg("Don't match: $child");
178             }
179             }
180 82         1296 return @ret;
181             }
182             }
183              
184             sub glob_prepare_pattern {
185 22     22 0 4969 my ($pattern) = @_;
186 22         117 my @path = split $SEPCHAR, $pattern;
187              
188 22 100       142 my $is_absolute = $path[0] eq '' ? 1 : 0;
189 22 100       51 if ($is_absolute) {
190 3         6 shift @path;
191             }
192 22 50 33     88 if ($^O eq 'MSWin32' && $path[0] =~ /^[a-zA-Z]\:$/) {
193 0         0 $is_absolute = 1;
194             }
195              
196             @path = map {
197 22 100 33     38 if ($_ eq '**') {
  60 50       270  
    100          
    100          
    50          
198 8         18 $DEEPFLAG
199             } elsif ($_ eq '') {
200 0         0 $DIRFLAG
201             } elsif ($_ eq '.') {
202             ()
203 4         4 } elsif ($_ eq '..') {
204 2         4 $PARENTFLAG
205             } elsif ($^O eq 'MSWin32' && $_ =~ '^[a-zA-Z]\:$') {
206 0         0 \$_
207             } else {
208 46         78 glob_to_regex($_) # TODO: replace with original implementation?
209             }
210             } @path;
211              
212 22         75 return ( \$is_absolute, \@path );
213             }
214              
215             # this is not a private function. '**' was handled at glob_prepare_pattern() function.
216             sub glob_to_regex {
217 46     46 0 59 my $glob = shift;
218 46         65 my $regex = glob_to_regex_string($glob);
219 46 50       1323 return $NOCASE ? qr/^$regex$/i : qr/^$regex$/;
220             }
221              
222             sub glob_to_regex_string {
223 46     46 0 53 my $glob = shift;
224 46         42 my ($regex, $in_curlies, $escaping);
225 46         40 local $_;
226 46         51 my $first_byte = 1;
227 46         260 for ($glob =~ m/(.)/gs) {
228 158 100       269 if ($first_byte) {
229 46 50       75 if ($STRICT_LEADING_DOT) {
230 46 100       106 $regex .= '(?=[^\.])' unless $_ eq '.';
231             }
232 46         47 $first_byte = 0;
233             }
234 158 50       248 if ($_ eq '/') {
235 0         0 $first_byte = 1;
236             }
237 158 100 66     2886 if ($_ eq '.' || $_ eq '(' || $_ eq ')' || $_ eq '|' ||
    100 66        
    50 33        
    100 33        
    100 33        
    100 33        
    50 33        
      33        
      66        
      66        
238             $_ eq '+' || $_ eq '^' || $_ eq '$' || $_ eq '@' || $_ eq '%' ) {
239 11         18 $regex .= "\\$_";
240             }
241             elsif ($_ eq '*') {
242 19 50       87 $regex .= $escaping ? "\\*" :
    50          
243             $STRICT_WILDCARD_SLASH ? "[^/]*" : ".*";
244             }
245             elsif ($_ eq '?') {
246 0 0       0 $regex .= $escaping ? "\\?" :
    0          
247             $STRICT_WILDCARD_SLASH ? "[^/]" : ".";
248             }
249             elsif ($_ eq '{') {
250 1 50       5 $regex .= $escaping ? "\\{" : "(";
251 1 50       4 ++$in_curlies unless $escaping;
252             }
253             elsif ($_ eq '}' && $in_curlies) {
254 1 50       4 $regex .= $escaping ? "}" : ")";
255 1 50       3 --$in_curlies unless $escaping;
256             }
257             elsif ($_ eq ',' && $in_curlies) {
258 1 50       4 $regex .= $escaping ? "," : "|";
259             }
260             elsif ($_ eq "\\") {
261 0 0       0 if ($escaping) {
262 0         0 $regex .= "\\\\";
263 0         0 $escaping = 0;
264             }
265             else {
266 0         0 $escaping = 1;
267             }
268 0         0 next;
269             }
270             else {
271 125         134 $regex .= $_;
272 125         136 $escaping = 0;
273             }
274 158         218 $escaping = 0;
275             }
276              
277 46         123 return $regex;
278             }
279              
280             1;
281             __END__