File Coverage

blib/lib/Path/Tiny.pm
Criterion Covered Total %
statement 668 717 93.1
branch 390 476 81.9
condition 152 212 71.7
subroutine 104 106 98.1
pod 63 64 98.4
total 1377 1575 87.4


line stmt bran cond sub pod time code
1 29     29   2565534 use 5.008001;
  29         418  
2 29     29   170 use strict;
  29         58  
  29         699  
3 29     29   157 use warnings;
  29         53  
  29         2096  
4              
5             package Path::Tiny;
6             # ABSTRACT: File path utility
7              
8             our $VERSION = '0.144';
9              
10             # Dependencies
11 29     29   195 use Config;
  29         55  
  29         1586  
12 29     29   198 use Exporter 5.57 (qw/import/);
  29         412  
  29         1062  
13 29     29   229 use File::Spec 0.86 (); # shipped with 5.8.1
  29         413  
  29         654  
14 29     29   147 use Carp ();
  29         68  
  29         2305  
15              
16             our @EXPORT = qw/path/;
17             our @EXPORT_OK = qw/cwd rootdir tempfile tempdir/;
18              
19             use constant {
20 29         4962 PATH => 0,
21             CANON => 1,
22             VOL => 2,
23             DIR => 3,
24             FILE => 4,
25             TEMP => 5,
26             IS_WIN32 => ( $^O eq 'MSWin32' ),
27 29     29   202 };
  29         59  
28              
29             use overload (
30             q{""} => 'stringify',
31             bool => sub () { 1 },
32 29         191 fallback => 1,
33 29     29   4088 );
  29         3218  
34              
35             # FREEZE/THAW per Sereal/CBOR/Types::Serialiser protocol
36 2     2 0 7 sub THAW { return path( $_[2] ) }
37 29     29   3822 { no warnings 'once'; *TO_JSON = *FREEZE = \&stringify };
  29         60  
  29         15980  
