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 25     25   494 use strict;
  25         30  
  25         789  
11 25     25   97 use warnings;
  25         40  
  25         596  
12              
13 25     25   397 use Filesys::POSIX::Error qw(throw);
  25         26  
  25         19481  
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 975     975 1 6834 my ( $class, $path ) = @_;
57 975         2449 my @components = split( /\//, $path );
58 975         1091 my @ret;
59              
60 975 100 100     2449 if ( @components && _non_empty( $components[0] ) ) {
61 514         668 push @ret, $components[0];
62             }
63              
64 975 100       1619 if ( @components > 1 ) {
65 618 100       1136 push @ret, grep { _non_empty($_) && $_ ne '.' } @components[ 1 .. $#components ];
  1571         1678  
66             }
67              
68 975 100 100     1841 throw &Errno::EINVAL unless @components || _non_empty($path);
69              
70 974 100       1324 my @hier = _non_empty( $components[0] ) ? @ret : ( '', @ret );
71              
72 974 100 100     2023 if ( @hier == 1 && !_non_empty( $hier[0] ) ) {
73 34         46 @hier = ('/');
74             }
75              
76 974         3374 return bless \@hier, $class;
77             }
78              
79             sub _proxy {
80 1298     1298   1362 my ( $context, @args ) = @_;
81              
82 1298 100       2358 unless ( ref $context eq __PACKAGE__ ) {
83 23         101 return $context->new(@args);
84             }
85              
86 1275         1452 return $context;
87             }
88              
89             sub _non_empty {
90 4168     4168   3371 my ($string) = @_;
91              
92 4168 100       5880 return 0 unless defined $string;
93 3890 100       7222 return 0 if $string eq '';
94              
95 2959         8343 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 8     8 1 13 my $self = _proxy(@_);
110              
111 8         19 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 807     807 1 1464 my $self = _proxy(@_);
124 807         1255 my @hier = @$self;
125              
126 807         2756 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 238     238 1 352 my $self = _proxy(@_);
138 238         353 my @hier = @$self;
139              
140 238 100       416 if ( @hier > 1 ) {
141 144         336 my @parts = @hier[ 0 .. $#hier - 1 ];
142              
143 144 100 100     350 if ( @parts == 1 && !_non_empty( $parts[0] ) ) {
144 23         106 return '/';
145             }
146              
147 121         571 return join( '/', @parts );
148             }
149              
150 94 100       387 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 245     245 1 823 my ( $self, $ext ) = ( _proxy( @_[ 0 .. 1 ] ), $_[2] );
164 245         480 my @hier = @$self;
165              
166 245         302 my $name = $hier[$#hier];
167 245 100       336 $name =~ s/$ext$// if _non_empty($ext);
168              
169 245         653 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 974     974 1 795 my ($self) = @_;
181 974         1547 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 9     9 1 16 my ( $self, @parts ) = @_;
193              
194 9 100       16 return push @$self, grep { $_ && $_ ne '.' } map { split /\// } @parts;
  13         43  
  10         16  
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 6     6 1 14 my ( $self, $path ) = @_;
207 6 100       31 $path = __PACKAGE__->new($path) unless ref $path eq __PACKAGE__;
208              
209 6 100       16 $path->push( grep { $_ && $_ ne '.' } $self->components );
  5         20  
210 6         15 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 7 my ( $self, $path ) = @_;
223 2 100       9 $path = __PACKAGE__->new($path) unless ref $path eq __PACKAGE__;
224              
225 2         5 $self->push( grep { $_ ne '.' } $path->components );
  6         13  
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 54 my ($self) = @_;
238 26         36 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 1475     1475 1 5021 my ($self) = @_;
249 1475         3067 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__