File Coverage

blib/lib/Filesys/POSIX/Path.pm
Criterion Covered Total %
statement 68 71 95.7
branch 34 36 94.4
condition 12 12 100.0
subroutine 16 17 94.1
pod 11 12 91.6
total 141 148 95.2


line stmt bran cond sub pod time code
1             # Copyright (c) 2014, cPanel, Inc.
2             # All rights reserved.
3             # http://cpanel.net/
4             #
5             # This is free software; you can redistribute it and/or modify it under the same
6             # terms as Perl itself. See the LICENSE file for further details.
7              
8             package Filesys::POSIX::Path;
9              
10 26     26   435 use strict;
  26         28  
  26         549  
11 26     26   75 use warnings;
  26         24  
  26         506  
12              
13 26     26   416 use Filesys::POSIX::Error qw(throw);
  26         26  
  26         17202  
14              
15             =head1 NAME
16              
17             Filesys::POSIX::Path - Pathname manipulation utility class
18              
19             =head1 SYNOPSIS
20              
21             use Filesys::POSIX::Path;
22              
23             my $path = Filesys::POSIX::Path->new('/foo/bar/baz');
24              
25             printf("%s\n", $path->basename); # outputs 'baz'
26             printf("%s\n", $path->dirname); # outputs '/foo/bar'
27              
28             # outputs '/foo/bar/../baz'
29             printf("%s\n", $path->full('/foo/./././bar/../baz'));
30              
31             =head1 DESCRIPTION
32              
33             This module provides an object-oriented approach to path cleanup and
34             introspection.
35              
36             =head1 CREATING AN OBJECT
37              
38             =over
39              
40             =item Cnew($path)>
41              
42             Creates a new path object.
43              
44             The path is split on the forward slash (/) character into tokens; empty and
45             redundant tokens are discarded. Enough context is kept to help the methods
46             implemented in this module determine the nature of the path; if it is relative
47             to root, prefixed with './', or relative to the "current working directory".
48             An C reference blessed into this package's namespace is returned upon
49             success. An EINVAL is thrown if the path provided is empty.
50              
51             =back
52              
53             =cut
54              
55             sub new {
56 997     997 1 5874 my ( $class, $path ) = @_;
57 997         1843 my @components = split( /\//, $path );
58 997         806 my @ret;
59              
60 997 100 100     2183 if ( @components && _non_empty( $components[0] ) ) {
61 532         635 push @ret, $components[0];
62             }
63              
64 997 100       1467 if ( @components > 1 ) {
65 623 100       991 push @ret, grep { _non_empty($_) && $_ ne '.' } @components[ 1 .. $#components ];
  1601         1475  
66             }
67              
68 997 100 100     1689 throw &Errno::EINVAL unless @components || _non_empty($path);
69              
70 996 100       1171 my @hier = _non_empty( $components[0] ) ? @ret : ( '', @ret );
71              
72 996 100 100     1866 if ( @hier == 1 && !_non_empty( $hier[0] ) ) {
73 35         51 @hier = ('/');
74             }
75              
76 996         2665 return bless \@hier, $class;
77             }
78              
79             sub _proxy {
80 1322     1322   1168 my ( $context, @args ) = @_;
81              
82 1322 100       2000 unless ( ref $context eq __PACKAGE__ ) {
83 26         68 return $context->new(@args);
84             }
85              
86 1296         1268 return $context;
87             }
88              
89             sub _non_empty {
90 4260     4260   3130 my ($string) = @_;
91              
92 4260 100       5306 return 0 unless defined $string;
93 3977 100       6253 return 0 if $string eq '';
94              
95 3039         7715 return 1;
96             }
97              
98             =head1 PATH INTROSPECTION
99              
100             =over
101              
102             =item C<$path-Ecomponents>
103              
104             Return a list of the components parsed at object construction time.
105              
106             =cut
107              
108             sub components {
109 9     9 1 19 my $self = _proxy(@_);
110              
111 9         26 return @$self;
112             }
113              
114             =item C<$path-Efull>
115              
116             Returns a string representation of the full path. This is the same as:
117              
118             join('/', @$path);
119              
120             =cut
121              
122             sub full {
123 822     822 1 1397 my $self = _proxy(@_);
124 822         1153 my @hier = @$self;
125              
126 822         2546 return join( '/', @$self );
127             }
128              
129             =item C<$path-Edirname>
130              
131             Returns a string representation of all of the leading path elements, of course
132             save for the final path element.
133              
134             =cut
135              
136             sub dirname {
137 242     242 1 321 my $self = _proxy(@_);
138 242         319 my @hier = @$self;
139              
140 242 100       376 if ( @hier > 1 ) {
141 143         267 my @parts = @hier[ 0 .. $#hier - 1 ];
142              
143 143 100 100     298 if ( @parts == 1 && !_non_empty( $parts[0] ) ) {
144 23         84 return '/';
145             }
146              
147 120         425 return join( '/', @parts );
148             }
149              
150 99 100       315 return $hier[0] eq '/' ? '/' : '.';
151             }
152              
153             =item C<$path-Ebasename>
154              
155             =item C<$path-Ebasename($ext)>
156              
157             Returns the final path component. If called with an extension, then the method
158             will return the path component with the extension chopped off, if found.
159              
160             =cut
161              
162             sub basename {
163 249     249 1 774 my ( $self, $ext ) = ( _proxy( @_[ 0 .. 1 ] ), $_[2] );
164 249         397 my @hier = @$self;
165              
166 249         273 my $name = $hier[$#hier];
167 249 100       287 $name =~ s/$ext$// if _non_empty($ext);
168              
169 249         583 return $name;
170             }
171              
172             =item C<$path-Eshift>
173              
174             Useful for iterating over the components of the path object. Shifts the
175             internal start-of-array pointer by one, and returns the previous first value.
176              
177             =cut
178              
179             sub shift {
180 1024     1024 1 711 my ($self) = @_;
181 1024         1379 return shift @$self;
182             }
183              
184             =item C<$path-Epush(@parts)>
185              
186             Push new components onto the current path object. Each part will be tokenized
187             on the forward slash (/) character, and useless items will be discarded.
188              
189             =cut
190              
191             sub push {
192 10     10 1 25 my ( $self, @parts ) = @_;
193              
194 10 100       24 return push @$self, grep { $_ && $_ ne '.' } map { split /\// } @parts;
  13         54  
  10         19  
195             }
196              
197             =item C<$path-Econcat($pathname)>
198              
199             A new C object is created based on $pathname, and the
200             current path object's non-empty components are pushed onto that new instance.
201             The new path object is returned.
202              
203             =cut
204              
205             sub concat {
206 7     7 1 15 my ( $self, $path ) = @_;
207 7 100       34 $path = __PACKAGE__->new($path) unless ref $path eq __PACKAGE__;
208              
209 7 100       15 $path->push( grep { $_ && $_ ne '.' } $self->components );
  5         21  
210 7         19 return $path;
211             }
212              
213             =item C<$path-Econcat($pathname)>
214              
215             A new C object is created based on C<$pathname>, and the
216             new path object's non-empty components are pushed onto the current path object.
217             The current C<$path> reference is then returned.
218              
219             =cut
220              
221             sub append {
222 2     2 0 13 my ( $self, $path ) = @_;
223 2 100       10 $path = __PACKAGE__->new($path) unless ref $path eq __PACKAGE__;
224              
225 2         4 $self->push( grep { $_ ne '.' } $path->components );
  6         9  
226 2         4 return $self;
227             }
228              
229             =item C<$path-Epop>
230              
231             Pops the final path component off of the path object list, and returns that
232             value.
233              
234             =cut
235              
236             sub pop {
237 26     26 1 52 my ($self) = @_;
238 26         32 return pop @$self;
239             }
240              
241             =item C<$path-Ecount>
242              
243             Returns the number of components in the current path object.
244              
245             =cut
246              
247             sub count {
248 1544     1544 1 4566 my ($self) = @_;
249 1544         2761 return scalar @$self;
250             }
251              
252             =item C<$path-Eis_absolute>
253              
254             Returns true if the current path object represents an absolute path.
255              
256             =cut
257              
258             sub is_absolute {
259 0     0 1   my ($self) = @_;
260              
261 0 0         return 1 unless _non_empty( $self->[0] );
262 0           return 0;
263             }
264              
265             =back
266              
267             =cut
268              
269             1;
270              
271             __END__