File Coverage

blib/lib/Path/Tiny.pm
Criterion Covered Total %
statement 692 729 94.9
branch 405 486 83.3
condition 166 230 72.1
subroutine 104 106 98.1
pod 63 64 98.4
total 1430 1615 88.5


line stmt bran cond sub pod time code
1 29     29   4723105 use 5.008001;
  29         118  
2 29     29   4471 use strict;
  29         1902  
  29         3079  
3 29     29   2362 use warnings;
  29         62  
  29         6774  
4              
5             package Path::Tiny;
6             # ABSTRACT: File path utility
7              
8             our $VERSION = '0.150';
9              
10             # Dependencies
11 29     29   1953 use Config;
  29         2248  
  29         1828  
12 29     29   1738 use Exporter 5.57 (qw/import/);
  29         537  
  29         1218  
13 29     29   160 use File::Spec 0.86 (); # shipped with 5.8.1
  29         2436  
  29         640  
14 29     29   162 use Carp ();
  29         1602  
  29         6893  
15              
16             our @EXPORT = qw/path/;
17             our @EXPORT_OK = qw/cwd rootdir tempfile tempdir/;
18              
19             use constant {
20 29         17289 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         77  
28              
29             use overload (
30             q{""} => 'stringify',
31             bool => sub () { 1 },
32 29         266 fallback => 1,
33 29     29   1992 );
  29         5091  
34              
35             # FREEZE/THAW per Sereal/CBOR/Types::Serialiser protocol
36 2     2 0 7 sub THAW { return path( $_[2] ) }
37 29     29   4149 { no warnings 'once'; *TO_JSON = *FREEZE = \&stringify };
  29         58  
  29         18371  
38              
39             my $HAS_UU; # has Unicode::UTF8; lazily populated
40              
41             sub _check_UU {
42 4     4   22 local $SIG{__DIE__}; # prevent outer handler from being called
43 4         30 !!eval {
44 4         1117 require Unicode::UTF8;
45 1         1081 Unicode::UTF8->VERSION(0.58);
46 1         13 1;
47             };
48             }
49              
50             my $HAS_PU; # has PerlIO::utf8_strict; lazily populated
51              
52             sub _check_PU {
53 4     4   694 local $SIG{__DIE__}; # prevent outer handler from being called
54 4         10 !!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         38 require Encode;
58 4         1087 require PerlIO::utf8_strict;
59 2         2012 PerlIO::utf8_strict->VERSION(0.003);
60 2         21 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 2470     2470   7374 return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT\z/ ) : ( $_[0] eq '/' );
91             }
92              
93             BEGIN {
94 29     29   15060 *_same = IS_WIN32() ? sub { lc( $_[0] ) eq lc( $_[1] ) } : sub { $_[0] eq $_[1] };
  337     337   993  
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   888969 my ( $mode, $symbolic ) = @_;
103 1173         6244 for my $clause ( split /,\s*/, $symbolic ) {
104 2366 100       11304 if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) {
105 2365         7639 my ( $who, $action, $perms ) = ( $1, $2, $3 );
106 2365         5546 $who =~ s/a/ugo/g;
107 2365         5206 for my $w ( split //, $who ) {
108 7391         9243 my $p = 0;
109 7391         20463 $p |= $MODEBITS{"$w$_"} for split //, $perms;
110 7391 100       13315 if ( $action eq '=' ) {
111 2081         4756 $mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p;
112             }
113             else {
114 5310 100       11419 $mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p );
115             }
116             }
117             }
118             else {
119 1         189 Carp::croak("Invalid mode clause '$clause' for chmod()");
120             }
121             }
122 1172         5653 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   257 { package flock; use warnings::register }
  29         70  
  29         253440  
