File Coverage

lib/Badger/Filesystem/Virtual.pm
Criterion Covered Total %
statement 72 78 92.3
branch 27 38 71.0
condition 12 22 54.5
subroutine 7 8 87.5
pod 6 6 100.0
total 124 152 81.5


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Filesystem::Virtual
4             #
5             # DESCRIPTION
6             # Subclass of Badger::Filesystem which implements a virtual filesystem
7             # composed from several source directories, conceptually layered on
8             # top of each other.
9             #
10             # AUTHOR
11             # Andy Wardley
12             #
13             #========================================================================
14              
15             package Badger::Filesystem::Virtual;
16              
17 6     6   999 use Badger::Debug ':dump';
  6         13  
  6         36  
18             use Badger::Class
19 6         64 version => 0.01,
20             debug => 0,
21             base => 'Badger::Filesystem',
22             # accessors => 'root', # interferes
23             utils => 'blessed',
24             constants => 'ARRAY CODE',
25             constant => {
26             VFS => __PACKAGE__,
27             virtual => __PACKAGE__,
28             PATH_METHOD => 'path',
29             PATHS_METHOD => 'paths',
30             ROOTS_METHOD => 'roots',
31             },
32             exports => {
33             any => 'VFS',
34             },
35             messages => {
36             bad_root => 'Invalid root directory: %s',
37             max_roots => 'The number of virtual filesystem roots exceeds the max_roots limit of %s',
38 6     6   38 };
  6         7  
