File Coverage

blib/lib/Mojo/File.pm
Criterion Covered Total %
statement 150 152 98.6
branch 54 60 90.0
condition 12 15 80.0
subroutine 51 52 98.0
pod 32 33 96.9
total 299 312 95.8


line stmt bran cond sub pod time code
1             package Mojo::File;
2 80     1611   1111562 use Mojo::Base -strict;
  80         199  
  80         685  
3 80     80   790 use overload '@{}' => sub { shift->to_array }, bool => sub {1}, '""' => sub { ${$_[0]} }, fallback => 1;
  80     2292   196  
  80     2340   1510  
  1746     55   3969  
  132         817  
  5539         53938  
  5539         129418  
4              
5 80     80   9859 use Carp qw(croak);
  80         218  
  80         5185  
6 80     80   658 use Cwd qw(getcwd);
  80         192  
  80         4887  
7 80     80   574 use Exporter qw(import);
  80         219  
  80         2826  
8 80     80   534 use File::Basename ();
  80         197  
  80         2120  
9 80     80   45504 use File::Copy qw(copy move);
  80         203862  
  80         5292  
10 80     80   634 use File::Find qw(find);
  80         201  
  80         6705  
11 80     80   619 use File::Path ();
  80         180  
  80         1822  
12 80     80   35086 use File::Spec::Functions qw(abs2rel canonpath catfile file_name_is_absolute rel2abs splitdir);
  80         67832  
  80         6091  
13 80     80   41564 use File::stat ();
  80         581030  
  80         1919  
14 80     80   65067 use File::Temp ();
  80         890705  
  80         2214  
15 80     80   631 use IO::File ();
  80         197  
  80         1475  
16 80     80   35290 use Mojo::Collection;
  80         241  
  80         4149  
17 80     80   616 use Mojo::Util qw(decode deprecated encode);
  80         316  
  80         217644  
18              
19             our @EXPORT_OK = ('curfile', 'path', 'tempdir', 'tempfile');
20              
21 171     171 1 803 sub basename { File::Basename::basename ${shift()}, @_ }
  171         11027  
22              
23 472     472 1 3141 sub child { $_[0]->new(${shift()}, @_) }
  472         1855  