38              
39             my $HAS_UU; # has Unicode::UTF8; lazily populated
40              
41             sub _check_UU {
42 4     4   27 local $SIG{__DIE__}; # prevent outer handler from being called
43 4         13 !!eval {
44 4         1085 require Unicode::UTF8;
45 1         819 Unicode::UTF8->VERSION(0.58);
46 1         12 1;
47             };
48             }
49              
50             my $HAS_PU; # has PerlIO::utf8_strict; lazily populated
51              
52             sub _check_PU {
53 4     4   2537 local $SIG{__DIE__}; # prevent outer handler from being called
54 4         13 !!eval {
55             # MUST preload Encode or $SIG{__DIE__} localization fails
56             # on some Perl 5.8.8 (maybe other 5.8.*) compiled with -O2.
57 4         1265 require Encode;
58 4         45546 require PerlIO::utf8_strict;
59 0         0 PerlIO::utf8_strict->VERSION(0.003);
60 0         0 1;
61             };
62             }
63              
64             my $HAS_FLOCK = $Config{d_flock} || $Config{d_fcntl_can_lock} || $Config{d_lockf};
65              
66             # notions of "root" directories differ on Win32: \\server\dir\ or C:\ or \
67             my $SLASH = qr{[\\/]};
68             my $NOTSLASH = qr{[^\\/]};
69             my $DRV_VOL = qr{[a-z]:}i;
70             my $UNC_VOL = qr{$SLASH $SLASH $NOTSLASH+ $SLASH $NOTSLASH+}x;
71             my $WIN32_ROOT = qr{(?: $UNC_VOL $SLASH | $DRV_VOL $SLASH | $SLASH )}x;
72              
73             sub _win32_vol {
74 0     0   0 my ( $path, $drv ) = @_;
75 0         0 require Cwd;
76 0         0 my $dcwd = eval { Cwd::getdcwd($drv) }; # C: -> C:\some\cwd
  0         0  
77             # getdcwd on non-existent drive returns empty string
78             # so just use the original drive Z: -> Z:
79 0 0 0     0 $dcwd = "$drv" unless defined $dcwd && length $dcwd;
80             # normalize dwcd to end with a slash: might be C:\some\cwd or D:\ or Z:
81 0         0 $dcwd =~ s{$SLASH?\z}{/};
82             # make the path absolute with dcwd
83 0         0 $path =~ s{^$DRV_VOL}{$dcwd};
84 0         0 return $path;
85             }
86              
87             # This is a string test for before we have the object; see is_rootdir for well-formed
88             # object test
89             sub _is_root {
90 2463     2463   6185 return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT\z/ ) : ( $_[0] eq '/' );
91             }
92              
93             BEGIN {
94 29     29   10500 *_same = IS_WIN32() ? sub { lc( $_[0] ) eq lc( $_[1] ) } : sub { $_[0] eq $_[1] };
  337     337   829  
95             }
96              
97             # mode bits encoded for chmod in symbolic mode
98             my %MODEBITS = ( om => 0007, gm => 0070, um => 0700 ); ## no critic
99             { my $m = 0; $MODEBITS{$_} = ( 1 << $m++ ) for qw/ox ow or gx gw gr ux uw ur/ };
100              
101             sub _symbolic_chmod {
102 1173     1173   762789 my ( $mode, $symbolic ) = @_;
103 1173         5769 for my $clause ( split /,\s*/, $symbolic ) {
104 2366 100       9639 if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) {
105 2365         6720 my ( $who, $action, $perms ) = ( $1, $2, $3 );
106 2365         5195 $who =~ s/a/ugo/g;
107 2365         5610 for my $w ( split //, $who ) {
108 7391         9332 my $p = 0;
109 7391         18876 $p |= $MODEBITS{"$w$_"} for split //, $perms;
110 7391 100       13747 if ( $action eq '=' ) {
111 2081         4433 $mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p;
112             }
113             else {
114 5310 100       10937 $mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p );
115             }
116             }
117             }
118             else {
119 1         85 Carp::croak("Invalid mode clause '$clause' for chmod()");
120             }
121             }
122 1172         5499 return $mode;
123             }
124              
125             # flock doesn't work on NFS on BSD or on some filesystems like lustre.
126             # Since program authors often can't control or detect that, we warn once
127             # instead of being fatal if we can detect it and people who need it strict
128             # can fatalize the 'flock' category
129              
130             #<<< No perltidy
131 29     29   258 { package flock; use warnings::register }
  29         60  
  29         204098  
