File Coverage

lib/Path/Extended/Tiny.pm
Criterion Covered Total %
statement 311 338 92.0
branch 157 200 78.5
condition 34 51 66.6
subroutine 74 85 87.0
pod 6 52 11.5
total 582 726 80.1


line stmt bran cond sub pod time code
1             package Path::Extended::Tiny;
2              
3 22     22   387529 use strict;
  22         41  
  22         831  
4 22     22   91 use warnings;
  22         30  
  22         587  
5 22     22   98 use Carp;
  22         31  
  22         1696  
6 22     22   17584 use Path::Tiny ();
  22         311744  
  22         622  
7 22     22   179 use Scalar::Util ();
  22         38  
  22         539  
8 22     22   95 use Exporter 5.57 qw/import/;
  22         511  
  22         3336  
9              
10             our $VERSION = '0.07';
11              
12             our @EXPORT = qw(file dir file_or_dir dir_or_file);
13             our @EXPORT_OK = (@Path::Tiny::EXPORT, @Path::Tiny::EXPORT_OK);
14             our $AUTOLOAD;
15              
16             use overload
17 75     75   825 '""' => sub { shift->_path },
18 2     2   47 'cmp' => sub { return "$_[0]" cmp "$_[1]" },
19 59     59   528 'bool' => sub { shift->_boolify },
20 22     22   117 '*{}' => sub { shift->_handle };
  22     1   30  
  22         263  
  1         8  
21              
22 0     0 0 0 sub new { shift; _new(@_) }
  0         0  
23              
24             sub _new {
25 227     227   83618 my $path;
26 227 100       567 if (ref $_[0] eq __PACKAGE__) {
27 35         68 $path = shift->[0];
28 35 50       85 if (@_) {
29 35         81 my $new = Path::Tiny::path(@_);
30 35 100       687 if ($new->is_absolute) {
31 1         31 $path = $new;
32             } else {
33 34         834 $path = $path->child($new);
34             }
35             }
36             } else {
37 192         514 $path = Path::Tiny::path(@_);
38             }
39 227         4914 bless [$path], __PACKAGE__;
40             }
41              
42 59     59   113 sub _boolify { 1 }
43 3     3   507 sub _handle { $_[0]->[1] }
44              
45             *file = *dir = *subdir = *file_or_dir = *dir_or_file = \&_new;
46              
47             # ::Entity methods
48              
49 4     4 0 779 sub path { $_[0]->[0]->stringify }
50 0     0 0 0 sub stringify { $_[0]->[0]->stringify }
51 32     32 1 140 sub parent { _new($_[0]->[0]->parent) }
52 513     513   26358 sub _path { $_[0]->[0][0] }
53              
54 35 100   35 0 60 sub is_dir { -d $_[0]->_path ? 1 : 0 }
55 178 100 100 178 0 1511 sub is_open { $_[0]->[1] || $_[0]->[2] ? 1 : 0 }
56 7     7 0 1124 sub is_absolute { $_[0]->[0]->is_absolute }
57 0     0 0 0 sub resolve { $_[0]->[0] = $_[0]->[0]->realpath; $_[0] }
  0         0  
