File Coverage

blib/lib/App/lntree.pm
Criterion Covered Total %
statement 55 65 84.6
branch 16 32 50.0
condition 2 6 33.3
subroutine 10 11 90.9
pod 1 3 33.3
total 84 117 71.7


line stmt bran cond sub pod time code
1             package App::lntree;
2             BEGIN {
3 2     2   167912 $App::lntree::VERSION = '0.0013';
4             }
5             # ABSTRACT: Create a best-effort symlink-based mirror of a directory
6              
7 2     2   19 use strict;
  2         5  
  2         59  
8 2     2   10 use warnings;
  2         3  
  2         56  
9              
10             # TODO Source as file, target as file?
11             # TODO Absolute source, absolute target?
12             # TODO Test file/directory/symlink overwriting
13              
14 2     2   1754 use Path::Class;
  2         111205  
  2         146  
15 2     2   18 use File::Spec;
  2         2  
  2         36  
16 2     2   7859 use File::Spec::Link;
  2         3444  
  2         88  
17 2     2   1829 use Getopt::Usaginator <<_END_;
  2         70570  
  2         14  
18              
19             Usage: lntree
20              
21             _END_
22              
23             sub run {
24 0     0 0 0 my $self = shift;
25 0         0 my @arguments = @_;
26              
27 0 0       0 usage 0 unless @arguments;
28 0 0       0 usage "Missing or " unless @arguments > 1;
29              
30 0         0 my $source = shift @arguments;
31 0         0 my $target = shift @arguments;
32              
33 0 0       0 usage "Missing " unless defined $source;
34 0 0       0 usage "Source directory ($source) does not exist or is not a directory" unless -d $source;
35 0 0       0 usage "Target directory ($target) already exists and is a file" if -f $target;
36              
37 0         0 $self->lntree( $source, $target );
38             }
39              
40             sub lntree {
41 5     5 1 11950 my $self = shift;
42 5         11 my $source = shift;
43 5         7 my $target = shift;
44              
45 5 50       15 die "Missing source" unless defined $source;
46 5 50       11 die "Missing target" unless defined $target;
47 5 50       133 die "Source directory ($source) does not exist or is not a directory" unless -d $source;
48 5 100       217 die "Target directory ($target) already exists and is a file" if -f $target;
49              
50 4         125 my $dry_run = 0;
51              
52 4         14 $source = dir $source;
53 4         181 $target = dir $target;
54 4         175 my $absolute = $target->is_absolute;
55             $source->recurse( callback => sub {
56 31     31   8039 my $file = shift;
57 31         89 my ( $from_path, $to_path ) = App::lntree->resolve( $source, $target, $file );
58 31 100       96 if ( -d $file ) {
59 10         298 my $dir = $target->subdir( $to_path );
60 10 50       524 $dry_run or $dir->mkpath;
61             }
62             else {
63 21         841 my $file = $target->file( $to_path );
64 21         1672 my $link_path = $from_path;
65 21 100       57 if ( -l $file ) {
    100          
66 2 50 33     136 $dry_run or unlink $file or warn "Unable to unlink symlink \"$to_path\": $!\n";
67             }
68             elsif ( -e $file ) {
69 2         195 return;
70             }
71 19 50 33     1886 $dry_run or symlink $link_path, $file or die "Unable to symlink \"$link_path -> \"$to_path\": $!\n";
72             }
73 4         133 } );
74             }
75              
76             sub resolve {
77 37     37 0 3711 my $self = shift;
78 37         86 my $from = dir shift;
79 37         1753 my $to = dir shift;
80 37         1408 my $path = shift;
81              
82 37         150 my $absolute = File::Spec->file_name_is_absolute( $to );
83              
84 37         590 my $from_path;
85 37 100       72 if ( $absolute ) {
86 32         197 $from_path = File::Spec->rel2abs( $path );
87             }
88             else {
89 5         21 my @path = File::Spec->splitdir( $path );
90 5         18 my $depth = @path - ( 1 + $from->dir_list );
91 5         48 $from_path = File::Spec->canonpath( join '/', ( ( '..' ) x $depth ), File::Spec->abs2rel( $path, $to ) );
92             }
93              
94 37         2180 my $to_path = File::Spec->canonpath( join '/', File::Spec->abs2rel( $path, $from ) );
95              
96 37         3608 return ( $from_path, $to_path );
97             }
98              
99             1;
100              
101              
102              
103             =pod
104              
105             =head1 NAME
106              
107             App::lntree - Create a best-effort symlink-based mirror of a directory
108              
109             =head1 VERSION
110              
111             version 0.0013
112              
113             =head1 SYNOPSIS
114              
115             lntree ~/project1 target/
116             lntree ~/project2 target/
117              
118             # target/ is now a combination of project1 & project2, with project2 taking precedence
119              
120             =head1 DESCRIPTION
121              
122             App::lntree is a utility for making a best-effort symlink-based mirror of a directory. The algorithm is:
123              
124             - Directories are always recreated, NOT symlinked
125             - A symlink conflict will be resolved by removing the original symlink
126             - Regular files (including directories) are left untouched
127              
128             =head1 USAGE
129              
130             =head2 lntree
131              
132             Create a symlink mirror of into , creating if necessary
133              
134             =head1 AUTHOR
135              
136             Robert Krimen
137              
138             =head1 COPYRIGHT AND LICENSE
139              
140             This software is copyright (c) 2010 by Robert Krimen.
141              
142             This is free software; you can redistribute it and/or modify it under
143             the same terms as the Perl 5 programming language system itself.
144              
145             =cut
146              
147              
148             __END__