132             #>>>
133              
134             my $WARNED_NO_FLOCK = 0;
135              
136             sub _throw {
137 16     16   657 my ( $self, $function, $file, $msg ) = @_;
138 16 50 33     84 if ( $function =~ /^flock/
      33        
139             && $! =~ /operation not supported|function not implemented/i
140             && !warnings::fatal_enabled('flock') )
141             {
142 0 0       0 if ( !$WARNED_NO_FLOCK ) {
143 0         0 warnings::warn( flock => "Flock not available: '$!': continuing in unsafe mode" );
144 0         0 $WARNED_NO_FLOCK++;
145             }
146             }
147             else {
148 16 100       102 $msg = $! unless defined $msg;
149 16 100       124 Path::Tiny::Error->throw( $function, ( defined $file ? $file : $self->[PATH] ),
150             $msg );
151             }
152 0         0 return;
153             }
154              
155             # cheapo option validation
156             sub _get_args {
157 1435     1435   3158 my ( $raw, @valid ) = @_;
158 1435 100 100     5224 if ( defined($raw) && ref($raw) ne 'HASH' ) {
159 6         42 my ( undef, undef, undef, $called_as ) = caller(1);
160 6         39 $called_as =~ s{^.*::}{};
161 6         463 Carp::croak("Options for $called_as must be a hash reference");
162             }
163 1429         2210 my $cooked = {};
164 1429         2522 for my $k (@valid) {
165 2403 100       6196 $cooked->{$k} = delete $raw->{$k} if exists $raw->{$k};
166             }
167 1429 100       4090 if ( keys %$raw ) {
168 8         49 my ( undef, undef, undef, $called_as ) = caller(1);
169 8         59 $called_as =~ s{^.*::}{};
170 8         619 Carp::croak( "Invalid option(s) for $called_as: " . join( ", ", keys %$raw ) );
171             }
172 1421         3020 return $cooked;
173             }
174              
175             #--------------------------------------------------------------------------#
176             # Constructors
177             #--------------------------------------------------------------------------#
178              
179             #pod =construct path
180             #pod
181             #pod $path = path("foo/bar");
182             #pod $path = path("/tmp", "file.txt"); # list
183             #pod $path = path("."); # cwd
184             #pod
185             #pod Constructs a C object. It doesn't matter if you give a file or
186             #pod directory path. It's still up to you to call directory-like methods only on
187             #pod directories and file-like methods only on files. This function is exported
188             #pod automatically by default.
189             #pod
190             #pod The first argument must be defined and have non-zero length or an exception
191             #pod will be thrown. This prevents subtle, dangerous errors with code like
192             #pod C<< path( maybe_undef() )->remove_tree >>.
193             #pod
194             #pod B: If and only if the B character of the B argument
195             #pod to C is a tilde ('~'), then tilde replacement will be applied to the
196             #pod first path segment. A single tilde will be replaced with C and a
197             #pod tilde followed by a username will be replaced with output of
198             #pod C. B.
199             #pod See L for more.
200             #pod
201             #pod On Windows, if the path consists of a drive identifier without a path component
202             #pod (C or C), it will be expanded to the absolute path of the current
203             #pod directory on that volume using C.
204             #pod
205             #pod If called with a single C argument, the original is returned unless
206             #pod the original is holding a temporary file or directory reference in which case a
207             #pod stringified copy is made.
208             #pod
209             #pod $path = path("foo/bar");
210             #pod $temp = Path::Tiny->tempfile;
211             #pod
212             #pod $p2 = path($path); # like $p2 = $path
213             #pod $t2 = path($temp); # like $t2 = path( "$temp" )
214             #pod
215             #pod This optimizes copies without proliferating references unexpectedly if a copy is
216             #pod made by code outside your control.
217             #pod
218             #pod Current API available since 0.017.
219             #pod
220             #pod =cut
221              
222             sub path {
223 294     294 1 160405 my $path = shift;
224             Carp::croak("Path::Tiny paths require defined, positive-length parts")
225 294 100       745 unless 1 + @_ == grep { defined && length } $path, @_;
  326 100       2218  
226              
227             # non-temp Path::Tiny objects are effectively immutable and can be reused
228 289 100 100     1358 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      66        
229 4         20 return $path;
230             }
231              
232             # stringify objects
233 285         483 $path = "$path";
234              
235             # do any tilde expansions
236 285         865 my ($tilde) = $path =~ m{^(~[^/]*)};
237 285 100       636 if ( defined $tilde ) {
238             # Escape File::Glob metacharacters
239 28         139 (my $escaped = $tilde) =~ s/([\[\{\*\?\\])/\\$1/g;
240 28         147 require File::Glob;
241 28         1772 my ($homedir) = File::Glob::bsd_glob($escaped);
242 28 50 33     280 if (defined $homedir && ! $File::Glob::ERROR) {
243 28         38 $homedir =~ tr[\\][/] if IS_WIN32();
244 28         463 $path =~ s{^\Q$tilde\E}{$homedir};
245             }
246             }
247              
248 285         668 unshift @_, $path;
249 285         902 goto &_pathify;
250             }
251              
252             # _path is like path but without tilde expansion
253             sub _path {
254 1684     1684   3272 my $path = shift;
255             Carp::croak("Path::Tiny paths require defined, positive-length parts")
256 1684 50       3484 unless 1 + @_ == grep { defined && length } $path, @_;
  2409 50       8542  
257              
258             # non-temp Path::Tiny objects are effectively immutable and can be reused
259 1684 100 100     8618 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      100        
260 118         379 return $path;
261             }
262              
263             # stringify objects
264 1566         3228 $path = "$path";
265              
266 1566         4559 unshift @_, $path;
267 1566         4410 goto &_pathify;
268             }
269              
270             # _pathify expects one or more string arguments, then joins and canonicalizes
271             # them into an object.
272             sub _pathify {
273 1851     1851   2972 my $path = shift;
274              
275             # expand relative volume paths on windows; put trailing slash on UNC root
276 1851         2401 if ( IS_WIN32() ) {
277             $path = _win32_vol( $path, $1 ) if $path =~ m{^($DRV_VOL)(?:$NOTSLASH|\z)};
278             $path .= "/" if $path =~ m{^$UNC_VOL\z};
279             }
280              
281             # concatenations stringifies objects, too
282 1851 100       3553 if (@_) {
283 612 100       1136 $path .= ( _is_root($path) ? "" : "/" ) . join( "/", @_ );
284             }
285              
286              
287             # canonicalize, but with unix slashes and put back trailing volume slash
288 1851         7242 my $cpath = $path = File::Spec->canonpath($path);
289 1851         2523 $path =~ tr[\\][/] if IS_WIN32();
290 1851 50       3500 $path = "/" if $path eq '/..'; # for old File::Spec
291 1851         2250 $path .= "/" if IS_WIN32() && $path =~ m{^$UNC_VOL\z};
292              
293             # root paths must always have a trailing slash, but other paths must not
294 1851 100       3127 if ( _is_root($path) ) {
295 57         269 $path =~ s{/?\z}{/};
296             }
297             else {
298 1794         3748 $path =~ s{/\z}{};
299             }
300              
301 1851         8628 bless [ $path, $cpath ], __PACKAGE__;
302             }
303              
304             #pod =construct new
305             #pod
306             #pod $path = Path::Tiny->new("foo/bar");
307             #pod
308             #pod This is just like C, but with method call overhead. (Why would you
309             #pod do that?)
310             #pod
311             #pod Current API available since 0.001.
312             #pod
313             #pod =cut
314              
315 2     2 1 359 sub new { shift; path(@_) }
  2         5  