24              
25             sub chmod {
26 4     4 1 21 my ($self, $mode) = @_;
27 4 100       179 chmod $mode, $$self or croak qq{Can't chmod file "$$self": $!};
28 3         14 return $self;
29             }
30              
31             sub copy_to {
32 2     2 1 11 my ($self, $to) = @_;
33 2 50       12 copy($$self, $to) or croak qq{Can't copy file "$$self" to "$to": $!};
34 2 100       403 return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
35             }
36              
37 274     274 1 68264 sub curfile { __PACKAGE__->new(Cwd::realpath((caller)[1])) }
38              
39 92     92 1 718 sub dirname { $_[0]->new(scalar File::Basename::dirname ${$_[0]}) }
  92         3141  
40              
41 97 100   97 1 485 sub extname { shift->basename =~ /.+\.([^.]+)$/ ? $1 : '' }
42              
43 51     51 1 99 sub is_abs { file_name_is_absolute ${shift()} }
  51         346  
44              
45             sub list {
46 31   100 31 1 155 my ($self, $options) = (shift, shift // {});
47              
48 31 100       498 return Mojo::Collection->new unless -d $$self;
49 29 50       1074 opendir(my $dir, $$self) or croak qq{Can't open directory "$$self": $!};
50 29 100       875 my @files = grep { $_ ne '.' && $_ ne '..' } readdir $dir;
  250         871  
51 29 100       134 @files = grep { !/^\./ } @files unless $options->{hidden};
  172         344  
52 29         87 @files = map { catfile $$self, $_ } @files;
  188         695  
53 29 100       101 @files = grep { !-d } @files unless $options->{dir};
  170         2688  
54              
55 29         175 return Mojo::Collection->new(map { $self->new($_) } sort @files);
  157         301  
56             }
57              
58             sub list_tree {
59 227   100 227 1 1380 my ($self, $options) = (shift, shift // {});
60              
61             # This may break in the future, but is worth it for performance
62 227 100       1897 local $File::Find::skip_pattern = qr/^\./ unless $options->{hidden};
63              
64             # The File::Find documentation lies, this is needed for CIFS
65 227 50       836 local $File::Find::dont_use_nlink = 1 if $options->{dont_use_nlink};
66              
67 227         422 my %all;
68             my $wanted = sub {
69 2725 100   2725   9589 if ($options->{max_depth}) {
70 62         179 (my $rel = $File::Find::name) =~ s!^\Q$$self\E/?!!;
71 62 100       200 $File::Find::prune = 1 if splitdir($rel) >= $options->{max_depth};
72             }
73 2725 100 100     136458 $all{$File::Find::name}++ if $options->{dir} || !-d $File::Find::name;
74 227         1475 };
75 227 100       21823 find {wanted => $wanted, no_chdir => 1}, $$self if -d $$self;
76 227         1470 delete $all{$$self};
77              
78 227         2087 return Mojo::Collection->new(map { $self->new(canonpath $_) } sort keys %all);
  2020         6026  
79             }
80              
81 1     1 1 797 sub lstat { File::stat::lstat(${shift()}) }
  1         8  
82              
83             sub make_path {
84 23     23 1 46 my $self = shift;
85 23         2161 File::Path::make_path $$self, @_;
86 23         128 return $self;
87             }
88              
89             sub move_to {
90 7     7 1 29 my ($self, $to) = @_;
91 7 50       43 move($$self, $to) or croak qq{Can't move file "$$self" to "$to": $!};
92 7 100       529 return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
93             }
94              
95             sub new {
96 10323     10323 1 104459 my $class = shift;
97 10323 100       17249 croak 'Invalid path' if grep { !defined } @_;
  16629         41096  
98 10321 100       40301 my $value = @_ == 1 ? $_[0] : @_ > 1 ? catfile @_ : canonpath getcwd;
    100          
99 10321   66     53054 return bless \$value, ref $class || $class;
100             }
101              
102             sub open {
103 130     130 1 1664 my $self = shift;
104 130         911 my $handle = IO::File->new;
105 130 100       5476 $handle->open($$self, @_) or croak qq{Can't open file "$$self": $!};
106 129         13065 return $handle;
107             }
108              
109 3439     3439 1 62866 sub path { __PACKAGE__->new(@_) }
  55         2527  
110              
111 817     817 1 5975 sub realpath { $_[0]->new(Cwd::realpath ${$_[0]}) }
  817         66174  
112              
113             sub remove {
114 52     52 1 144 my ($self, $mode) = @_;
115 52 100 66     3765 unlink $$self or croak qq{Can't remove file "$$self": $!} if -e $$self;
116 51         1024 return $self;
117             }
118              
119             sub remove_tree {
120 2     2 1 6 my $self = shift;
121 2         1006 File::Path::remove_tree $$self, @_;
122 2         23 return $self;
123             }
124              
125             sub sibling {
126 270     270 1 675 my $self = shift;
127 270         8154 return $self->new(scalar File::Basename::dirname($self), @_);
128             }
129              
130             sub slurp {
131 114     114 1 914 my ($self, $encoding) = @_;
132              
133 114 100       6972 CORE::open my $file, '<', $$self or croak qq{Can't open file "$$self": $!};
134 113         697 my $ret = my $content = '';
135 113         873 while ($ret = $file->sysread(my $buffer, 131072, 0)) { $content .= $buffer }
  113         4516  
136 113 50       1925 croak qq{Can't read from file "$$self": $!} unless defined $ret;
137              
138 113 100       2296 return $encoding ? decode($encoding, $content) : $content;
139             }
140              
141             sub spew {
142 46     46 1 377 my ($self, $content, $encoding) = @_;
143 46 100       130 $content = encode($encoding, $content) if $encoding;
144 46 100       1581 CORE::open my $file, '>', $$self or croak qq{Can't open file "$$self": $!};
145 45 100 50     712 ($file->syswrite($content) // -1) == length $content or croak qq{Can't write to file "$$self": $!};
146 43         2947 return $self;
147             }
148              
149             # DEPRECATED!
150             sub spurt {
151 0     0 0 0 deprecated 'Mojo::File::spurt is deprecated in favor of Mojo::File::spew';
152 0         0 shift->spew(join '', @_);
153             }
154              
155 4     4 1 41 sub stat { File::stat::stat(${shift()}) }
  4         21  
156              
157 1     1 1 18 sub tap { shift->Mojo::Base::tap(@_) }
158              
159 35     35 1 62539 sub tempdir { __PACKAGE__->new(File::Temp->newdir(@_)) }
160              
161 53     53 1 4538 sub tempfile { __PACKAGE__->new(File::Temp->new(@_)) }
162              
163 137     137 1 403 sub to_abs { $_[0]->new(rel2abs ${$_[0]}) }
  137         722  
164              
165 2163     2163 1 3066 sub to_array { [splitdir ${shift()}] }
  2163         5677  
166              
167 2040     2040 1 3345 sub to_rel { $_[0]->new(abs2rel(${$_[0]}, $_[1])) }
  2040         5968  
168              
169 3142     3142 1 4896 sub to_string {"${$_[0]}"}
  3142         33347  
170              
171             sub touch {
172 4     4 1 830 my $self = shift;
173 4 100       76 $self->open('>') unless -e $$self;
174 4 50       84 utime undef, undef, $$self or croak qq{Can't touch file "$$self": $!};
175 4         30 return $self;
176             }
177              
178 1     1 1 640 sub with_roles { shift->Mojo::Base::with_roles(@_) }
179              
180             1;
181              
182             =encoding utf8
183              
184             =head1 NAME
185              
186             Mojo::File - File system paths
187              
188             =head1 SYNOPSIS
189              
190             use Mojo::File;
191              
192             # Portably deal with file system paths
193             my $path = Mojo::File->new('/home/sri/.vimrc');
194             say $path->slurp;
195             say $path->dirname;
196             say $path->basename;
197             say $path->extname;
198             say $path->sibling('.bashrc');
199              
200             # Use the alternative constructor
201             use Mojo::File qw(path);
202             my $path = path('/tmp/foo/bar')->make_path;
203             $path->child('test.txt')->spew('Hello Mojo!');
204              
205             =head1 DESCRIPTION
206              
207             L is a scalar-based container for file system paths that provides a friendly API for dealing with different
208             operating systems.
209              
210             # Access scalar directly to manipulate path
211             my $path = Mojo::File->new('/home/sri/test');
212             $$path .= '.txt';
213              
214             =head1 FUNCTIONS
215              
216             L implements the following functions, which can be imported individually.
217              
218             =head2 curfile
219              
220             my $path = curfile;
221              
222             Construct a new scalar-based L object for the absolute path to the current source file.
223              
224             =head2 path
225              
226             my $path = path;
227             my $path = path('/home/sri/.vimrc');
228             my $path = path('/home', 'sri', '.vimrc');
229             my $path = path(File::Temp->newdir);
230              
231             Construct a new scalar-based L object, defaults to using the current working directory.
232              
233             # "foo/bar/baz.txt" (on UNIX)
234             path('foo', 'bar', 'baz.txt');
235              
236             =head2 tempdir
237              
238             my $path = tempdir;
239             my $path = tempdir('tempXXXXX');
240              
241             Construct a new scalar-based L object for a temporary directory with L.
242              
243             # Longer version
244             my $path = path(File::Temp->newdir('tempXXXXX'));
245              
246             =head2 tempfile
247              
248             my $path = tempfile;
249             my $path = tempfile(DIR => '/tmp');
250              
251             Construct a new scalar-based L object for a temporary file with L.
252              
253             # Longer version
254             my $path = path(File::Temp->new(DIR => '/tmp'));
255              
256             =head1 METHODS
257              
258             L implements the following methods.
259              
260             =head2 basename
261              
262             my $name = $path->basename;
263             my $name = $path->basename('.txt');
264              
265             Return the last level of the path with L.
266              
267             # ".vimrc" (on UNIX)
268             path('/home/sri/.vimrc')->basename;
269              
270             # "test" (on UNIX)
271             path('/home/sri/test.txt')->basename('.txt');
272              
273             =head2 child
274              
275             my $child = $path->child('.vimrc');
276              
277             Return a new L object relative to the path.
278              
279             # "/home/sri/.vimrc" (on UNIX)
280             path('/home')->child('sri', '.vimrc');
281              
282             =head2 chmod
283              
284             $path = $path->chmod(0644);
285              
286             Change file permissions.
287              
288             =head2 copy_to
289              
290             my $destination = $path->copy_to('/home/sri');
291             my $destination = $path->copy_to('/home/sri/.vimrc.backup');
292              
293             Copy file with L and return the destination as a L object.
294              
295             =head2 dirname
296              
297             my $name = $path->dirname;
298              
299             Return all but the last level of the path with L as a L object.
300              
301             # "/home/sri" (on UNIX)
302             path('/home/sri/.vimrc')->dirname;
303              
304             =head2 extname
305              
306             my $ext = $path->extname;
307              
308             Return file extension of the path.
309              
310             # "js"
311             path('/home/sri/test.js')->extname;
312              
313             =head2 is_abs
314              
315             my $bool = $path->is_abs;
316              
317             Check if the path is absolute.
318              
319             # True (on UNIX)
320             path('/home/sri/.vimrc')->is_abs;
321              
322             # False (on UNIX)
323             path('.vimrc')->is_abs;
324              
325             =head2 list
326              
327             my $collection = $path->list;
328             my $collection = $path->list({hidden => 1});
329              
330             List all files in the directory and return a L object containing the results as L
331             objects. The list does not include C<.> and C<..>.
332              
333             # List files
334             say for path('/home/sri/myapp')->list->each;
335              
336             These options are currently available:
337              
338             =over 2
339              
340             =item dir
341              
342             dir => 1
343              
344             Include directories.
345              
346             =item hidden
347              
348             hidden => 1
349              
350             Include hidden files.
351              
352             =back
353              
354             =head2 list_tree
355              
356             my $collection = $path->list_tree;
357             my $collection = $path->list_tree({hidden => 1});
358              
359             List all files recursively in the directory and return a L object containing the results as
360             L objects. The list does not include C<.> and C<..>.
361              
362             # List all templates
363             say for path('/home/sri/myapp/templates')->list_tree->each;
364              
365             These options are currently available:
366              
367             =over 2
368              
369             =item dir
370              
371             dir => 1
372              
373             Include directories.
374              
375             =item dont_use_nlink
376              
377             dont_use_nlink => 1
378              
379             Force L to always stat directories.
380              
381             =item hidden
382              
383             hidden => 1
384              
385             Include hidden files and directories.
386              
387             =item max_depth
388              
389             max_depth => 3
390              
391             Maximum number of levels to descend when searching for files.
392              
393             =back
394              
395             =head2 lstat
396              
397             my $stat = $path->lstat;
398              
399             Return a L object for the symlink.
400              
401             # Get symlink size
402             say path('/usr/sbin/sendmail')->lstat->size;
403              
404             # Get symlink modification time
405             say path('/usr/sbin/sendmail')->lstat->mtime;
406              
407             =head2 make_path
408              
409             $path = $path->make_path;
410             $path = $path->make_path({mode => 0711});
411              
412             Create the directories if they don't already exist, any additional arguments are passed through to L.
413              
414             =head2 move_to
415              
416             my $destination = $path->move_to('/home/sri');
417             my $destination = $path->move_to('/home/sri/.vimrc.backup');
418              
419             Move file with L and return the destination as a L object.
420              
421             =head2 new
422              
423             my $path = Mojo::File->new;
424             my $path = Mojo::File->new('/home/sri/.vimrc');
425             my $path = Mojo::File->new('/home', 'sri', '.vimrc');
426             my $path = Mojo::File->new(File::Temp->new);
427             my $path = Mojo::File->new(File::Temp->newdir);
428              
429             Construct a new L object, defaults to using the current working directory.
430              
431             # "foo/bar/baz.txt" (on UNIX)
432             Mojo::File->new('foo', 'bar', 'baz.txt');
433              
434             =head2 open
435              
436             my $handle = $path->open('+<');
437             my $handle = $path->open('r+');
438             my $handle = $path->open(O_RDWR);
439             my $handle = $path->open('<:encoding(UTF-8)');
440              
441             Open file with L.
442              
443             # Combine "fcntl.h" constants
444             use Fcntl qw(O_CREAT O_EXCL O_RDWR);
445             my $handle = path('/home/sri/test.pl')->open(O_RDWR | O_CREAT | O_EXCL);
446              
447             =head2 realpath
448              
449             my $realpath = $path->realpath;
450              
451             Resolve the path with L and return the result as a L object.
452              
453             =head2 remove
454              
455             $path = $path->remove;
456              
457             Delete file.
458              
459             =head2 remove_tree
460              
461             $path = $path->remove_tree;
462             $path = $path->remove_tree({keep_root => 1});
463              
464             Delete this directory and any files and subdirectories it may contain, any additional arguments are passed through to
465             L.
466              
467             =head2 sibling
468              
469             my $sibling = $path->sibling('.vimrc');
470              
471             Return a new L object relative to the directory part of the path.
472              
473             # "/home/sri/.vimrc" (on UNIX)
474             path('/home/sri/.bashrc')->sibling('.vimrc');
475              
476             # "/home/sri/.ssh/known_hosts" (on UNIX)
477             path('/home/sri/.bashrc')->sibling('.ssh', 'known_hosts');
478              
479             =head2 slurp
480              
481             my $bytes = $path->slurp;
482             my $chars = $path->slurp('UTF-8');
483              
484             Read all data at once from the file. If an encoding is provided, an attempt will be made to decode the content.
485              
486             =head2 spew
487              
488             $path = $path->spew($bytes);
489             $path = $path->spew($chars, 'UTF-8');
490              
491             Write all data at once to the file. If an encoding is provided, an attempt to encode the content will be made prior to
492             writing.
493              
494             =head2 stat
495              
496             my $stat = $path->stat;
497              
498             Return a L object for the path.
499              
500             # Get file size
501             say path('/home/sri/.bashrc')->stat->size;
502              
503             # Get file modification time
504             say path('/home/sri/.bashrc')->stat->mtime;
505              
506             =head2 tap
507              
508             $path = $path->tap(sub {...});
509              
510             Alias for L.
511              
512             =head2 to_abs
513              
514             my $absolute = $path->to_abs;
515              
516             Return absolute path as a L object, the path does not need to exist on the file system.
517              
518             =head2 to_array
519              
520             my $parts = $path->to_array;
521              
522             Split the path on directory separators.
523              
524             # "home:sri:.vimrc" (on UNIX)
525             join ':', @{path('/home/sri/.vimrc')->to_array};
526              
527             =head2 to_rel
528              
529             my $relative = $path->to_rel('/some/base/path');
530              
531             Return a relative path from the original path to the destination path as a L object.
532              
533             # "sri/.vimrc" (on UNIX)
534             path('/home/sri/.vimrc')->to_rel('/home');
535              
536             =head2 to_string
537              
538             my $str = $path->to_string;
539              
540             Stringify the path.
541              
542             =head2 touch
543              
544             $path = $path->touch;
545              
546             Create file if it does not exist or change the modification and access time to the current time.
547              
548             # Safely read file
549             say path('.bashrc')->touch->slurp;
550              
551             =head2 with_roles
552              
553             my $new_class = Mojo::File->with_roles('Mojo::File::Role::One');
554             my $new_class = Mojo::File->with_roles('+One', '+Two');
555             $path = $path->with_roles('+One', '+Two');
556              
557             Alias for L.
558              
559             =head1 OPERATORS
560              
561             L overloads the following operators.
562              
563             =head2 array
564              
565             my @parts = @$path;
566              
567             Alias for L.
568              
569             =head2 bool
570              
571             my $bool = !!$path;
572              
573             Always true.
574              
575             =head2 stringify
576              
577             my $str = "$path";
578              
579             Alias for L.
580              
581             =head1 SEE ALSO
582              
583             L, L, L.
584              
585             =cut