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