File Coverage

blib/lib/File/Fu/File.pm
Criterion Covered Total %
statement 100 171 58.4
branch 19 68 27.9
condition 6 23 26.0
subroutine 26 34 76.4
pod 23 23 100.0
total 174 319 54.5


line stmt bran cond sub pod time code
1             package File::Fu::File;
2             $VERSION = v0.0.8;
3              
4 13     13   67 use warnings;
  13         28  
  13         471  
5 13     13   123 use strict;
  13         24  
  13         361  
6 13     13   71 use Carp;
  13         20  
  13         764  
7              
8 13     13   15031 use IO::File ();
  13         268316  
  13         420  
9              
10             =head1 NAME
11              
12             File::Fu::File - a filename object
13              
14             =head1 SYNOPSIS
15              
16             use File::Fu;
17              
18             my $file = File::Fu->file("path/to/file");
19             $file %= '.extension';
20             $file->e and warn "$file exists";
21              
22             $file->l and warn "$file is a link to ", $file->readlink;
23              
24             =cut
25              
26 13     13   123 use base 'File::Fu::Base';
  13         34  
  13         12152  
27              
28 13     13   15355 use Class::Accessor::Classy;
  13         71146  
  13         121  
29             lv 'file';
30             ro 'dir'; aka dir => 'dirname', 'parent';
31 13     13   3181 no Class::Accessor::Classy;
  13         42  
  13         62  
32              
33             #use overload ();
34              
35             =head1 Constructor
36              
37             =head2 new
38              
39             my $file = File::Fu::File->new($path);
40              
41             my $file = File::Fu::File->new(@path);
42              
43             =cut
44              
45             sub new {
46 122     122 1 741 my $package = shift;
47 122   66     452 my $class = ref($package) || $package;
48 122         379 my $self = {$class->_init(@_)};
49 122         325 bless($self, $class);
50 122         712 return($self);
51             } # end subroutine new definition
52             ########################################################################
53              
54             =head2 new_direct
55              
56             my $file = File::Fu::File->new_direct(
57             dir => $dir_obj,
58             file => $name
59             );
60              
61             =cut
62              
63             sub new_direct {
64 196     196 1 326 my $package = shift;
65 196   33     728 my $class = ref($package) || $package;
66 196         814 my $self = {@_};
67 196         440 bless($self, $class);
68 196         1127 return($self);
69             } # end subroutine new_direct definition
70             ########################################################################
71              
72             =head1 Class Constants
73              
74             =head2 dir_class
75              
76             Return the corresponding dir class for this file object. Default:
77             L.
78              
79             my $dc = $class->dir_class;
80              
81             =head2 is_dir
82              
83             Always false for a file.
84              
85             =head2 is_file
86              
87             Always true for a file.
88              
89             =cut
90              
91 13     13   8396 use constant dir_class => 'File::Fu::Dir';
  13         29  
  13         951  
92 13     13   69 use constant is_dir => 0;
  13         27  
  13         846  
93 13     13   307 use constant is_file => 1;
  13         27  
  13         35716  
