File Coverage

blib/lib/Mojo/File.pm
Criterion Covered Total %
statement 161 161 100.0
branch 62 68 91.1
condition 30 34 88.2
subroutine 53 53 100.0
pod 34 34 100.0
total 340 350 97.1


line stmt bran cond sub pod time code
1             package Mojo::File;
2 83     2669   1438071 use Mojo::Base -strict;
  83         144  
  83         490  
3 83     83   34979 use overload '@{}' => sub { shift->to_array }, bool => sub {1}, '""' => sub { ${$_[0]} }, fallback => 1;
  83     2331   96561  
  83     1909   760  
  1857     43   2851  
  98         517  
  5323         43720  
  5323         98947  
4              
5 83     83   7004 use Carp qw(croak);
  83         104  
  83         3818  
6 83     83   328 use Cwd qw(getcwd);
  83         103  
  83         3247  
7 83     83   310 use Exporter qw(import);
  83         119  
  83         1864  
8 83     83   427 use File::Basename ();
  83         279  
  83         1618  
9 83     83   37073 use File::Copy qw(copy move);
  83         255255  
  83         4658  
10 83     83   484 use File::Path ();
  83         103  
  83         2015  
11 83     83   28089 use File::Spec::Functions qw(abs2rel canonpath catfile file_name_is_absolute rel2abs splitdir);
  83         50119  
  83         5843  
12 83     83   36641 use File::stat ();
  83         452447  
  83         1850  
13 83     83   64488 use File::Temp ();
  83         990925  
  83         2415  
14 83     83   26661 use IO::File ();
  83         52682  
  83         2076  
15 83     83   32832 use Mojo::Collection;
  83         252  
  83         3993  
16 83     83   417 use Mojo::Util qw(decode encode);
  83         113  
  83         203687  
17              
18             our @EXPORT_OK = ('curfile', 'path', 'tempdir', 'tempfile');
19              
20 174     174 1 658 sub basename { File::Basename::basename ${shift()}, @_ }
  174         9448  
21              
22 503     503 1 34841 sub child { $_[0]->new(${shift()}, @_) }
  503         1466  
