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   2539855 use 5.008001;
  29         417  
2 29     29   209 use strict;
  29         53  
  29         698  
3 29     29   150 use warnings;
  29         55  
  29         1690  
4              
5             package Path::Tiny;
6             # ABSTRACT: File path utility
7              
8             our $VERSION = '0.143'; # TRIAL
9              
10             # Dependencies
11 29     29   203 use Config;
  29         56  
  29         1503  
12 29     29   176 use Exporter 5.57 (qw/import/);
  29         428  
  29         1080  
13 29     29   174 use File::Spec 0.86 (); # shipped with 5.8.1
  29         422  
  29         609  
14 29     29   141 use Carp ();
  29         61  
  29         2262  
15              
16             our @EXPORT = qw/path/;
17             our @EXPORT_OK = qw/cwd rootdir tempfile tempdir/;
18              
19             use constant {
20 29         5010 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   192 };
  29         94  
28              
29             use overload (
30             q{""} => 'stringify',
31             bool => sub () { 1 },
32 29         181 fallback => 1,
33 29     29   3853 );
  29         3041  
34              
35             # FREEZE/THAW per Sereal/CBOR/Types::Serialiser protocol
36 2     2 0 7 sub THAW { return path( $_[2] ) }
37 29     29   3878 { no warnings 'once'; *TO_JSON = *FREEZE = \&stringify };
  29         73  
  29         15641  
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         836 require Unicode::UTF8;
45 1         487 Unicode::UTF8->VERSION(0.58);
46 1         9 1;
47             };
48             }
49              
50             my $HAS_PU; # has PerlIO::utf8_strict; lazily populated
51              
52             sub _check_PU {
53 4     4   2307 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         1147 require Encode;
58 4         43541 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   6302 return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT\z/ ) : ( $_[0] eq '/' );
91             }
92              
93             BEGIN {
94 29     29   10560 *_same = IS_WIN32() ? sub { lc( $_[0] ) eq lc( $_[1] ) } : sub { $_[0] eq $_[1] };
  337     337   806  
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   617736 my ( $mode, $symbolic ) = @_;
103 1173         5490 for my $clause ( split /,\s*/, $symbolic ) {
104 2366 100       9266 if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) {
105 2365         6632 my ( $who, $action, $perms ) = ( $1, $2, $3 );
106 2365         5096 $who =~ s/a/ugo/g;
107 2365         5327 for my $w ( split //, $who ) {
108 7391         8888 my $p = 0;
109 7391         17975 $p |= $MODEBITS{"$w$_"} for split //, $perms;
110 7391 100       12601 if ( $action eq '=' ) {
111 2081         4061 $mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p;
112             }
113             else {
114 5310 100       10165 $mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p );
115             }
116             }
117             }
118             else {
119 1         99 Carp::croak("Invalid mode clause '$clause' for chmod()");
120             }
121             }
122 1172         4923 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   238 { package flock; use warnings::register }
  29         83  
  29         199236  
132             #>>>
133              
134             my $WARNED_NO_FLOCK = 0;
135              
136             sub _throw {
137 16     16   700 my ( $self, $function, $file, $msg ) = @_;
138 16 50 33     94 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       133 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   3139 my ( $raw, @valid ) = @_;
158 1435 100 100     5419 if ( defined($raw) && ref($raw) ne 'HASH' ) {
159 6         35 my ( undef, undef, undef, $called_as ) = caller(1);
160 6         37 $called_as =~ s{^.*::}{};
161 6         465 Carp::croak("Options for $called_as must be a hash reference");
162             }
163 1429         2165 my $cooked = {};
164 1429         2542 for my $k (@valid) {
165 2403 100       6252 $cooked->{$k} = delete $raw->{$k} if exists $raw->{$k};
166             }
167 1429 100       3794 if ( keys %$raw ) {
168 8         46 my ( undef, undef, undef, $called_as ) = caller(1);
169 8         50 $called_as =~ s{^.*::}{};
170 8         613 Carp::croak( "Invalid option(s) for $called_as: " . join( ", ", keys %$raw ) );
171             }
172 1421         2940 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 159892 my $path = shift;
224             Carp::croak("Path::Tiny paths require defined, positive-length parts")
225 294 100       746 unless 1 + @_ == grep { defined && length } $path, @_;
  326 100       2224  
226              
227             # non-temp Path::Tiny objects are effectively immutable and can be reused
228 289 100 100     1449 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      66        
229 4         15 return $path;
230             }
231              
232             # stringify objects
233 285         508 $path = "$path";
234              
235             # do any tilde expansions
236 285         809 my ($tilde) = $path =~ m{^(~[^/]*)};
237 285 100       564 if ( defined $tilde ) {
238             # Escape File::Glob metacharacters
239 28         153 (my $escaped = $tilde) =~ s/([\[\{\*\?\\])/\\$1/g;
240 28         151 require File::Glob;
241 28         1843 my ($homedir) = File::Glob::bsd_glob($escaped);
242 28 50 33     309 if (defined $homedir && ! $File::Glob::ERROR) {
243 28         40 $homedir =~ tr[\\][/] if IS_WIN32();
244 28         443 $path =~ s{^\Q$tilde\E}{$homedir};
245             }
246             }
247              
248 285         687 unshift @_, $path;
249 285         898 goto &_pathify;
250             }
251              
252             # _path is like path but without tilde expansion
253             sub _path {
254 1684     1684   3186 my $path = shift;
255             Carp::croak("Path::Tiny paths require defined, positive-length parts")
256 1684 50       3542 unless 1 + @_ == grep { defined && length } $path, @_;
  2409 50       8688  
257              
258             # non-temp Path::Tiny objects are effectively immutable and can be reused
259 1684 100 100     9070 if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) {
      100        
260 118         404 return $path;
261             }
262              
263             # stringify objects
264 1566         3221 $path = "$path";
265              
266 1566         4598 unshift @_, $path;
267 1566         4436 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   2933 my $path = shift;
274              
275             # expand relative volume paths on windows; put trailing slash on UNC root
276 1851         2402 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       3563 if (@_) {
283 612 100       1137 $path .= ( _is_root($path) ? "" : "/" ) . join( "/", @_ );
284             }
285              
286              
287             # canonicalize, but with unix slashes and put back trailing volume slash
288 1851         7203 my $cpath = $path = File::Spec->canonpath($path);
289 1851         2637 $path =~ tr[\\][/] if IS_WIN32();
290 1851 50       3558 $path = "/" if $path eq '/..'; # for old File::Spec
291 1851         2364 $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       3125 if ( _is_root($path) ) {
295 57         283 $path =~ s{/?\z}{/};
296             }
297             else {
298 1794         3564 $path =~ s{/\z}{};
299             }
300              
301 1851         8751 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 364 sub new { shift; path(@_) }
  2         4  
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 17273 require Cwd;
334 10         111 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 148 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