File Coverage

blib/lib/Directory/Scratch.pm
Criterion Covered Total %
statement 231 237 97.4
branch 82 104 78.8
condition 19 25 76.0
subroutine 36 37 97.3
pod 20 20 100.0
total 388 423 91.7


line stmt bran cond sub pod time code
1             package Directory::Scratch; # git description: v0.15-1-g5e2e598
2             $Directory::Scratch::VERSION = '0.16';
3             # see POD after __END__.
4              
5 34     34   617780 use warnings;
  34         78  
  34         2111  
6 34     34   768 use strict;
  34         310  
  34         1766  
7 34     34   194 use Carp;
  34         59  
  34         2929  
8 34     34   31706 use File::Temp;
  34         777695  
  34         2964  
9 34     34   19085 use File::Copy;
  34         76596  
  34         2369  
10 34     34   18171 use Path::Class qw(dir file);
  34         720082  
  34         2809  
11 34     34   29393 use Path::Tiny;
  34         452240  
  34         2774  
12 34     34   359 use File::Spec;
  34         63  
  34         954  
13 34     34   230 use File::stat (); # no imports
  34         58  
  34         2198  
14              
15             my ($OUR_PLATFORM) = $File::Spec::ISA[0] =~ /::(\w+)$/;
16             my $PLATFORM = 'Unix';
17 34     34   564 use Scalar::Util qw(blessed);
  34         51  
  34         2464  
18              
19 34         314 use overload q{""} => \&base,
20 34     34   185 fallback => "yes, fallback";
  34         55  