58 21     21 1 2484 sub absolute { $_[0]->[0]->absolute }
59             sub relative {
60 41     41 0 56 my $self = shift;
61 41 100       93 my $base = @_ == 1 ? shift : {@_}->{base};
62 41         121 $self->[0]->relative($base);
63             }
64             sub unlink {
65 31     31 0 15963 my $self = shift;
66 31 100       87 $self->close if $self->is_open;
67 31 100       65 unlink $self->_path if $self->exists;
68             }
69 159 100   159 0 13778 sub exists { -e $_[0]->_path ? 1 : 0 }
70 0 0   0 0 0 sub is_writable { -w $_[0]->_path ? 1 : 0 }
71 0 0   0 0 0 sub is_readable { -r $_[0]->_path ? 1 : 0 }
72             sub copy_to {
73 4     4 0 16 my ($self, $dest) = @_;
74 4 100       16 unless ($dest) { return $self->_error("requires destination") }
  1         4  
75 3         1104 require File::Copy::Recursive;
76 3 50       9931 File::Copy::Recursive::rcopy($self->_path, $dest) or return $self->_error("Can't copy $self to $dest");
77 3         1864 $self;
78             }
79             sub move_to {
80 4     4 0 11 my ($self, $dest) = @_;
81 4 100       12 unless ($dest) { return $self->_error("requires destination") }
  1         4  
82 3 100       9 $self->close if $self->is_open;
83 3         18 require File::Copy::Recursive;
84 3 50       6 File::Copy::Recursive::rmove($self->_path, $dest) or return $self->_error("Can't move $self to $dest");
85 3         1937 @{$self} = @{_new($dest)};
  3         9  
  3         10  
86 3         7 $self;
87             }
88             sub rename_to {
89 4     4 0 9 my ($self, $dest) = @_;
90 4 100       10 unless ($dest) { return $self->_error("requires destination") }
  1         3  
91 3 100       7 $self->close if $self->is_open;
92 3 50       6 rename $self->_path, $dest or return $self->_error("Can't rename $self to $dest");
93 3         5 @{$self} = @{_new($dest)};
  3         10  
  3         8  
94 3         5 $self;
95             }
96 2     2 0 15 sub stat { $_[0]->[0]->stat }
97 0     0 0 0 sub lstat { $_[0]->[0]->lstat }
98              
99             # polymorphic methods for ::File and ::Dir
100              
101             sub logger {
102 2     2 0 17 my $self = shift;
103 2 50       8 if (@_) { $self->[3] = $_[0] ? 0 : 1 }
  2 50       11  
104             }
105 4 50   4 0 4 sub log { my $self = shift; carp @_ unless $self->[3] }
  4         11  
106 4     4   12 sub _error { shift->log(@_); return }
  4         16  
107 6     6 1 43 sub basename { $_[0]->[0]->basename }
108             sub open {
109 72     72 0 134 my ($self, $mode) = @_;
110 72 100       177 $self->close if $self->is_open;
111 72 100       176 if (-d $self->_path) {
112 22 50       81 opendir my $dh, $self->_path or return $self->_error("Can't open $self: $!");
113 22         60 $self->[2] = $dh;
114             } else {
115 50 100 100     266 unless ($mode && $mode =~ /:/) {
116 49   100     220 $mode = IO::Handle::_open_mode_string($mode || 'r');
117             }
118 50 100       748 open my $fh, $mode, $self->_path or return $self->_error("Can't open $self: $!");
119 49         184 $self->[1] = $fh;
120             }
121 71         143 $self;
122             }
123             sub close {
124 80     80 0 1765 my $self = shift;
125 80         87 my $ret;
126 80 100       232 if ($self->[1]) {
127 51         1291 $ret = CORE::close $self->[1];
128 51         111 $self->[1] = undef;
129             }
130 80 100       245 if ($self->[2]) {
131 22         177 $ret = closedir $self->[2];
132 22         34 $self->[2] = undef;
133             }
134 80         137 $ret;
135             }
136             sub read {
137 98     98 0 392 my $self = shift;
138 98 100       192 if ($self->[1]) { return $self->[1]->read(@_) }
  1         8  
139 97 100       192 if ($self->[2]) { return readdir $self->[2] }
  95         614  
140             }
141             sub seek {
142 4     4 0 572 my $self = shift;
143 4 100       15 if ($self->[1]) { return seek $self->[1], shift, shift }
  1         7  
144 3 100 50     14 if ($self->[2]) { return seekdir $self->[2], shift || 0 }
  1         10  
145             }
146             sub tell {
147 8     8 0 277 my $self = shift;
148 8 100       24 if ($self->[1]) { return tell $self->[1] }
  1         5  
149 7 100       38 if ($self->[2]) { return telldir $self->[2] }
  5         32  
150             }
151             sub remove {
152 4     4 1 5355 my ($self, @args) = @_;
153 4 50       9 $self->close if $self->is_open;
154 4 50       11 return $self unless $self->exists;
155 4 50       10 if (-d $self->_path) {
    0          
156 4         37 require File::Path;
157 4 50       5 eval { File::Path::rmtree($self->_path, @args); 1 }
  4         16  
  4         20  
158             or return $self->_error($@);
159             }
160             elsif (-f $self->_path) {
161 0         0 CORE::unlink $self->_path;
162             }
163 4         20 $self;
164             }
165              
166             # ::File methods
167              
168 29     29 0 149 sub touch { $_[0]->[0]->touch; $_[0] }
  29         4716  
