File Coverage

blib/lib/Path/Abstract/Underload.pm
Criterion Covered Total %
statement 188 194 96.9
branch 84 94 89.3
condition 38 44 86.3
subroutine 41 41 100.0
pod 0 24 0.0
total 351 397 88.4


line stmt bran cond sub pod time code
1             package Path::Abstract::Underload;
2              
3 5     5   25 use warnings;
  5         7  
  5         138  
4 5     5   15 use strict;
  5         5  
  5         281  
5              
6             =head1 NAME
7              
8             Path::Abstract::Underload - Path::Abstract without stringification overloading
9              
10             =head1 SYNOPSIS
11              
12             use Path::Abstract::Underload;
13              
14             my $path = Path::Abstract::Underload->new("/apple/banana");
15              
16             # $parent is "/apple"
17             my $parent = $path->parent;
18              
19             # $cherry is "/apple/banana/cherry.txt"
20             my $cherry = $path->child("cherry.txt");
21              
22             =cut
23              
24             use Sub::Exporter -setup => {
25             exports => [ path => sub { sub {
26 0         0 return __PACKAGE__->new(@_)
27 5         40 } } ],
  0         0  
28 5     5   19 };
  5         5  
29 5     5   1486 use Scalar::Util qw/blessed/;
  5         6  
  5         203  
30 5     5   17 use Carp;
  5         5  
  5         643  
31              
32             require Path::Abstract::Fast; # For now...
33              
34             =head1 DESCRIPTION
35              
36             This is a version of Path::Abstract without the magic "use overload ..." stringification.
37              
38             Unfortunately, without overloading, you can't do this:
39              
40             my $path = Path::Abstract::Underload->new("/a/path/to/somewhere");
41              
42             print "$path\n"; # Will print out something like "Path::Abstract::Underload=SCALAR(0xdffaa0)\n"
43              
44             You'll have to do this instead:
45              
46             print $path->get, "\n"; Will print out "/a/path/to/somewhere\n"
47             # Note, you can also use $path->stringify or $path->path
48              
49             # You could also do this (but it's safer to do one of the above):
50             print $$path, "\n";
51              
52             Or, just use L
53              
54             =head1 Documentation & usage
55              
56             See L for documentation & usage
57              
58             =cut
59              
60             sub new {
61 554     554 0 154448 my $path = "";
62 554         891 my $self = bless \$path, shift;
63 554         922 $self->set(@_);
64 554         1892 return $self;
65             }
66              
67             sub clone {
68 49     49 0 46 my $self = shift;
69 49         58 my $path = $$self;
70 49         165 return bless \$path, ref $self;
71             }
72              
73             sub _canonize(@) {
74 5     5   21 no warnings 'uninitialized';
  5         4  
  5         1601  
75             @_ = map {
76 1125 100 66     2225 $_ = ref && (ref eq "Path::Abstract::Underload" || blessed $_ && $_->isa("Path::Abstract::Underload")) ? $$_ : $_;
77 1125 100       2221 length() ? $_ : ();
78             } map {
79 786 100   786   919 ref eq "ARRAY" ? @$_ : $_
  1117         2359  
80             } @_;
81 786   100     2628 my $leading = $_[0] && substr($_[0], 0, 1) eq '/';
82 786         1092 my $path = join '/', @_;
83 786   100     1925 my $trailing = $path && substr($path, -1) eq '/';
84              
85             # From File::Spec::Unix::canonpath
86 786         979 $path =~ s|/{2,}|/|g; # xx////xx -> xx/xx
87 786         684 $path =~ s{(?:/\.)+(?:/|\z)}{/}g; # xx/././xx -> xx/xx
88 786 50       1380 $path =~ s|^(?:\./)+||s unless $path eq "./"; # ./xx -> xx
89 786         620 $path =~ s|^/(?:\.\./)+|/|; # /../../xx -> xx
90 786         580 $path =~ s|^/\.\.$|/|; # /.. -> /
91 786 100       1423 $path =~ s|/\z|| unless $path eq "/"; # xx/ -> xx
92 786 100 100     2419 $path .= '/' if $path ne "/" && $trailing;
93              
94 786 100       1136 $path =~ s/^\/+// unless $leading;
95 786         1659 return $path;
96             }
97              
98             sub set {
99 713     713 0 595 my $self = shift;
100 713         915 $$self = _canonize @_;
101 713         1160 return $self;
102             }
103              
104             sub is_empty {
105 525     525 0 394 my $self = shift;
106 525         1363 return $$self eq "";
107             }
108 5     5   23 for (qw(is_nil)) { no strict 'refs'; *$_ = \&is_empty }
  5         5  
  5         1062  
