File Coverage

lib/Badger/Filesystem.pm
Criterion Covered Total %
statement 244 253 96.4
branch 97 140 69.2
condition 49 96 51.0
subroutine 56 60 93.3
pod 49 49 100.0
total 495 598 82.7


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Filesystem
4             #
5             # DESCRIPTION
6             # OO representation of a filesystem.
7             #
8             # AUTHOR
9             # Andy Wardley
10             #
11             #========================================================================
12              
13             package Badger::Filesystem;
14              
15 70     70   9356 use File::Spec;
  70         151  
  70         1922  
16 70     70   300 use Cwd 'getcwd';
  70         107  
  70         14471  
17             use Badger::Class
18             version => 0.01,
19             debug => 0,
20             base => 'Badger::Prototype Badger::Filesystem::Base',
21             import => 'class',
22             utils => 'params is_object random_name',
23             constants => 'HASH ARRAY TRUE REFS PKG',
24             constant => {
25             virtual => 0,
26             NO_FILENAME => 1,
27             FILESPEC => 'File::Spec',
28             FINDBIN => 'FindBin',
29             ROOTDIR => File::Spec->rootdir,
30             CURDIR => File::Spec->curdir,
31             UPDIR => File::Spec->updir,
32             FS => 'Badger::Filesystem',
33             VFS => 'Badger::Filesystem::Virtual',
34             UFS => 'Badger::Filesystem::Universal',
35             PATH => 'Badger::Filesystem::Path',
36             FILE => 'Badger::Filesystem::File',
37             DIRECTORY => 'Badger::Filesystem::Directory',
38             VISITOR => 'Badger::Filesystem::Visitor',
39             },
40             exports => {
41             any => 'FS PATH FILE DIR DIRECTORY cwd getcwd rel2abs abs2rel',
42             tags => {
43             types => 'Path File Dir Directory Cwd Bin',
44             dirs => 'ROOTDIR UPDIR CURDIR',
45             },
46             hooks => {
47             VFS => sub {
48             # load VFS module and call its export() method
49 2         13 class(shift->VFS)->load->pkg->export(shift, shift)
50             },
51             UFS => sub {
52             # load UFS module and call its export() method
53 1         5 class(shift->UFS)->load->pkg->export(shift, shift)
54             },
55 70         3415 '$Bin' => \&_export_findbin_hook,
56             },
57             },
58             messages => {
59             open_failed => 'Failed to open %s %s: %s',
60             delete_failed => 'Failed to delete %s %s: %s',
61             bad_volume => 'Volume mismatch: %s vs %s',
62             bad_stat => 'Nothing known about %s',
63             copy_failed => 'Failed to %s file from %s to %s: %s',
64             no_path => 'Unable to determine location of %s',
65 70     70   3400 };
  70         316  
66              
67 70     70   30377 use Badger::Filesystem::File;
  70         159  
  70         563  
68 70     70   515 use Badger::Filesystem::Directory;
  70         117  
  70         396  
