File Coverage

blib/lib/Template/Direct/Directory.pm
Criterion Covered Total %
statement 65 172 37.7
branch 28 104 26.9
condition 6 29 20.6
subroutine 14 31 45.1
pod 26 26 100.0
total 139 362 38.4


line stmt bran cond sub pod time code
1             package Template::Direct::Directory;
2              
3             =head1 NAME
4              
5             Template::Direct::Directory - Controls the access to a set directory
6              
7             =head1 SYNOPSIS
8              
9             use Directory;
10              
11             my $directory = Template::Direct::Directory->new( '/etc' );
12              
13             my $file = $directory->open( 'foo.txt' );
14              
15             my $parent = $directory->parent();
16              
17             my @children = $directory->list();
18              
19             my @results = $directory->search();
20              
21             =head1 DESCRIPTION
22            
23             Loads a directory for use with fileDirectives
24            
25             =head1 METHODS
26              
27             =cut
28            
29             our $VERSION = "3.02";
30              
31             use overload
32 2         25 '""' => \&autoscalar,
33             'bool' => \&autobool,
34             'eq' => \&autoeq,
35 2     2   12 'ne' => \&autone;
  2         6  
36              
37 2     2   253 use strict;
  2         5  
  2         75  
38 2     2   13 use Carp qw/carp cluck/;
  2         3  
  2         117  
39 2     2   1554 use Template::Direct::Directory::File;
  2         4  
  2         5595  