132             #>>>
133              
134             my $WARNED_NO_FLOCK = 0;
135              
136             sub _throw {
137 26     26   679 my ( $self, $function, $file, $msg ) = @_;
138 26 50 33     146 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 26 100       157 $msg = $! unless defined $msg;
149 26 100       189 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 1436     1436   3708 my ( $raw, @valid ) = @_;
158 1436 100 100     6122 if ( defined($raw) && ref($raw) ne 'HASH' ) {
159 6         19 my ( undef, undef, undef, $called_as ) = caller(1);
160 6         31 $called_as =~ s{^.*::}{};
161 6         602 Carp::croak("Options for $called_as must be a hash reference");
162             }
163 1430         2520 my $cooked = {};
164 1430         2808 for my $k (@valid) {
165 2404 100       7391 $cooked->{$k} = delete $raw->{$k} if exists $raw->{$k};
166             }
167 1430 100       3825 if ( keys %$raw ) {
168 8         30 my ( undef, undef, undef, $called_as ) = caller(1);
169 8         40 $called_as =~ s{^.*::}{};
170 8         781 Carp::croak( "Invalid option(s) for $called_as: " . join( ", ", keys %$raw ) );
171             }
172 1422         3505 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 5382981 my $path = shift;
224             Carp::croak("Path::Tiny paths require defined, positive-length parts")
225 294 100       1006 unless 1 + @_ == grep { defined && length } $path, @_;
  326 100       2635  
226              
227             # non-temp Path::Tiny objects are effectively immutable and can be reused
228 289 100 100     1787 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      66        
229 4         22 return $path;
230             }
231              
232             # stringify objects
233 285         524 $path = "$path";
234              
235             # do any tilde expansions
236 285         1031 my ($tilde) = $path =~ m{^(~[^/]*)};
237 285 100       690 if ( defined $tilde ) {
238             # Escape File::Glob metacharacters
239 28         140 (my $escaped = $tilde) =~ s/([\[\{\*\?\\])/\\$1/g;
240 28         162 require File::Glob;
241 28         2005 my ($homedir) = File::Glob::bsd_glob($escaped);
242 28 50 33     369 if (defined $homedir && ! $File::Glob::ERROR) {
243 28         35 $homedir =~ tr[\\][/] if IS_WIN32();
244 28         577 $path =~ s{^\Q$tilde\E}{$homedir};
245             }
246             }
247              
248 285         754 unshift @_, $path;
249 285         1086 goto &_pathify;
250             }
251              
252             # _path is like path but without tilde expansion
253             sub _path {
254 1688     1688   3419 my $path = shift;
255             Carp::croak("Path::Tiny paths require defined, positive-length parts")
256 1688 50       5265 unless 1 + @_ == grep { defined && length } $path, @_;
  2416 50       10524  
257              
258             # non-temp Path::Tiny objects are effectively immutable and can be reused
259 1688 100 100     10464 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      100        
260 118         510 return $path;
261             }
262              
263             # stringify objects
264 1570         3466 $path = "$path";
265              
266 1570         5391 unshift @_, $path;
267 1570         5114 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 1855     1855   3252 my $path = shift;
274              
275             # expand relative volume paths on windows; put trailing slash on UNC root
276 1855         2807 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 1855 100       4304 if (@_) {
283 615 100       1339 $path .= ( _is_root($path) ? "" : "/" ) . join( "/", @_ );
284             }
285              
286              
287             # canonicalize, but with unix slashes and put back trailing volume slash
288 1855         9790 my $cpath = $path = File::Spec->canonpath($path);
289 1855         2866 $path =~ tr[\\][/] if IS_WIN32();
290 1855 50       4177 $path = "/" if $path eq '/..'; # for old File::Spec
291 1855         2430 $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 1855 100       3977 if ( _is_root($path) ) {
295 57         360 $path =~ s{/?\z}{/};
296             }
297             else {
298 1798         4300 $path =~ s{/\z}{};
299             }
300              
301 1855         12024 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 259 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 23863 require Cwd;
334 10         112 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 262161 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