File Coverage

blib/lib/Mojo/File.pm
Criterion Covered Total %
statement 160 160 100.0
branch 62 68 91.1
condition 21 25 84.0
subroutine 54 54 100.0
pod 34 34 100.0
total 331 341 97.0


line stmt bran cond sub pod time code
1             package Mojo::File;
2 83     1615   2150301 use Mojo::Base -strict;
  83         178  
  83         3113  
3 83     83   45672 use overload '@{}' => sub { shift->to_array }, bool => sub {1}, '""' => sub { ${$_[0]} }, fallback => 1;
  83     3042   155626  
  83     1493   1090  
  1857     43   4953  
  98         813  
  5911         75815  
  5911         181400  
4              
5 83     83   11761 use Carp qw(croak);
  83         2206  
  83         6917  
6 83     83   528 use Cwd qw(getcwd);
  83         158  
  83         4985  
7 83     83   480 use Exporter qw(import);
  83         240  
  83         2837  
8 83     83   798 use File::Basename ();
  83         242  
  83         2659  
9 83     83   51932 use File::Copy qw(copy move);
  83         383378  
  83         11362  
10 83     83   725 use File::Find qw(find);
  83         155  
  83         10374  
11 83     83   596 use File::Path ();
  83         187  
  83         2573  
12 83     83   39790 use File::Spec::Functions qw(abs2rel canonpath catfile file_name_is_absolute rel2abs splitdir);
  83         77854  
  83         8519  
13 83     83   47670 use File::stat ();
  83         719571  
  83         2942  
14 83     83   93434 use File::Temp ();
  83         1613238  
  83         3444  
15 83     83   42018 use IO::File ();
  83         77420  
  83         2892  
16 83     83   46186 use Mojo::Collection;
  83         471  
  83         5878  
17 83     83   660 use Mojo::Util qw(decode encode);
  83         172  
  83         343194  
18              
19             our @EXPORT_OK = ('curfile', 'path', 'tempdir', 'tempfile');
20              
21 174     174 1 1323 sub basename { File::Basename::basename ${shift()}, @_ }
  174         15138  
22              
23 498     498 1 56249 sub child { $_[0]->new(${shift()}, @_) }
  498         2229  
