File Coverage

blib/lib/App/mvr.pm
Criterion Covered Total %
statement 53 62 85.4
branch 28 38 73.6
condition 8 12 66.6
subroutine 10 11 90.9
pod 1 1 100.0
total 100 124 80.6


line stmt bran cond sub pod time code
1             package App::mvr;
2 13     13   252698 use v5.14.0;
  13         51  
  13         560  
3 13     13   74 use strict;
  13         24  
  13         691  
4 13     13   68 use warnings;
  13         29  
  13         686  
5             # ABSTRACT: like mv, but clever
6             our $VERSION = '0.005'; # VERSION
7              
8 13     13   69 use Exporter qw(import);
  13         25  
  13         740  
9             our @EXPORT = qw(mvr);
10              
11 13     13   15029 use Path::Tiny 0.034;
  13         231896  
  13         1123  
12 13     13   19765 use Try::Tiny;
  13         32298  
  13         885  
13 13     13   93 use Carp;
  13         49  
  13         12129  
14              
15             our $VERBOSE = 0;
16              
17              
18             my $duplicates = sub {
19             my $A = shift;
20             my $B = shift;
21             return if $A->stat->size != $B->stat->size; # avoid reading file off disk
22              
23             # Pull out the big guns
24             return $A->digest eq $B->digest;
25             };
26              
27             sub mvr {
28 17     17 1 45607 my %args = @_;
29 17   33     103 $args{dest} //= delete $args{destination};
30 17 100       98 $args{source} = [delete $args{source}] unless ref $args{source} eq 'ARRAY';
31              
32 17         99 my $dest = path( $args{dest} );
33 17   66     562 my $dest_is_dir = $dest->exists && $dest->is_dir;
34 17         187 croak sprintf("target `%s' is not a directory\n", $dest)
35 17 100 100     1336 if @{ $args{source} } > 1 and !$dest_is_dir;
36              
37 16 100       1148 STDOUT->autoflush(1) if $VERBOSE;
38 16         126446 foreach my $from ( map { path($_) } @{ $args{source} } ) {
  18         125  
  16         66  
39 18 100       684 print "\r${from}\e[K" if $VERBOSE == 1;
40              
41 18 50       3628 unless ($from->exists) {
42 0         0 carp sprintf("Cannot stat `%s': No such file or directory\n", $from);
43 0         0 next;
44             }
45 18 100       824 my $to = path( $dest, ($dest_is_dir ? $from->basename : ()) );
46 18 100       828 croak sprintf("`%s' and `%s' are the same file\n", $to, $from)
47             if $from->absolute eq $to->absolute;
48              
49 17 100       1915 if ($to->exists) {
50 11 100       239 if ($args{deduplicate}) {
51 4 50       229 print STDERR "File already exists; checking for duplication..."
52             if $VERBOSE > 1;
53 4 100       22 if ($duplicates->($from, $to)) {
54 2 50       24483 printf STDERR
55             " `%s' and `%s' are duplicates; removing the source file.\n",
56             $from->basename, $to->basename
57             if $VERBOSE > 1;
58 2         169 $from->remove;
59 2         328 $to->touch;
60 2         68 next;
61             }
62             else {
63 2 50       7688 printf STDERR
64             " `%s' and `%s' are not duplicates.\n",
65             $from->basename, $to->basename
66             if $VERBOSE > 1;
67             }
68             }
69              
70 9         124 my ($prefix, $suffix) = $to->basename =~ m{^(.*)\.(\w+)$};
71 9 100 66     173 $to = Path::Tiny->tempfile(
    100          
72             UNLINK => 0,
73             TEMPLATE => ($prefix // $to->basename) . '-XXXXXX',
74             DIR => $dest_is_dir ? $dest : $dest->dirname,
75             ( $suffix ? (SUFFIX => ".$suffix") : () ),
76             );
77 9 100       102515 printf STDERR "File already exists; renaming `%s' to `%s'\n",
78             $from->basename, $to->basename
79             if $VERBOSE > 1;
80             }
81              
82             try {
83 15     15   640 $from->move($to);
84             }
85             catch {
86 13     13   18880 use POSIX qw(:errno_h);
  13         133143  
  13         113  
87 0 0   0     if ($_->{err} == EXDEV) { # Invalid cross-device link
88 0 0         printf STDERR "File can't be renamed across filesystems; copying `%s' to `%s' instead...",
89             $from->basename, $to->basename
90             if $VERBOSE > 1;
91 0           $from->copy($to);
92 0           $to->touch( $from->stat->mtime );
93 0 0         print STDERR " done. Removing original file.\n" if $VERBOSE > 1;
94 0           $from->remove;
95             }
96             else {
97 0           die $_;
98             }
99 15         644 };
100             }
101             }
102              
103             1;
104              
105             __END__