File Coverage

blib/lib/File/LinkDir.pm
Criterion Covered Total %
statement 115 154 74.6
branch 51 114 44.7
condition 8 24 33.3
subroutine 13 13 100.0
pod 3 3 100.0
total 190 308 61.6


line stmt bran cond sub pod time code
1             package File::LinkDir;
2              
3 8     8   246045 use strict;
  8         23  
  8         325  
4 8     8   44 use warnings;
  8         18  
  8         271  
5              
6 8     8   224 use 5.008;
  8         30  
  8         383  
7 8     8   44 use Cwd qw;
  8         20  
  8         436  
8 8     8   47 use File::Find;
  8         21  
  8         510  
9 8     8   45 use File::Path qw;
  8         13  
  8         580  
10 8     8   8867 use File::Spec::Functions qw;
  8         8291  
  8         19874  
11              
12             our $VERSION = '1.02';
13             $VERSION = eval $VERSION;
14              
15             sub new
16             {
17 7     7 1 887 my $class = shift;
18              
19 7         22 my $self = {};
20 7         23 bless $self, $class;
21              
22 7         60 $self->{version} = $VERSION;
23              
24 7         42 $self->init( @_ );
25              
26 7         164 return $self;
27             }
28              
29             sub init
30             {
31 7     7 1 16 my $self = shift;
32 7         17 my %opt;
33 7         32 my @opts = @_;
34              
35 7 50       34 return if $opts[0] eq 'skipinit';
36              
37 7         25 $self->{addignore} = [];
38 7         23 $self->{ignore} = '(?:.*/)?.(?:git(?!config\b).*|svn)(?:/.*)?$';
39 7         24 $self->{force} = 0;
40 7         18 $self->{hard} = 0;
41 7         18 $self->{dryrun} = 0;
42              
43 7         32 while( @opts )
44             {
45 20         42 my ( $opt, $value ) = ( shift @opts, shift @opts );
46 20 100       54 if ( $opt eq 'addignore' )
47             {
48 1         2 for my $rx ( @{$value} )
  1         2  
49             {
50 5         6 local $@;
51 5         8 eval { $rx = qr/$rx/ };
  5         60  
52 5 50       18 die "Invalid regex passed to addignore: $@\n" if $@;
53             }
54 1         5 $self->{$opt} = $value;
55             }
56             else
57             {
58 19         71 $self->{$opt} = $value;
59             }
60             }
61              
62             {
63 7         12 local $@;
  7         15  
64 7         17 eval { $self->{ignore} = qr/$self->{ignore}/ };
  7         346  
65 7 50       51 die "Invalid regex passed to ignore: $@\n" if $@;
66             }
67              
68 7         17 for my $rx ( @{ $self->{addignore} } )
  7         26  
69             {
70 5         6 local $@;
71 5         33 eval { $rx = qr/$rx/ };
  5         17  
72 5 50       19 die "Invalid regex passed to addignore: $@\n" if $@;
73             }
74            
75 7 50       34 die "You must supply a source directory\n" unless ( defined $self->{source} );
76 7         636 $self->{source} = abs_path( $self->{source} );
77 7 50       197 die "You must supply a valid source directory\n" unless ( -d $self->{source} );
78 7 50       83 $self->{source} =~ /^(.*)$/ && ($self->{source} = $1);
79              
80 7 50       28 die "You must supply a dest directory\n" unless ( defined $self->{dest} );
81 7         564 $self->{dest} = abs_path( $self->{dest} );
82 7 50       150 die "You must supply a valid dest directory\n" unless ( -d $self->{dest} );
83 7 50       68 $self->{dest} =~ /^(.*)$/ && ($self->{dest} = $1);
84             }
85              
86             sub run
87             {
88 6     6 1 41 my $self = shift;
89              
90 6         44 my $pwd = getcwd();
91 6 50       40 $pwd =~ /^(.*)$/ && ($pwd = $1);
92              
93 6 50       183 chdir $self->{source} or die "Couldn't chdir to '$self->{source}'\n";
94              
95             $self->{recursive}
96 6 100   40   226 ? find( { wanted => sub { $self->_recursive() }, no_chdir => 1 }, $self->{source} )
  40         99  
97             : $self->_normal();
98              
99 6 50       110 chdir $pwd or die "Couldn't chdir to '$pwd'\n";
100             }
101              
102             sub _recursive
103             {
104 40     40   55 my $self = shift;
105              
106 40         60 my $source = $self->{source};
107 40         53 my $dest = $self->{dest};
108              
109 40         49 my $file = $File::Find::name;
110 40         285 $file =~ s{^$source/}{};
111              
112 40 50       331 return if $file =~ $self->{ignore};
113 40 50       47 return if grep { $file =~ /$_/ } @{ $self->{addignore} };
  0         0  
  40         104  
114 40 100 66     1165 return unless -f $file || -l $file;
115              
116 36 50 33     382 if ( -l $file && -l "$dest/$file")
117             {
118             # skip if it's a link which is already in place
119 0 0       0 return if readlink( $file ) eq readlink( "$dest/$file" );
120             }
121              
122 36 50 33     1307 if ( ! -l $file && -l "$dest/$file" && stat "$dest/$file" )
      33        
123             {
124             # skip if it's file that has already been linked
125 0 0       0 return if ( stat "$dest/$file" )[1] == ( stat $file )[1];
126             }
127            
128 36 50 33     1325 if ( -e "$dest/$file" || -l "$dest/$file" )
129             {
130 0 0 0     0 if ( ! -l "$dest/$file" && -d "$dest/$file" )
131             {
132 0         0 warn "Won't replace dir '$dest/$file' with a link\n";
133 0         0 return;
134             }
135              
136 0 0       0 if ( ! $self->{force} )
137             {
138 0 0       0 $self->{dryrun}
139             ? warn "force is off, would not overwrite '$dest/$file'\n"
140             : warn "force is off, not overwriting '$dest/$file'\n"
141             ;
142 0         0 return;
143             }
144            
145 0 0       0 if ( $self->{dryrun} )
146             {
147 0         0 warn "Would overwrite '$dest/$file' -> '$source/$file'\n";
148 0         0 return;
149             }
150             else
151             {
152 0 0       0 warn "Overwriting '$dest/$file' -> '$source/$file'\n" if $self->{verbose};
153 0 0       0 if ( ! unlink "$dest/$file" )
154             {
155 0         0 warn "Can't remove '$dest/$file': $!\n";
156 0         0 return;
157             }
158             }
159             }
160             else
161             {
162 36 50       85 if ( $self->{dryrun} )
163             {
164 0         0 warn "Would create '$dest/$file' --> '$source/$file'\n";
165 0         0 return;
166             }
167 36 50       1244 warn "Creating '$dest/$file' -> '$source/$file'\n" if $self->{verbose};
168             }
169 36         162 my $path = catpath( ( splitpath( "$dest/$file" ) )[0,1], '' );
170 36 100       1435 if ( ! -d $path )
171             {
172 2         5 local $@;
173 2         6 eval { make_path($path) };
  2         602  
174 2 50       9 if ( $@ )
175             {
176 0         0 warn "Failed to create dir '$path': $@\n";
177 0         0 return;
178             }
179             }
180              
181 36 100       2193 my $success = -l $file
    50          
182             ? symlink readlink($file), "$dest/$file"
183             : $self->{hard}
184             ? link "$source/$file", "$dest/$file"
185             : symlink "$source/$file", "$dest/$file";
186              
187 36 50       787 warn "Can't create '$dest/$file': $!\n" unless $success;
188             }
189              
190             sub _normal
191             {
192 4     4   10 my $self = shift;
193              
194 4         8 my $source = $self->{source};
195 4         11 my $dest = $self->{dest};
196              
197 4 50       206 opendir my $dir_handle, $source or die "Can't open the dir $source: $!; aborted";
198              
199 4         126 while ( defined ( my $file = readdir $dir_handle ) )
200             {
201 48 50       216 $file =~ /^(.*)$/ && ($file = $1); # I'm open to suggestions
202            
203 48 100       200 next if $file =~ /^\.{1,2}$/;
204 40 100       178 next if $file =~ $self->{ignore};
205 36 100       37 next if grep { $file =~ /$_/ } @{ $self->{addignore} };
  50         150  
  36         88  
206              
207 31 50 33     1374 if ( -l "$dest/$file" && stat "$dest/$file" )
208             {
209 0 0       0 next if ( stat "$dest/$file" )[1] == ( stat $file )[1];
210             }
211            
212 31 50 33     1065 if ( -e "$dest/$file" || -l "$dest/$file" )
213             {
214 0 0       0 if ( ! $self->{force} )
215             {
216 0 0       0 $self->{dryrun}
217             ? warn "force is off, would not overwrite '$dest/$file'\n"
218             : warn "force is off, not overwriting '$dest/$file'\n"
219             ;
220 0         0 next;
221             }
222            
223 0 0       0 if ( $self->{dryrun} )
224             {
225 0         0 warn "Would overwrite '$dest/$file' -> '$source/$file'\n";
226 0         0 next;
227             }
228             else
229             {
230 0 0       0 warn "Overwriting '$dest/$file' -> '$source/$file'\n" if $self->{verbose};
231              
232 0 0       0 if ( -d "$dest/$file" )
    0          
233             {
234 0         0 local $@;
235 0         0 eval { remove_tree("$dest/$file") };
  0         0  
236 0 0       0 if ( $@ )
237             {
238 0         0 warn "Failed to remove directory '$dest/$file': $@\n";
239 0         0 next;
240             }
241             }
242             elsif ( ! unlink( "$dest/$file" ) )
243             {
244 0         0 warn "Failed to remove file '$dest/$file': $!\n";
245 0         0 next;
246             }
247             }
248             }
249            
250 31 50       76 if ( $self->{dryrun} )
251             {
252 0         0 warn "Would create '$dest/$file' -> '$source/$file'\n";
253 0         0 next;
254             }
255            
256 31 50       78 warn "Creating '$dest/$file' -> '$source/$file'\n" if $self->{verbose};
257 31 100       56 if ( $self->{hard} )
258             {
259 10 100       199 if ( -d "$source/$file" )
260             {
261 1         150 warn "Can't create '$dest/$file' as a hard link, skipping\n";
262             }
263             else
264             {
265 9 50       434 link "$source/$file", "$dest/$file" or warn "Can't create '$dest/$file': $!\n";
266             }
267             }
268             else
269             {
270 21 50       1046 symlink "$source/$file", "$dest/$file" or warn "Can't create '$dest/$file': $!\n";
271             }
272             }
273             }
274              
275             =pod
276              
277             =encoding utf-8
278              
279             =head1 NAME
280              
281             File::LinkDir - Create links in one directory for files in another
282              
283             =head1 SYNOPSIS
284              
285             use File::LinkDir;
286             my $linkdir = File::LinkDir->new( 'source' => '/path/to/dir', 'dest' => '/dest/path', 'hard' => 1, 'recursive' => 1 );
287             $linkdir->run();
288             $linkdir->init( 'source' => '/new/path', 'dest' => '/new/dest', );
289             $linkdir->run();
290              
291             =head1 DESCRIPTION
292              
293             By default, File::LinkDir will create symlinks in the destination directory for all top-level files, directories or symlinks found in the source directory. This is very useful for keeping the dot files in your C<$HOME> under version control. A typical use case:
294              
295             use File::LinkDir;
296             my $linkdir = File::LinkDir->new( 'source' => '.', 'dest' => '~' );
297             $linkdir->run();
298              
299             =head1 METHODS
300              
301             =head2 new
302              
303             Creates a new File::LinkDir object. This will call init() to set the options unless you pass 'skipinit' as the first argument.
304              
305             =head2 init
306              
307             Initializes the object according to the options that were passed. This is automatically called by new() but can be called if you want to reuse the object for other directories.
308              
309             =head2 run
310              
311             Creates the links based on the options that were used in new() and/or init().
312              
313             =head1 OPTIONS
314              
315             =head2 dryrun
316              
317             C 1>
318              
319             Prints what would have been done without actually doing it.
320              
321             =head2 source
322              
323             C DIR>
324              
325             The source directory.
326            
327             =head2 dest
328              
329             C DIR>
330              
331             The destination directory.
332              
333             =head2 recursive
334              
335             C 1>
336              
337             With C 1>, it will not create symlinks to subdirectories
338             found in the source directory. It will instead recurse into them and create
339             symlinks for any files or symlinks it finds. Any subdirectories not found in
340             the destination directory will be created. This approach is useful for
341             destination directories where programs or users other than yourself might add
342             things to subdirectories which you don't want ending up in your working tree
343             implicitly. F is a good example.
344              
345             In both cases, symlinks from the source directory will be copied as-is. This
346             makes sense because the symlinks might be relative.
347              
348             =head2 ignore
349              
350             C RX>
351              
352             RX is a regex matching files to ignore. If C 1> is not
353             specified, it defaults to ignoring F<.git> (plus F<.gitignore>,
354             F<.gitmodules>, etc, but not F<.gitconfig>) and F<.svn> directories and
355             their contents.
356              
357             =head2 addignore
358              
359             C RX>
360              
361             Like C RX> but doesn't replace the default.
362              
363             =head2 force
364              
365             C 1>
366              
367             Remove and/or overwrite existing files/dirs.
368              
369             =head2 hard
370              
371             C 1>
372              
373             Creates hard links instead of symlinks.
374              
375             =head1 AUTHOR
376              
377             Hinrik Örn Sigurðsson, Ehinrik.sig@gmail.comE
378             Matthew Musgrove, Emr.muskrat@gmail.comE
379              
380             =head1 BUGS
381              
382             Please report any bugs or feature requests to C, or through
383             the web interface at L. I will be notified, and then you'll
384             automatically be notified of progress on your bug as I make changes.
385              
386             =head1 SUPPORT
387              
388             You can find documentation for this module with the perldoc command.
389              
390             perldoc File::LinkDir
391              
392              
393             You can also look for information at:
394              
395             =over 4
396              
397             =item * RT: CPAN's request tracker
398              
399             L
400              
401             =item * AnnoCPAN: Annotated CPAN documentation
402              
403             L
404              
405             =item * CPAN Ratings
406              
407             L
408              
409             =item * Search CPAN
410              
411             L
412              
413             =back
414              
415             =head1 COPYRIGHT
416              
417             Copyright (c) 2009-2010 Hinrik Örn Sigurðsson and Matthew Musgrove
418              
419             =head1 LICENSE
420              
421             This program is free software; you can redistribute it and/or modify it
422             under the terms of either: the GNU General Public License as published
423             by the Free Software Foundation; or the Artistic License.
424              
425             See http://dev.perl.org/licenses/ for more information.
426              
427             =cut
428              
429             1; # End of File::LinkDir
430