Integrate perlio:
[p5sagit/p5-mst-13.2.git] / lib / File / DosGlob.pm
CommitLineData
08aa1457 1#!perl -w
2
3#
4# Documentation at the __END__
5#
6
7package File::DosGlob;
8
b75c8c73 9our $VERSION = '1.00';
10use strict;
11
08aa1457 12sub doglob {
13 my $cond = shift;
14 my @retval = ();
15 #print "doglob: ", join('|', @_), "\n";
16 OUTER:
b75c8c73 17 for my $pat (@_) {
08aa1457 18 my @matched = ();
19 my @globdirs = ();
20 my $head = '.';
21 my $sepchr = '/';
b75c8c73 22 my $tail;
23 next OUTER unless defined $pat and $pat ne '';
08aa1457 24 # if arg is within quotes strip em and do no globbing
b75c8c73 25 if ($pat =~ /^"(.*)"\z/s) {
26 $pat = $1;
27 if ($cond eq 'd') { push(@retval, $pat) if -d $pat }
28 else { push(@retval, $pat) if -e $pat }
08aa1457 29 next OUTER;
30 }
4dd406c2 31 # wildcards with a drive prefix such as h:*.pm must be changed
32 # to h:./*.pm to expand correctly
b75c8c73 33 if ($pat =~ m|^([A-Za-z]:)[^/\\]|s) {
4dd406c2 34 substr($_,0,2) = $1 . "./";
35 }
b75c8c73 36 if ($pat =~ m|^(.*)([\\/])([^\\/]*)\z|s) {
08aa1457 37 ($head, $sepchr, $tail) = ($1,$2,$3);
38 #print "div: |$head|$sepchr|$tail|\n";
b75c8c73 39 push (@retval, $pat), next OUTER if $tail eq '';
08aa1457 40 if ($head =~ /[*?]/) {
41 @globdirs = doglob('d', $head);
42 push(@retval, doglob($cond, map {"$_$sepchr$tail"} @globdirs)),
43 next OUTER if @globdirs;
44 }
4dd406c2 45 $head .= $sepchr if $head eq '' or $head =~ /^[A-Za-z]:\z/s;
b75c8c73 46 $pat = $tail;
08aa1457 47 }
48 #
49 # If file component has no wildcards, we can avoid opendir
b75c8c73 50 unless ($pat =~ /[*?]/) {
08aa1457 51 $head = '' if $head eq '.';
52 $head .= $sepchr unless $head eq '' or substr($head,-1) eq $sepchr;
b75c8c73 53 $head .= $pat;
08aa1457 54 if ($cond eq 'd') { push(@retval,$head) if -d $head }
55 else { push(@retval,$head) if -e $head }
56 next OUTER;
57 }
58 opendir(D, $head) or next OUTER;
59 my @leaves = readdir D;
60 closedir D;
61 $head = '' if $head eq '.';
62 $head .= $sepchr unless $head eq '' or substr($head,-1) eq $sepchr;
63
64 # escape regex metachars but not glob chars
b75c8c73 65 $pat =~ s:([].+^\-\${}[|]):\\$1:g;
08aa1457 66 # and convert DOS-style wildcards to regex
b75c8c73 67 $pat =~ s/\*/.*/g;
68 $pat =~ s/\?/.?/g;
08aa1457 69
b75c8c73 70 #print "regex: '$pat', head: '$head'\n";
71 my $matchsub = sub { $_[0] =~ m|^$pat\z|is };
08aa1457 72 INNER:
73 for my $e (@leaves) {
74 next INNER if $e eq '.' or $e eq '..';
75 next INNER if $cond eq 'd' and ! -d "$head$e";
76 push(@matched, "$head$e"), next INNER if &$matchsub($e);
77 #
78 # [DOS compatibility special case]
79 # Failed, add a trailing dot and try again, but only
80 # if name does not have a dot in it *and* pattern
81 # has a dot *and* name is shorter than 9 chars.
82 #
83 if (index($e,'.') == -1 and length($e) < 9
b75c8c73 84 and index($pat,'\\.') != -1) {
08aa1457 85 push(@matched, "$head$e"), next INNER if &$matchsub("$e.");
86 }
87 }
88 push @retval, @matched if @matched;
89 }
90 return @retval;
91}
92
93#
fb73857a 94# this can be used to override CORE::glob in a specific
95# package by saying C<use File::DosGlob 'glob';> in that
96# namespace.
08aa1457 97#
fb73857a 98
99# context (keyed by second cxix arg provided by core)
100my %iter;
101my %entries;
102
103sub glob {
b75c8c73 104 my($pat,$cxix) = @_;
163d180b 105 my @pat;
fb73857a 106
107 # glob without args defaults to $_
108 $pat = $_ unless defined $pat;
109
163d180b 110 # extract patterns
111 if ($pat =~ /\s/) {
112 require Text::ParseWords;
113 @pat = Text::ParseWords::parse_line('\s+',0,$pat);
114 }
115 else {
116 push @pat, $pat;
117 }
118
fb73857a 119 # assume global context if not provided one
120 $cxix = '_G_' unless defined $cxix;
121 $iter{$cxix} = 0 unless exists $iter{$cxix};
122
123 # if we're just beginning, do it all first
124 if ($iter{$cxix} == 0) {
163d180b 125 $entries{$cxix} = [doglob(1,@pat)];
fb73857a 126 }
127
128 # chuck it all out, quick or slow
129 if (wantarray) {
130 delete $iter{$cxix};
131 return @{delete $entries{$cxix}};
132 }
133 else {
134 if ($iter{$cxix} = scalar @{$entries{$cxix}}) {
135 return shift @{$entries{$cxix}};
136 }
137 else {
138 # return undef for EOL
139 delete $iter{$cxix};
140 delete $entries{$cxix};
141 return undef;
142 }
143 }
144}
08aa1457 145
b75c8c73 146{
147 no strict 'refs';
148
149 sub import {
08aa1457 150 my $pkg = shift;
95d94a4f 151 return unless @_;
08aa1457 152 my $sym = shift;
4dd406c2 153 my $callpkg = ($sym =~ s/^GLOBAL_//s ? 'CORE::GLOBAL' : caller(0));
95d94a4f 154 *{$callpkg.'::'.$sym} = \&{$pkg.'::'.$sym} if $sym eq 'glob';
b75c8c73 155 }
08aa1457 156}
08aa1457 1571;
158
159__END__
160
161=head1 NAME
162
163File::DosGlob - DOS like globbing and then some
164
08aa1457 165=head1 SYNOPSIS
166
167 require 5.004;
3cb6de81 168
fb73857a 169 # override CORE::glob in current package
170 use File::DosGlob 'glob';
3cb6de81 171
95d94a4f 172 # override CORE::glob in ALL packages (use with extreme caution!)
173 use File::DosGlob 'GLOBAL_glob';
174
08aa1457 175 @perlfiles = glob "..\\pe?l/*.p?";
176 print <..\\pe?l/*.p?>;
3cb6de81 177
fb73857a 178 # from the command line (overrides only in main::)
08aa1457 179 > perl -MFile::DosGlob=glob -e "print <../pe*/*p?>"
08aa1457 180
181=head1 DESCRIPTION
182
183A module that implements DOS-like globbing with a few enhancements.
dfb634a9 184It is largely compatible with perlglob.exe (the M$ setargv.obj
08aa1457 185version) in all but one respect--it understands wildcards in
186directory components.
187
188For example, C<<..\\l*b\\file/*glob.p?>> will work as expected (in
189that it will find something like '..\lib\File/DosGlob.pm' alright).
190Note that all path components are case-insensitive, and that
191backslashes and forward slashes are both accepted, and preserved.
192You may have to double the backslashes if you are putting them in
193literally, due to double-quotish parsing of the pattern by perl.
194
163d180b 195Spaces in the argument delimit distinct patterns, so
196C<glob('*.exe *.dll')> globs all filenames that end in C<.exe>
197or C<.dll>. If you want to put in literal spaces in the glob
198pattern, you can escape them with either double quotes, or backslashes.
199e.g. C<glob('c:/"Program Files"/*/*.dll')>, or
200C<glob('c:/Program\ Files/*/*.dll')>. The argument is tokenized using
201C<Text::ParseWords::parse_line()>, so see L<Text::ParseWords> for details
202of the quoting rules used.
203
08aa1457 204Extending it to csh patterns is left as an exercise to the reader.
205
206=head1 EXPORTS (by request only)
207
208glob()
209
210=head1 BUGS
211
212Should probably be built into the core, and needs to stop
213pandering to DOS habits. Needs a dose of optimizium too.
214
215=head1 AUTHOR
216
6e238990 217Gurusamy Sarathy <gsar@activestate.com>
08aa1457 218
219=head1 HISTORY
220
221=over 4
222
223=item *
224
95d94a4f 225Support for globally overriding glob() (GSAR 3-JUN-98)
226
227=item *
228
fb73857a 229Scalar context, independent iterator context fixes (GSAR 15-SEP-97)
230
231=item *
232
08aa1457 233A few dir-vs-file optimizations result in glob importation being
23410 times faster than using perlglob.exe, and using perlglob.bat is
235only twice as slow as perlglob.exe (GSAR 28-MAY-97)
236
237=item *
238
239Several cleanups prompted by lack of compatible perlglob.exe
240under Borland (GSAR 27-MAY-97)
241
242=item *
243
244Initial version (GSAR 20-FEB-97)
245
246=back
247
248=head1 SEE ALSO
249
250perl
251
dfb634a9 252perlglob.bat
253
163d180b 254Text::ParseWords
255
08aa1457 256=cut
257