109              
110             sub is_root {
111 45     45 0 44 my $self = shift;
112 45         175 return $$self eq "/";
113             }
114              
115             sub is_tree {
116 30     30 0 34 my $self = shift;
117 30         172 return substr($$self, 0, 1) eq "/";
118             }
119              
120             sub is_branch {
121 19     19 0 18 my $self = shift;
122 19 100       64 Path::Abstract->_0_093_warn if $Path::Abstract::_0_093_warn;
123             # return $$self && substr($$self, 0, 1) ne "/";
124 19   100     501 return ! $$self || substr($$self, 0, 1) ne "/";
125             }
126              
127             sub to_tree {
128 11     11 0 13 my $self = shift;
129 11 100       17 $$self = "/$$self" unless $self->is_tree;
130 11         85 return $self;
131             }
132              
133             sub to_branch {
134 11     11 0 10 my $self = shift;
135 11         24 $$self =~ s/^\///;
136 11         89 return $self;
137             }
138              
139             sub list {
140 23     23 0 32 my $self = shift;
141 23 100       77 Path::Abstract->_0_093_warn if $Path::Abstract::_0_093_warn;
142 23         414 return grep { length $_ } split m/\//, $$self;
  49         164  
143             }
144 5     5   25 for (qw()) { no strict 'refs'; *$_ = \&list }
  5         12  
  5         1762  
145              
146             sub split {
147 4     4 0 5 my $self = shift;
148 4 50       9 Path::Abstract->_0_093_warn if $Path::Abstract::_0_093_warn;
149 4         21 my @split = split m/(?<=.)\/(?=.)/, $$self;
150 4         23 return @split;
151             }
152              
153             sub first {
154 19     19 0 25 my $self = shift;
155 19 100       58 Path::Abstract->_0_093_warn if $Path::Abstract::_0_093_warn;
156 19         328 return $self->at(0);
157             }
158              
159             sub last {
160 22     22 0 31 my $self = shift;
161 22 100       72 Path::Abstract->_0_093_warn if $Path::Abstract::_0_093_warn;
162 22         351 return $self->at(-1);
163             }
164              
165             sub at {
166 65     65 0 82 my $self = shift;
167 65 100       107 return '' if $self->is_empty;
168 54         176 my @path = split '/', $$self;
169 54 50 66     151 return '' if 1 == @path && '' eq $path[0];
170 54         54 my $index = shift;
171 54 100 100     195 if (0 > $index) {
    100          
172 25         27 $index += @path;
173             }
174             elsif (! defined $path[0] || ! length $path[0]) {
175 19         20 $index += 1
176             }
177 54 100       108 return '' if $index >= @path;
178 48 100 100     258 $index -= 1 if $index == @path - 1 && ! defined $path[$index] || ! length $path[$index];
      66        
179 48 100 66     171 return '' unless defined $path[$index] && length $path[$index];
180 45         1875 return $path[$index];
181             }
182              
183             sub beginning {
184 8     8 0 12 my $self = shift;
185 8         39 my ($beginning) = $$self =~ m{^(\/?[^/]*)};
186 8         29 return $beginning;
187             }
188              
189             sub ending {
190 38     38 0 43 my $self = shift;
191 38         163 my ($ending) = $$self =~ m{([^/]*\/?)$};
192 38         93 return $ending;
193             }
194              
195             sub get {
196 501     501 0 55195 my $self = shift;
197 501         4013 return $$self;
198             }
199 5     5   21 for (qw(path stringify)) { no strict 'refs'; *$_ = \&get }
  5         5  
  5         291  