69              
70             #-----------------------------------------------------------------------
71             # special export hooks to make $Bin available from FindBin
72             #-----------------------------------------------------------------------
73              
74             sub _export_findbin_hook {
75 2     2   4 my ($class, $target) = @_;
76 2         8 class($class->FINDBIN)->load;
77 2         24 $class->export_symbol($target, Bin => \$FindBin::Bin);
78             };
79              
80              
81             #-----------------------------------------------------------------------
82             # aliases
83             #-----------------------------------------------------------------------
84              
85             *DIR = \&DIRECTORY; # constant class name
86             *Dir = \&Directory; # constructor sub
87             *dir = \&directory; # object method
88             *split_dir = \&split_directory; # ...because typing 'directory'
89             *join_dir = \&join_directory; # gets tedious quickly
90             *collapse_dir = \&collapse_directory;
91             *dir_exists = \&directory_exists;
92             *create_dir = \&create_directory;
93             *delete_dir = \&delete_directory;
94             *open_dir = \&open_directory;
95             *read_dir = \&read_directory;
96             *temp_dir = \&temp_directory;
97             *dir_child = \&directory_child;
98             *dir_children = \&directory_children;
99             *mkdir = \&create_directory;
100             *rmdir = \&delete_directory;
101             *touch = \&touch_file;
102              
103              
104             #-----------------------------------------------------------------------
105             # In this base class definitive paths are the same as absolute paths.
106             # However, in subclasses (like Badger::Filesystem::Virtual) we want
107             # to differentiate between absolute paths in a virtual filesystem
108             # (e.g. /about/badger.html) and the definitive paths that they map to
109             # in a real file system (e.g. /home/abw/web/badger/about/badger.html).
110             # We make the further distinction between definitive paths used for
111             # reading or writing, and call the appropriate method to perform any
112             # virtual -> real mapping before operating on any file or directory.
113             # But like I said, these are just hooks for subclasses to use if they
114             # need them. In the base class, we patch them straight into the plain
115             # old absolute() method.
116             #-----------------------------------------------------------------------
117              
118             *definitive = \&absolute;
119             *definitive_read = \&absolute;
120             *definitive_write = \&absolute;
121              
122              
123             #-----------------------------------------------------------------------
124             # factory subroutines
125             #-----------------------------------------------------------------------
126              
127             sub Path {
128 58 100   58 1 228 return PATH unless @_;
129 28 50 66     90 return @_ == 1 && is_object(PATH, $_[0])
130             ? $_[0] # return existing Path object
131             : FS->path(@_); # or construct a new one
132             }
133              
134             sub File {
135 291 100   291 1 972 return FILE unless @_;
136 10 50 66     47 return @_ == 1 && is_object(FILE, $_[0])
137             ? $_[0] # ditto for File object
138             : FS->file(@_);
139             }
140              
141             sub Directory {
142 246 100   246 1 1066 return DIRECTORY unless @_;
143 12 50 66     83 return @_ == 1 && is_object(DIRECTORY, $_[0])
144             ? $_[0] # ditto for Directory object
145             : FS->directory(@_);
146             }
147              
148             sub Cwd {
149 3     3 1 11 FS->directory
150             }
151              
152             sub Bin {
153 17     17 1 111 class(FINDBIN)->load;
154 17         108 FS->directory($FindBin::Bin);
155             }
156              
157              
158             #-----------------------------------------------------------------------
159             # generated methods
160             #-----------------------------------------------------------------------
161              
162             class->methods(
163             # define methods for path/root/updir/curdir that access a prototype
164             # object when called as class methods.
165             map {
166             my $name = $_; # fresh copy of lexical for binding in closure
167             $name => sub {
168 4     4   14 $_[0]->prototype->{ $name };
        4      
        4      
169             }
170             }
171             qw( rootdir updir curdir separator )
172             );
173              
174              
175             #-----------------------------------------------------------------------
176             # constructor methods
177             #-----------------------------------------------------------------------
178              
179             sub init {
180 26     26 1 73 my ($self, $config) = @_;
181              
182             # NEW CODE: trying to abstract out the file specification so that I
183             # can slot in a Universal file spec decoy which always generates URIs
184             my $spec = $self->{ spec }
185             = $config->{ spec }
186             || $config->{ filespec }
187 26   33     395 || $self->FILESPEC;
188            
189 26         48 $self->debug("spec is $spec") if DEBUG;
190            
191             # The tokens used to represent the root directory ('/'), the
192             # parent directory ('..') and current directory ('.') default to
193             # constants grokked from File::Spec. To determine the path separator
194             # we have to resort to an ugly hack. The File::Spec module hard-codes
195             # the path separator in the catdir() method so we have to make a round-
196             # trip through catdir() to grok the separator in a cross-platform manner
197 26   33     235 $self->{ rootdir } = $config->{ rootdir } || $spec->rootdir;
198 26   33     186 $self->{ updir } = $config->{ updir } || $spec->updir;
199 26   33     250 $self->{ curdir } = $config->{ curdir } || $spec->curdir;
200 26   33     99 $self->{ separator } = $config->{ separator } || do {
201             my $sep = FILESPEC->catdir(('badger') x 2);
202             $sep =~ s/badger//g;
203             $sep;
204             };
205              
206             # flag to indicate if directory scans should return all entries
207 26   50     117 $self->{ all_entries } = $config->{ all_entries } || 0;
208              
209             # current working can be specified explicitly, otherwise we leave it
210             # undefined and let cwd() call getcwd() determine it dynamically
211 26         54 $self->{ cwd } = $config->{ cwd };
212            
213             # additional options, e.g. codec, encoding
214 26         158 $self->init_options($config);
215            
216 26         51 return $self;
217             }
218              
219             sub spec {
220             return ref $_[0] eq HASH
221             ? $_[0]->{ spec }
222 3960 50   3960 1 11395 : FILESPEC;
223             }
224              
225             sub path {
226 29     29 1 55 Path->new( shift->_child_args( path => @_ ) );
227             }
228              
229             sub file {
230 280     280 1 530 File->new( shift->_child_args( file => @_ ) );
231             }
232              
233             sub directory {
234 232     232 1 465 my $self = shift;
235 232         452 my $args = $self->_child_args( directory => @_ );
236            
237             # default directory is the current working directory
238             $args->{ path } = $self->cwd
239 232 100 66     729 if exists $args->{ path } && ! defined $args->{ path };
240            
241 232         375 Directory->new($args);
242             }
243              
244             sub root {
245 1     1 1 4 my $self = shift->prototype;
246 1         4 $self->directory($self->{ rootdir });
247             }
248              
249             sub cwd {
250 548     548 1 555 my $cwd;
251 548 100       754 if (@_) {
252             # called as an object or class method
253 541         1001 my $self = shift->prototype;
254             # if we have a hard-coded cwd set then return that, otherwise call
255             # getcwd to return the real current working directory. NOTE: we don't
256             # cache the dynamically resolved cwd as it'll change if chdir() is called
257 541   66     4646 $cwd = $self->{ cwd } || getcwd;
258             }
259             else {
260             # called as a subroutine
261 7         56 $cwd = getcwd;
262             }
263             # pass through File::Spec to sanitise path to local filesystem
264             # convention - otherwise we get /forward/slashes on Win32
265 548         7286 FILESPEC->canonpath($cwd);
266             }
267              
268              
269             #-----------------------------------------------------------------------
270             # path manipulation methods
271             #-----------------------------------------------------------------------
272              
273             sub merge_paths {
274 99     99 1 186 my ($self, $base, $path) = @_;
275 99         134 my $spec = $self->spec;
276 99         794 my @p1 = $spec->splitpath($base);
277 99         492 my @p2 = $spec->splitpath($path);
278              
279             # check volumes match
280 99 50 33     344 if (defined $p2[0] and length $p2[0]) {
281 0   0     0 $p1[0] ||= $p2[0];
282 0 0       0 return $self->error_msg( bad_volume => $p1[0], $p1[0] )
283             unless $p1[0] eq $p2[0];
284             }
285 99         104 shift(@p2);
286 99   50     215 my $vol = shift(@p1) || '';
287 99         121 my $file = pop @p2;
288            
289 99         915 $spec->catpath($vol, $spec->catdir(@p1, @p2), $file);
290             }
291            
292             sub join_path {
293 6     6 1 6 my $self = shift;
294 6 50       13 my @args = map { defined($_) ? $_ : '' } @_[0..2];
  18         28  
295 6         8 my $spec = $self->spec;
296 6         47 $spec->canonpath( $spec->catpath(@args) );
297             }
298              
299             sub join_directory {
300 2454     2454 1 2479 my $self = shift;
301 2454 100       3503 my $dir = @_ == 1 ? shift : [ @_ ];
302 2454         3117 my $spec = $self->spec;
303 2454 0       3370 $self->debug("join_dir(", ref $dir eq ARRAY ? '[' . join(', ', @$dir) . ']' : $dir, ")\n") if $DEBUG;
    50          
304 2454 100       9754 ref $dir eq ARRAY
305             ? $spec->catdir(@$dir)
306             : $spec->canonpath($dir);
307             }
308              
309             sub split_path {
310 527     527 1 580 my $self = shift;
311 527         738 my $path = $self->join_directory(@_);
312 527 50       819 my @split = map { defined($_) ? $_ : '' } $self->spec->splitpath($path);
  1581         2853  
313 527 50       940 $self->debug("split_path($path) => ", join(', ', @split), "\n") if $DEBUG;
314 527 50       2208 return wantarray ? @split : \@split;
315             }
316              
317             sub split_directory {
318 95     95 1 112 my $self = shift;
319 95         155 my $path = $self->join_directory(@_);
320 95         170 my @split = $self->spec->splitdir($path);
321 95 100       376 return wantarray ? @split : \@split;
322             }
323              
324             sub collapse_directory {
325 75     75 1 205 my $self = shift->prototype;
326 75         194 my @dirs = $self->split_directory(shift);
327 75         201 my ($up, $cur) = @$self{qw( updir curdir )};
328 75         97 my ($node, @path);
329 75         179 while (@dirs) {
330 446         471 $node = shift @dirs;
331 446 50       684 if ($node eq $cur) {
    100          
332             # do nothing
333             }
334             elsif ($node eq $up) {
335 20 50       46 pop @path if @path;
336             }
337             else {
338 426         615 push(@path, $node);
339             }
340             }
341 75         173 $self->join_directory(@path);
342             }
343              
344             sub slash_directory {
345 4     4 1 8 my $self = shift->prototype;
346 4         10 my $path = $self->absolute(shift);
347 4   66     16 my $slash = $self->{ slashed } ||= do {
348 2         7 my $sep = quotemeta $self->{ separator };
349 2         58 qr/$sep$/;
350             };
351 4 50       23 $path .= $self->{ separator } unless $path =~ $slash;
352 4         17 return $path;
353             }
354              
355              
356             #-----------------------------------------------------------------------
357             # absolute and relative path tests and transmogrifiers
358             #-----------------------------------------------------------------------
359              
360             sub is_absolute {
361 104     104 1 125 my $self = shift;
362             # $self->debug("args: ", $self->dump_data(\@_));
363 104 100       171 $self->spec->file_name_is_absolute(
364             $self->join_directory(@_)
365             ) ? 1 : 0;
366             }
367              
368             sub is_relative {
369 1 50   1 1 8 shift->is_absolute(@_) ? 0 : 1;
370             }
371              
372             sub absolute {
373 608     608 1 695 my $self = shift;
374 608         814 my $path = $self->join_directory(shift);
375 608         900 my $spec = $self->spec;
376 608 100       2379 return $path if $spec->file_name_is_absolute($path);
377 528   33     1323 $spec->catdir(shift || $self->cwd, $path);
378             }
379              
380             sub relative {
381 1     1 1 2 my $self = shift;
382 1   33     3 $self->spec->abs2rel($self->join_directory(shift), shift || $self->cwd);
383             }
384              
385              
386             #-----------------------------------------------------------------------
387             # file/directory test methods
388             #-----------------------------------------------------------------------
389              
390             sub path_exists {
391 0     0 1 0 shift->stat_path(@_);
392             }
393              
394             sub file_exists {
395 71     71 1 83 my $self = shift;
396 71   100     115 my $stats = $self->stat_path(shift) || return;
397 69 100       360 return -f _ ? $stats : 0; # relies on cached stat
398             }
399              
400             sub directory_exists {
401 38     38 1 54 my $self = shift;
402 38   100     106 my $stats = $self->stat_path(shift) || return;
403 35 100       166 return -d _ ? $stats : 0; # relies on cached stat
404             }
405              
406             sub stat_path {
407 147     147 1 178 my $self = shift;
408 147   100     287 my $path = $self->definitive_read(shift) || return;
409 142         3178 my @stats = (stat($path), -r _, -w _, -x _, -o _, $path);
410              
411             return $self->error_msg( bad_stat => $self->{ path } )
412 142 50       403 unless @stats;
413              
414             return wantarray
415             ? @stats
416 142 50       534 : \@stats;
417             }
418              
419             sub chmod_path {
420 1     1 1 11 my $self = shift;
421 1         4 my $path = $self->definitive_write(shift);
422 1         29 chmod(shift, $path);
423             }
424              
425              
426             #-----------------------------------------------------------------------
427             # file manipulation methods
428             #-----------------------------------------------------------------------
429              
430             sub create_file {
431 2     2 1 5 my ($self, $path) = @_;
432 2 50       5 unless (-e $self->definitive_write($path)) {
433 2         66 $self->write_file($path); # calls definitive_write again
434             }
435 2         13 return 1;
436             }
437              
438             sub touch_file {
439 2     2 1 5 my ($self, $path) = @_;
440 2         6 my $definitive = $self->definitive_write($path);
441 2 100       41 if (-e $definitive) {
442 1         3 my $now = time();
443 1         22 utime $now, $now, $definitive;
444             }
445             else {
446 1         5 $self->write_file($path); # calls definitive_write again
447             }
448             }
449              
450             sub delete_file {
451 11     11 1 13 my $self = shift;
452 11         25 my $path = $self->definitive_write(shift);
453 11 50       915 unlink($path)
454             || return $self->error_msg( delete_failed => file => $path => $! );
455             }
456              
457             sub open_file {
458 47     47 1 79 my $self = shift;
459 47         66 my $name = shift;
460 47   50     114 my $mode = $_[0] || 'r'; # leave it in @_ for IO::File
461 47 50 33     179 my $opts = @_ && ref $_[-1] eq HASH ? pop(@_) : { };
462 47 100       172 my $path = $mode eq 'r'
463             ? $self->definitive_read($name)
464             : $self->definitive_write($name);
465 47 50 33     256 return $self->error_msg( no_path => $name )
466             unless defined $path && length $path;
467              
468 47         4779 require IO::File;
469 47 50       62431 $self->debug("about to open file $path (", join(', ', @_), ")\n") if $DEBUG;
470              
471 47   33     241 my $fh = IO::File->new($path, @_)
472             || $self->error_msg( open_failed => file => $path => $! );
473              
474             $fh->binmode( $opts->{ encoding } )
475 47 100       27013 if $opts->{ encoding };
476              
477 47         134 return $fh;
478             }
479              
480             sub read_file {
481 27     27 1 50 my $self = shift;
482 27 50 33     156 my $opts = @_ && ref $_[-1] eq HASH ? pop(@_) : { };
483 27         103 my $fh = $self->open_file(shift, 'r', $opts);
484             return wantarray
485             ? <$fh>
486 27 100       168 : do { local $/ = undef; <$fh> };
  25         116  
  25         1224  
487             }
488              
489             sub write_file {
490 15     15 1 25 my $self = shift;
491 15 100 66     88 my $opts = @_ && ref $_[-1] eq HASH ? pop(@_) : { };
492 15         56 my $fh = $self->open_file(shift, 'w', $opts);
493 15 100       161 return $fh unless @_; # return handle if no args
494 11         142 print $fh @_; # or print args and close
495 11         96 $fh->close;
496 11         1103 return 1;
497             }
498              
499             sub append_file {
500 2     2 1 4 my $self = shift;
501 2 50 33     13 my $opts = @_ && ref $_[-1] eq HASH ? pop(@_) : { };
502 2         8 my $fh = $self->open_file(shift, 'a', $opts);
503 2 100       17 return $fh unless @_; # return handle if no args
504 1         3 print $fh @_; # or print args and close
505 1         4 $fh->close;
506 1         33 return 1;
507             }
508              
509              
510             sub copy_file {
511 3     3 1 7 shift->_file_copy( copy => @_ );
512             }
513              
514             sub move_file {
515 1     1 1 3 shift->_file_copy( move => @_ );
516             }
517              
518             sub _file_copy {
519 4     4   423 require File::Copy;
520              
521 4         2043 my ($self, $action, $from, $to, $params)
522             = (shift, shift, shift, shift, params(@_));
523            
524 4 100       10 my $src
    50          
525             = is_object(PATH, $from) ? $from->definitive # path object
526             : ref($from) ? $from # file handle
527             : $self->definitive_read($from); # file path
528              
529 4 50       12 my $dest
    100          
530             = is_object(PATH, $to) ? $to->definitive # as above
531             : ref($to) ? $to
532             : $self->definitive_write($to);
533            
534 4 50       14 my $code
    100          
535             = $action eq 'copy' ? \&File::Copy::copy
536             : $action eq 'move' ? \&File::Copy::move
537             : return $self->error( invalid => action => $action );
538              
539 4         5 my $file;
540              
541 4 50       9 unless (ref $dest) {
542             # NOTE: don't use $self->file($dest) because $self could be a
543             # VFS and $dest is already a definitive path
544 4         5 $file = File($dest);
545             # capture our current working directory
546 4         9 my $cwd = cwd;
547             eval {
548             # Change to the destination volume if one exists.
549             # Should work for any volume except Windows shares
550             # where resulting behavior is version dependent.
551 4 50       12 chdir $file->volume if ($file->volume);
552             # this code strips volume information
553             $file->directory->must_exist(
554             $params->{ mkdir },
555             $params->{ dir_mode },
556 4         9 );
557             # change back to the current working directory
558 4         42 chdir $cwd;
559 4 50       7 } or do {
560             # capture any exception from above
561             # change back to the oringial cwd
562             # and rethrow the execption.
563 0 0       0 if ($@) {
564 0         0 chdir $cwd;
565 0         0 die $@;
566             }
567             }
568             }
569              
570 4 50       12 $code->($src, $dest)
571             || return $self->error_msg( copy_failed => $action, $from, $to, $! );
572              
573 4         2246 my $mode = $params->{ file_mode };
574 4 100       11 $mode = $params->{ mode } unless defined $mode;
575              
576 4 100 66     29 $file->chmod($mode)
577             if $file && defined $mode;
578              
579 4   33     24 return $file || $dest;
580             }
581              
582              
583             #-----------------------------------------------------------------------
584             # directory manipulation methods
585             #-----------------------------------------------------------------------
586              
587             sub create_directory {
588 5     5 1 10 my $self = shift;
589 5         13 my $path = $self->definitive_write(shift);
590              
591 5         32 require File::Path;
592              
593 5 50       9 eval {
594 5         10 local $Carp::CarpLevel = 1;
595 5         972 File::Path::mkpath($path, 0, @_)
596             } || return $self->error($@);
597             }
598            
599             sub delete_directory {
600 3     3 1 5 my $self = shift;
601 3         8 my $path = $self->definitive_write(shift);
602              
603 3         14 require File::Path;
604 3         685 File::Path::rmtree($path, @_)
605             }
606              
607             sub open_directory {
608 67     67 1 62 my $self = shift;
609 67         97 my $path = $self->definitive_read(shift);
610              
611 67         1985 require IO::Dir;
612 67 50       29299 $self->debug("Opening directory: $path\n") if $DEBUG;
613              
614 67   33     176 return IO::Dir->new($path, @_)
615             || $self->error_msg( open_failed => directory => $path => $! );
616             }
617              
618             sub read_directory {
619 67     67 1 67 my $self = shift;
620 67         97 my $dirh = $self->open_directory(shift);
621 67         4054 my $all = shift;
622 67         83 my ($path, @paths);
623 67         148 while (defined ($path = $dirh->read)) {
624 420         3207 push(@paths, $path);
625             }
626             @paths = $self->spec->no_upwards(@paths)
627 67 50 33     959 unless $all || ref $self && $self->{ all_entries };
      66        
628              
629 67         185 $dirh->close;
630 67 50       1133 return wantarray ? @paths : \@paths;
631             }
632              
633             sub directory_child {
634 292     292 1 344 my $self = shift;
635 292         432 my $path = $self->join_directory(@_);
636 292         571 stat $self->definitive_read($path);
637 292 50       1400 -d _ ? $self->directory($path) :
    100          
638             -f _ ? $self->file($path) :
639             $self->path($path);
640             }
641            
642             sub directory_children {
643 68     68 1 91 my $self = shift;
644 68         70 my $dir = shift;
645             my @paths = map {
646 68         110 $self->directory_child($dir, $_)
  292         1333  
647             } $self->read_directory($dir, @_);
648 68 50       320 return wantarray ? @paths : \@paths;
649             }
650              
651              
652             #-----------------------------------------------------------------------
653             # temporary directory/file methods
654             #-----------------------------------------------------------------------
655              
656             sub temp_directory {
657 3     3 1 11 my $self = shift;
658 3         187 return $self->directory( FILESPEC->tmpdir, @_ )->must_exist(1);
659             }
660              
661             sub temp_file {
662 1     1 1 2 my $self = shift;
663 1 50       3 return $self->temp_directory->file( @_ ? @_ : random_name() )
664             }
665              
666              
667             #-----------------------------------------------------------------------
668             # visitor methods
669             #-----------------------------------------------------------------------
670              
671             sub visitor {
672 14     14 1 15 my $self = shift;
673 14         25 my $vtype = $self->VISITOR;
674 14         22 class($vtype)->load;
675            
676 14 100 100     54 return @_ && is_object($vtype => $_[0])
677             ? shift
678             : $vtype->new(@_);
679             }
680              
681             sub visit {
682 0     0 1 0 shift->root->visit(@_);
683             }
684              
685             sub collect {
686 0     0 1 0 shift->visit(@_)->collect;
687             }
688              
689             sub accept {
690 0     0 1 0 shift->root->accept(@_);
691             }
692              
693             #-----------------------------------------------------------------------
694             # internal methods
695             #-----------------------------------------------------------------------
696              
697             sub _child_args {
698 541     541   1271 my $self = shift->prototype;
699 541         650 my $type = shift;
700 541         545 my $args = { %{ $self->{ options } } };
  541         1003  
701              
702 541 100 100     1689 if (@_ && ref $_[-1] eq HASH) {
703 76         109 my $more = pop @_;
704 76         172 @$args{ keys %$more } = values %$more;
705             }
706              
707 541 100       1097 if (@_ > 1) {
    100          
708 11         39 $args->{ path } = [@_];
709             }
710             elsif (@_ == 1) {
711 525         844 $args->{ path } = shift;
712             }
713             else {
714 5         11 $args->{ path } = undef;
715             }
716              
717 541         670 $args->{ filesystem } = $self;
718 541         1327 return $args;
719             }
720              
721              
722              
723             1;
724              
725             __END__