169              
170 4     4 0 311 sub openr { shift->open('r') }
171 2     2 0 15 sub openw { shift->open('w') }
172             sub sysopen {
173 2     2 0 3 my $self = shift;
174 2 100       4 $self->close if $self->is_open;
175 2 50       7 CORE::sysopen my $fh, $self->_path, @_ or return $self->_error("Can't open $self: $!");
176 2         5 $self->[1] = $fh;
177 2         6 $self;
178             }
179             sub binmode {
180 4     4 0 7 my $self = shift;
181 4 100       18 return unless $self->[1];
182 3 50       12 @_ ? CORE::binmode $self->[1], shift : CORE::binmode $self->[1];
183             }
184              
185             # IO methods
186             BEGIN {
187 22     22   33630 no strict 'refs';
  22         46  
  22         1954  
188 22     22   59 for my $method (qw(
189             print printf say getline getlines sysread write syswrite
190             autoflush flush printflush getc ungetc truncate blocking
191             eof fileno error sync fcntl ioctl
192             )) {
193             *$method = sub {
194 73     73   1257 my $self = shift;
195 73 100       1107 $self->[1] and $self->[1]->$method(@_);
196 462         46901 };
197             }
198             }
199              
200             sub lock_ex {
201 2     2 0 16 my $self = shift;
202 2         15 require Fcntl;
203 2 100       19 $self->[1] and flock $self->[1], Fcntl::LOCK_EX();
204             }
205              
206             sub lock_sh {
207 2     2 0 5 my $self = shift;
208 2         13 require Fcntl;
209 2 100       20 $self->[1] and flock $self->[1], Fcntl::LOCK_SH();
210             }
211              
212             sub sysseek {
213 2     2 0 5 my ($self, $pos, $whence) = @_;
214 2 100       21 $self->[1] and sysseek $self->[1], $pos, $whence;
215             }
216              
217             sub slurp {
218 12     12 1 29 my ($self, @args) = @_;
219 12         28 my $opts = _opts(@args);
220 12   50     57 my $iomode = $opts->{iomode} || 'r';
221 12         26 $self->open($iomode);
222 12 50       24 unless ($self->is_open) {
223 0         0 return $self->_error("Can't read $self: $!");
224             }
225 12 100       27 $self->binmode if $opts->{binmode};
226              
227 12         15 my @callbacks;
228             my $callback = sub {
229 15     15   20 my $line = shift;
230 15         20 for my $subr (@callbacks) { $line = $subr->(local $_ = $line) }
  7         48  
231 15         70 $line;
232 12         100 };
233 12 100       32 if ( $opts->{chomp} ) {
234 1     2   4 push @callbacks, sub { my $line = shift; chomp $line; $line };
  2         2  
  2         2  
  2         5  
235             }
236 12 100       30 if ( $opts->{decode} ) {
237 2         14 require Encode;
238             push @callbacks, sub {
239 2     2   8 Encode::decode( $opts->{decode}, shift )
240 2         9 };
241             }
242 12 100       29 if ( $opts->{callback} ) {
243 2         5 push @callbacks, $opts->{callback};
244             }
245 12         12 my $filter;
246 12 100       26 if (my $rule = $opts->{filter}) {
247 2         16 $filter = qr/$rule/;
248             }
249 12 50       23 $opts->{ignore_return_value} = 1 if !defined wantarray;
250              
251             # shortcut
252 12 100 66     48 if (!@callbacks and !$filter and !wantarray) {
      66        
253 5         5 my $got = do { local $/; $self->getline };
  5         17  
  5         13  
254 5         236 $self->close;
255 5         46 return $got;
256             }
257              
258 7         11 my @lines;
259 7         22 while(defined (my $line = $self->getline)) {
260 15         450 $line = $callback->($line);
261 15 100 100     75 next if $filter && $line !~ /$filter/;
262 12 50       38 push @lines, $line unless $opts->{ignore_return_value};
263             }
264 7         179 $self->close;
265 7 100       64 return wantarray ? @lines : join '', @lines;
266             }
267              
268             sub grep {
269 2     2 0 2587 my ($self, $rule, @args) = @_;
270 2         6 my $opts = _opts(@args);
271 2         6 $opts->{filter} = $rule;
272 2         5 $self->slurp($opts);
273             }
274              
275             sub save {
276 27     27 0 453 my ($self, $content, @args) = @_;
277 27         72 my $opts = _opts(@args);
278 27         214 my $path = $self->[0];
279 27 100       74 if ($opts->{mkdir}) {
280 4         22 $path->parent->mkpath;
281             }
282 27 50 33     1206 my $mode = $opts->{mode} || $opts->{append} ? '>>' : '>';
283 27         93 $self->open($mode);
284 27 50       62 unless ($self->is_open) {
285 0         0 return $self->_error("Can't save $self: $!");
286             }
287 27 50 33     95 if ($opts->{lock} && !$self->lock_ex) {
288 0         0 return $self->_error("Can't lock $self: $!");
289             }
290 27 100       83 $self->binmode if $opts->{binmode};
291              
292 27         87 my @callbacks;
293             my $callback = sub {
294 27     27   41 my $line = shift;
295 27         55 for my $subr (@callbacks) { $line = $subr->(local $_ = $line) }
  4         51  
296             $line
297 27         128 };
  27         145  
298 27 100       66 if ( $opts->{encode} ) {
299 2         555 require Encode;
300             push @callbacks, sub {
301 2     2   9 Encode::encode( $opts->{encode}, shift )
302 2         8675 };
303             }
304 27 100       64 if ( $opts->{callback} ) {
305 2         4 push @callbacks, $opts->{callback};
306             }
307              
308             $self->print(
309 27         53 map { $callback->($_) }
  0         0  
310 27 50       82 ref $content eq 'ARRAY' ? @{ $content } : $content
311             );
312 27         373 $self->close;
313              
314 27 100       68 if ($opts->{mtime}) {
315 1         7 $self->mtime($opts->{mtime});
316             }
317 27         189 $self;
318             }
319              
320             sub size {
321 24     24 0 60 my $self = shift;
322 24 100       53 return unless $self->exists;
323 23         92 $self->[0]->stat->size;
324             }
325              
326             sub mtime {
327 5     5 0 340 my $self = shift;
328 5 100       14 return unless $self->exists;
329 4 100       15 if (@_) {
330 2         9 utime $_[0], $_[0], $self->_path;
331             } else {
332 2         12 $self->[0]->stat->mtime;
333             }
334             }
335              
336             # ::Dir methods
337              
338             sub mkdir {
339 24     24 0 51 my $self = shift;
340 24 100       69 unless ($self->exists) {
341 23         167 require File::Path;
342 23 50       38 eval { File::Path::mkpath($self->_path); 1 }
  23         57  
  23         97  
343             or return $self->_error($@);
344             }
345 24         78 $self;
346             }
347             *mkpath = \&mkdir;
348              
349 2 100   2 0 21 sub rewind { $_[0]->[2] and rewinddir $_[0]->[2] }
350 4     4 0 2344 sub find { shift->_find(0, @_) }
351 2     2 0 921 sub find_dir { shift->_find(1, @_) }
352             sub _find {
353 7     7   25 my ($self, $is_dir, $rule, %opts) = @_;
354 7         661 require Text::Glob;
355 7         702 $rule = Text::Glob::glob_to_regex($rule);
356 7         569 my $iter = $self->[0]->iterator({recurse => 1});
357 7         164 my @paths;
358 7         15 while(my $path = $iter->()) {
359 12 100       1092 next unless ($is_dir ? -d $path : -f $path);
    100          
360 10         136 my $relpath = $path->relative($self->[0]);
361 10 100       803 next unless $relpath =~ /$rule/;
362 8 50       64 next if $relpath =~ m{/\.};
363 8         42 push @paths, _new($path);
364             }
365 7 100       649 if ($opts{callback}) {
366 2         9 @paths = $opts{callback}->(@paths);
367             }
368 7         83 @paths;
369             }
370             sub rmdir {
371 16     16 0 2015 my ($self, @args) = @_;
372 16 50       58 $self->close if $self->is_open;
373 16 100 66     52 if ($self->exists && -d $self->_path) {
374 15         106 require File::Path;
375 15 50       28 eval { File::Path::rmtree($self->_path, @args); 1 }
  15         43  
  15         124  
376             or return $self->_error($@);
377             }
378 16         221 $self;
379             }
380             *rmtree = \&rmdir;
381              
382             sub next {
383 5     5 0 176 my $self = shift;
384 5 100       12 $self->open unless $self->is_open;
385 5         10 my $next = $self->read;
386 5 100       10 unless (defined $next) {
387 1         2 $self->close;
388 1         2 return;
389             }
390 4         10 $self->[0]->child($next);
391             }
392             sub children {
393 18     18 1 38 my ($self, %opts) = @_;
394 18 50       30 $self->open or return;
395 18         19 my @children;
396 18         34 while (defined(my $path = $self->read)) {
397 69 100 100     319 next if (!$opts{all} && ($path eq '.' || $path eq '..'));
      33        
398 33         121 my $child = $self->[0]->child($path);
399 33 100 66     823 if ($opts{prune} or $opts{no_hidden}) {
400 25 50       174 if (ref $opts{prune} eq ref qr//) {
    50          
401 0 0       0 next if $path =~ /$opts{prune}/;
402             }
403 0     0   0 elsif (ref $opts{prune} eq ref sub {}) {
404 0 0       0 next if $opts{prune}->(_new($child));
405             }
406             else {
407 25 100       63 next if $path =~ /^\./;
408             }
409             }
410 30         89 push @children, _new($child);
411             }
412 18         79 $self->close;
413 18         94 return @children;
414             }
415              
416             sub recurse {
417 5     5 0 49 my $self = shift;
418             my %opts = (
419             preorder => 1,
420             depthfirst => 0,
421             prune => 1,
422 5 100 66 0   57 (@_ == 1 && ref $_[0] eq ref sub {}) ? (callback => $_[0]) : @_
  0         0  
423             );
424 5 50       19 my $callback = $opts{callback}
425             or Carp::croak "Must provide a 'callback' parameter to recurse()";
426 5         8 my @queue = ($self);
427              
428 5         6 my $visit_entry;
429             my $visit_dir =
430             $opts{depthfirst} && $opts{preorder}
431             ? sub {
432 5     5   5 my $dir = shift;
433 5         9 $callback->($dir);
434 5         102 unshift @queue, $dir->children( prune => $opts{prune} );
435             }
436             : $opts{preorder}
437             ? sub {
438 8     8   9 my $dir = shift;
439 8         16 $callback->($dir);
440 8         179 push @queue, $dir->children( prune => $opts{prune} );
441             }
442             : sub {
443 5     5   5 my $dir = shift;
444 5         13 $visit_entry->($_) for $dir->children( prune => $opts{prune} );
445 5         119 $callback->($dir);
446 5 100 100     45 };
    100          
447              
448             $visit_entry = sub {
449 35     35   93 my $entry = shift;
450 35 100       78 if ($entry->is_dir) { $visit_dir->($entry) }
  18         31  
451 17         34 else { $callback->($entry) }
452 5         19 };
453              
454 5         13 while (@queue) {
455 27         218 $visit_entry->( shift @queue );
456             }
457             }
458 0     0 0 0 sub volume { $_[0]->[0]->volume }
459             sub subsumes {
460 2     2 0 4 my ($self, $other) = @_;
461 2         44 $self->[0]->subsumes($other);
462             }
463              
464             sub contains {
465 0     0 0 0 my ($self, $other) = @_;
466 0   0     0 return !!(-d $self and (-e $other or -l $other) and $self->subsumes($other));
467             }
468              
469 41 100 66 41   209 sub _opts { @_ == 1 && ref $_[0] eq ref {} ? $_[0] : {@_} }
470              
471             sub AUTOLOAD {
472 0 0   0     return if $AUTOLOAD =~ /::DESTROY$/;
473 0           $AUTOLOAD =~ s/.*:://;
474 0           $AUTOLOAD =~ s/_pt$//;
475 0           my $self = shift;
476 0 0         if (ref $self) { $self->[0]->$AUTOLOAD(@_); }
  0            
477 0           else { Path::Tiny->$AUTOLOAD(@_) }
478             }
479              
480             1;
481              
482             __END__