200              
201             sub push {
202 73     73 0 85 my $self = shift;
203 73         111 $$self = _canonize $$self, @_;
204 73         252 return $self;
205             }
206 5     5   16 for (qw(down)) { no strict 'refs'; *$_ = \&push }
  5         4  
  5         2213  
207              
208             sub child {
209 15     15 0 1266 my $self = shift;
210 15         31 my $child = $self->clone;
211 15         36 return $child->push(@_);
212             }
213              
214             sub append {
215 10     10 0 19 my $self = shift;
216 10 50       21 return $self unless @_;
217 10         30 $self->set($$self . join '/', @_);
218 10         15 return $self;
219             }
220              
221             sub extension {
222 30     30 0 37 my $self = shift;
223              
224 30         37 my $extension;
225 30 50 66     140 if (@_ && ! defined $_[0]) {
    100          
226 0         0 $extension = '';
227             }
228             elsif (ref $_[0] eq '') {
229 26         30 $extension = shift;
230             }
231              
232 30         22 my $options;
233 30 100       64 if (ref $_[0] eq 'HASH') {
234 5         6 $options = shift;
235             }
236             else {
237 25         67 $options = { match => shift };
238             }
239              
240 30   100     78 my $matcher = $options->{match} || 1;
241 30 100       57 if ('*' eq $matcher) {
242 1         2 $matcher = '';
243             }
244 30 50 66     185 if (ref $matcher eq 'Regexp') {
    50          
245             }
246             elsif ($matcher eq '' || $matcher =~ m/^\d+$/) {
247 30         305 $matcher = qr/((?:\.[^\.]+){1,$matcher})$/;
248             }
249             else {
250 0         0 $matcher = qr/$matcher/;
251             }
252              
253 30         51 my $ending = $self->ending;
254 30 100       47 if (! defined $extension) {
255 10 100 100     16 return '' if $self->is_empty || $self->is_root;
256 8         88 return join '', $ending =~ $matcher;
257             }
258             else {
259 20 100       53 if ('' eq $extension) {
    100          
260             }
261             elsif ($extension !~ m/^\./) {
262 3         5 $extension = '.' . $extension;
263             }
264              
265 20 100 100     32 if ($self->is_empty || $self->is_root) {
266 6         11 $self->append($extension);
267             }
268             else {
269 14 100       105 if ($ending =~ s/$matcher/$extension/) {
270 13         23 $self->pop;
271 13         21 $self->push($ending);
272             }
273             else {
274 1         3 $self->append($extension);
275             }
276             }
277 20         89 return $self;
278             }
279            
280             }
281              
282             my %pop_re = (
283             '' => qr{(/)?([^/]+)(/)?$},
284             '$' => qr{(/)?([^/]+/?)()$},
285             );
286              
287             sub _pop {
288 190     190   151 my $self = shift;
289 190 50       208 return '' if $self->is_empty;
290 190         216 my $count = shift @_;
291 190 100       329 $count = 1 unless defined $count;
292 190         150 my ($greedy_lead, $re);
293 190 100       623 if ($count =~ s/([\^\$\*])$//) {
294 84 100       200 $greedy_lead = 1 if $1 ne '$';
295 84 100       179 $re = $pop_re{'$'} if $1 ne '^';
296             }
297 190 100       363 $re = $pop_re{''} unless $re;
298 190 100       285 $count = 1 unless length $count;
299              
300             {
301 190         156 my @popped;
  190         188  
302 5     5   21 no warnings 'uninitialized';
  5         5  
  5         1237  
303              
304 190         409 while ($count--) {
305 273 100       1722 if ($$self =~ s/$re//) {
306 262         363 my $popped = $2;
307 262 50       568 unshift(@popped, $popped) if $popped;
308 262 100       634 if (! length $$self) {
309 86 100       109 if ($greedy_lead) {
310 32         58 substr $popped[0], 0, 0, $1;
311             }
312             else {
313 54         95 $$self .= $1;
314             }
315 86         112 last;
316             }
317             }
318             else {
319 11         15 last;
320             }
321             }
322              
323 190         416 return \@popped;
324             }
325             }
326              
327             #my %pop_re = (
328             # '' => qr{(.)?([^/]+)/?$},
329             # '+' => qr{(.)?([^/]+)/?$},
330             # '*' => qr{(.)?([^/]+/?)$},
331             #);
332              
333             #sub _pop {
334             # my $self = shift;
335             # return '' if $self->is_empty;
336             # my $count = shift @_;
337             # $count = 1 unless defined $count;
338             # my ($greed, $greed_plus, $greed_star);
339             # if ($count =~ s/([+*])$//) {
340             # $greed = $1;
341             # if ($greed eq '+') { $greed_plus = 1 }
342             # else { $greed_star = 1 }
343             # }
344             # else {
345             # $greed = '';
346             # }
347             # my $re = $pop_re{$greed};
348             # $count = 1 unless length $count;
349             # my @popped;
350              
351             # while ($count--) {
352             # if ($$self =~ s/$re//) {
353             # my $popped = $2;
354             # unshift(@popped, $popped) if $popped;
355             # if ($1 && $1 eq '/' && ! length $$self) {
356             # if ($greed) {
357             # substr $popped[0], 0, 0, $1;
358             # }
359             # else {
360             # $$self = $1;
361             # }
362             # last;
363             # }
364             # elsif (! $$self) {
365             # last;
366             # }
367             # }
368             # }
369             # return \@popped;
370             #}
371              
372             sub pop {
373 150     150 0 479 my $self = shift;
374 150 100       213 return (ref $self)->new('') if $self->is_empty;
375 137         245 my $popped = $self->_pop(@_);
376 137         418 return (ref $self)->new(join '/', @$popped);
377             }
378              
379             sub up {
380 69     69 0 85 my $self = shift;
381 69 100       100 return $self if $self->is_empty;
382 53         107 $self->_pop(@_);
383 53         225 return $self;
384             }
385              
386             #sub up {
387             # my $self = shift;
388             # return $self if $self->is_empty;
389             # my $count = 1;
390             # $count = shift @_ if @_;
391             # while (! $self->is_empty && $count--) {
392             # if ($$self =~ s/(^|^\/|\/)([^\/]+)$//) {
393             # if ($1 && ! length $$self) {
394             # $$self = $1;
395             # last;
396             # }
397             # elsif (! $$self) {
398             # last;
399             # }
400             # }
401             # }
402             # return $self;
403             #}
404              
405             sub parent {
406 11     11 0 9 my $self = shift;
407 11         15 my $parent = $self->clone;
408 11         19 return $parent->up(1, @_);
409             }
410              
411             BEGIN {
412 5     5   22 no strict 'refs';
  5         4  
  5         491  
413 5     5   5 eval { require Path::Class };
  5         2007  
414 5 50       140053 if ($@) {
415 0         0 *dir = *file = sub { croak "Path::Class is not available" };
  0         0  
416             }
417             else {
418 5     1   30 *file = sub { return Path::Class::file(shift->get, @_) };
  1         7  
419 5     1   163 *dir = sub { return Path::Class::dir(shift->get, @_) };
  1         1134  
420             }
421             }
422              
423             1; # End of Path::Abstract::Underload