File Coverage

lib/Path/Extended/Tiny.pm
Criterion Covered Total %
statement 311 337 92.2
branch 155 198 78.2
condition 32 48 66.6
subroutine 74 84 88.1
pod 6 52 11.5
total 578 719 80.3


line stmt bran cond sub pod time code
1             package Path::Extended::Tiny;
2              
3 22     22   386913 use strict;
  22         48  
  22         867  
4 22     22   100 use warnings;
  22         29  
  22         573  
5 22     22   91 use Carp;
  22         35  
  22         1708  
6 22     22   16499 use Path::Tiny ();
  22         292145  
  22         628  
7 22     22   176 use Scalar::Util ();
  22         36  
  22         503  
8 22     22   91 use Exporter 5.57 qw/import/;
  22         497  
  22         3240  
9              
10             our $VERSION = '0.06';
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   593 '""' => sub { shift->_path },
18 2     2   46 'cmp' => sub { return "$_[0]" cmp "$_[1]" },
19 59     59   446 'bool' => sub { shift->_boolify },
20 22     22   121 '*{}' => sub { shift->_handle };
  22     1   35  
  22         245  
  1         7  
21              
22 0     0 0 0 sub new { shift; _new(@_) }
  0         0  
23              
24             sub _new {
25 227     227   89985 my $path;
26 227 100       560 if (ref $_[0] eq __PACKAGE__) {
27 35         71 $path = shift->[0];
28 35 50       83 if (@_) {
29 35         104 my $new = Path::Tiny::path(@_);
30 35 100       735 if ($new->is_absolute) {
31 1         22 $path = $new;
32             } else {
33 34         852 $path = $path->child($new);
34             }
35             }
36             } else {
37 192         533 $path = Path::Tiny::path(@_);
38             }
39 227         5003 bless [$path], __PACKAGE__;
40             }
41              
42 59     59   111 sub _boolify { 1 }
43 3     3   716 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 959 sub path { $_[0]->[0]->stringify }
50 0     0 0 0 sub stringify { $_[0]->[0]->stringify }
51 32     32 1 154 sub parent { _new($_[0]->[0]->parent) }
52 513     513   26493 sub _path { $_[0]->[0][0] }
53              
54 35 100   35 0 62 sub is_dir { -d $_[0]->_path ? 1 : 0 }
55 178 100 100 178 0 1548 sub is_open { $_[0]->[1] || $_[0]->[2] ? 1 : 0 }
56 7     7 0 937 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 3048 sub absolute { $_[0]->[0]->absolute }
59             sub relative {
60 41     41 0 61 my $self = shift;
61 41 100       90 my $base = @_ == 1 ? shift : {@_}->{base};
62 41         142 $self->[0]->relative($base);
63             }
64             sub unlink {
65 31     31 0 13924 my $self = shift;
66 31 100       238 $self->close if $self->is_open;
67 31 100       62 unlink $self->_path if $self->exists;
68             }
69 159 100   159 0 16818 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 19 my ($self, $dest) = @_;
74 4 100       15 unless ($dest) { return $self->_error("requires destination") }
  1         3  
75 3         1376 require File::Copy::Recursive;
76 3 50       11993 File::Copy::Recursive::rcopy($self->_path, $dest) or return $self->_error("Can't copy $self to $dest");
77 3         2088 $self;
78             }
79             sub move_to {
80 4     4 0 12 my ($self, $dest) = @_;
81 4 100       13 unless ($dest) { return $self->_error("requires destination") }
  1         3  
82 3 100       8 $self->close if $self->is_open;
83 3         21 require File::Copy::Recursive;
84 3 50       7 File::Copy::Recursive::rmove($self->_path, $dest) or return $self->_error("Can't move $self to $dest");
85 3         2320 @{$self} = @{_new($dest)};
  3         9  
  3         10  
86 3         8 $self;
87             }
88             sub rename_to {
89 4     4 0 13 my ($self, $dest) = @_;
90 4 100       12 unless ($dest) { return $self->_error("requires destination") }
  1         3  
91 3 100       8 $self->close if $self->is_open;
92 3 50       9 rename $self->_path, $dest or return $self->_error("Can't rename $self to $dest");
93 3         8 @{$self} = @{_new($dest)};
  3         12  
  3         8  
94 3         9 $self;
95             }
96 2     2 0 14 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 11 my $self = shift;
103 2 50       10 if (@_) { $self->[3] = $_[0] ? 0 : 1 }
  2 50       11  
104             }
105 4 50   4 0 5 sub log { my $self = shift; carp @_ unless $self->[3] }
  4         13  
106 4     4   10 sub _error { shift->log(@_); return }
  4         14  