39              
40             *definitive = \&definitive_write;
41              
42             our $MAX_ROOTS = 32 unless defined $MAX_ROOTS;
43              
44             sub init {
45 6     6 1 19 my ($self, $config) = @_;
46              
47             # let the base class have a go first, so it can set rootdir et al
48 6         32 $self->SUPER::init($config);
49              
50             # root can be a single item or list ref
51 6   66     26 my $root = $config->{ root } || $self->{ rootdir };
52 6 100       24 $root = [$root] unless ref $root eq ARRAY;
53 6         13 $self->{ root } = $root;
54              
55             # the dynamic flag indicates that the list of roots can change so we must
56             # recompute them each time we use them. max_roots sets a limit on the
57             # expansion to prevent runaways
58 6         14 $self->{ dynamic } = $config->{ dynamic };
59             $self->{ max_roots } =
60             defined $config->{ max_roots }
61             ? $config->{ max_roots }
62 6 50       22 : $MAX_ROOTS;
63              
64             # we must set cwd to / so that the relative -> absolute path translation
65             # works as expected. The concept of having a current working directory
66             # in a VFS is just a bit too weird to contemplate anyway.
67 6         30 $self->{ cwd } = $self->{ rootdir };
68              
69 6 50       25 $self->debug("Virtual root: ", join(', ', @$root), "\n" ) if $DEBUG;
70              
71 6         17 return $self;
72             }
73              
74             sub roots {
75 76     76 1 85 my $self = shift;
76              
77 76 100       147 if (my $roots = $self->{ roots }) {
78             return wantarray
79 68 100       185 ? @$roots
80             : $roots;
81             }
82              
83 8         18 my $max = $self->{ max_roots };
84 8         12 my @paths = @{ $self->{ root } };
  8         23  
85 8         15 my (@roots, $type, $paths, $dir, $code);
86              
87             # If a positive max_roots is defined then we'll limit the number of
88             # roots we resolve. If it's zero or negative then it will pre-decrement
89             # before being tested so will always be true
90 8   66     38 while (@paths && --$max) {
91 57   50     128 $dir = shift @paths || next;
92              
93 57   100     123 $type = ref $dir || do {
94             # non-reference paths get added as they are
95             $self->debug("discovered root directory: $dir\n") if DEBUG;
96             push(@roots, $dir);
97             next;
98             };
99              
100             # anything else can expand out to one or more paths, each of which
101             # can expand recursively, so we push all new paths back onto the
102             # candidate list and test each in turn.
103              
104 33 100       96 if ($type eq CODE) {
    100          
    50          
105             # call code ref
106 8         23 $paths = $dir->();
107 8         11 $self->debug(
108             "discovered root directories from code ref: ",
109             $self->dump_data_inline($paths), "\n"
110             ) if DEBUG;
111 8 50       24 unshift(@paths, ref $paths eq ARRAY ? @$paths : $paths);
112 8         28 next;
113             }
114             elsif ($type eq ARRAY) {
115             # expand list ref
116 3         8 $self->debug(
117             "discovered root directories from code ref: ",
118             join(', ', @$dir), "\n"
119             ) if DEBUG;
120 3         6 unshift(@paths, @$dir);
121 3         8 next;
122             }
123             elsif (blessed $dir) {
124             # see if object has a path(), paths() or roots() method
125             # TODO: this is broken - we don't want to recompute paths
126             # each time just because we're using an object that has a
127             # path
128 22 50 33     104 if ($code = $dir->can(PATH_METHOD)
129             || $dir->can(PATHS_METHOD)
130             || $dir->can(ROOTS_METHOD) ) {
131 22         47 $paths = $code->($dir);
132 22         28 $self->debug(
133             "discovered root directories from $type object: $paths / ",
134             $self->dump_data_inline($paths), "\n"
135             ) if DEBUG;
136 22 50       43 unshift(@paths, ref $paths eq ARRAY ? @$paths : $paths);
137 22         58 next;
138             }
139             }
140              
141 0         0 $self->error( bad_root => $dir );
142             }
143              
144             # anything left in @paths means we must have blown the max_roots limit
145             return $self->error_msg( max_roots => $self->{ max_roots } )
146 8 50       22 if @paths;
147              
148             # we can cache roots if all are static and the dynamic flag isn't set
149             $self->{ roots } = \@roots
150 8 100       29 unless $self->{ dynamic };
151              
152 8         10 $self->debug("resolved roots: [\n ", join("\n ", @roots), "\n]\n") if DEBUG;
153              
154             return wantarray
155             ? @roots
156 8 100       34 : \@roots;
157             }
158              
159             sub definitive_paths {
160 0     0 1 0 my $self = shift;
161 0         0 my $path = $self->absolute(@_);
162 0         0 my @paths = map { $self->merge_paths($_, $path) } $self->roots;
  0         0  
163             return wantarray
164             ? @paths
165 0 0       0 : \@paths;
166             }
167              
168             sub definitive_write {
169 9     9 1 14 my $self = shift;
170 9         8 $self->debug("definitive_write(", join(', ', @_), ")\n") if DEBUG;
171 9         22 my $path = $self->absolute(@_);
172 9         22 return $self->join_directory($self->roots->[0], $path);
173             }
174              
175             sub definitive_read {
176 63     63 1 78 my $self = shift;
177 63         122 my $path = $self->absolute(@_);
178 63         91 my ($base, $full);
179              
180 63         61 $self->debug("definitive_read($path)") if DEBUG;
181              
182 63         118 foreach $base ($self->roots) {
183 96         216 $full = $self->merge_paths($base, $path);
184 96         130 $self->debug("looking for [$base] + [$path] => $full\n") if DEBUG;
185 96 100       1820 return $full if -e $full;
186 38         91 $self->debug("not found\n") if DEBUG;
187             }
188 5         40 return undef;
189             }
190              
191             sub read_directory {
192 4     4 1 9 my $self = shift;
193 4         13 my $path = $self->absolute(shift);
194 4         9 my $all = shift;
195 4         9 my ($base, $full, $dirh, $item, @items, %seen);
196              
197 4         692 require IO::Dir;
198              
199 4         9027 foreach $base ($self->roots) {
200 10         110 $full = $self->join_directory($base, $path);
201 10         17 $self->debug("Opening directory: $full\n") if DEBUG;
202 10   50     27 $dirh = IO::Dir->new($full)
203             || next;
204             # Some directory may not exist, so ignore them
205             # || $self->error_msg( open_failed => directory => $full => $! );
206 10         760 while (defined ($item = $dirh->read)) {
207 57         465 if (DEBUG) {
208             if ($seen{ $item }) {
209             $self->debug("skipping $item (already seen)");
210             }
211             else {
212             $self->debug("adding $item");
213             }
214             }
215 57 100       156 push(@items, $item) unless $seen{ $item }++;
216             }
217 10         136 $dirh->close;
218             }
219             @items = $self->FILESPEC->no_upwards(@items)
220 4 50 33     160 unless $all || ref $self && $self->{ all_entries };
      33        
221              
222 4 50       31 return wantarray ? @items : \@items;
223             }
224              
225              
226             1;
227              
228             __END__