24              
25             sub chmod {
26 4     4 1 13 my ($self, $mode) = @_;
27 4 100       230 chmod $mode, $$self or croak qq{Can't chmod file "$$self": $!};
28 3         13 return $self;
29             }
30              
31             sub copy_to {
32 2     2 1 12 my ($self, $to) = @_;
33 2 50       14 copy($$self, $to) or croak qq{Can't copy file "$$self" to "$to": $!};
34 2 100       624 return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
35             }
36              
37 288     288 1 318596 sub curfile { __PACKAGE__->new(Cwd::realpath((caller)[1])) }
38              
39 97     97 1 926 sub dirname { $_[0]->new(scalar File::Basename::dirname ${$_[0]}) }
  97         5188  
40              
41             sub download {
42 13   100 13 1 126 my ($self, $url, $options) = (shift, shift, shift // {});
43             my $ua = $options->{ua}
44 13   66     59 || do { require Mojo::UserAgent; Mojo::UserAgent->new(max_redirects => 10, max_response_size => 0) };
45 13   100     51 my $tx = _download_error($ua->transactor->download($ua->head($url => $options->{headers} // {}), $$self));
46 10 100       84 return $tx ? !!_download_error($ua->start($tx)) : 1;
47             }
48              
49 96 100   96 1 546 sub extname { shift->basename =~ /.+\.([^.]+)$/ ? $1 : '' }
50              
51 49     49 1 96 sub is_abs { file_name_is_absolute ${shift()} }
  49         412  
52              
53             sub list {
54 31   100 31 1 192 my ($self, $options) = (shift, shift // {});
55              
56 31 100       704 return Mojo::Collection->new unless -d $$self;
57 29 50       1453 opendir(my $dir, $$self) or croak qq{Can't open directory "$$self": $!};
58 29 100       1341 my @files = grep { $_ ne '.' && $_ ne '..' } readdir $dir;
  250         983  
59 29 100       186 @files = grep { !/^\./ } @files unless $options->{hidden};
  172         440  
60 29         64 @files = map { catfile $$self, $_ } @files;
  188         868  
61 29 100       119 @files = grep { !-d } @files unless $options->{dir};
  170         3383  
62              
63 29         152 return Mojo::Collection->new(map { $self->new($_) } sort @files);
  157         372  
64             }
65              
66             sub list_tree {
67 236   100 236 1 1635 my ($self, $options) = (shift, shift // {});
68              
69             # This may break in the future, but is worth it for performance
70 236 100       2609 local $File::Find::skip_pattern = qr/^\./ unless $options->{hidden};
71              
72             # The File::Find documentation lies, this is needed for CIFS
73 236 50       979 local $File::Find::dont_use_nlink = 1 if $options->{dont_use_nlink};
74              
75 236         488 my %all;
76             my $wanted = sub {
77 2876 100   2876   9810 if ($options->{max_depth}) {
78 62         202 (my $rel = $File::Find::name) =~ s!^\Q$$self\E/?!!;
79 62 100       234 $File::Find::prune = 1 if splitdir($rel) >= $options->{max_depth};
80             }
81 2876 100 100     175106 $all{$File::Find::name}++ if $options->{dir} || !-d $File::Find::name;
82 236         1591 };
83 236 100       25508 find {wanted => $wanted, no_chdir => 1}, $$self if -d $$self;
84 236         1439 delete $all{$$self};
85              
86 236         2194 return Mojo::Collection->new(map { $self->new(canonpath $_) } sort keys %all);
  2145         6801  
87             }
88              
89 1     1 1 1331 sub lstat { File::stat::lstat(${shift()}) }
  1         5  
90              
91             sub make_path {
92 23     23 1 55 my $self = shift;
93 23         4422 File::Path::make_path $$self, @_;
94 23         153 return $self;
95             }
96              
97             sub move_to {
98 10     10 1 47 my ($self, $to) = @_;
99 10 50       65 move($$self, $to) or croak qq{Can't move file "$$self" to "$to": $!};
100 10 100       1465 return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
101             }
102              
103             sub new {
104 10653     10653 1 465667 my $class = shift;
105 10653 100       20609 croak 'Invalid path' if grep { !defined } @_;
  16523         44107  
106 10651 100       49634 my $value = @_ == 1 ? $_[0] : @_ > 1 ? catfile @_ : canonpath getcwd;
    100          
107 10651   66     67526 return bless \$value, ref $class || $class;
108             }
109              
110             sub open {
111 141     141 1 3019 my $self = shift;
112 141         1548 my $handle = IO::File->new;
113 141 100       10498 $handle->open($$self, @_) or croak qq{Can't open file "$$self": $!};
114 140         18676 return $handle;
115             }
116              
117 3349     3349 1 395801 sub path { __PACKAGE__->new(@_) }
  43         4539  
118              
119 919     919 1 7898 sub realpath { $_[0]->new(Cwd::realpath ${$_[0]}) }
  919         89104  
120              
121             sub remove {
122 52     52 1 182 my ($self, $mode) = @_;
123 52 100 66     8242 unlink $$self or croak qq{Can't remove file "$$self": $!} if -e $$self;
124 51         1353 return $self;
125             }
126              
127             sub remove_tree {
128 2     2 1 4 my $self = shift;
129 2         1675 File::Path::remove_tree $$self, @_;
130 2         21 return $self;
131             }
132              
133             sub sibling {
134 283     283 1 765 my $self = shift;
135 283         7952 return $self->new(scalar File::Basename::dirname($self), @_);
136             }
137              
138             sub slurp {
139 130     130 1 1124 my ($self, $encoding) = @_;
140              
141 130 100       6009 CORE::open my $file, '<', $$self or croak qq{Can't open file "$$self": $!};
142 129         910 my $ret = my $content = '';
143 129         1158 while ($ret = $file->sysread(my $buffer, 131072, 0)) { $content .= $buffer }
  128         5420  
144 129 50       2299 croak qq{Can't read from file "$$self": $!} unless defined $ret;
145              
146 129 100       2538 return $encoding ? decode($encoding, $content) : $content;
147             }
148              
149             sub spew {
150 55     55 1 309 my ($self, $content, $encoding) = @_;
151 55 100       233 $content = encode($encoding, $content) if $encoding;
152 55 100       4795 CORE::open my $file, '>', $$self or croak qq{Can't open file "$$self": $!};
153 54 100 50     1198 ($file->syswrite($content) // -1) == length $content or croak qq{Can't write to file "$$self": $!};
154 52         4538 return $self;
155             }
156              
157 1     1 1 93 sub spurt { shift->spew(join '', @_) }
158              
159 4     4 1 17 sub stat { File::stat::stat(${shift()}) }
  4         23  
160              
161 1     1 1 14 sub tap { shift->Mojo::Base::tap(@_) }
162              
163 38     38 1 447004 sub tempdir { __PACKAGE__->new(File::Temp->newdir(@_)) }
164              
165 53     53 1 9961 sub tempfile { __PACKAGE__->new(File::Temp->new(@_)) }
166              
167 142     142 1 438 sub to_abs { $_[0]->new(rel2abs ${$_[0]}) }
  142         1039  
168              
169 2292     2292 1 3885 sub to_array { [splitdir ${shift()}] }
  2292         6812  
170              
171 2165     2165 1 3853 sub to_rel { $_[0]->new(abs2rel(${$_[0]}, $_[1])) }
  2165         7300  
172              
173 3341     3341 1 7600 sub to_string {"${$_[0]}"}
  3341         49565  
174              
175             sub touch {
176 4     4 1 1381 my $self = shift;
177 4 100       121 $self->open('>') unless -e $$self;
178 4 50       101 utime undef, undef, $$self or croak qq{Can't touch file "$$self": $!};
179 4         48 return $self;
180             }
181              
182 1     1 1 769 sub with_roles { shift->Mojo::Base::with_roles(@_) }
183              
184             sub _download_error {
185 22     22   51 my $tx = shift;
186              
187 22 100       90 return $tx unless my $err = $tx->error;
188 6 100 100     47 return undef if $err->{message} eq 'Download complete' || $err->{message} eq 'Download incomplete';
189 3 100       301 croak "$err->{code} response: $err->{message}" if $err->{code};
190 2         527 croak "Download error: $err->{message}";
191             }
192              
193             1;
194              
195             =encoding utf8
196              
197             =head1 NAME
198              
199             Mojo::File - File system paths
200              
201             =head1 SYNOPSIS
202              
203             use Mojo::File;
204              
205             # Portably deal with file system paths
206             my $path = Mojo::File->new('/home/sri/.vimrc');
207             say $path->slurp;
208             say $path->dirname;
209             say $path->basename;
210             say $path->extname;
211             say $path->sibling('.bashrc');
212              
213             # Use the alternative constructor
214             use Mojo::File qw(path);
215             my $path = path('/tmp/foo/bar')->make_path;
216             $path->child('test.txt')->spew('Hello Mojo!');
217              
218             =head1 DESCRIPTION
219              
220             L is a scalar-based container for file system paths that provides a friendly API for dealing with different
221             operating systems.
222              
223             # Access scalar directly to manipulate path
224             my $path = Mojo::File->new('/home/sri/test');
225             $$path .= '.txt';
226              
227             =head1 FUNCTIONS
228              
229             L implements the following functions, which can be imported individually.
230              
231             =head2 curfile
232              
233             my $path = curfile;
234              
235             Construct a new scalar-based L object for the absolute path to the current source file.
236              
237             =head2 path
238              
239             my $path = path;
240             my $path = path('/home/sri/.vimrc');
241             my $path = path('/home', 'sri', '.vimrc');
242             my $path = path(File::Temp->newdir);
243              
244             Construct a new scalar-based L object, defaults to using the current working directory.
245              
246             # "foo/bar/baz.txt" (on UNIX)
247             path('foo', 'bar', 'baz.txt');
248              
249             =head2 tempdir
250              
251             my $path = tempdir;
252             my $path = tempdir('tempXXXXX');
253              
254             Construct a new scalar-based L object for a temporary directory with L.
255              
256             # Longer version
257             my $path = path(File::Temp->newdir('tempXXXXX'));
258              
259             =head2 tempfile
260              
261             my $path = tempfile;
262             my $path = tempfile(DIR => '/tmp');
263              
264             Construct a new scalar-based L object for a temporary file with L.
265              
266             # Longer version
267             my $path = path(File::Temp->new(DIR => '/tmp'));
268              
269             =head1 METHODS
270              
271             L implements the following methods.
272              
273             =head2 basename
274              
275             my $name = $path->basename;
276             my $name = $path->basename('.txt');
277              
278             Return the last level of the path with L.
279              
280             # ".vimrc" (on UNIX)
281             path('/home/sri/.vimrc')->basename;
282              
283             # "test" (on UNIX)
284             path('/home/sri/test.txt')->basename('.txt');
285              
286             =head2 child
287              
288             my $child = $path->child('.vimrc');
289              
290             Return a new L object relative to the path.
291              
292             # "/home/sri/.vimrc" (on UNIX)
293             path('/home')->child('sri', '.vimrc');
294              
295             =head2 chmod
296              
297             $path = $path->chmod(0644);
298              
299             Change file permissions.
300              
301             =head2 copy_to
302              
303             my $destination = $path->copy_to('/home/sri');
304             my $destination = $path->copy_to('/home/sri/.vimrc.backup');
305              
306             Copy file with L and return the destination as a L object.
307              
308             =head2 dirname
309              
310             my $name = $path->dirname;
311              
312             Return all but the last level of the path with L as a L object.
313              
314             # "/home/sri" (on UNIX)
315             path('/home/sri/.vimrc')->dirname;
316              
317             =head2 download
318              
319             my $bool = $path->download('https://example.com/test.tar.gz');
320             my $bool = $path->download('https://example.com/test.tar.gz', {headers => {Accept => '*/*'}});
321             my $bool = $path->download('https://example.com/test.tar.gz', {ua => Mojo::UserAgent->new});
322              
323             Download file from URL, returns true once the file has been downloaded completely. Incomplete downloads are resumed.
324             Follows C<10> redirects by default and does not limit the size of the response, which will be streamed memory
325             efficiently. Note that this method is B and might change without warning!
326              
327             =head2 extname
328              
329             my $ext = $path->extname;
330              
331             Return file extension of the path.
332              
333             # "js"
334             path('/home/sri/test.js')->extname;
335              
336             =head2 is_abs
337              
338             my $bool = $path->is_abs;
339              
340             Check if the path is absolute.
341              
342             # True (on UNIX)
343             path('/home/sri/.vimrc')->is_abs;
344              
345             # False (on UNIX)
346             path('.vimrc')->is_abs;
347              
348             =head2 list
349              
350             my $collection = $path->list;
351             my $collection = $path->list({hidden => 1});
352              
353             List all files in the directory and return a L object containing the results as L
354             objects. The list does not include C<.> and C<..>.
355              
356             # List files
357             say for path('/home/sri/myapp')->list->each;
358              
359             These options are currently available:
360              
361             =over 2
362              
363             =item dir
364              
365             dir => 1
366              
367             Include directories.
368              
369             =item hidden
370              
371             hidden => 1
372              
373             Include hidden files.
374              
375             =back
376              
377             =head2 list_tree
378              
379             my $collection = $path->list_tree;
380             my $collection = $path->list_tree({hidden => 1});
381              
382             List all files recursively in the directory and return a L object containing the results as
383             L objects. The list does not include C<.> and C<..>.
384              
385             # List all templates
386             say for path('/home/sri/myapp/templates')->list_tree->each;
387              
388             These options are currently available:
389              
390             =over 2
391              
392             =item dir
393              
394             dir => 1
395              
396             Include directories.
397              
398             =item dont_use_nlink
399              
400             dont_use_nlink => 1
401              
402             Force L to always stat directories.
403              
404             =item hidden
405              
406             hidden => 1
407              
408             Include hidden files and directories.
409              
410             =item max_depth
411              
412             max_depth => 3
413              
414             Maximum number of levels to descend when searching for files.
415              
416             =back
417              
418             =head2 lstat
419              
420             my $stat = $path->lstat;
421              
422             Return a L object for the symlink.
423              
424             # Get symlink size
425             say path('/usr/sbin/sendmail')->lstat->size;
426              
427             # Get symlink modification time
428             say path('/usr/sbin/sendmail')->lstat->mtime;
429              
430             =head2 make_path
431              
432             $path = $path->make_path;
433             $path = $path->make_path({mode => 0711});
434              
435             Create the directories if they don't already exist, any additional arguments are passed through to L.
436              
437             =head2 move_to
438              
439             my $destination = $path->move_to('/home/sri');
440             my $destination = $path->move_to('/home/sri/.vimrc.backup');
441              
442             Move file with L and return the destination as a L object.
443              
444             =head2 new
445              
446             my $path = Mojo::File->new;
447             my $path = Mojo::File->new('/home/sri/.vimrc');
448             my $path = Mojo::File->new('/home', 'sri', '.vimrc');
449             my $path = Mojo::File->new(File::Temp->new);
450             my $path = Mojo::File->new(File::Temp->newdir);
451              
452             Construct a new L object, defaults to using the current working directory.
453              
454             # "foo/bar/baz.txt" (on UNIX)
455             Mojo::File->new('foo', 'bar', 'baz.txt');
456              
457             =head2 open
458              
459             my $handle = $path->open('+<');
460             my $handle = $path->open('r+');
461             my $handle = $path->open(O_RDWR);
462             my $handle = $path->open('<:encoding(UTF-8)');
463              
464             Open file with L.
465              
466             # Combine "fcntl.h" constants
467             use Fcntl qw(O_CREAT O_EXCL O_RDWR);
468             my $handle = path('/home/sri/test.pl')->open(O_RDWR | O_CREAT | O_EXCL);
469              
470             =head2 realpath
471              
472             my $realpath = $path->realpath;
473              
474             Resolve the path with L and return the result as a L object.
475              
476             =head2 remove
477              
478             $path = $path->remove;
479              
480             Delete file.
481              
482             =head2 remove_tree
483              
484             $path = $path->remove_tree;
485             $path = $path->remove_tree({keep_root => 1});
486              
487             Delete this directory and any files and subdirectories it may contain, any additional arguments are passed through to
488             L.
489              
490             =head2 sibling
491              
492             my $sibling = $path->sibling('.vimrc');
493              
494             Return a new L object relative to the directory part of the path.
495              
496             # "/home/sri/.vimrc" (on UNIX)
497             path('/home/sri/.bashrc')->sibling('.vimrc');
498              
499             # "/home/sri/.ssh/known_hosts" (on UNIX)
500             path('/home/sri/.bashrc')->sibling('.ssh', 'known_hosts');
501              
502             =head2 slurp
503              
504             my $bytes = $path->slurp;
505             my $chars = $path->slurp('UTF-8');
506              
507             Read all data at once from the file. If an encoding is provided, an attempt will be made to decode the content.
508              
509             =head2 spew
510              
511             $path = $path->spew($bytes);
512             $path = $path->spew($chars, 'UTF-8');
513              
514             Write all data at once to the file. If an encoding is provided, an attempt to encode the content will be made prior to
515             writing.
516              
517             =head2 spurt
518              
519             $path = $path->spurt(@bytes);
520              
521             Alias for L that writes multiple chunks of bytes.
522              
523             =head2 stat
524              
525             my $stat = $path->stat;
526              
527             Return a L object for the path.
528              
529             # Get file size
530             say path('/home/sri/.bashrc')->stat->size;
531              
532             # Get file modification time
533             say path('/home/sri/.bashrc')->stat->mtime;
534              
535             =head2 tap
536              
537             $path = $path->tap(sub {...});
538              
539             Alias for L.
540              
541             =head2 to_abs
542              
543             my $absolute = $path->to_abs;
544              
545             Return absolute path as a L object, the path does not need to exist on the file system.
546              
547             =head2 to_array
548              
549             my $parts = $path->to_array;
550              
551             Split the path on directory separators.
552              
553             # "home:sri:.vimrc" (on UNIX)
554             join ':', @{path('/home/sri/.vimrc')->to_array};
555              
556             =head2 to_rel
557              
558             my $relative = $path->to_rel('/some/base/path');
559              
560             Return a relative path from the original path to the destination path as a L object.
561              
562             # "sri/.vimrc" (on UNIX)
563             path('/home/sri/.vimrc')->to_rel('/home');
564              
565             =head2 to_string
566              
567             my $str = $path->to_string;
568              
569             Stringify the path.
570              
571             =head2 touch
572              
573             $path = $path->touch;
574              
575             Create file if it does not exist or change the modification and access time to the current time.
576              
577             # Safely read file
578             say path('.bashrc')->touch->slurp;
579              
580             =head2 with_roles
581              
582             my $new_class = Mojo::File->with_roles('Mojo::File::Role::One');
583             my $new_class = Mojo::File->with_roles('+One', '+Two');
584             $path = $path->with_roles('+One', '+Two');
585              
586             Alias for L.
587              
588             =head1 OPERATORS
589              
590             L overloads the following operators.
591              
592             =head2 array
593              
594             my @parts = @$path;
595              
596             Alias for L.
597              
598             =head2 bool
599              
600             my $bool = !!$path;
601              
602             Always true.
603              
604             =head2 stringify
605              
606             my $str = "$path";
607              
608             Alias for L.
609              
610             =head1 SEE ALSO
611              
612             L, L, L.
613              
614             =cut