File Coverage

blib/lib/File/System/Table.pm
Criterion Covered Total %
statement 118 126 93.6
branch 30 46 65.2
condition 2 3 66.6
subroutine 21 22 95.4
pod 14 15 93.3
total 185 212 87.2


line stmt bran cond sub pod time code
1             package File::System::Table;
2              
3 3     3   19 use strict;
  3         6  
  3         119  
4 3     3   19 use warnings;
  3         4  
  3         99  
5              
6 3     3   22 use base 'File::System::Object';
  3         5  
  3         4130  
7              
8 3     3   21 use Carp;
  3         7  
  3         227  
9 3     3   17 use File::System;
  3         6  
  3         5197  
10              
11             our $VERSION = '1.15';
12              
13             =head1 NAME
14              
15             File::System::Table - A file system implementation for mounting other modules
16              
17             =head1 SYNOPSIS
18              
19             use File::System;
20              
21             my $root = File::System->new('Table',
22             '/' => [ 'Real', root => '/home/foo' ],
23             '/tmp' => [ 'Real', root => '/tmp' ],
24             '/bin' => [ 'Real', root => '/bin' ],
25             );
26              
27             my $file = $root->create('/tmp/dude', 'f');
28             my $fh = $file->open('w');
29             print $fh "Party on! Excellent!\n";
30             close $fh;
31              
32             =head1 DESCRIPTION
33              
34             This file system module allows for the creation of a tabular virtual file system. Each L is created with a root file system (at least) and then can have zero or more mounts to allow for more complicated file system handling. All mount points can be changed after the initial file system creation (except for the root, which is static).
35              
36             =head2 MOUNT POINTS
37              
38             There are a few rules regarding mount points that this system requires. This should be familiar to anyone familiar with Unix file system mounting:
39              
40             =over
41              
42             =item 1.
43              
44             The root mount point (F) is special and static. It cannot be unmounted except by deleting the file system object altogether.
45              
46             =item 2.
47              
48             A specific mount point cannot be mounted more than once. I.e., the following code would fail:
49              
50             $root = File::System->new('Table', '/' => [ 'Real' ]);
51             $root->mount('/tmp' => [ 'Real', root => '/tmp' ]);
52             $root->mount('/tmp' => [ 'Real', root => '/var/tmp' ]);
53             # ^^^ ERROR! Mount point already in use!
54              
55             =item 3.
56              
57             A file system may only be mounted onto existing containers. When mounting a path, the path must exist as per the already present mount table and that path must represent a container. Otherwise, an error will occur. I.e., the following code would fail:
58              
59             $root = File::System->new('Table', '/' => [ 'Real' ]);
60             $obj = $root->lookup('/foo');
61             $obj->remove('force') if defined $obj;
62             $root->mount('/foo' => [ 'Real', root => '/tmp' ]);
63             # ^^^ ERROR! Mount point does not exist!
64              
65             $root->mkfile('/foo');
66             $root->mount('/foo' => [ 'Real', root => '/tmp' ]);
67             # ^^^ ERROR! Mount point is not a container!
68              
69             =item 4.
70              
71             Any content or containers within a container that is mounted to within the parent is immediately invisible. These objects are hidden by the child mount until the file system is unmounted.
72              
73             =item 5.
74              
75             A mount point cannot be set above an existing mount point so that it would hide an existing mount. I.e., the following code would fail:
76              
77             $root = File::System->new('Table', '/' => [ 'Real' ]);
78             $obj = $root->mkdir('/foo/bar');
79             $obj->mount('/foo/bar' => [ 'Real', root => '/tmp' ]);
80             $obj->mount('/foo' => [ 'Real', root => '/var/tmp' ]);
81             # ^^^ ERROR! Mount point hides an already mounted file system!
82              
83             =item 6.
84              
85             As a corollary to the fifth principle, a mount point cannot be removed above another mount point below. If you mount one file system within another, the inner file system must be unmounted prior to unmounting the outer.
86              
87             =back
88              
89             Because of these rules it is obvious that the order in which mounting takes place is significant and will affect the outcome. As such, the root mount must always be specified first in the constructor.
90              
91             =head2 MOUNT TABLE API
92              
93             This file system module provides a constructor (duh) and a few extra methods. All other methods are given in the documentation of L.
94              
95             =over
96              
97             =item $root = File::System-Enew('Table', '/' =E $fs, ...)
98              
99             The constructor establishes the initial mount table for the file system. The mount table must always contain at least one entry for the root directory (F). The root directory entry must always be the first entry given as well.
100              
101             Each entry is made of two elements, the path to mount to and then a reference to either a reference to the file system object responsible for files under that mount point, or an array reference that can be passed to L to create a file system object.
102              
103             =cut
104              
105             sub new {
106 3     3 1 8 my $class = shift;
107              
108 3 50       11 $_[0] eq '/'
109             or croak "The first mount point given must always be the root (/), but found '$_[0]' instead.";
110              
111 3         14 my $self = bless { cwd => '/' }, $class;
112              
113 3         16 while (my ($mp, $fs) = splice @_, 0, 2) {
114 4         15 $self->mount($mp, $fs);
115             }
116              
117 3         16 return $self;
118             }
119              
120             =item $obj-Emount($path, $fs)
121              
122             Each entry is made of two elements, the path to mount to and then a reference to either a reference to the file system object responsible for files under that mount point, or an array reference that can be passed to L to create a file system object.
123              
124             =cut
125              
126             sub mount {
127 8     8 1 3590 my $self = shift;
128 8         45 my $path = $self->normalize_path(shift);
129 8         33 my $fs = $self->_init_fs(shift);
130              
131 8 100       36 if ($path eq '/') {
132 3 50       273 if (defined $self->{mounts}) {
133 0         0 croak "The root mount point cannot be overridden.";
134             } else {
135 3         40 $self->{cwd_fs} = $self->{mounts}{$path} = $fs;
136             }
137             } else {
138 5         26 my $dir = $self->lookup($path);
139              
140 5 50       20 defined $dir
141             or croak "The mount point '$path' does not exist.";
142              
143 5 50       412 $dir->is_container
144             or croak "The mount point '$path' is not a container.";
145              
146 5         14 my @inner = grep /^$path/, keys %{ $self->{mounts} };
  5         107  
147 5 50       22 croak "The mount point '$inner[0]' must be unmounted before mount point '$path' may be used."
148             if @inner;
149              
150 5 100       247 $dir->has_children
151             and carp "Mounting on mount point '$path' will hide some files.";
152              
153 5         722 $self->{mounts}{$path} = $fs;
154             }
155             }
156              
157             =item $unmounted_fs = $fs-Eunmount($path)
158              
159             Unmounts the file system mounted to the given path. This method will raise an exception if the user attempts to unmount a path that has no file system mounted.
160              
161             This method returns the file system that was mounted at the given path.
162              
163             =cut
164              
165             sub unmount {
166 4     4 1 7387 my $self = shift;
167 4         27 my $path = $self->normalize_path(shift);
168              
169 4 50       19 $path eq '/'
170             and croak "The root mount point cannot be unmounted.";
171              
172 4 50       25 defined $self->{mounts}{$path}
173             or croak "No file system is mounted at '$path'. Therefore it cannot be unmounted.";
174              
175 4         9 my @inner = grep /^$path./, keys %{ $self->{mounts} };
  4         101  
176 4 50       20 croak "Mount point '$inner[0]' must be unmounted before '$path'"
177             if @inner;
178              
179 4         44 delete $self->{mounts}{$path};
180             }
181              
182             =item @paths = $fs-Emounts
183              
184             Returns the list of all paths that have been mounted to.
185              
186             =cut
187              
188             sub mounts {
189 225     225 1 8244 my $self = shift;
190 225         500 return keys %{ $self->{mounts} };
  225         1227  
191             }
192              
193             =back
194              
195             =cut
196              
197             sub _init_fs {
198 8     8   14 my $self = shift;
199 8         10 my $fs = shift;
200              
201 8 50       65 if (UNIVERSAL::isa($fs, 'File::System::Object')) {
    50          
202 0         0 return $fs;
203             } elsif (ref $fs eq 'ARRAY') {
204 8         71 return File::System->new(@$fs);
205             } else {
206 0         0 croak "File system must be an array reference or an actual File::System::Object. '$fs' is neither of these. See the documentation of File::System::Table for details.";
207             }
208             }
209              
210             sub _resolve_fs {
211 27334     27334   43094 my $self = shift;
212 27334         73286 my $path = $self->normalize_path(shift);
213              
214             # The mount point we want should be the longest one which matches our
215             # given path name.
216 5044         31168 my ($mp) =
217 69253         3474854 sort { -(length($a) <=> length($b)) }
218 27334         87432 grep { $path =~ /^$_/ }
219 27334         49454 keys %{ $self->{mounts} };
220            
221 27334         70128 my $rel_path = substr $path, length($mp);
222 27334 100       86900 $rel_path = '/'.$rel_path unless $rel_path =~ /^\//;
223              
224 27334         120586 return ($self->{mounts}{$mp}, $rel_path);
225             }
226              
227             sub root {
228 1686     1686 1 2617 my $self = shift;
229              
230 1686         14219 return bless {
231             cwd => '/',
232             cwd_fs => $self->{mounts}{'/'},
233             mounts => $self->{mounts},
234             }, ref $self;
235             }
236              
237             sub exists {
238 492     492 1 163676 my $self = shift;
239 492   66     2126 my ($fs, $path) = $self->_resolve_fs(shift || $self->path);
240 492         2006 return $fs->exists($path);
241             }
242              
243             sub lookup {
244 26387     26387 1 439111 my $self = shift;
245 26387         116420 my $cwd = $self->normalize_path($_[0]);
246 26387         75858 my ($fs, $path) = $self->_resolve_fs(shift);
247              
248 26387         104621 my $cwd_fs = $fs->lookup($path);
249              
250 26387 100       63169 return undef unless defined $cwd_fs;
251              
252 26376         310059 return bless {
253             cwd => $cwd,
254             cwd_fs => $cwd_fs,
255             mounts => $self->{mounts},
256             }, ref $self;
257             }
258              
259             my @delegates = qw/
260             is_valid
261             properties
262             settable_properties
263             set_property
264             remove
265             has_content
266             is_container
267             is_readable
268             is_seekable
269             is_writable
270             is_appendable
271             open
272             content
273             has_children
274             children_paths
275             /;
276              
277             for my $name (@delegates) {
278             eval qq(
279             #line 287 "File::Sytem::Table ($name)"
280             sub $name {
281             my \$self = shift;
282             return \$self->{cwd_fs}->$name(\@_);
283             }
284             );
285             die $@ if $@;
286             }
287              
288             sub get_property {
289 108874     108874 1 161108 my $self = shift;
290 108874         177104 local $_ = shift;
291              
292             SWITCH: {
293 108874 100       165983 /^path$/ && do {
  108874         370138  
294 100761         588988 return $self->{cwd};
295             };
296 8113 100       25760 /^dirname$/ && do {
297 5495         20853 return $self->dirname_of_path($self->{cwd});
298             };
299 2618 100       7651 /^basename$/ && do {
300 1826         20289 return $self->basename_of_path($self->{cwd});
301             };
302 792         2845 DEFAULT: {
303 792         921 return $self->{cwd_fs}->get_property($_);
304             }
305             }
306             }
307              
308             sub rename {
309 382     382 1 548 my $self = shift;
310 382         756 my $name = shift;
311              
312 382 50       459 grep { $self->{cwd} eq $_ } keys %{ $self->{mounts} }
  940         2688  
  382         1521  
313             and croak "Cannot rename the mount point '$self'";
314              
315 382         1793 $self->{cwd_fs}->rename($name);
316            
317 382         2269 $self->{cwd} =~ s#[^/]+$ #$name#x;
318              
319 382         1642 return $self;
320             }
321              
322             sub move {
323 382     382 1 597 my $self = shift;
324 382         534 my $path = shift;
325 382         541 my $force = shift;
326              
327 382 50       1694 UNIVERSAL::isa($path, 'File::System::Table')
328             or croak "Move failed; the '$path' object is not a 'File::System::Table'";
329              
330 382         1857 $self->{cwd_fs}->move($path->{cwd_fs}, $force);
331 382         1202 $self->{cwd} = $self->normalize_path($path->path.'/'.$self->basename);
332              
333 382         1659 return $self;
334             }
335              
336             sub copy {
337 191     191 1 294 my $self = shift;
338 191         322 my $path = shift;
339 191         411 my $force = shift;
340            
341 191 50       822 UNIVERSAL::isa($path, 'File::System::Table')
342             or croak "Copy failed; the '$path' object is not a 'File::System::Table'";
343              
344 191         1095 my $copy = $self->{cwd_fs}->copy($path->{cwd_fs}, $force);
345 191         872 my $copy_cwd = $self->normalize_path($path->path.'/'.$self->basename);
346              
347 191         1761 return bless {
348             cwd_fs => $copy,
349             cwd => $copy_cwd,
350             mounts => $self->{mounts},
351             }, ref $self;
352             }
353            
354             sub children {
355 2137     2137 1 3774 my $self = shift;
356             return
357 2137         9560 map { $self->lookup($_) }
  3098         16161  
358             grep !/^\.\.?$/, $self->{cwd_fs}->children_paths;
359             }
360              
361             sub child {
362 242     242 1 16215 my $self = shift;
363 242         390 my $name = shift;
364              
365 242 50       764 $self->is_container
366             or croak "The child method called on non-container.";
367              
368 242 50       742 $name !~ /\//
369             or croak "Argument to child must not be a path.";
370              
371 242         607 return $self->lookup($name);
372             }
373              
374             sub is_createable {
375 0     0 0 0 my $self = shift;
376 0         0 my $path = $self->normalize_path($_[0]);
377 0         0 my ($fs, $rel_path) = $self->_resolve_fs(shift);
378 0         0 my $type = shift;
379              
380 0         0 return $fs->is_creatable($rel_path, $type);
381             }
382              
383             sub create {
384 455     455 1 6961731 my $self = shift;
385 455         2325 my $path = $self->normalize_path($_[0]);
386 455         1746 my ($fs, $rel_path) = $self->_resolve_fs(shift);
387 455         1008 my $type = shift;
388              
389 455         2122 my $obj = $fs->create($rel_path, $type);
390              
391 455 50       2296 return undef unless defined $obj;
392              
393 455         3877 return bless {
394             cwd => $path,
395             cwd_fs => $obj,
396             mounts => $self->{mounts},
397             }, ref $self;
398             }
399              
400             =head1 BUGS
401              
402             The C and C methods will fail if used between file systems. This can be remedied, but it will require some delicate planning that hasn't yet been done.
403              
404             =head1 SEE ALSO
405              
406             L, L, L, L
407              
408             =head1 AUTHOR
409              
410             Andrew Sterling Hanenkamp, Ehanenkamp@cpan.orgE
411              
412             =head1 COPYRIGHT AND LICENSE
413              
414             Copyright 2005 Andrew Sterling Hanenkamp. All Rights Reserved.
415              
416             This library is distributed and licensed under the same terms as Perl itself.
417              
418             =cut
419              
420             1