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   522 use strict;
  26         26  
  26         550  
11 26     26   75 use warnings;
  26         31  
  26         496  
12              
13 26     26   361 use Filesys::POSIX::Error qw(throw);
  26         25  
  26         17864  
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 5313 my ( $class, $path ) = @_;
57 997         1887 my @components = split( /\//, $path );
58 997         829 my @ret;
59              
60 997 100 100     2258 if ( @components && _non_empty( $components[0] ) ) {
61 532         612 push @ret, $components[0];
62             }
63              
64 997 100       1503 if ( @components > 1 ) {
65 623 100       1062 push @ret, grep { _non_empty($_) && $_ ne '.' } @components[ 1 .. $#components ];
  1601         1482  
66             }
67              
68 997 100 100     1729 throw &Errno::EINVAL unless @components || _non_empty($path);
69              
70 996 100       1156 my @hier = _non_empty( $components[0] ) ? @ret : ( '', @ret );
71              
72 996 100 100     1926 if ( @hier == 1 && !_non_empty( $hier[0] ) ) {
73 35         48 @hier = ('/');
74             }
75              
76 996         2438 return bless \@hier, $class;
77             }
78              
79             sub _proxy {
80 1322     1322   1190 my ( $context, @args ) = @_;
81              
82 1322 100       2032 unless ( ref $context eq __PACKAGE__ ) {
83 26         62 return $context->new(@args);
84             }
85              
86 1296         1260 return $context;
87             }
88              
89             sub _non_empty {
90 4260     4260   3080 my ($string) = @_;
91              
92 4260 100       5281 return 0 unless defined $string;
93 3977 100       6364 return 0 if $string eq '';
94              
95 3039         7685 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 15 my $self = _proxy(@_);
110              
111 9         30 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 1307 my $self = _proxy(@_);
124 822         1145 my @hier = @$self;
125              
126 822         2588 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 311 my $self = _proxy(@_);
138 242         316 my @hier = @$self;
139              
140 242 100       389 if ( @hier > 1 ) {
141 143         256 my @parts = @hier[ 0 .. $#hier - 1 ];
142              
143 143 100 100     327 if ( @parts == 1 && !_non_empty( $parts[0] ) ) {
144 23         88 return '/';
145             }
146              
147 120         428 return join( '/', @parts );
148             }
149              
150 99 100       327 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 770 my ( $self, $ext ) = ( _proxy( @_[ 0 .. 1 ] ), $_[2] );
164 249         388 my @hier = @$self;
165              
166 249         271 my $name = $hier[$#hier];
167 249 100       279 $name =~ s/$ext$// if _non_empty($ext);
168              
169 249         563 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 656 my ($self) = @_;
181 1024         1371 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 26 my ( $self, @parts ) = @_;
193              
194 10 100       18 return push @$self, grep { $_ && $_ ne '.' } map { split /\// } @parts;
  13         40  
  10         17  
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 11 my ( $self, $path ) = @_;
207 7 100       38 $path = __PACKAGE__->new($path) unless ref $path eq __PACKAGE__;
208              
209 7 100       19 $path->push( grep { $_ && $_ ne '.' } $self->components );
  5         23  
210 7         16 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 16 my ( $self, $path ) = @_;
223 2 100       12 $path = __PACKAGE__->new($path) unless ref $path eq __PACKAGE__;
224              
225 2         3 $self->push( grep { $_ ne '.' } $path->components );
  6         9  
226 2         3 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 51 my ($self) = @_;
238 26         31 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 4121 my ($self) = @_;
249 1544         2738 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__