File Coverage

blib/lib/Directory/Scratch.pm
Criterion Covered Total %
statement 232 238 97.4
branch 82 104 78.8
condition 19 25 76.0
subroutine 36 37 97.3
pod 20 20 100.0
total 389 424 91.7


line stmt bran cond sub pod time code
1             package Directory::Scratch; # git description: v0.16-5-g2428a09
2             $Directory::Scratch::VERSION = '0.17';
3             # see POD after __END__.
4              
5 34     34   570601 use warnings;
  34         79  
  34         1425  
6 34     34   184 use strict;
  34         49  
  34         1203  
7 34     34   170 use Carp;
  34         51  
  34         2893  
8 34     34   30958 use File::Temp;
  34         786507  
  34         3034  
9 34     34   20308 use File::Copy;
  34         80826  
  34         2556  
10 34     34   19442 use Path::Class qw(dir file);
  34         735771  
  34         2592  
11 34     34   27310 use Path::Tiny;
  34         447432  
  34         2660  
12 34     34   301 use File::Spec;
  34         60  
  34         920  
13 34     34   249 use File::stat (); # no imports
  34         61  
  34         2135  
14              
15             my ($OUR_PLATFORM) = $File::Spec::ISA[0] =~ /::(\w+)$/;
16             my $PLATFORM = 'Unix';
17 34     34   787 use Scalar::Util qw(blessed);
  34         50  
  34         2403  