107 6     6 1 49 sub basename { $_[0]->[0]->basename }
108             sub open {
109 72     72 0 129 my ($self, $mode) = @_;
110 72 100       157 $self->close if $self->is_open;
111 72 100       171 if (-d $self->_path) {
112 22 50       96 opendir my $dh, $self->_path or return $self->_error("Can't open $self: $!");
113 22         67 $self->[2] = $dh;
114             } else {
115 50 100 100     273 unless ($mode && $mode =~ /:/) {
116 49   100     218 $mode = IO::Handle::_open_mode_string($mode || 'r');
117             }
118 50 100       777 open my $fh, $mode, $self->_path or return $self->_error("Can't open $self: $!");
119 49         178 $self->[1] = $fh;
120             }
121 71         138 $self;
122             }
123             sub close {
124 80     80 0 2461 my $self = shift;
125 80         75 my $ret;
126 80 100       227 if ($self->[1]) {
127 51         1190 $ret = CORE::close $self->[1];
128 51         99 $self->[1] = undef;
129             }
130 80 100       241 if ($self->[2]) {
131 22         173 $ret = closedir $self->[2];
132 22         33 $self->[2] = undef;
133             }
134 80         133 $ret;
135             }
136             sub read {
137 98     98 0 382 my $self = shift;
138 98 100       204 if ($self->[1]) { return $self->[1]->read(@_) }
  1         9  
139 97 100       212 if ($self->[2]) { return readdir $self->[2] }
  95         641  
140             }
141             sub seek {
142 4     4 0 657 my $self = shift;
143 4 100       16 if ($self->[1]) { return seek $self->[1], shift, shift }
  1         6  
144 3 100 50     18 if ($self->[2]) { return seekdir $self->[2], shift || 0 }
  1         17  
145             }
146             sub tell {
147 8     8 0 334 my $self = shift;
148 8 100       26 if ($self->[1]) { return tell $self->[1] }
  1         4  
149 7 100       27 if ($self->[2]) { return telldir $self->[2] }
  5         26  
150             }
151             sub remove {
152 4     4 1 5765 my ($self, @args) = @_;
153 4 50       13 $self->close if $self->is_open;
154 4 50       12 return $self unless $self->exists;
155 4 50       16 if (-d $self->_path) {
    0          
156 4         32 require File::Path;
157 4 50       8 eval { File::Path::rmtree($self->_path, @args); 1 }
  4         10  
  4         18  
158             or return $self->_error($@);
159             }
160             elsif (-f $self->_path) {
161 0         0 CORE::unlink $self->_path;
162             }
163 4         26 $self;
164             }
165              
166             # ::File methods
167              
168 29     29 0 149 sub touch { $_[0]->[0]->touch; $_[0] }
  29         5034  