40              
41             our %Cache;
42              
43             =head2 I<$class>->new( $dir )
44              
45             Create a new Directory object located at $dir.
46              
47             =cut
48             sub new
49             {
50 4     4 1 13 my ($cache, $dir, %p) = @_;
51 4         12 my $self = bless {}, $cache;
52              
53 4 50 0     12 carp "Directory Error: 'Directory' is a required field" and return if not $dir;
54 4         21 ($self->{'Directory'}) = $self->_clean_path( Directory => $dir, Parents => 1 );
55              
56 4 50       17 if(defined($Cache{$self->{'Directory'}})) {
57 0 0       0 warn "Using cached Directory $self->{'Directory'}\n" if $ENV{'DIR_DEBUG'};
58 0         0 return $Cache{$self->{'Directory'}};
59             }
60              
61 4 50 0     33 carp "Directory Error: files are not allowed as Directories (please use Directory::File)"
62             and return if -f $dir;
63              
64 4 50       11 if( $p{'Create'} ) {
65 0         0 $self->mkpath( $self->{'Directory'} );
66             }
67              
68 4 50       10 $Cache{$self->path()} = $self if -f $self->path();
69              
70 4         17 return $self;
71             }
72              
73             =head2 I<$dir>->save( $filename, $data, %options )
74              
75             Save a file in this directory (quickly)
76              
77             =cut
78             sub save
79             {
80 0     0 1 0 my ($self, $file, $data, %p) = @_;
81 0         0 my ($path, $isfile, $parent) = $self->_clean_path(%p, File => $file );
82              
83 0 0 0     0 carp "Directory Error: file is a required field when saving" and return if not $path;
84 0 0 0     0 carp "Directory Error: Directories can not be saved" and return if not $isfile;
85              
86 0 0       0 if(defined($data)) {
87 0 0       0 if(not -d $parent) {
88 0         0 $parent->mkpath( $parent->path() );
89             }
90 0         0 my $file = Template::Direct::Directory::File->new( $path, Create => 1, %p, Parent => $parent );
91 0 0       0 return if(not defined($file));
92 0         0 $file->save( $data );
93 0         0 return $file;
94             } else {
95 0         0 carp "Directory Error: No data to save, required Data\n";
96             }
97 0         0 return;
98             }
99              
100             =head2 I<$dir>->loadFile( $filename, %options )
101              
102             Load a file object child, options include:
103              
104             * Create - Create this file if it doesn't exist
105              
106             =cut
107 2     2 1 16 sub loadFile { shift->load( @_, File => 1 ) }
108              
109             =head2 I<$dir>->loadDir( $directory, %options )
110              
111             Load a sub directory, options include:
112              
113             * Create - Create this file if it doesn't exist
114              
115             =cut
116 0     0 1 0 sub loadDir { shift->load( @_ ) }
117              
118             =head2 I<$dir>->load( $path, %options )
119              
120             Generic load a file or sub directory object with options:
121              
122             * Create - Create this filename as a directory if it doesn't exist
123             * CreateFile - Create this filename as a file if it doesn't exist
124             * File - Force loading as a file object.
125              
126             =cut
127             sub load
128             {
129 2     2 1 8 my ($self, $file, %p) = @_;
130 2         9 my ($path, $isfile, $parent) = $self->_clean_path( %p, Directory => $file );
131 2 50       15 $p{'Create'} = $p{'CreateFile'} if not $p{'Create'};
132 2 50       25 return Template::Direct::Directory::File->new( $path, %p, Parent => $parent ) if $isfile;
133 0         0 return Template::Direct::Directory->new( $path, %p, Parent => $self );
134             }
135              
136             =head2 I<$dir>->delete( $filename, %p )
137              
138             Delete a file from this directory.
139              
140             =cut
141             sub delete
142             {
143 0     0 1 0 my ($self, $file, %p) = @_;
144 0         0 my ($path, $isfile, $parent) = $self->_clean_path( %p, Directory => $file );
145 0 0       0 if($isfile) {
146 0         0 my $file = Template::Direct::Directory::File->new( $path, %p, Parent => $parent );
147 0 0       0 if($file) {
148 0         0 return $file->delete();
149             }
150             } else {
151 0         0 return $self->prune( $path );
152             }
153              
154 0         0 return;
155             }
156              
157             =head2 I<$dir>->_clean_path( %p )
158              
159             Takes %p and returns corrected, localised paths.
160              
161             =cut
162             sub _clean_path
163             {
164 6     6   27 my ($self, %p) = @_;
165              
166 6 50 66     30 my $path = ($p{'File'} and not $p{'Directory'}) ? $p{'File'} : $p{'Directory'};
167 6 50       16 return if not $path;
168              
169 6 100       14 if(not $p{'Parents'}) {
170 2 50 0     7 carp "Unable to clean path because Diretory object is involid" and return if not $self->path;
171 2         5 $path = $self->path.$path;
172             }
173              
174             #carp "Cleaning path $path ".(-d $path ? 1 : 0)."/".(-f $path ? 1 : 0)."\n";
175              
176 6 100 66     110 if($p{'CreateFile'} or -f $path) {
177 2         6 $p{'Create'} = 1;
178 2         4 $p{'File'} = 1;
179             }
180              
181 6 50       16 $path = $self->useElements($path, $p{'Elements'}) if $p{'Elements'};
182            
183 6 100       15 $path .= "/" if not $p{'File'};
184 6         14 $path =~ s/([^\/]+)\/\.\.\///g; # No parent directories allowed in children
185 6 50 66     27 warn "Removing Parent $1\n" if $1 and $ENV{'DIR_DEBUG'};
186 6         57 $path =~ s/\/\.\//\//g; # No current directories allowed
187 6         26 $path =~ s/\/+/\//g; # Remove double directories.
188              
189 6 100       14 if($p{'File'}) {
190 2         14 my ($dir, $filename) = $path =~ /^(.+)\/([^\/]+)$/;
191 2 50       15 my $parent = $dir ? Template::Direct::Directory->new( $dir, Create => $p{'CreatePath'} ) : $self;
192 2         11 return ($filename, 1, $parent);
193             } else {
194 4         62 return ($path, 0);
195             }
196             }
197              
198             =head2 I<$dir>->clearCache( %p )
199              
200             Clear directory and file objects that are cached.
201              
202             =cut
203             sub clearCache
204             {
205 0     0 1 0 my ($self, %p) = @_;
206 0         0 my ($file, $isfile, $parent) = $self->_clean_path(%p);
207              
208 0 0       0 if($isfile) {
    0          
209 0         0 return delete($parent->{'Cache'}->{$file});
210             } elsif($file) {
211 0         0 my $dir = Template::Direct::Directory->new( $file );
212 0         0 return $dir->clearCache;
213             } else {
214 0 0       0 warn "Clearing Cache for ".$self->path."\n" if $ENV{'DIR_DEBUG'};
215 0         0 $self->{'Cache'} = {};
216 0         0 return 1;
217             }
218             }
219              
220             =head2 I<$ir>->clearCaches()
221              
222             Clear all directory and file objects that are cached.
223              
224             =cut
225             sub clearCaches
226             {
227 0     0 1 0 my ($self) = @_;
228 0         0 foreach my $dir (values(%Cache)) {
229 0         0 $dir->clearCache;
230             }
231 0         0 %Cache = ();
232 0         0 return;
233             }
234              
235             =head2 I<$dir>->fromCache( )
236              
237             Was this object loaded from cache (for testing)
238              
239             =cut
240 0     0 1 0 sub fromCache { shift->{'fromCache'} }
241              
242             =head2 I<$dir>->saveCache( $filename, $data )
243              
244             Save a cache for filename with data.
245              
246             =cut
247             sub saveCache
248             {
249 2     2 1 5 my ($self, $filename, $data) = @_;
250 2 50       8 if($data) {
251 2 50       8 warn "DIR,Cache,save DONE $filename\n" if $ENV{'DIR_DEBUG'};
252 2         9 $self->{'Cache'}->{$filename} = $data;
253             } else {
254 0 0       0 warn "DIR,Cache,save FAILED (No Data) $filename\n" if $ENV{'DIR_DEBUG'};
255             }
256 2         7 return 1;
257             }
258              
259             =head2 I<$dir>->loadCache( $filename )
260              
261             Load a specific cache at filename if it exists.
262              
263             =cut
264             sub loadCache
265             {
266 2     2 1 13 my ($self, $filename) = @_;
267 2 50       8 if($self->{'Cache'}->{$filename}) {
268 0 0       0 warn "DIR,Cache,load DONE $filename\n" if $ENV{'DIR_DEBUG'};
269 0         0 $self->{'Cache'}->{$filename}->{'fromCache'} = 1;
270 0         0 return $self->{'Cache'}->{$filename};
271             } else {
272 2 50       8 warn "DIR,Cache,load FAILED $filename\n" if $ENV{'DIR_DEBUG'};
273 2         10 return;
274             }
275             }
276              
277             =head2 I<$dir>->path( )
278              
279             Returns this directories full path.
280              
281             =cut
282             sub path
283             {
284 43     43 1 50 my ($self) = @_;
285 43         367 return $self->{'Directory'};
286             }
287              
288             =head2 I<$dir>->name( )
289              
290             Returns this folders name.
291              
292             =cut
293             sub name
294             {
295 0     0 1 0 my ($self) = @_;
296 0         0 my $path = $self->path();
297 0 0       0 ($self->{'Name'}) = $path =~ /([^\/]+)\/*$/ if not $self->{'Name'};
298 0         0 return $self->{'Name'};
299             }
300              
301             =head2 I<$dir>->mkpath( $directory )
302              
303             Create a directory and all parents from this directory.
304              
305             =cut
306             sub mkpath
307             {
308 0     0 1 0 my ($self, $newdir) = @_;
309              
310 0         0 my @dirs = split('/', $newdir);
311 0 0       0 my $path = $newdir =~ /^\// ? '/' : $self->path;
312            
313 0         0 foreach my $dir (@dirs)
314             {
315 0 0 0     0 if($dir and not -d $path.$dir)
316             {
317 0 0       0 warn "file: Creating directory '$path$dir'\n" if $ENV{'DIR_DEBUG'};
318 0 0       0 if(mkdir($path.$dir))
319             {
320 0         0 $path = $path.$dir."/";
321             } else {
322 0 0       0 carp "file Error: Could not create directory '$path$dir' ($!)" if $ENV{'DIR_DEBUG'};
323 0         0 return;
324             }
325             } else {
326 0         0 $path = $path.$dir."/";
327             }
328             }
329              
330 0         0 return $self;
331             }
332              
333             =head2 I<$dir>->prune( $path )
334              
335             Removes all empty directories from path to this directory.
336              
337             =cut
338             sub prune
339             {
340 0     0 1 0 my ($self, $path) = @_;
341            
342 0 0       0 $path = '' if not $path;
343 0 0       0 if($path !~ /^\//) {
344 0         0 $path = $self->path.$path;
345             }
346            
347 0         0 my @dirs = split('/', $path);
348 0         0 my $removed = '';
349              
350 0         0 while(my $dir = pop(@dirs)) {
351 0 0       0 if(-d $path) {
352 0 0       0 if(rmdir($path)) {
353             # Ensure the directory is no longer cached
354 0         0 delete($Cache{$path});
355             # A record of removal route
356 0         0 $removed = $dir.'/'.$removed;
357             # Next path to try and remove
358 0         0 $path = join('/', @dirs);
359             } else {
360 0         0 last;
361             }
362             } else {
363 0         0 carp "file Error: Could not prune directory '$path'";
364 0         0 return;
365             }
366             }
367              
368 0         0 return $removed;
369             }
370              
371             =head2 <$class>->useElements( $path, $elements )
372              
373             Should parts of the path or filename be replaced by a defined hash? (used by load, save, delete)
374              
375             =cut
376             sub useElements
377             {
378 0     0 1 0 my ($self, $path, $elements) = @_;
379              
380 0 0 0     0 carp "file Error: Filename is a required field\n" and return if not $path;
381 0 0       0 $path =~ s/(?{$1} ? $elements->{$1} : "\$".$1 /eg;
  0         0  
382              
383 0         0 return $path;
384             }
385              
386             =head2 I<$dir>->exist( %p )
387              
388             Does this directory or child exist.
389              
390             =cut
391             sub exist {
392 14     14 1 17 my ($self) = @_;
393 14 50       28 return -d $self->path() ? 1 : 0;
394             }
395              
396             =head2 <$dir>->parent( )
397              
398             Return a new directory object containing the parent directory.
399              
400             =cut
401             sub parent
402             {
403 0     0 1 0 my ($self) = @_;
404              
405 0 0       0 if($self->path =~ /^(.+)\/[^\/]+?\/$/)
406             {
407 0         0 my $newpath = $1;
408 0         0 my $parent = Template::Direct::Directory->new( $newpath );
409 0         0 return $parent;
410             }
411 0         0 return $self;
412             }
413              
414             =head2 I<$dir>->list( %p )
415              
416             List all directories and files in this directory, load each as an object.
417              
418             =cut
419             sub list
420             {
421 0     0 1 0 my ($self, %p) = @_;
422              
423 0         0 my @results;
424 0 0       0 opendir( LISTDIR, $self->path ) or return [];
425 0         0 foreach my $dir (readdir( LISTDIR )) {
426 0 0 0     0 if($dir ne "." and $dir ne "..") {
427 0 0 0     0 push @results, $dir and next if $p{'Text'};
428 0         0 push @results, $self->load( $dir );
429             }
430             }
431 0         0 closedir( LISTDIR );
432 0         0 return \@results;
433             }
434              
435             =head2 I<$dir>->hlist( )
436              
437             Return a clean list of filename children.
438              
439             =cut
440             sub hlist
441             {
442 0     0 1 0 my ($self) = @_;
443 0         0 my %results;
444 0         0 foreach (@{$self->list}) {
  0         0  
445 0         0 $results{$_->name} = $_;
446             }
447 0         0 return \%results;
448             }
449              
450             =head2 I<$dir>->isfile( )
451              
452             Returns false
453              
454             =cut
455 0     0 1 0 sub isfile { 0 }
456              
457             =head2 I<$dir>->isdir( )
458              
459             Returns true
460              
461             =cut
462 0     0 1 0 sub isdir { 1 }
463              
464             =head1 OVERLOADED
465              
466             =head2 I<$dir>->autoeq( $cmp )
467              
468             Compare directory location string.
469              
470             =cut
471 0     0 1 0 sub autoeq { shift()->path() eq shift(); }
472              
473             =head2 I<$dir>->autone( $cmp )
474              
475             Compare directory location string does not equal.
476              
477             =cut
478 0     0 1 0 sub autone { shift()->path() ne shift(); }
479              
480             =head2 I<$dir>->autoscalar( $cmp )
481              
482             Return path of this directory in string context.
483              
484             =cut
485             sub autoscalar
486             {
487 3     3 1 5 my ($self) = @_;
488 3         7 return $self->path();
489             }
490              
491             =head2 I<$dir>->autobool( $cmp )
492              
493             Does this directory exist when used in a boolean context.
494              
495             =cut
496             sub autobool
497             {
498 18     18 1 27 my ($self) = @_;
499 18         42 my ($package) = caller;
500 18 100       66 return $self->exist if $package ne ref($self);
501 4         12 return $self;
502             }
503              
504             =head1 AUTHOR
505              
506             Copyright, Martin Owens 2008, AGPL
507              
508             =cut
509             1;