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.17-3-ga9b3e32
2             $Directory::Scratch::VERSION = '0.18';
3             # see POD after __END__.
4              
5 34     34   477220 use warnings;
  34         66  
  34         1148  
6 34     34   147 use strict;
  34         43  
  34         970  
7 34     34   137 use Carp;
  34         45  
  34         2584  
8 34     34   26096 use File::Temp;
  34         694827  
  34         2647  
9 34     34   18140 use File::Copy;
  34         71092  
  34         2147  
10 34     34   16139 use Path::Class qw(dir file);
  34         625315  
  34         2434  
11 34     34   24419 use Path::Tiny 0.060;
  34         398113  
  34         2643  
12 34     34   275 use File::Spec;
  34         53  
  34         870  
13 34     34   158 use File::stat (); # no imports
  34         57  
  34         1934  
14              
15             my ($OUR_PLATFORM) = $File::Spec::ISA[0] =~ /::(\w+)$/;
16             my $PLATFORM = 'Unix';
17 34     34   518 use Scalar::Util qw(blessed);
  34         51  
  34         2238  
18              
19 34         294 use overload q{""} => \&base,
20 34     34   160 fallback => "yes, fallback";
  34         48  
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   1086 my $class = shift;
26 34 100       13157 return unless @_;
27 1         2 $PLATFORM = shift;
28 1         61 eval("require File::Spec::$PLATFORM");
29 1 50       1583 croak "Don't know how to deal with platform '$PLATFORM'" if $@;
30 1         24 return $PLATFORM;
31             }
32              
33             # create an instance
34             sub new {
35 37     37 1 8371 my $class = shift;
36 37         79 my $self = {};
37 37         67 my %args;
38              
39 37         61 eval { %args = @_ };
  37         115  
40 37 50       124 croak 'Invalid number of arguments to Directory::Scratch->new' if $@;
41 37         77 my $platform = $PLATFORM;
42 37 50       199 $platform = $args{platform} if defined $args{platform};
43            
44             # explicitly default CLEANUP to 1
45 37 100       159 $args{CLEANUP} = 1 unless exists $args{CLEANUP};
46            
47             # don't clean up if environment variable is set
48 37 50 33     174 $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         53 my @file_temp_args;
54              
55             # convert DIR from their format to a Path::Class
56 37 100       114 $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         479 for(qw(CLEANUP DIR)){
60 74 100       246 push @file_temp_args, ($_ => $args{$_}) if $args{$_};
61             }
62            
63             # this is a positional argument, not a named argument
64 37 100       179 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     146 push @file_temp_args, (TMPDIR => 1) if($args{TEMPLATE} && !$args{DIR});
69            
70             # keep this around for C
71 37         119 $self->{args} = \%args;
72              
73             # create the directory!
74 37         208 my $base = dir(File::Temp::tempdir(@file_temp_args));
75 37 50       26334 croak "Couldn't create a tempdir: $!" unless -d $base;
76 37         1742 $self->{base} = $base;
77              
78 37         110 bless $self, $class;
79 37         146 $self->platform($platform); # set platform for this instance
80 37         121 return $self;
81             }
82              
83             sub child {
84 4     4 1 2028 my $self = shift;
85 4         6 my %args;
86            
87 4 100 66     293 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         5 $args{DIR} = $self->base->stringify;
95            
96 2         39 return Directory::Scratch->new(%args);
97             }
98              
99             sub base {
100 401     401 1 4130 my $self = shift;
101 401         846 return $self->{base};#->stringify;
102             }
103              
104             sub platform {
105 279     279 1 286 my $self = shift;
106 279         282 my $desired = shift;
107              
108 279 100       560 if($desired){
109 37         2974 eval "require File::Spec::$desired";
110 37 50       168 croak "Unknown platform '$desired'" if $@;
111 37         1047 $self->{platform} = $desired;
112             }
113            
114 279         446 return $self->{platform};
115             }
116              
117             # make Path::Class's foreign_* respect the instance's desired platform
118             sub _foreign_file {
119 218     218   227 my $self = shift;
120 218         342 my $platform = $self->platform;
121              
122 218 50       408 if($platform){
123 218         548 my $file = Path::Class::foreign_file($platform, @_);
124 218         35634 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         55 my $platform = $self->platform;
134              
135 24 50       66 if($platform){
136 24         83 my $dir = Path::Class::foreign_dir($platform, @_);
137 24         3709 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 3669 my $self = shift;
146 47         58 my $file = shift;
147 47         92 my $base = $self->base;
148 47         95 my $path = $self->_foreign_file($base, $file);
149 47 100       7239 return dir($path) if -d $path;
150 37 100       1381 return $path if -e $path;
151 13         362 return; # undef otherwise
152             }
153              
154             sub stat {
155 2     2 1 564 my $self = shift;
156 2         3 my $file = shift;
157 2         4 my $path = $self->_foreign_file($self->base, $file);
158              
159 2 100       290 if(wantarray){
160 1         2 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 5489 my $self = shift;
168 18         29 my $dir = shift;
169 18         53 my $base = $self->base;
170 18         62 $dir = $self->_foreign_dir($base, $dir);
171 18         1857 $dir->mkpath;
172 17 100 100     3259 return $dir if (-e $dir && -d $dir);
173 2         71 croak "Error creating $dir: $!";
174             }
175              
176             sub link {
177 8     8 1 1963 my $self = shift;
178 8         15 my $from = shift;
179 8         9 my $to = shift;
180 8         19 my $base = $self->base;
181              
182 8 50       31 croak "Symlinks are not supported on MSWin32"
183             if $^O eq 'MSWin32';
184              
185 8         16 $from = $self->_foreign_file($base, $from);
186 8         1218 $to = $self->_foreign_file($base, $to);
187              
188 8 100       1256 symlink($from, $to)
189             or croak "Couldn't link $from to $to: $!";
190            
191 6         616 return $to;
192             }
193              
194             sub chmod {
195 2     2 1 1949 my $self = shift;
196 2         3 my $mode = shift;
197 2         4 my @paths = @_;
198            
199 2         3 my @translated = map { $self->_foreign_file($self->base, $_) } @paths;
  4         289  
200 2         325 return chmod $mode, @translated;
201             }
202              
203             sub read {
204 28     28 1 3464 my $self = shift;
205 28         38 my $file = shift;
206 28         57 my $base = $self->base;
207            
208 28         59 $file = $self->_foreign_file($base, $file);
209              
210 28 50       4407 croak "Cannot read $file: is a directory" if -d $file;
211            
212 28 100       1078 if(wantarray){
213 17         51 my @lines = path($file->stringify)->lines;
214 17         2617 chomp @lines;
215 17         126 return @lines;
216             }
217             else {
218 11         31 my $scalar = path($file->stringify)->slurp;
219 11         1882 chomp $scalar;
220 11         78 return $scalar;
221             }
222             }
223              
224             sub write {
225 58     58 1 1089 my $self = shift;
226 58         81 my $file = shift;
227 58         138 my $base = $self->base;
228            
229 58         134 my $path = $self->_foreign_file($base, $file);
230 58         9588 $path->parent->mkpath;
231 58 50       3014 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         2015 my (undef, undef, undef, $method) = caller(1);
236              
237 58         97 my $args;
238 58 100 100     361 if(defined $method && $method eq 'Directory::Scratch::append'){
239 3   100     14 local $, = $, || "\n";
240 3 50       11 path($path->stringify)->append(@_, '')
241             or croak "Error writing file: $!";
242             }
243             else { # (cut'n'paste)++
244 55   100     254 local $, = $, || "\n";
245 55 50       161 path($path->stringify)->append({ truncate => 1 }, @_, '')
246             or croak "Error writing file: $!";
247             }
248 58         13890 return 1;
249             }
250              
251             sub append {
252 3     3 1 1699 return &write(@_); # magic!
253             }
254              
255             sub tempfile {
256 121     121 1 1188 my $self = shift;
257 121         127 my $path = shift;
258              
259 121 50       238 if(!defined $path){
260 121         326 $path = $self->base;
261             }
262             else {
263 0         0 $path = $self->_foreign_dir($self->base, $path);
264             }
265            
266 121         350 my ($fh, $filename) = File::Temp::tempfile( DIR => $path );
267 121         33175 $filename = file($filename); # "class"ify the file
268 121 100       9237 if(wantarray){
269 120         274 return ($fh, $filename);
270             }
271            
272             # XXX: I don't know why you would want to do this...
273 1         11 return $fh;
274             }
275              
276             sub openfile {
277 52     52 1 2442 my $self = shift;
278 52         70 my $file = shift;
279 52         123 my $base = $self->base;
280              
281 52         134 my $path = $self->_foreign_file($base, $file);
282 52         9727 $path->dir->mkpath;
283 52 50       3861 croak 'Parent directory '. $path->dir.
284             ' does not exist, and could not be created'
285             unless -d $path->dir;
286 52 50       2064 open(my $fh, '+>', $path) or croak "Failed to open $path: $!";
287 52 100       5864 return ($fh, $path) if(wantarray);
288 2         19 return $fh;
289             }
290              
291             sub touch {
292 49     49 1 14754 my $self = shift;
293 49         72 my $file = shift;
294 49         134 my ($fh, $path) = $self->openfile($file);
295            
296 49 50       205 $self->write($file, @_) || croak 'failed to write file: $!';
297 49         428 return $path;
298             }
299              
300              
301             sub ls {
302 11     11 1 25955 my $self = shift;
303 11         22 my $dir = shift;
304 11         31 my $base = $self->base;
305 11         43 my $path = dir($base);
306 11         836 my @result;
307              
308 11 100       36 if($dir){
309 6         20 $dir = $self->_foreign_dir($dir);
310 6         581 $path = $self->exists($dir);
311 6 100       479 croak "No path `$dir' in temporary directory" if !$path;
312            
313 5 100       36 return (file($dir)) if !-d $path;
314 3         93 $path = dir($base, $dir);
315             }
316            
317             $path->recurse( callback =>
318             sub {
319 49     49   16779 my $file = shift;
320 49 100       157 return if $file eq $path;
321              
322 41         1794 push @result, $file->relative($base);
323             }
324 8         199 );
325            
326 8         1663 return @result;
327             }
328              
329             sub create_tree {
330 2     2 1 291 my $self = shift;
331 2 100       3 my %tree = %{shift()||{}};
  2         13  
332            
333 2         7 foreach my $element (keys %tree){
334 5         87 my $value = $tree{$element};
335 5 100       13 if('SCALAR' eq ref $value){
336 1         3 $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         10 $self->touch($element, @lines);
342             }
343             }
344             }
345              
346             sub delete {
347 11     11 1 1239 my $self = shift;
348 11         16 my $path = shift;
349 11         19 my $base = $self->base;
350              
351 11         23 $path = $self->_foreign_file($base, $path);
352            
353 11 100       1740 croak "No such file or directory '$path'" if !-e $path;
354            
355 10 100       364 if(-d _){ # reuse stat() from -e test
356 5   66     14 return (scalar rmdir $path or croak "Couldn't remove directory $path: $!");
357             }
358             else {
359 5   33     15 return (scalar unlink $path or croak "Couldn't unlink $path: $!");
360             }
361            
362             }
363              
364             sub cleanup {
365 3     3 1 691 my $self = shift;
366 3         9 my $base = $self->base;
367            
368             # capture warnings
369 3         4 my @errors;
370             local $SIG{__WARN__} = sub {
371 0     0   0 push @errors, @_;
372 3         23 };
373            
374 3         11 File::Path::rmtree( $base->stringify );
375              
376 3 50       1505 if ( @errors > 0 ) {
377 0         0 croak "cleanup() method failed: $!\n@errors";
378             }
379              
380 3         10 $self->{args}->{CLEANUP} = 1; # it happened, so update this
381 3         20 return 1;
382             }
383              
384             sub randfile {
385 119     119 1 136641 my $self = shift;
386              
387             # make sure we can do this
388 119         187 eval {
389 119         745 require String::Random;
390             };
391 119 50       290 croak 'randfile: String::Random is required' if $@;
392              
393             # setup some defaults
394 119         164 my( $min, $max ) = ( 1024, 131072 );
395              
396 119 100       278 if ( @_ == 2 ) {
    100          
397 107         169 ($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       235 confess "randfile: Cannot request a maximum length < 1"
404             if ( $max < 1 );
405            
406 119         273 my ($fh, $name) = $self->tempfile;
407 119 50       261 croak "Could not open $name: $!" if !$fh;
408 119         931 close $fh;
409            
410 119         590 my $rand = String::Random->new();
411 119         1999 path($name)->append({ truncate => 1 }, $rand->randregex(".{$min,$max}"));
412            
413 119         780034 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   21615 my $self = shift;
419 36 100       1007 carp "Warning: not cleaning up files in ". $self->base
420             if !$self->{args}->{CLEANUP};
421             }
422              
423             1;
424              
425             __END__