18              
19 34         301 use overload q{""} => \&base,
20 34     34   184 fallback => "yes, fallback";
  34         51  
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   967 my $class = shift;
26 34 100       14123 return unless @_;
27 1         4 $PLATFORM = shift;
28 1         78 eval("require File::Spec::$PLATFORM");
29 1 50       1777 croak "Don't know how to deal with platform '$PLATFORM'" if $@;
30 1         34 return $PLATFORM;
31             }
32              
33             # create an instance
34             sub new {
35 37     37 1 10912 my $class = shift;
36 37         87 my $self = {};
37 37         62 my %args;
38              
39 37         58 eval { %args = @_ };
  37         106  
40 37 50       132 croak 'Invalid number of arguments to Directory::Scratch->new' if $@;
41 37         79 my $platform = $PLATFORM;
42 37 50       198 $platform = $args{platform} if defined $args{platform};
43            
44             # explicitly default CLEANUP to 1
45 37 100       174 $args{CLEANUP} = 1 unless exists $args{CLEANUP};
46            
47             # don't clean up if environment variable is set
48 37 50 33     172 $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         58 my @file_temp_args;
54              
55             # convert DIR from their format to a Path::Class
56 37 100       113 $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         493 for(qw(CLEANUP DIR)){
60 74 100       264 push @file_temp_args, ($_ => $args{$_}) if $args{$_};
61             }
62            
63             # this is a positional argument, not a named argument
64 37 100       163 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     140 push @file_temp_args, (TMPDIR => 1) if($args{TEMPLATE} && !$args{DIR});
69            
70             # keep this around for C
71 37         113 $self->{args} = \%args;
72              
73             # create the directory!
74 37         209 my $base = dir(File::Temp::tempdir(@file_temp_args));
75 37 50       27064 croak "Couldn't create a tempdir: $!" unless -d $base;
76 37         1820 $self->{base} = $base;
77              
78 37         118 bless $self, $class;
79 37         150 $self->platform($platform); # set platform for this instance
80 37         129 return $self;
81             }
82              
83             sub child {
84 4     4 1 1618 my $self = shift;
85 4         8 my %args;
86            
87 4 100 66     290 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       7 %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         6 $args{DIR} = $self->base->stringify;
95            
96 2         47 return Directory::Scratch->new(%args);
97             }
98              
99             sub base {
100 401     401 1 3251 my $self = shift;
101 401         939 return $self->{base};#->stringify;
102             }
103              
104             sub platform {
105 279     279 1 300 my $self = shift;
106 279         302 my $desired = shift;
107              
108 279 100       585 if($desired){
109 37         2756 eval "require File::Spec::$desired";
110 37 50       159 croak "Unknown platform '$desired'" if $@;
111 37         1123 $self->{platform} = $desired;
112             }
113            
114 279         471 return $self->{platform};
115             }
116              
117             # make Path::Class's foreign_* respect the instance's desired platform
118             sub _foreign_file {
119 218     218   238 my $self = shift;
120 218         369 my $platform = $self->platform;
121              
122 218 50       468 if($platform){
123 218         611 my $file = Path::Class::foreign_file($platform, @_);
124 218         37595 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   33 my $self = shift;
133 24         60 my $platform = $self->platform;
134              
135 24 50       72 if($platform){
136 24         90 my $dir = Path::Class::foreign_dir($platform, @_);
137 24         3742 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 4776 my $self = shift;
146 47         70 my $file = shift;
147 47         98 my $base = $self->base;
148 47         108 my $path = $self->_foreign_file($base, $file);
149 47 100       8244 return dir($path) if -d $path;
150 37 100       1591 return $path if -e $path;
151 13         521 return; # undef otherwise
152             }
153              
154             sub stat {
155 2     2 1 933 my $self = shift;
156 2         3 my $file = shift;
157 2         5 my $path = $self->_foreign_file($self->base, $file);
158              
159 2 100       281 if(wantarray){
160 1         4 return stat $path; # core stat, returns a list
161             }
162            
163 1         5 return File::stat::stat($path); # returns an object
164             }
165              
166             sub mkdir {
167 18     18 1 8139 my $self = shift;
168 18         36 my $dir = shift;
169 18         52 my $base = $self->base;
170 18         74 $dir = $self->_foreign_dir($base, $dir);
171 18         2026 $dir->mkpath;
172 17 100 100     3803 return $dir if (-e $dir && -d $dir);
173 2         121 croak "Error creating $dir: $!";
174             }
175              
176             sub link {
177 8     8 1 1150 my $self = shift;
178 8         11 my $from = shift;
179 8         12 my $to = shift;
180 8         18 my $base = $self->base;
181              
182 8 50       26 croak "Symlinks are not supported on MSWin32"
183             if $^O eq 'MSWin32';
184              
185 8         15 $from = $self->_foreign_file($base, $from);
186 8         1102 $to = $self->_foreign_file($base, $to);
187              
188 8 100       1084 symlink($from, $to)
189             or croak "Couldn't link $from to $to: $!";
190            
191 6         493 return $to;
192             }
193              
194             sub chmod {
195 2     2 1 1512 my $self = shift;
196 2         3 my $mode = shift;
197 2         6 my @paths = @_;
198            
199 2         4 my @translated = map { $self->_foreign_file($self->base, $_) } @paths;
  4         315  
200 2         314 return chmod $mode, @translated;
201             }
202              
203             sub read {
204 28     28 1 3488 my $self = shift;
205 28         45 my $file = shift;
206 28         73 my $base = $self->base;
207            
208 28         70 $file = $self->_foreign_file($base, $file);
209              
210 28 50       5612 croak "Cannot read $file: is a directory" if -d $file;
211            
212 28 100       1401 if(wantarray){
213 17         72 my @lines = path($file->stringify)->lines;
214 17         3082 chomp @lines;
215 17         150 return @lines;
216             }
217             else {
218 11         38 my $scalar = path($file->stringify)->slurp;
219 11         2160 chomp $scalar;
220 11         97 return $scalar;
221             }
222             }
223              
224             sub write {
225 58     58 1 1098 my $self = shift;
226 58         87 my $file = shift;
227 58         156 my $base = $self->base;
228            
229 58         137 my $path = $self->_foreign_file($base, $file);
230 58         10698 $path->parent->mkpath;
231 58 50       3340 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         2080 my (undef, undef, undef, $method) = caller(1);
236              
237 58         98 my $args;
238 58 100 100     391 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     257 local $, = $, || "\n";
245 55 50       183 path($path->stringify)->append({ truncate => 1 }, @_, '')
246             or croak "Error writing file: $!";
247             }
248 58         15284 return 1;
249             }
250              
251             sub append {
252 3     3 1 1466 return &write(@_); # magic!
253             }
254              
255             sub tempfile {
256 121     121 1 2099 my $self = shift;
257 121         137 my $path = shift;
258              
259 121 50       245 if(!defined $path){
260 121         306 $path = $self->base;
261             }
262             else {
263 0         0 $path = $self->_foreign_dir($self->base, $path);
264             }
265            
266 121         390 my ($fh, $filename) = File::Temp::tempfile( DIR => $path );
267 121         41741 $filename = file($filename); # "class"ify the file
268 121 100       10176 if(wantarray){
269 120         337 return ($fh, $filename);
270             }
271            
272             # XXX: I don't know why you would want to do this...
273 1         13 return $fh;
274             }
275              
276             sub openfile {
277 52     52 1 3051 my $self = shift;
278 52         78 my $file = shift;
279 52         138 my $base = $self->base;
280              
281 52         155 my $path = $self->_foreign_file($base, $file);
282 52         9564 $path->dir->mkpath;
283 52 50       4024 croak 'Parent directory '. $path->dir.
284             ' does not exist, and could not be created'
285             unless -d $path->dir;
286 52 50       2071 open(my $fh, '+>', $path) or croak "Failed to open $path: $!";
287 52 100       6049 return ($fh, $path) if(wantarray);
288 2         12 return $fh;
289             }
290              
291             sub touch {
292 49     49 1 17394 my $self = shift;
293 49         106 my $file = shift;
294 49         163 my ($fh, $path) = $self->openfile($file);
295            
296 49 50       234 $self->write($file, @_) || croak 'failed to write file: $!';
297 49         422 return $path;
298             }
299              
300              
301             sub ls {
302 11     11 1 20690 my $self = shift;
303 11         21 my $dir = shift;
304 11         36 my $base = $self->base;
305 11         41 my $path = dir($base);
306 11         846 my @result;
307              
308 11 100       35 if($dir){
309 6         24 $dir = $self->_foreign_dir($dir);
310 6         568 $path = $self->exists($dir);
311 6 100       467 croak "No path `$dir' in temporary directory" if !$path;
312            
313 5 100       35 return (file($dir)) if !-d $path;
314 3         84 $path = dir($base, $dir);
315             }
316            
317             $path->recurse( callback =>
318             sub {
319 49     49   15866 my $file = shift;
320 49 100       143 return if $file eq $path;
321              
322 41         1580 push @result, $file->relative($base);
323             }
324 8         197 );
325            
326 8         1472 return @result;
327             }
328              
329             sub create_tree {
330 2     2 1 606 my $self = shift;
331 2 100       3 my %tree = %{shift()||{}};
  2         12  
332            
333 2         9 foreach my $element (keys %tree){
334 5         57 my $value = $tree{$element};
335 5 100       10 if('SCALAR' eq ref $value){
336 1         5 $self->mkdir($element);
337             }
338             else {
339 4         6 my @lines = ($value);
340 4 100       11 @lines = @$value if 'ARRAY' eq ref $value;
341 4         7 $self->touch($element, @lines);
342             }
343             }
344             }
345              
346             sub delete {
347 11     11 1 2266 my $self = shift;
348 11         19 my $path = shift;
349 11         27 my $base = $self->base;
350              
351 11         31 $path = $self->_foreign_file($base, $path);
352            
353 11 100       1938 croak "No such file or directory '$path'" if !-e $path;
354            
355 10 100       433 if(-d _){ # reuse stat() from -e test
356 5   66     17 return (scalar rmdir $path or croak "Couldn't remove directory $path: $!");
357             }
358             else {
359 5   33     20 return (scalar unlink $path or croak "Couldn't unlink $path: $!");
360             }
361            
362             }
363              
364             sub cleanup {
365 3     3 1 430 my $self = shift;
366 3         8 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         18 };
373            
374 3         11 File::Path::rmtree( $base->stringify );
375              
376 3 50       1529 if ( @errors > 0 ) {
377 0         0 croak "cleanup() method failed: $!\n@errors";
378             }
379              
380 3         9 $self->{args}->{CLEANUP} = 1; # it happened, so update this
381 3         21 return 1;
382             }
383              
384             sub randfile {
385 119     119 1 192744 my $self = shift;
386              
387             # make sure we can do this
388 119         216 eval {
389 119         1016 require String::Random;
390             };
391 119 50       320 croak 'randfile: String::Random is required' if $@;
392              
393             # setup some defaults
394 119         184 my( $min, $max ) = ( 1024, 131072 );
395              
396 119 100       308 if ( @_ == 2 ) {
    100          
397 107         178 ($min, $max) = @_;
398             }
399             elsif ( @_ == 1 ) {
400 11         12 $max = $_[0];
401 11 100       30 $min = int(rand($max)) if ( $min > $max );
402             }
403 119 50       292 confess "randfile: Cannot request a maximum length < 1"
404             if ( $max < 1 );
405            
406 119         318 my ($fh, $name) = $self->tempfile;
407 119 50       273 croak "Could not open $name: $!" if !$fh;
408 119         1058 close $fh;
409            
410 119         684 my $rand = String::Random->new();
411 119         2410 path($name)->append({ truncate => 1 }, $rand->randregex(".{$min,$max}"));
412            
413 119         2226753 return file($name);
414             }
415              
416             # throw a warning if CLEANUP is off and cleanup hasn't been called
417             sub DESTROY {
418 36     36   25763 my $self = shift;
419 36 100       1136 carp "Warning: not cleaning up files in ". $self->base
420             if !$self->{args}->{CLEANUP};
421             }
422              
423             1;
424              
425             __END__