File Coverage

lib/Path/Extended/Tiny.pm
Criterion Covered Total %
statement 314 341 92.0
branch 157 200 78.5
condition 34 51 66.6
subroutine 75 86 87.2
pod 6 52 11.5
total 586 730 80.2


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