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   2502247 use 5.008001;
  29         406  
2 29     29   173 use strict;
  29         55  
  29         640  
3 29     29   151 use warnings;
  29         55  
  29         1690  
4              
5             package Path::Tiny;
6             # ABSTRACT: File path utility
7              
8             our $VERSION = '0.142';
9              
10             # Dependencies
11 29     29   183 use Config;
  29         58  
  29         1495  
12 29     29   187 use Exporter 5.57 (qw/import/);
  29         397  
  29         1159  
13 29     29   179 use File::Spec 0.86 (); # shipped with 5.8.1
  29         452  
  29         558  
14 29     29   137 use Carp ();
  29         71  
  29         2240  
15              
16             our @EXPORT = qw/path/;
17             our @EXPORT_OK = qw/cwd rootdir tempfile tempdir/;
18              
19             use constant {
20 29         5026 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   187 };
  29         73  
28              
29             use overload (
30             q{""} => 'stringify',
31             bool => sub () { 1 },
32 29         191 fallback => 1,
33 29     29   3762 );
  29         2996  
34              
35             # FREEZE/THAW per Sereal/CBOR/Types::Serialiser protocol
36 2     2 0 6 sub THAW { return path( $_[2] ) }
37 29     29   3736 { no warnings 'once'; *TO_JSON = *FREEZE = \&stringify };
  29         59  
  29         15485  
38              
39             my $HAS_UU; # has Unicode::UTF8; lazily populated
40              
41             sub _check_UU {
42 4     4   21 local $SIG{__DIE__}; # prevent outer handler from being called
43 4         10 !!eval {
44 4         884 require Unicode::UTF8;
45 1         592 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   2402 local $SIG{__DIE__}; # prevent outer handler from being called
54 4         11 !!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         1228 require Encode;
58 4         43638 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   6080 return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT\z/ ) : ( $_[0] eq '/' );
91             }
92              
93             BEGIN {
94 29     29   10622 *_same = IS_WIN32() ? sub { lc( $_[0] ) eq lc( $_[1] ) } : sub { $_[0] eq $_[1] };
  337     337   808  
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   621095 my ( $mode, $symbolic ) = @_;
103 1173         5524 for my $clause ( split /,\s*/, $symbolic ) {
104 2366 100       9344 if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) {
105 2365         6535 my ( $who, $action, $perms ) = ( $1, $2, $3 );
106 2365         4848 $who =~ s/a/ugo/g;
107 2365         5308 for my $w ( split //, $who ) {
108 7391         8888 my $p = 0;
109 7391         18009 $p |= $MODEBITS{"$w$_"} for split //, $perms;
110 7391 100       13261 if ( $action eq '=' ) {
111 2081         4222 $mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p;
112             }
113             else {
114 5310 100       10053 $mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p );
115             }
116             }
117             }
118             else {
119 1         87 Carp::croak("Invalid mode clause '$clause' for chmod()");
120             }
121             }
122 1172         5024 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   276 { package flock; use warnings::register }
  29         77  
  29         196496  
132             #>>>
133              
134             my $WARNED_NO_FLOCK = 0;
135              
136             sub _throw {
137 16     16   525 my ( $self, $function, $file, $msg ) = @_;
138 16 50 33     79 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       98 $msg = $! unless defined $msg;
149 16 100       119 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   3159 my ( $raw, @valid ) = @_;
158 1435 100 100     5267 if ( defined($raw) && ref($raw) ne 'HASH' ) {
159 6         41 my ( undef, undef, undef, $called_as ) = caller(1);
160 6         36 $called_as =~ s{^.*::}{};
161 6         440 Carp::croak("Options for $called_as must be a hash reference");
162             }
163 1429         2195 my $cooked = {};
164 1429         2817 for my $k (@valid) {
165 2403 100       6138 $cooked->{$k} = delete $raw->{$k} if exists $raw->{$k};
166             }
167 1429 100       3550 if ( keys %$raw ) {
168 8         44 my ( undef, undef, undef, $called_as ) = caller(1);
169 8         48 $called_as =~ s{^.*::}{};
170 8         679 Carp::croak( "Invalid option(s) for $called_as: " . join( ", ", keys %$raw ) );
171             }
172 1421         2987 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 157510 my $path = shift;
224             Carp::croak("Path::Tiny paths require defined, positive-length parts")
225 294 100       729 unless 1 + @_ == grep { defined && length } $path, @_;
  326 100       2219  
226              
227             # non-temp Path::Tiny objects are effectively immutable and can be reused
228 289 100 100     1435 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      66        
229 4         14 return $path;
230             }
231              
232             # stringify objects
233 285         458 $path = "$path";
234              
235             # do any tilde expansions
236 285         812 my ($tilde) = $path =~ m{^(~[^/]*)};
237 285 100       594 if ( defined $tilde ) {
238             # Escape File::Glob metacharacters
239 28         118 (my $escaped = $tilde) =~ s/([\[\{\*\?\\])/\\$1/g;
240 28         154 require File::Glob;
241 28         1679 my ($homedir) = File::Glob::bsd_glob($escaped);
242 28 50 33     275 if (defined $homedir && ! $File::Glob::ERROR) {
243 28         35 $homedir =~ tr[\\][/] if IS_WIN32();
244 28         467 $path =~ s{^\Q$tilde\E}{$homedir};
245             }
246             }
247              
248 285         719 unshift @_, $path;
249 285         899 goto &_pathify;
250             }
251              
252             # _path is like path but without tilde expansion
253             sub _path {
254 1684     1684   3122 my $path = shift;
255             Carp::croak("Path::Tiny paths require defined, positive-length parts")
256 1684 50       3666 unless 1 + @_ == grep { defined && length } $path, @_;
  2409 50       8823  
257              
258             # non-temp Path::Tiny objects are effectively immutable and can be reused
259 1684 100 100     8502 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      100        
260 118         435 return $path;
261             }
262              
263             # stringify objects
264 1566         3169 $path = "$path";
265              
266 1566         4635 unshift @_, $path;
267 1566         4411 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   2918 my $path = shift;
274              
275             # expand relative volume paths on windows; put trailing slash on UNC root
276 1851         2344 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       3523 if (@_) {
283 612 100       1169 $path .= ( _is_root($path) ? "" : "/" ) . join( "/", @_ );
284             }
285              
286              
287             # canonicalize, but with unix slashes and put back trailing volume slash
288 1851         8132 my $cpath = $path = File::Spec->canonpath($path);
289 1851         2578 $path =~ tr[\\][/] if IS_WIN32();
290 1851 50       3477 $path = "/" if $path eq '/..'; # for old File::Spec
291 1851         2262 $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       3156 if ( _is_root($path) ) {
295 57         282 $path =~ s{/?\z}{/};
296             }
297             else {
298 1794         3545 $path =~ s{/\z}{};
299             }
300              
301 1851         8504 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 323 sub new { shift; path(@_) }
  2         6  
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 17185 require Cwd;
334 10         107 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 139 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