316              
317             #pod =construct cwd
318             #pod
319             #pod $path = Path::Tiny->cwd; # path( Cwd::getcwd )
320             #pod $path = cwd; # optional export
321             #pod
322             #pod Gives you the absolute path to the current directory as a C object.
323             #pod This is slightly faster than C<< path(".")->absolute >>.
324             #pod
325             #pod C may be exported on request and used as a function instead of as a
326             #pod method.
327             #pod
328             #pod Current API available since 0.018.
329             #pod
330             #pod =cut
331              
332             sub cwd {
333 10     10 1 17432 require Cwd;
334 10         108 return _path( Cwd::getcwd() );
335             }
336              
337             #pod =construct rootdir
338             #pod
339             #pod $path = Path::Tiny->rootdir; # /
340             #pod $path = rootdir; # optional export
341             #pod
342             #pod Gives you C<< File::Spec->rootdir >> as a C object if you're too
343             #pod picky for C.
344             #pod
345             #pod C may be exported on request and used as a function instead of as a
346             #pod method.
347             #pod
348             #pod Current API available since 0.018.
349             #pod
350             #pod =cut
351              
352 3     3 1 158 sub rootdir { _path( File::Spec->rootdir ) }
353              
354             #pod =construct tempfile, tempdir
355             #pod
356             #pod $temp = Path::Tiny->tempfile( @options );
357             #pod $temp = Path::Tiny->tempdir( @options );
358             #pod $temp = $dirpath->tempfile( @options );
359             #pod $temp = $dirpath->tempdir( @options );
360             #pod $temp = tempfile( @options ); # optional export
361             #pod $temp = tempdir( @options ); # optional export
362             #pod
363             #pod C passes the options to C<< File::Temp->new >> and returns a
364             #pod C object with the file name. The C option will be enabled
365             #pod by default, but you can override that by passing C<< TMPDIR => 0 >> along with
366             #pod the options. (If you use an absolute C