94              
95             ########################################################################
96              
97             =for internal head2 _init
98             my %fields = $class->_init(@_);
99              
100             =cut
101              
102             sub _init {
103 122     122   163 my $class = shift;
104 122 50       421 my @dirs = @_ or croak("file must have a name");
105 122         201 my $file = pop(@dirs);
106 122 100       386 if($file =~ m#/#) {
107 33 50       176 croak("strange mix: ", join(',', @_, $file)) if(@dirs);
108 33         294 my %p = $class->dir_class->_init($file);
109 33         60 @dirs = @{$p{dirs}};
  33         118  
110 33         99 $file = pop(@dirs);
111             }
112              
113 122         601 return(dir => $class->dir_class->new(@dirs), file => $file);
114             } # end subroutine _init definition
115             ########################################################################
116              
117             =head1 Parts
118              
119             =head2 basename
120              
121             Returns a new object representing only the file part of the name.
122              
123             my $obj = $file->basename;
124              
125             =cut
126              
127             sub basename {
128 84     84 1 1422 my $self = shift;
129 84         2362 $self->new($self->file);
130             } # end subroutine basename definition
131             ########################################################################
132              
133             =head1 Methods
134              
135             =head2 stringify
136              
137             my $string = $file->stringify;
138              
139             =cut
140              
141             sub stringify {
142 514     514 1 16280 my $self = shift;
143 514         15300 my $dir = $self->dir;
144             #warn "stringify(..., $_[1], $_[2])";
145             #Carp::carp("stringify ", overload::StrVal($self), " ($self->{file})");
146 514 100       5593 $dir = $dir->is_cwd ? '' : $dir->stringify;
147 514         17502 return($dir . $self->file);
148             } # end subroutine stringify definition
149             ########################################################################
150              
151             =head2 append
152              
153             Append a string only to the filename part.
154              
155             $file->append('.gz');
156              
157             $file %= '.gz';
158              
159             (Yeah... I tried to use .=, but overloading hates me.)
160              
161             =cut
162              
163             sub append {
164 3     3 1 6 my $self = shift;
165 3         6 my ($tail) = @_;
166 3         89 $self->file .= $tail;
167 3         19 $self;
168             } # end subroutine append definition
169             ########################################################################
170              
171             =head2 map
172              
173             $file->map(sub {...});
174              
175             $file &= sub {...};
176              
177             =cut
178              
179             sub map :method {
180 2     2 1 4 my $self = shift;
181 2         3 my ($sub) = shift;
182 2         58 local $_ = $self->file;
183 2         15 $sub->();
184 2         60 $self->file = $_;
185 2         10 $self;
186             } # end subroutine map definition
187             ########################################################################
188              
189             =head2 absolute
190              
191             Get an absolute name (without checking the filesystem.)
192              
193             my $abs = $file->absolute;
194              
195             =cut
196              
197             sub absolute {
198 1     1 1 346 my ($self) = shift;
199 1         24 return($self->dir->absolute->file($self->file));
200             } # end subroutine absolutely definition
201             ########################################################################
202              
203             =head2 absolutely
204              
205             Get an absolute name (resolved on the filesytem.)
206              
207             my $abs = $file->absolutely;
208              
209             =cut
210              
211             sub absolutely {
212 13     13 1 45 my $self = shift;
213 13         612 return($self->dir->absolutely->file($self->file));
214             } # end subroutine absolutely definition
215             ########################################################################
216              
217             =head1 Doing stuff
218              
219             =head2 open
220              
221             Open the file with $mode ('<', 'r', '>', 'w', etc) -- see L.
222              
223             my $fh = $file->open($mode, $permissions);
224              
225             Throws an error if anything goes wrong or if the resulting filehandle
226             happens to be a directory.
227              
228             =cut
229              
230             # TODO should probably have our own filehandle so we can close in the
231             # destructor and croak there too?
232              
233             sub open :method {
234 75     75 1 2548 my $self = shift;
235 75 100       437 my $fh = IO::File->new($self, @_) or croak("cannot open '$self' $!");
236 74 50       6099 -d $fh and croak("$self is a directory");
237 74         865 return($fh);
238             } # end subroutine open definition
239             ########################################################################
240              
241              
242             =head2 sysopen
243              
244             Interface to the sysopen() builtin. The value of $mode is a text string
245             joined by '|' characters which must be valid O_* constants from Fcntl.
246              
247             my $fh = $file->sysopen($mode, $perms);
248              
249             =cut
250              
251             sub sysopen :method {
252 0     0 1 0 my $self = shift;
253 0         0 my ($mode, $perms) = @_;
254 0         0 my $m = 0;
255 0         0 foreach my $w (split /\|/, $mode) {
256 0         0 my $word = 'O_' . uc($w);
257 0 0       0 my $x = Fcntl->can($word) or croak("'$word' not found in Fcntl");
258 0         0 $m |= $x->();
259             }
260              
261 0         0 my $fh = IO::Handle->new;
262 0 0 0     0 sysopen($fh, "$self", $m, $perms || 0666)
263             or croak("error on sysopen '$self' - $!");
264              
265 0         0 return($fh);
266             } # sysopen ############################################################
267              
268             =head2 piped_open
269              
270             Opens a read pipe. The file is appended to @command.
271              
272             my $fh = $file->piped_open(@command);
273              
274             Example: useless use of cat.
275              
276             my $fh = $file->piped_open('cat');
277              
278             This interface is deprecated (maybe) because it is limited to commands
279             which take the $file as the last argument. See run() for the way of the
280             future.
281              
282             =cut
283              
284             sub piped_open {
285 0     0 1 0 my $self = shift;
286 0         0 my (@command) = @_;
287              
288             # TODO some way to decide where self goes in @command
289 0         0 push(@command, $self);
290              
291             # TODO closing STDIN and such before the fork?
292              
293             # TODO here is where we need our own filehandle object again
294 0 0       0 my $pid = open(my $fh, '-|', @command) or
295             croak("cannot exec '@command' $!");
296 0         0 return($fh);
297             } # end subroutine piped_open definition
298             ########################################################################
299              
300             =head2 run
301              
302             Treat C<$file> as a program and execute a pipe open.
303              
304             my $fh = $file->run(@args);
305              
306             If called in void context, runs C with autodie semantics and
307             multi-arg form (suppresses shell interpolation.)
308              
309             $file->run(@args);
310              
311             No special treatment is made for whether $file is relative or not (the
312             underlying C/C will search your path.) Use
313             File::Fu->which() to get an absolute path beforehand.
314              
315             File::Fu->which('ls')->run('-l');
316              
317             =cut
318              
319             sub run {
320 0     0 1 0 my $self = shift;
321 0         0 my (@args) = @_;
322              
323 0 0       0 if(defined wantarray) {
324             # TODO use IPC::Run
325 0         0 my $fh = IO::Handle->new;
326 0         0 my @command = ($self, @args);
327 0 0       0 my $pid = open($fh, '-|', @command) or
328             croak("cannot exec '@command' $!");
329 0         0 return($fh);
330             }
331             else {
332 0         0 my $ret = system {$self} $self, @args;
  0         0  
333 0 0       0 croak("error executing '$self'", $ret < 0 ? " $!" : '') if($ret);
    0          
334             }
335             } # run ################################################################
336              
337             =head2 touch
338              
339             Update the timestamp of a file (or create it.)
340              
341             $file->touch;
342              
343             =cut
344              
345             sub touch {
346 68     68 1 123 my $self = shift;
347 68 50       635 if(-e $self) {
348 0         0 $self->utime(time);
349             }
350             else {
351 68         1680 $self->open('>');
352             }
353 67         277 return($self);
354             } # end subroutine touch definition
355             ########################################################################
356              
357             =head2 mkfifo
358              
359             my $file = $file->mkfifo($mode);
360              
361             =cut
362              
363             sub mkfifo :method {
364 0     0 1 0 my $self = shift;
365 0         0 my ($mode) = @_;
366              
367 0   0     0 $mode ||= 0700;
368 0         0 require POSIX;
369 0 0       0 POSIX::mkfifo("$self", $mode) or croak("mkfifo '$self' failed $!");
370              
371 0         0 return $self;
372             } # mkfifo #############################################################
373              
374             =head2 link
375              
376             my $link = $file->link($name);
377              
378             =cut
379              
380             sub link :method {
381 1     1 1 9 my $self = shift;
382 1         2 my ($name) = @_;
383 1 50       3 link($self, $name) or croak("link '$self' to '$name' failed $!");
384 1         61 return($self->new($name));
385             } # end subroutine link definition
386             ########################################################################
387              
388             =head2 symlink
389              
390             my $link = $file->symlink($linkname);
391              
392             Note that symlinks are relative to where they live.
393              
394             my $dir = File::Fu->dir("foo");
395             my $file = $dir+'file';
396             # $file->symlink($dir+'link'); is a broken link
397             my $link = $file->basename->symlink($dir+'link');
398              
399             =head2 relative_symlink
400              
401             See L.
402              
403             =cut
404              
405             sub symlink :method {
406 4     4 1 18 my $self = shift;
407 4         9 my ($name) = @_;
408 4 50       11 symlink($self, $name) or
409             croak("symlink '$self' to '$name' failed $!");
410 4         308 return($self->new($name));
411             } # end subroutine symlink definition
412             ########################################################################
413              
414             # TODO
415             # my $link = $file->dwimlink(absolute|relative|samedir => $linkname);
416              
417             =head2 unlink
418              
419             $file->unlink;
420              
421             =cut
422              
423             sub unlink :method {
424 15     15 1 5810 my $self = shift;
425 15 50       52 unlink("$self") or croak("unlink '$self' failed $!");
426             } # end subroutine unlink definition
427             ########################################################################
428              
429             =head2 remove
430              
431             A forced unlink (chmod the file if it is not writable.)
432              
433             $file->remove;
434              
435             =cut
436              
437             sub remove {
438 0     0 1 0 my $self = shift;
439              
440 0 0       0 $self->chmod(0200) unless($self->w);
441 0         0 $self->unlink;
442             } # remove #############################################################
443              
444             =head2 readlink
445              
446             my $to = $file->readlink;
447              
448             =cut
449              
450             sub readlink :method {
451 4     4 1 975 my $self = shift;
452 4         13 my $name = readlink($self);
453 4 100       79 defined($name) or croak("cannot readlink '$self' $!");
454 2         8 return($self->new($name));
455             } # end subroutine readlink definition
456             ########################################################################
457              
458             ########################################################################
459             { # a closure for this variable
460             my $has_slurp;
461              
462             =head2 read
463              
464             Read the entire file into memory (or swap!)
465              
466             my @lines = $file->read;
467              
468             my $file = $file->read;
469              
470             If File::Slurp is available, options to read_file will be passed along.
471             See L.
472              
473             =cut
474              
475             sub read :method {
476 3     3 1 1034 my $self = shift;
477 3         7 my @args = @_;
478              
479 3   50     14 $has_slurp ||= eval {require File::Slurp; 1} || -1;
      66        
480              
481 3 50       16 if($has_slurp > 0) {
482 0         0 local $Carp::CarpLevel = 1;
483 0         0 return(File::Slurp::read_file("$self", @args, err_mode => 'croak'));
484             }
485             else {
486 3 50       13 croak("must have File::Slurp for fancy reads") if(@args);
487              
488 3         11 my $fh = $self->open;
489 3 100       23 local $/ = wantarray ? $/ : undef;
490 3         137 return(<$fh>);
491             }
492             } # end subroutine read definition
493             ########################################################################
494              
495             =head2 write
496              
497             Write the file's contents. Returns the $file object for chaining.
498              
499             $file = $file->write($content);
500              
501             If File::Slurp is available, $content may be either a scalar, scalar
502             ref, or array ref.
503              
504             $file->write($content, %args);
505              
506             =cut
507              
508             sub write {
509 0     0 1   my $self = shift;
510 0           my ($content, @args) = @_;
511              
512 0   0       $has_slurp ||= eval {require File::Slurp; 1} || -1;
      0        
513              
514 0 0         if($has_slurp > 0) {
515 0           local $Carp::CarpLevel = 1;
516 0           File::Slurp::write_file("$self",
517             {@args, err_mode => 'croak'},
518             $content
519             );
520             }
521             else {
522 0 0 0       croak("must have File::Slurp for fancy writes")
523             if(@args or ref($content));
524 0           my $fh = $self->open('>');
525 0           print $fh $content;
526 0 0         close($fh) or croak("write '$self' failed: $!");
527             }
528              
529 0           return $self;
530             } # end subroutine write definition
531             ########################################################################
532             } # File::Slurp closure
533             ########################################################################
534              
535             =head2 copy
536              
537             Copies $file to $dest (which can be a file or directory) and returns the
538             name of the new file as an object.
539              
540             my $new = $file->copy($dest);
541              
542             Note that if $dest is already a File object, that existing object will
543             be returned.
544              
545             =cut
546              
547             sub copy {
548 0     0 1   my $self = shift;
549 0           my ($dest) = shift;
550 0           my (%opts) = @_;
551              
552             # decide if this is file-to-dir or file-to-file
553 0 0         if(-d $dest) {
554 0           $dest = $self->dir_class->new($dest)->file($self->basename);
555             }
556             else {
557 0 0         $dest = $self->new($dest) unless(ref($dest));
558             }
559 0 0         if($dest->e) {
560 0 0         croak("'$dest' and '$self' are the same file")
561             if($self->is_same($dest));
562             }
563              
564             # TODO here's another good reason to have our own filehandle object:
565             # This fh-copy should be in there.
566 0           my $ifh = $self->open;
567 0           my $ofh = $dest->open('>');
568 0           binmode($_) for($ifh, $ofh);
569 0           while(1) {
570 0           my $buf;
571 0 0         defined(my $r = sysread($ifh, $buf, 1024)) or
572             croak("sysread failed $!");
573 0 0         $r or last;
574             # why did File::Copy::copy do it like this?
575 0           for(my $t = my $w = 0; $w < $r; $w += $t) {
576 0 0         $t = syswrite($ofh, $buf, $r - $w, $w) or
577             croak("syswrite failed $!");
578             }
579             }
580 0 0         close($ofh) or croak("write '$dest' failed: $!");
581             # TODO some form of rollback?
582              
583             # TODO handle opts
584             #if($opts{preserve}) {
585             # # TODO chmod/chown and such
586             # $dest->utime($self->stat->mtime);
587             #}
588              
589 0           return($dest);
590             } # copy ###############################################################
591              
592             =head2 move
593              
594             my $new = $file->move($dest);
595              
596             =cut
597              
598             sub move {
599 0     0 1   my $self = shift;
600 0           my $new = $self->copy(@_); # TODO can use rename?
601 0           $self->unlink;
602 0           return($new);
603             } # move ###############################################################
604              
605             ########################################################################
606              
607             =head1 AUTHOR
608              
609             Eric Wilhelm @
610              
611             http://scratchcomputing.com/
612              
613             =head1 BUGS
614              
615             If you found this module on CPAN, please report any bugs or feature
616             requests through the web interface at L. I will be
617             notified, and then you'll automatically be notified of progress on your
618             bug as I make changes.
619              
620             If you pulled this development version from my /svn/, please contact me
621             directly.
622              
623             =head1 COPYRIGHT
624              
625             Copyright (C) 2008 Eric L. Wilhelm, All Rights Reserved.
626              
627             =head1 NO WARRANTY
628              
629             Absolutely, positively NO WARRANTY, neither express or implied, is
630             offered with this software. You use this software at your own risk. In
631             case of loss, no person or entity owes you anything whatsoever. You
632             have been warned.
633              
634             =head1 LICENSE
635              
636             This program is free software; you can redistribute it and/or modify it
637             under the same terms as Perl itself.
638              
639             =cut
640              
641             require File::Fu;
642             # vi:ts=2:sw=2:et:sta
643             1;