169              
170 4     4 0 424 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       6 CORE::sysopen my $fh, $self->_path, @_ or return $self->_error("Can't open $self: $!");
176 2         5 $self->[1] = $fh;
177 2         7 $self;
178             }
179             sub binmode {
180 4     4 0 7 my $self = shift;
181 4 100       17 return unless $self->[1];
182 3 50       11 @_ ? CORE::binmode $self->[1], shift : CORE::binmode $self->[1];
183             }
184              
185             # IO methods
186             BEGIN {
187 22     22   31761 no strict 'refs';
  22         44  
  22         1967  
188 22     22   57 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   1616 my $self = shift;
195 73 100       1062 $self->[1] and $self->[1]->$method(@_);
196 462         45278 };
197             }
198             }
199              
200             sub lock_ex {
201 2     2 0 21 my $self = shift;
202 2         16 require Fcntl;
203 2 100       24 $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         11 require Fcntl;
209 2 100       17 $self->[1] and flock $self->[1], Fcntl::LOCK_SH();
210             }
211              
212             sub sysseek {
213 2     2 0 4 my ($self, $pos, $whence) = @_;
214 2 100       18 $self->[1] and sysseek $self->[1], $pos, $whence;
215             }
216              
217             sub slurp {
218 12     12 1 31 my ($self, @args) = @_;
219 12         27 my $opts = _opts(@args);
220 12   50     51 my $iomode = $opts->{iomode} || 'r';
221 12         30 $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       36 $self->binmode if $opts->{binmode};
226              
227 12         11 my @callbacks;
228             my $callback = sub {
229 15     15   18 my $line = shift;
230 15         24 for my $subr (@callbacks) { $line = $subr->(local $_ = $line) }
  7         41  
231 15         67 $line;
232 12         53 };
233 12 100       28 if ( $opts->{chomp} ) {
234 1     2   2 push @callbacks, sub { my $line = shift; chomp $line; $line };
  2         4  
  2         6  
  2         6  
235             }
236 12 100       33 if ( $opts->{decode} ) {
237 2         11 require Encode;
238             push @callbacks, sub {
239 2     2   9 Encode::decode( $opts->{decode}, shift )
240 2         6 };
241             }
242 12 100       23 if ( $opts->{callback} ) {
243 2         4 push @callbacks, $opts->{callback};
244             }
245 12         15 my $filter;
246 12 100       24 if (my $rule = $opts->{filter}) {
247 2         15 $filter = qr/$rule/;
248             }
249 12 50       24 $opts->{ignore_return_value} = 1 if !defined wantarray;
250              
251             # shortcut
252 12 100 66     45 if (!@callbacks and !$filter and !wantarray) {
      66        
253 5         6 my $got = do { local $/; $self->getline };
  5         14  
  5         11  
254 5         186 $self->close;
255 5         38 return $got;
256             }
257              
258 7         6 my @lines;
259 7         20 while(defined (my $line = $self->getline)) {
260 15         395 $line = $callback->($line);
261 15 100 100     52 next if $filter && $line !~ /$filter/;
262 12 50       36 push @lines, $line unless $opts->{ignore_return_value};
263             }
264 7         184 $self->close;
265 7 100       64 return wantarray ? @lines : join '', @lines;
266             }
267              
268             sub grep {
269 2     2 0 2180 my ($self, $rule, @args) = @_;
270 2         5 my $opts = _opts(@args);
271 2         4 $opts->{filter} = $rule;
272 2         5 $self->slurp($opts);
273             }
274              
275             sub save {
276 27     27 0 642 my ($self, $content, @args) = @_;
277 27         78 my $opts = _opts(@args);
278 27         218 my $path = $self->[0];
279 27 100       71 if ($opts->{mkdir}) {
280 4         21 $path->parent->mkpath;
281             }
282 27 50 33     1325 my $mode = $opts->{mode} || $opts->{append} ? '>>' : '>';
283 27         73 $self->open($mode);
284 27 50       78 unless ($self->is_open) {
285 0         0 return $self->_error("Can't save $self: $!");
286             }
287 27 50 33     96 if ($opts->{lock} && !$self->lock_ex) {
288 0         0 return $self->_error("Can't lock $self: $!");
289             }
290 27 100       90 $self->binmode if $opts->{binmode};
291              
292 27         78 my @callbacks;
293             my $callback = sub {
294 27     27   45 my $line = shift;
295 27         51 for my $subr (@callbacks) { $line = $subr->(local $_ = $line) }
  4         39  
296             $line
297 27         121 };
  27         135  
298 27 100       61 if ( $opts->{encode} ) {
299 2         620 require Encode;
300             push @callbacks, sub {
301 2     2   9 Encode::encode( $opts->{encode}, shift )
302 2         7674 };
303             }
304 27 100       63 if ( $opts->{callback} ) {
305 2         4 push @callbacks, $opts->{callback};
306             }
307              
308             $self->print(
309 27         43 map { $callback->($_) }
  0         0  
310 27 50       81 ref $content eq 'ARRAY' ? @{ $content } : $content
311             );
312 27         346 $self->close;
313              
314 27 100       67 if ($opts->{mtime}) {
315 1         5 $self->mtime($opts->{mtime});
316             }
317 27         166 $self;
318             }
319              
320             sub size {
321 24     24 0 65 my $self = shift;
322 24 100       75 return unless $self->exists;
323 23         87 $self->[0]->stat->size;
324             }
325              
326             sub mtime {
327 5     5 0 409 my $self = shift;
328 5 100       9 return unless $self->exists;
329 4 100       11 if (@_) {
330 2         6 utime $_[0], $_[0], $self->_path;
331             } else {
332 2         9 $self->[0]->stat->mtime;
333             }
334             }
335              
336             # ::Dir methods
337              
338             sub mkdir {
339 24     24 0 52 my $self = shift;
340 24 100       69 unless ($self->exists) {
341 23         303 require File::Path;
342 23 50       38 eval { File::Path::mkpath($self->_path); 1 }
  23         49  
  23         85  
343             or return $self->_error($@);
344             }
345 24         63 $self;
346             }
347             *mkpath = \&mkdir;
348              
349 2 100   2 0 21 sub rewind { $_[0]->[2] and rewinddir $_[0]->[2] }
350 4     4 0 1408 sub find { shift->_find(0, @_) }
351 2     2 0 522 sub find_dir { shift->_find(1, @_) }
352             sub _find {
353 7     7   18 my ($self, $is_dir, $rule, %opts) = @_;
354 7         503 require Text::Glob;
355 7         628 $rule = Text::Glob::glob_to_regex($rule);
356 7         492 my $iter = $self->[0]->iterator({recurse => 1});
357 7         145 my @paths;
358 7         12 while(my $path = $iter->()) {
359 12 100       1130 next unless ($is_dir ? -d $path : -f $path);
    100          
360 10         128 my $relpath = $path->relative($self->[0]);
361 10 100       734 next unless $relpath =~ /$rule/;
362 8 50       55 next if $relpath =~ m{/\.};
363 8         29 push @paths, _new($path);
364             }
365 7 100       552 if ($opts{callback}) {
366 2         7 @paths = $opts{callback}->(@paths);
367             }
368 7         73 @paths;
369             }
370             sub rmdir {
371 16     16 0 2395 my ($self, @args) = @_;
372 16 50       53 $self->close if $self->is_open;
373 16 100 66     41 if ($self->exists && -d $self->_path) {
374 15         104 require File::Path;
375 15 50       27 eval { File::Path::rmtree($self->_path, @args); 1 }
  15         38  
  15         96  
376             or return $self->_error($@);
377             }
378 16         151 $self;
379             }
380             *rmtree = \&rmdir;
381              
382             sub next {
383 5     5 0 297 my $self = shift;
384 5 100       10 $self->open unless $self->is_open;
385 5         12 my $next = $self->read;
386 5 100       14 unless (defined $next) {
387 1         4 $self->close;
388 1         3 return;
389             }
390 4         16 $self->[0]->child($next);
391             }
392             sub children {
393 18     18 1 37 my ($self, %opts) = @_;
394 18 50       40 $self->open or return;
395 18         33 my @children;
396 18         36 while (defined(my $path = $self->read)) {
397 69 100 100     323 next if (!$opts{all} && ($path eq '.' || $path eq '..'));
      33        
398 33         85 my $child = $self->[0]->child($path);
399 33 100 66     857 if ($opts{prune} or $opts{no_hidden}) {
400 25 50       146 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       64 next if $path =~ /^\./;
408             }
409             }
410 30         89 push @children, _new($child);
411             }
412 18         34 $self->close;
413 18         99 return @children;
414             }
415              
416             sub recurse {
417 5     5 0 48 my $self = shift;
418 5         29 my %opts = (preorder => 1, depthfirst => 0, prune => 1, @_);
419 5 50       19 my $callback = $opts{callback}
420             or Carp::croak "Must provide a 'callback' parameter to recurse()";
421 5         9 my @queue = ($self);
422              
423 5         6 my $visit_entry;
424             my $visit_dir =
425             $opts{depthfirst} && $opts{preorder}
426             ? sub {
427 5     5   13 my $dir = shift;
428 5         10 $callback->($dir);
429 5         108 unshift @queue, $dir->children( prune => $opts{prune} );
430             }
431             : $opts{preorder}
432             ? sub {
433 8     8   10 my $dir = shift;
434 8         16 $callback->($dir);
435 8         200 push @queue, $dir->children( prune => $opts{prune} );
436             }
437             : sub {
438 5     5   6 my $dir = shift;
439 5         12 $visit_entry->($_) for $dir->children( prune => $opts{prune} );
440 5         107 $callback->($dir);
441 5 100 100     50 };
    100          
442              
443             $visit_entry = sub {
444 35     35   92 my $entry = shift;
445 35 100       59 if ($entry->is_dir) { $visit_dir->($entry) }
  18         35  
446 17         38 else { $callback->($entry) }
447 5         18 };
448              
449 5         21 while (@queue) {
450 27         215 $visit_entry->( shift @queue );
451             }
452             }
453 0     0 0 0 sub volume { $_[0]->[0]->volume }
454             sub subsumes {
455 2     2 0 3 my ($self, $other) = @_;
456 2         47 $self->[0]->subsumes($other);
457             }
458              
459             sub contains {
460 0     0 0 0 my ($self, $other) = @_;
461 0   0     0 return !!(-d $self and (-e $other or -l $other) and $self->subsumes($other));
462             }
463              
464 41 100 66 41   198 sub _opts { @_ == 1 && ref $_[0] eq ref {} ? $_[0] : {@_} }
465              
466             sub AUTOLOAD {
467 0 0   0     return if $AUTOLOAD =~ /::DESTROY$/;
468 0           $AUTOLOAD =~ s/.*:://;
469 0           $AUTOLOAD =~ s/_pt$//;
470 0           my $self = shift;
471 0 0         if (ref $self) { $self->[0]->$AUTOLOAD(@_); }
  0            
472 0           else { Path::Tiny->$AUTOLOAD(@_) }
473             }
474              
475             1;
476              
477             __END__