21              
22             # allow the user to specify which OS's semantics he wants to use
23             # if platform is undef, then we won't do any translation at all
24             sub import {
25 34     34   283 my $class = shift;
26 34 100       15375 return unless @_;
27 1         3 $PLATFORM = shift;
28 1         167 eval("require File::Spec::$PLATFORM");
29 1 50       2019 croak "Don't know how to deal with platform '$PLATFORM'" if $@;
30 1         25 return $PLATFORM;
31             }
32              
33             # create an instance
34             sub new {
35 37     37 1 9473 my $class = shift;
36 37         100 my $self = {};
37 37         73 my %args;
38              
39 37         72 eval { %args = @_ };
  37         148  
40 37 50       149 croak 'Invalid number of arguments to Directory::Scratch->new' if $@;
41 37         93 my $platform = $PLATFORM;
42 37 50       215 $platform = $args{platform} if defined $args{platform};
43            
44             # explicitly default CLEANUP to 1
45 37 100       182 $args{CLEANUP} = 1 unless exists $args{CLEANUP};
46            
47             # don't clean up if environment variable is set
48 37 50 33     204 $args{CLEANUP} = 0
49             if(defined $ENV{PERL_DIRECTORYSCRATCH_CLEANUP} &&
50             $ENV{PERL_DIRECTORYSCRATCH_CLEANUP} == 0);
51            
52             # TEMPLATE is a special case, since it's positional in File::Temp
53 37         59 my @file_temp_args;
54              
55             # convert DIR from their format to a Path::Class
56 37 100       127 $args{DIR} = Path::Class::foreign_dir($platform, $args{DIR}) if $args{DIR};
57            
58             # change our arg format to one that File::Temp::tempdir understands
59 37         566 for(qw(CLEANUP DIR)){
60 74 100       279 push @file_temp_args, ($_ => $args{$_}) if $args{$_};
61             }
62            
63             # this is a positional argument, not a named argument
64 37 100       204 unshift @file_temp_args, $args{TEMPLATE} if $args{TEMPLATE};
65            
66             # fix TEMPLATE to do what we mean; if TEMPLATE is set then TMPDIR
67             # needs to be set also
68 37 100 100     300 push @file_temp_args, (TMPDIR => 1) if($args{TEMPLATE} && !$args{DIR});
69            
70             # keep this around for C
71 37         135 $self->{args} = \%args;
72              
73             # create the directory!
74 37         230 my $base = dir(File::Temp::tempdir(@file_temp_args));
75 37 50       83857 croak "Couldn't create a tempdir: $!" unless -d $base;
76 37         2027 $self->{base} = $base;
77              
78 37         123 bless $self, $class;
79 37         172 $self->platform($platform); # set platform for this instance
80 37         163 return $self;
81             }
82              
83             sub child {
84 4     4 1 1857 my $self = shift;
85 4         8 my %args;
86            
87 4 100 66     291 croak 'Invalid reference passed to Directory::Scratch->child'
88             if !blessed $self || !$self->isa(__PACKAGE__);
89            
90             # copy args from parent object
91 2 50       8 %args = %{$self->{_args}} if exists $self->{_args};
  0         0  
92            
93             # force the directory end up as a child of the parent, though
94 2         7 $args{DIR} = $self->base->stringify;
95            
96 2         55 return Directory::Scratch->new(%args);
97             }
98              
99             sub base {
100 401     401 1 4510 my $self = shift;
101 401         919 return $self->{base};#->stringify;
102             }
103              
104             sub platform {
105 279     279 1 350 my $self = shift;
106 279         317 my $desired = shift;
107              
108 279 100       661 if($desired){
109 37         3159 eval "require File::Spec::$desired";
110 37 50       183 croak "Unknown platform '$desired'" if $@;
111 37         1180 $self->{platform} = $desired;
112             }
113            
114 279         530 return $self->{platform};
115             }
116              
117             # make Path::Class's foreign_* respect the instance's desired platform
118             sub _foreign_file {
119 218     218   543 my $self = shift;
120 218         592 my $platform = $self->platform;
121              
122 218 50       472 if($platform){
123 218         692 my $file = Path::Class::foreign_file($platform, @_);
124 218         41782 return $file->as_foreign($OUR_PLATFORM);
125             }
126             else {
127 0         0 return Path::Class::file(@_);
128             }
129             }
130              
131             sub _foreign_dir {
132 24     24   36 my $self = shift;
133 24         64 my $platform = $self->platform;
134              
135 24 50       74 if($platform){
136 24         97 my $dir = Path::Class::foreign_dir($platform, @_);
137 24         3722 return $dir->as_foreign($OUR_PLATFORM);
138             }
139             else {
140 0         0 return Path::Class::dir(@_);
141             }
142             }
143              
144             sub exists {
145 47     47 1 6170 my $self = shift;
146 47         70 my $file = shift;
147 47         121 my $base = $self->base;
148 47         157 my $path = $self->_foreign_file($base, $file);
149 47 100       9249 return dir($path) if -d $path;
150 37 100       1749 return $path if -e $path;
151 13         558 return; # undef otherwise
152             }
153              
154             sub stat {
155 2     2 1 955 my $self = shift;
156 2         5 my $file = shift;
157 2         4 my $path = $self->_foreign_file($self->base, $file);
158              
159 2 100       341 if(wantarray){
160 1         3 return stat $path; # core stat, returns a list
161             }
162            
163 1         4 return File::stat::stat($path); # returns an object
164             }
165              
166             sub mkdir {
167 18     18 1 7717 my $self = shift;
168 18         35 my $dir = shift;
169 18         60 my $base = $self->base;
170 18         75 $dir = $self->_foreign_dir($base, $dir);
171 18         2145 $dir->mkpath;
172 17 100 100     3890 return $dir if (-e $dir && -d $dir);
173 2         72 croak "Error creating $dir: $!";
174             }
175              
176             sub link {
177 8     8 1 2705 my $self = shift;
178 8         17 my $from = shift;
179 8         14 my $to = shift;
180 8         25 my $base = $self->base;
181              
182 8 50       41 croak "Symlinks are not supported on MSWin32"
183             if $^O eq 'MSWin32';
184              
185 8         25 $from = $self->_foreign_file($base, $from);
186 8         1761 $to = $self->_foreign_file($base, $to);
187              
188 8 100       1643 symlink($from, $to)
189             or croak "Couldn't link $from to $to: $!";
190            
191 6         751 return $to;
192             }
193              
194             sub chmod {
195 2     2 1 1792 my $self = shift;
196 2         6 my $mode = shift;
197 2         7 my @paths = @_;
198            
199 2         4 my @translated = map { $self->_foreign_file($self->base, $_) } @paths;
  4         403  
200 2         385 return chmod $mode, @translated;
201             }
202              
203             sub read {
204 28     28 1 4193 my $self = shift;
205 28         47 my $file = shift;
206 28         81 my $base = $self->base;
207            
208 28         76 $file = $self->_foreign_file($base, $file);
209              
210 28 50       5401 croak "Cannot read $file: is a directory" if -d $file;
211            
212 28 100       1311 if(wantarray){
213 17         63 my @lines = path($file->stringify)->lines;
214 17         3142 chomp @lines;
215 17         156 return @lines;
216             }
217             else {
218 11         42 my $scalar = path($file->stringify)->slurp;
219 11         2394 chomp $scalar;
220 11         106 return $scalar;
221             }
222             }
223              
224             sub write {
225 58     58 1 1400 my $self = shift;
226 58         88 my $file = shift;
227 58         167 my $base = $self->base;
228            
229 58         149 my $path = $self->_foreign_file($base, $file);
230 58         10737 $path->parent->mkpath;
231 58 50       3346 croak "Couldn't create parent dir ". $path->parent. ": $!"
232             unless -e $path->parent;
233            
234             # figure out if we're "write" or "append"
235 58         2160 my (undef, undef, undef, $method) = caller(1);
236              
237 58         120 my $args;
238 58 100 100     428 if(defined $method && $method eq 'Directory::Scratch::append'){
239 3   100     15 local $, = $, || "\n";
240 3 50       12 path($path->stringify)->append(@_, '')
241             or croak "Error writing file: $!";
242             }
243             else { # (cut'n'paste)++
244 55   100     275 local $, = $, || "\n";
245 55 50       197 path($path->stringify)->spew(@_, '')
246             or croak "Error writing file: $!";
247             }
248 58         22882 return 1;
249             }
250              
251             sub append {
252 3     3 1 1609 return &write(@_); # magic!
253             }
254              
255             sub tempfile {
256 121     121 1 1859 my $self = shift;
257 121         139 my $path = shift;
258              
259 121 50       249 if(!defined $path){
260 121         282 $path = $self->base;
261             }
262             else {
263 0         0 $path = $self->_foreign_dir($self->base, $path);
264             }
265            
266 121         374 my ($fh, $filename) = File::Temp::tempfile( DIR => $path );
267 121         33008 $filename = file($filename); # "class"ify the file
268 121 100       9233 if(wantarray){
269 120         269 return ($fh, $filename);
270             }
271            
272             # XXX: I don't know why you would want to do this...
273 1         14 return $fh;
274             }
275              
276             sub openfile {
277 52     52 1 3657 my $self = shift;
278 52         81 my $file = shift;
279 52         156 my $base = $self->base;
280              
281 52         176 my $path = $self->_foreign_file($base, $file);
282 52         10414 $path->dir->mkpath;
283 52 50       4521 croak 'Parent directory '. $path->dir.
284             ' does not exist, and could not be created'
285             unless -d $path->dir;
286 52 50       2343 open(my $fh, '+>', $path) or croak "Failed to open $path: $!";
287 52 100       6357 return ($fh, $path) if(wantarray);
288 2         16 return $fh;
289             }
290              
291             sub touch {
292 49     49 1 15824 my $self = shift;
293 49         96 my $file = shift;
294 49         209 my ($fh, $path) = $self->openfile($file);
295            
296 49 50       237 $self->write($file, @_) || croak 'failed to write file: $!';
297 49         1536 return $path;
298             }
299              
300              
301             sub ls {
302 11     11 1 23705 my $self = shift;
303 11         21 my $dir = shift;
304 11         37 my $base = $self->base;
305 11         42 my $path = dir($base);
306 11         873 my @result;
307              
308 11 100       37 if($dir){
309 6         20 $dir = $self->_foreign_dir($dir);
310 6         585 $path = $self->exists($dir);
311 6 100       464 croak "No path `$dir' in temporary directory" if !$path;
312            
313 5 100       30 return (file($dir)) if !-d $path;
314 3         73 $path = dir($base, $dir);
315             }
316            
317             $path->recurse( callback =>
318             sub {
319 49     49   15248 my $file = shift;
320 49 100       144 return if $file eq $path;
321              
322 41         1462 push @result, $file->relative($base);
323             }
324 8         176 );
325            
326 8         1426 return @result;
327             }
328              
329             sub create_tree {
330 2     2 1 607 my $self = shift;
331 2 100       2 my %tree = %{shift()||{}};
  2         13  
332            
333 2         7 foreach my $element (keys %tree){
334 5         60 my $value = $tree{$element};
335 5 100       17 if('SCALAR' eq ref $value){
336 1         4 $self->mkdir($element);
337             }
338             else {
339 4         7 my @lines = ($value);
340 4 100       10 @lines = @$value if 'ARRAY' eq ref $value;
341 4         9 $self->touch($element, @lines);
342             }
343             }
344             }
345              
346             sub delete {
347 11     11 1 2692 my $self = shift;
348 11         22 my $path = shift;
349 11         39 my $base = $self->base;
350              
351 11         36 $path = $self->_foreign_file($base, $path);
352            
353 11 100       2451 croak "No such file or directory '$path'" if !-e $path;
354            
355 10 100       514 if(-d _){ # reuse stat() from -e test
356 5   66     18 return (scalar rmdir $path or croak "Couldn't remove directory $path: $!");
357             }
358             else {
359 5   33     23 return (scalar unlink $path or croak "Couldn't unlink $path: $!");
360             }
361            
362             }
363              
364             sub cleanup {
365 3     3 1 662 my $self = shift;
366 3         13 my $base = $self->base;
367            
368             # capture warnings
369 3         5 my @errors;
370             local $SIG{__WARN__} = sub {
371 0     0   0 push @errors, @_;
372 3         29 };
373            
374 3         15 File::Path::rmtree( $base->stringify );
375              
376 3 50       2001 if ( @errors > 0 ) {
377 0         0 croak "cleanup() method failed: $!\n@errors";
378             }
379              
380 3         13 $self->{args}->{CLEANUP} = 1; # it happened, so update this
381 3         27 return 1;
382             }
383              
384             sub randfile {
385 119     119 1 151425 my $self = shift;
386              
387             # make sure we can do this
388 119         201 eval {
389 119         783 require String::Random;
390             };
391 119 50       346 croak 'randfile: String::Random is required' if $@;
392              
393             # setup some defaults
394 119         179 my( $min, $max ) = ( 1024, 131072 );
395              
396 119 100       305 if ( @_ == 2 ) {
    100          
397 107         177 ($min, $max) = @_;
398             }
399             elsif ( @_ == 1 ) {
400 11         14 $max = $_[0];
401 11 100       30 $min = int(rand($max)) if ( $min > $max );
402             }
403 119 50       224 confess "randfile: Cannot request a maximum length < 1"
404             if ( $max < 1 );
405            
406 119         305 my ($fh, $name) = $self->tempfile;
407 119 50       263 croak "Could not open $name: $!" if !$fh;
408            
409 119         558 my $rand = String::Random->new();
410 119         2096 path($name)->spew($rand->randregex(".{$min,$max}"));
411            
412 119         851994 return file($name);
413             }
414              
415             # throw a warning if CLEANUP is off and cleanup hasn't been called
416             sub DESTROY {
417 36     36   25831 my $self = shift;
418 36 100       1145 carp "Warning: not cleaning up files in ". $self->base
419             if !$self->{args}->{CLEANUP};
420             }
421              
422             1;
423              
424             __END__