23              
24             sub chmod {
25 4     4 1 9 my ($self, $mode) = @_;
26 4 100       203 chmod $mode, $$self or croak qq{Can't chmod file "$$self": $!};
27 3         9 return $self;
28             }
29              
30             sub copy_to {
31 2     2 1 8 my ($self, $to) = @_;
32 2 50       10 copy($$self, $to) or croak qq{Can't copy file "$$self" to "$to": $!};
33 2 100       397 return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
34             }
35              
36 288     288 1 256307 sub curfile { __PACKAGE__->new(Cwd::realpath((caller)[1])) }
37              
38 97     97 1 646 sub dirname { $_[0]->new(scalar File::Basename::dirname ${$_[0]}) }
  97         3275  
39              
40             sub download {
41 13   100 13 1 101 my ($self, $url, $options) = (shift, shift, shift // {});
42             my $ua = $options->{ua}
43 13   66     44 || do { require Mojo::UserAgent; Mojo::UserAgent->new(max_redirects => 10, max_response_size => 0) };
44 13   100     44 my $tx = _download_error($ua->transactor->download($ua->head($url => $options->{headers} // {}), $$self));
45 10 100       61 return $tx ? !!_download_error($ua->start($tx)) : 1;
46             }
47              
48 96 100   96 1 381 sub extname { shift->basename =~ /.+\.([^.]+)$/ ? $1 : '' }
49              
50 49     49 1 62 sub is_abs { file_name_is_absolute ${shift()} }
  49         274  
51              
52             sub list {
53 31   100 31 1 114 my ($self, $options) = (shift, shift // {});
54              
55 31 100       457 return Mojo::Collection->new unless -d $$self;
56 29 50       811 opendir(my $dir, $$self) or croak qq{Can't open directory "$$self": $!};
57 29 100       732 my @files = grep { $_ ne '.' && $_ ne '..' } readdir $dir;
  250         633  
58 29 100       99 @files = grep { !/^\./ } @files unless $options->{hidden};
  172         297  
59 29         49 @files = map { catfile $$self, $_ } @files;
  188         578  
60 29 100       79 @files = grep { !-d } @files unless $options->{dir};
  170         1719  
61              
62 29         150 return Mojo::Collection->new(map { $self->new($_) } sort @files);
  157         244  
63             }
64              
65             sub list_tree {
66 239   100 239 1 1305 my ($self, $options) = (shift, shift // {});
67 239 100       3357 return Mojo::Collection->new unless -d $$self;
68              
69 230         528 my (@results, $walk);
70             $walk = sub {
71 746     746   1384 my ($path, $depth) = @_;
72 746 50       21224 opendir my $dh, $path or return;
73 746 100       13988 my @names = sort grep { $_ ne '.' && $_ ne '..' } readdir $dh;
  4162         10765  
74 746 100       2090 @names = grep { !/^\./ } @names unless $options->{hidden};
  2596         3965  
75 746         1140 for my $name (@names) {
76 2662         4343 my $child = $self->new($path, $name);
77 2662         29110 my $is_dir = -d $$child;
78 2662 100 100     7272 push @results, $child if $options->{dir} || !$is_dir;
79             __SUB__->($$child, $depth + 1)
80 2662 100 100     16594 if $is_dir && !-l $$child && (!$options->{max_depth} || $depth + 1 < $options->{max_depth});
      100        
      100        
81             }
82 230         1487 };
83 230         706 $walk->($$self, 0);
84              
85 230         1917 return Mojo::Collection->new(@results);
86             }
87              
88 1     1 1 998 sub lstat { File::stat::lstat(${shift()}) }
  1         7  
89              
90             sub make_path {
91 24     24 1 39 my $self = shift;
92 24         2744 File::Path::make_path $$self, @_;
93 24         91 return $self;
94             }
95              
96             sub move_to {
97 10     10 1 29 my ($self, $to) = @_;
98 10 50       48 move($$self, $to) or croak qq{Can't move file "$$self" to "$to": $!};
99 10 100       787 return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
100             }
101              
102             sub new {
103 11176     11176 1 244810 my $class = shift;
104 11176 100       14173 croak 'Invalid path' if grep { !defined } @_;
  19713         30891  
105 11174 100       38474 my $value = @_ == 1 ? $_[0] : @_ > 1 ? catfile @_ : canonpath getcwd;
    100          
106 11174   66     39801 return bless \$value, ref $class || $class;
107             }
108              
109             sub open {
110 141     141 1 1477 my $self = shift;
111 141         958 my $handle = IO::File->new;
112 141 100       4598 $handle->open($$self, @_) or croak qq{Can't open file "$$self": $!};
113 140         12330 return $handle;
114             }
115              
116 3349     3349 1 259233 sub path { __PACKAGE__->new(@_) }
  43         2514  
117              
118 919     919 1 4213 sub realpath { $_[0]->new(Cwd::realpath ${$_[0]}) }
  919         53412  
119              
120             sub remove {
121 52     52 1 108 my ($self, $mode) = @_;
122 52 100 66     5312 unlink $$self or croak qq{Can't remove file "$$self": $!} if -e $$self;
123 51         814 return $self;
124             }
125              
126             sub remove_tree {
127 2     2 1 6 my $self = shift;
128 2         1032 File::Path::remove_tree $$self, @_;
129 2         13 return $self;
130             }
131              
132             sub sibling {
133 283     283 1 426 my $self = shift;
134 283         5708 return $self->new(scalar File::Basename::dirname($self), @_);
135             }
136              
137             sub slurp {
138 130     130 1 938 my ($self, $encoding) = @_;
139              
140 130 100       4580 CORE::open my $file, '<', $$self or croak qq{Can't open file "$$self": $!};
141 129         553 my $ret = my $content = '';
142 129         757 while ($ret = $file->sysread(my $buffer, 131072, 0)) { $content .= $buffer }
  128         3849  
143 129 50       1117 croak qq{Can't read from file "$$self": $!} unless defined $ret;
144              
145 129 100       1845 return $encoding ? decode($encoding, $content) : $content;
146             }
147              
148             sub spew {
149 57     57 1 204 my ($self, $content, $encoding) = @_;
150 57 100       142 $content = encode($encoding, $content) if $encoding;
151 57 100       3551 CORE::open my $file, '>', $$self or croak qq{Can't open file "$$self": $!};
152 56 100 50     857 ($file->syswrite($content) // -1) == length $content or croak qq{Can't write to file "$$self": $!};
153 54         2977 return $self;
154             }
155              
156 1     1 1 8 sub spurt { shift->spew(join '', @_) }
157              
158 4     4 1 18 sub stat { File::stat::stat(${shift()}) }
  4         22  
159              
160 1     1 1 7 sub tap { shift->Mojo::Base::tap(@_) }
161              
162 39     39 1 240130 sub tempdir { __PACKAGE__->new(File::Temp->newdir(@_)) }
163              
164 53     53 1 5318 sub tempfile { __PACKAGE__->new(File::Temp->new(@_)) }
165              
166 142     142 1 264 sub to_abs { $_[0]->new(rel2abs ${$_[0]}) }
  142         532  
167              
168 2292     2292 1 2285 sub to_array { [splitdir ${shift()}] }
  2292         3947  
169              
170 2165     2165 1 2328 sub to_rel { $_[0]->new(abs2rel(${$_[0]}, $_[1])) }
  2165         4227  
171              
172 3363     3363 1 5594 sub to_string {"${$_[0]}"}
  3363         31083  
173              
174             sub touch {
175 4     4 1 927 my $self = shift;
176 4 100       91 $self->open('>') unless -e $$self;
177 4 50       68 utime undef, undef, $$self or croak qq{Can't touch file "$$self": $!};
178 4         19 return $self;
179             }
180              
181 1     1 1 670 sub with_roles { shift->Mojo::Base::with_roles(@_) }
182              
183             sub _download_error {
184 22     22   36 my $tx = shift;
185              
186 22 100       62 return $tx unless my $err = $tx->error;
187 6 100 100     58 return undef if $err->{message} eq 'Download complete' || $err->{message} eq 'Download incomplete';
188 3 100       227 croak "$err->{code} response: $err->{message}" if $err->{code};
189 2         435 croak "Download error: $err->{message}";
190             }
191              
192             1;
193              
194             =encoding utf8
195              
196             =head1 NAME
197              
198             Mojo::File - File system paths
199              
200             =head1 SYNOPSIS
201              
202             use Mojo::File;
203              
204             # Portably deal with file system paths
205             my $path = Mojo::File->new('/home/sri/.vimrc');
206             say $path->slurp;
207             say $path->dirname;
208             say $path->basename;
209             say $path->extname;
210             say $path->sibling('.bashrc');
211              
212             # Use the alternative constructor
213             use Mojo::File qw(path);
214             my $path = path('/tmp/foo/bar')->make_path;
215             $path->child('test.txt')->spew('Hello Mojo!');
216              
217             =head1 DESCRIPTION
218              
219             L is a scalar-based container for file system paths that provides a friendly API for dealing with different
220             operating systems.
221              
222             # Access scalar directly to manipulate path
223             my $path = Mojo::File->new('/home/sri/test');
224             $$path .= '.txt';
225              
226             =head1 FUNCTIONS
227              
228             L implements the following functions, which can be imported individually.
229              
230             =head2 curfile
231              
232             my $path = curfile;
233              
234             Construct a new scalar-based L object for the absolute path to the current source file.
235              
236             =head2 path
237              
238             my $path = path;
239             my $path = path('/home/sri/.vimrc');
240             my $path = path('/home', 'sri', '.vimrc');
241             my $path = path(File::Temp->newdir);
242              
243             Construct a new scalar-based L object, defaults to using the current working directory.
244              
245             # "foo/bar/baz.txt" (on UNIX)
246             path('foo', 'bar', 'baz.txt');
247              
248             =head2 tempdir
249              
250             my $path = tempdir;
251             my $path = tempdir('tempXXXXX');
252              
253             Construct a new scalar-based L object for a temporary directory with L.
254              
255             # Longer version
256             my $path = path(File::Temp->newdir('tempXXXXX'));
257              
258             =head2 tempfile
259              
260             my $path = tempfile;
261             my $path = tempfile(DIR => '/tmp');
262              
263             Construct a new scalar-based L object for a temporary file with L.
264              
265             # Longer version
266             my $path = path(File::Temp->new(DIR => '/tmp'));
267              
268             =head1 METHODS
269              
270             L implements the following methods.
271              
272             =head2 basename
273              
274             my $name = $path->basename;
275             my $name = $path->basename('.txt');
276              
277             Return the last level of the path with L.
278              
279             # ".vimrc" (on UNIX)
280             path('/home/sri/.vimrc')->basename;
281              
282             # "test" (on UNIX)
283             path('/home/sri/test.txt')->basename('.txt');
284              
285             =head2 child
286              
287             my $child = $path->child('.vimrc');
288              
289             Return a new L object relative to the path.
290              
291             # "/home/sri/.vimrc" (on UNIX)
292             path('/home')->child('sri', '.vimrc');
293              
294             =head2 chmod
295              
296             $path = $path->chmod(0644);
297              
298             Change file permissions.
299              
300             =head2 copy_to
301              
302             my $destination = $path->copy_to('/home/sri');
303             my $destination = $path->copy_to('/home/sri/.vimrc.backup');
304              
305             Copy file with L and return the destination as a L object.
306              
307             =head2 dirname
308              
309             my $name = $path->dirname;
310              
311             Return all but the last level of the path with L as a L object.
312              
313             # "/home/sri" (on UNIX)
314             path('/home/sri/.vimrc')->dirname;
315              
316             =head2 download
317              
318             my $bool = $path->download('https://example.com/test.tar.gz');
319             my $bool = $path->download('https://example.com/test.tar.gz', {headers => {Accept => '*/*'}});
320             my $bool = $path->download('https://example.com/test.tar.gz', {ua => Mojo::UserAgent->new});
321              
322             Download file from URL, returns true once the file has been downloaded completely. Incomplete downloads are resumed.
323             Follows C<10> redirects by default and does not limit the size of the response, which will be streamed memory
324             efficiently. Note that this method is B and might change without warning!
325              
326             =head2 extname
327              
328             my $ext = $path->extname;
329              
330             Return file extension of the path.
331              
332             # "js"
333             path('/home/sri/test.js')->extname;
334              
335             =head2 is_abs
336              
337             my $bool = $path->is_abs;
338              
339             Check if the path is absolute.
340              
341             # True (on UNIX)
342             path('/home/sri/.vimrc')->is_abs;
343              
344             # False (on UNIX)
345             path('.vimrc')->is_abs;
346              
347             =head2 list
348              
349             my $collection = $path->list;
350             my $collection = $path->list({hidden => 1});
351              
352             List all files in the directory and return a L object containing the results as L
353             objects. The list does not include C<.> and C<..>.
354              
355             # List files
356             say for path('/home/sri/myapp')->list->each;
357              
358             These options are currently available:
359              
360             =over 2
361              
362             =item dir
363              
364             dir => 1
365              
366             Include directories.
367              
368             =item hidden
369              
370             hidden => 1
371              
372             Include hidden files.
373              
374             =back
375              
376             =head2 list_tree
377              
378             my $collection = $path->list_tree;
379             my $collection = $path->list_tree({hidden => 1});
380              
381             List all files recursively in the directory and return a L object containing the results as
382             L objects. The list does not include C<.> and C<..>, and symbolic links to directories are not followed.
383              
384             # List all templates
385             say for path('/home/sri/myapp/templates')->list_tree->each;
386              
387             These options are currently available:
388              
389             =over 2
390              
391             =item dir
392              
393             dir => 1
394              
395             Include directories.
396              
397             =item hidden
398              
399             hidden => 1
400              
401             Include hidden files and directories.
402              
403             =item max_depth
404              
405             max_depth => 3
406              
407             Maximum number of levels to descend when searching for files.
408              
409             =back
410              
411             =head2 lstat
412              
413             my $stat = $path->lstat;
414              
415             Return a L object for the symlink.
416              
417             # Get symlink size
418             say path('/usr/sbin/sendmail')->lstat->size;
419              
420             # Get symlink modification time
421             say path('/usr/sbin/sendmail')->lstat->mtime;
422              
423             =head2 make_path
424              
425             $path = $path->make_path;
426             $path = $path->make_path({mode => 0711});
427              
428             Create the directories if they don't already exist, any additional arguments are passed through to L.
429              
430             =head2 move_to
431              
432             my $destination = $path->move_to('/home/sri');
433             my $destination = $path->move_to('/home/sri/.vimrc.backup');
434              
435             Move file with L and return the destination as a L object.
436              
437             =head2 new
438              
439             my $path = Mojo::File->new;
440             my $path = Mojo::File->new('/home/sri/.vimrc');
441             my $path = Mojo::File->new('/home', 'sri', '.vimrc');
442             my $path = Mojo::File->new(File::Temp->new);
443             my $path = Mojo::File->new(File::Temp->newdir);
444              
445             Construct a new L object, defaults to using the current working directory.
446              
447             # "foo/bar/baz.txt" (on UNIX)
448             Mojo::File->new('foo', 'bar', 'baz.txt');
449              
450             =head2 open
451              
452             my $handle = $path->open('+<');
453             my $handle = $path->open('r+');
454             my $handle = $path->open(O_RDWR);
455             my $handle = $path->open('<:encoding(UTF-8)');
456              
457             Open file with L.
458              
459             # Combine "fcntl.h" constants
460             use Fcntl qw(O_CREAT O_EXCL O_RDWR);
461             my $handle = path('/home/sri/test.pl')->open(O_RDWR | O_CREAT | O_EXCL);
462              
463             =head2 realpath
464              
465             my $realpath = $path->realpath;
466              
467             Resolve the path with L and return the result as a L object.
468              
469             =head2 remove
470              
471             $path = $path->remove;
472              
473             Delete file.
474              
475             =head2 remove_tree
476              
477             $path = $path->remove_tree;
478             $path = $path->remove_tree({keep_root => 1});
479              
480             Delete this directory and any files and subdirectories it may contain, any additional arguments are passed through to
481             L.
482              
483             =head2 sibling
484              
485             my $sibling = $path->sibling('.vimrc');
486              
487             Return a new L object relative to the directory part of the path.
488              
489             # "/home/sri/.vimrc" (on UNIX)
490             path('/home/sri/.bashrc')->sibling('.vimrc');
491              
492             # "/home/sri/.ssh/known_hosts" (on UNIX)
493             path('/home/sri/.bashrc')->sibling('.ssh', 'known_hosts');
494              
495             =head2 slurp
496              
497             my $bytes = $path->slurp;
498             my $chars = $path->slurp('UTF-8');
499              
500             Read all data at once from the file. If an encoding is provided, an attempt will be made to decode the content.
501              
502             =head2 spew
503              
504             $path = $path->spew($bytes);
505             $path = $path->spew($chars, 'UTF-8');
506              
507             Write all data at once to the file. If an encoding is provided, an attempt to encode the content will be made prior to
508             writing.
509              
510             =head2 spurt
511              
512             $path = $path->spurt(@bytes);
513              
514             Alias for L that writes multiple chunks of bytes.
515              
516             =head2 stat
517              
518             my $stat = $path->stat;
519              
520             Return a L object for the path.
521              
522             # Get file size
523             say path('/home/sri/.bashrc')->stat->size;
524              
525             # Get file modification time
526             say path('/home/sri/.bashrc')->stat->mtime;
527              
528             =head2 tap
529              
530             $path = $path->tap(sub {...});
531              
532             Alias for L.
533              
534             =head2 to_abs
535              
536             my $absolute = $path->to_abs;
537              
538             Return absolute path as a L object, the path does not need to exist on the file system.
539              
540             =head2 to_array
541              
542             my $parts = $path->to_array;
543              
544             Split the path on directory separators.
545              
546             # "home:sri:.vimrc" (on UNIX)
547             join ':', @{path('/home/sri/.vimrc')->to_array};
548              
549             =head2 to_rel
550              
551             my $relative = $path->to_rel('/some/base/path');
552              
553             Return a relative path from the original path to the destination path as a L object.
554              
555             # "sri/.vimrc" (on UNIX)
556             path('/home/sri/.vimrc')->to_rel('/home');
557              
558             =head2 to_string
559              
560             my $str = $path->to_string;
561              
562             Stringify the path.
563              
564             =head2 touch
565              
566             $path = $path->touch;
567              
568             Create file if it does not exist or change the modification and access time to the current time.
569              
570             # Safely read file
571             say path('.bashrc')->touch->slurp;
572              
573             =head2 with_roles
574              
575             my $new_class = Mojo::File->with_roles('Mojo::File::Role::One');
576             my $new_class = Mojo::File->with_roles('+One', '+Two');
577             $path = $path->with_roles('+One', '+Two');
578              
579             Alias for L.
580              
581             =head1 OPERATORS
582              
583             L overloads the following operators.
584              
585             =head2 array
586              
587             my @parts = @$path;
588              
589             Alias for L.
590              
591             =head2 bool
592              
593             my $bool = !!$path;
594              
595             Always true.
596              
597             =head2 stringify
598              
599             my $str = "$path";
600              
601             Alias for L.
602              
603             =head1 SEE ALSO
604              
605             L, L, L.
606              
607             =cut