File Coverage

blib/lib/App/perlmv.pm
Criterion Covered Total %
statement 240 376 63.8
branch 90 200 45.0
condition 21 55 38.1
subroutine 32 48 66.6
pod 0 19 0.0
total 383 698 54.8


line stmt bran cond sub pod time code
1             ## no critic: ValuesAndExpressions::ProhibitCommaSeparatedStatements
2              
3             package App::perlmv;
4              
5             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
6             our $DATE = '2022-06-24'; # DATE
7             our $DIST = 'App-perlmv'; # DIST
8             our $VERSION = '0.606'; # VERSION
9              
10 18     18   269344 use 5.010001;
  18         52  
11 18     18   78 use strict;
  18         28  
  18         278  
12 18     18   65 use warnings;
  18         24  
  18         515  
13             #use Log::ger;
14              
15 18     18   83 use Cwd qw(abs_path getcwd);
  18         28  
  18         852  
16             #use Data::Dump qw(dump);
17 18     18   6577 use File::Copy;
  18         32437  
  18         894  
18 18     18   112 use File::Find;
  18         32  
  18         703  
19 18     18   7371 use File::MoreUtil qw(l_abs_path);
  18         30651  
  18         1184  
20 18     18   118 use File::Path qw(make_path);
  18         27  
  18         610  
21 18     18   88 use File::Spec;
  18         29  
  18         433  
22 18     18   6866 use Getopt::Long::Complete qw(GetOptionsWithCompletion);
  18         14412  
  18         21857  
23              
24             sub new {
25 39     39 0 1026518 my ($class) = @_;
26              
27             # determine home
28 39         73 my $homedir;
29              
30 39 50       139 if ( $ENV{'TESTING_HOME'} ) {
31 39         80 $homedir = $ENV{'TESTING_HOME'};
32             } else {
33 0         0 eval {
34 0         0 require File::HomeDir;
35 0         0 $homedir = File::HomeDir->my_home;
36             };
37              
38 0   0     0 $homedir //= $ENV{'HOME'};
39              
40 0 0       0 die "FATAL: Can't determine home directory\n" unless $homedir;
41             }
42              
43 39         532 my $self = {
44             codes => [],
45             dry_run => 0,
46             homedir => $homedir,
47             sort_mode => 1, # 1=sort ascibetically, -1=reverse, 0=no sort
48             overwrite => 0,
49             process_dir => 1,
50             process_symlink => 1,
51             recursive => 0,
52             verbose => 0,
53             args => {},
54             };
55              
56 39         93 bless $self, $class;
57              
58 39         110 return $self;
59             }
60              
61              
62             sub parse_opts {
63 0     0 0 0 my $self = shift;
64              
65             GetOptionsWithCompletion(
66             sub {
67 0     0   0 my %args = @_;
68 0         0 my $word = $args{word};
69 0         0 my $type = $args{type};
70 0         0 my $seen_opts = $args{seen_opts};
71              
72 0 0       0 if ($type eq 'arg') {
    0          
73 0         0 my $argpos = $args{argpos};
74 0 0 0     0 if ($argpos == 0 &&
      0        
      0        
      0        
75             !exists($seen_opts->{'-x'}) && !exists($seen_opts->{'--execute'}) &&
76             !exists($seen_opts->{'-e'}) && !exists($seen_opts->{'--eval'})) {
77 0         0 require Complete::App::perlmv;
78 0         0 return Complete::App::perlmv::complete_perlmv_scriptlet(word=>$word);
79             } else {
80 0         0 require Complete::File;
81 0         0 return Complete::File::complete_file(word=>$word);
82             }
83             } elsif ($type eq 'optval') {
84 0 0       0 my $opts = ref $args{opt} eq 'ARRAY' ? $args{opt} : [$args{opt}];
85 0         0 my @comps;
86 0 0       0 if (grep { $_ =~ /\A(-x|--execute|-D|--delete|-s|--show|-w|--write)\z/ } @$opts) {
  0         0  
87 0         0 require Complete::App::perlmv;
88 0         0 push @comps, Complete::App::perlmv::complete_perlmv_scriptlet(word=>$word);
89             }
90 0 0       0 if (grep { $_ eq '-M' || $_ eq '--mode' } @$opts) {
  0 0       0  
91 0         0 require Complete::Util;
92 0         0 push @comps, Complete::Util::complete_array_elem(word=>$word, array=>[qw/copy symlink link move rename/]);
93             }
94 0         0 require Complete::Util;
95 0         0 return Complete::Util::combine_answers(@comps);
96             }
97              
98 0         0 undef;
99             },
100             'c|check' => \$self->{ 'check' },
101             'D|delete=s' => \$self->{ 'delete' },
102             'd|dry-run' => \$self->{ 'dry_run' },
103             'e|eval=s' => $self->{ 'codes' },
104 0     0   0 'h|help' => sub { $self->print_help() },
105             'l|list' => \$self->{ 'list' },
106             'M|mode=s' => \$self->{ 'mode' },
107             'o|overwrite' => \$self->{ 'overwrite' },
108             'p|parents' => \$self->{ 'parents' },
109             'R|recursive' => \$self->{ 'recursive' },
110 0     0   0 'r|reverse' => sub { $self->{sort_mode} = -1 },
111             's|show=s' => \$self->{ 'show' },
112 0     0   0 'T|no-sort' => sub { $self->{sort_mode} = 0 },
113             'v|verbose' => \$self->{ 'verbose' },
114             'w|write=s' => \$self->{ 'write' },
115 0     0   0 'f|files' => sub { $self->{ 'process_dir' } = 0 },
116 0     0   0 'S|no-symlinks' => sub { $self->{ 'process_symlink'} = 0 },
117 0     0   0 'V|version' => sub { $self->print_version() },
118             # we use \scalar to differentiate between -x and -e
119 0     0   0 'x|execute=s' => sub { push @{$self->{'codes'}}, \$_[1]},
  0         0  
120             'a|arg=s%' => $self->{'args'},
121 0     0   0 '<>' => sub { $self->parse_extra_opts(@_) },
122 0 0       0 ) or $self->print_help();
123             }
124              
125             sub parse_extra_opts {
126 0     0 0 0 my ( $self, $arg ) = @_;
127              
128             # do our own globbing in windows, this is convenient
129 0 0       0 if ( $^O =~ /win32/i ) {
130 0 0       0 if ( $arg =~ /[*?{}\[\]]/ ) { push @{ $self->{'items'} }, glob "$arg" }
  0         0  
  0         0  
131 0         0 else { push @{ $self->{'items'} }, "$arg" }
  0         0  
132             } else {
133 0         0 push @{ $self->{'items'} }, "$arg";
  0         0  
134             }
135             }
136              
137             sub run {
138 0     0 0 0 my $self = shift;
139              
140 0         0 $self->parse_opts();
141              
142             # -m is reserved for file mode
143 0 0       0 my $default_mode =
    0          
    0          
    0          
144             $0 =~ /perlcp/ ? 'copy' :
145             $0 =~ /perlln_s/ ? 'symlink' :
146             $0 =~ /perlln/ ? 'link' :
147             $0 =~ /perlmv/ ? 'move' :
148             'rename';
149              
150 0 0       0 $self->{'dry_run'} and $self->{'verbose'}++;
151 0   0     0 $self->{'mode'} //= $default_mode;
152              
153 0 0       0 if ( $self->{'list'} ) {
154 0         0 $self->load_scriptlets();
155 0         0 foreach my $key ( sort keys %{ $self->{'scriptlets'} } ) {
  0         0  
156 0 0       0 print $self->{'verbose'} ?
157             $self->format_scriptlet_source($key) . "\n" :
158             "$key\n";
159             }
160              
161 0         0 exit 0;
162             }
163              
164 0 0       0 if ( $self->{'show'} ) {
165 0         0 print $self->format_scriptlet_source( $self->{'show'} );
166 0         0 exit 0;
167             }
168              
169 0 0       0 if ( $self->{'write'} ) {
170 0         0 die "Please specify code of scriptlet" unless @{ $self->{'codes'} }
171 0 0 0     0 && !ref( $self->{'codes'}[0] );
172 0         0 $self->store_scriptlet( $self->{'write'}, $self->{'codes'}[0] );
173 0         0 exit 0;
174             }
175              
176 0 0       0 if ( $self->{'delete'} ) {
177 0         0 $self->delete_user_scriptlet( $self->{'delete'} );
178 0         0 exit 0;
179             }
180              
181 0 0       0 unless (@{ $self->{'codes'} }) {
  0         0  
182             die 'FATAL: Must specify code (-e) or scriptlet name (-x/first argument)'
183 0 0       0 unless $self->{'items'};
184 0         0 push @{ $self->{'codes'} }, \( scalar shift @{ $self->{'items'} } );
  0         0  
  0         0  
185             }
186             # convert all scriptlet names into their code
187 0         0 for (@{ $self->{'codes'} }) {
  0         0  
188 0 0       0 $_ = $self->get_scriptlet_code($$_) if ref($_) eq 'SCALAR';
189             }
190              
191             die "FATAL: Please specify some files in arguments\n"
192 0 0       0 unless $self->{'items'};
193              
194 0         0 $self->rename();
195             }
196              
197             sub print_version {
198 0     0 0 0 print "perlmv version $App::perlmv::VERSION\n";
199 0         0 exit 0;
200             }
201              
202             sub print_help {
203 0     0 0 0 my $self = shift;
204 0         0 print <<'USAGE';
205             Rename files using Perl code.
206              
207             Usage:
208              
209             # Show help
210             perlmv -h
211              
212             # Execute a single scriptlet
213             perlmv [options]
214              
215             # Execute code from command line
216             perlmv [options] -e
217              
218             # Execute multiple scriptlets/command-line codes
219             perlmv [options] [ -x | -e ]+
220              
221             # Create a new scriptlet
222             perlmv -e -w
223              
224             # List available scriptlets
225             perlmv -l
226              
227             # Show source code of a scriptlet
228             perlmv -s
229              
230             # Delete scriptlet
231             perlmv -d
232              
233             Options:
234              
235             -c (--compile) Only test compile code, do not run it on the arguments
236             -D (--delete) Delete scriptlet
237             -d (--dry-run) Dry-run (implies -v)
238             -e (--execute) Specify Perl code to rename file (\$_). Can be specified
239             multiple times.
240             -f (--files) Only process files, do not process directories
241             -h (--help) Show this help
242             -l (--list) list all scriptlets
243             -M (--mode) Specify mode, default is 'mv' (or 'm'). Use 'rename' or 'r'
244             for rename (the same as mv but won't do cross devices), 'copy' or 'c' to
245             copy instead of rename, 'symlink' or 's' to create a symbolic link, and
246             'link' or 'l' to create a (hard) link.
247             -o (--overwrite) Overwrite (by default, ".1", ".2", and so on will be appended
248             to avoid overwriting existing files)
249             -p (--parents) Create intermediate directories
250             -R (--recursive) Recursive
251             -r (--reverse) reverse order of processing (by default asciibetically)
252             -S (--no-symlinks) Do not process symlinks
253             -s (--show) Show source code for scriptlet
254             -T (--no-sort) do not sort files (default is sort ascibetically)
255             -V (--version) Print version and exit
256             -v (--verbose) Verbose
257             -w (--write) Write code specified in -e as scriptlet
258             -x Execute a scriptlet. Can be specified multiple times. -x is optional
259             if there is only one scriptlet to execute, and scriptlet name is specified
260             as the first argument, and there is no -e specified.
261             -a (--arg) Supply arguments for code/scriptlet.
262              
263             USAGE
264              
265 0         0 exit 0;
266             }
267              
268             sub get_scriptlet_code {
269 22     22 0 330 my ( $self, $name ) = @_;
270 22         82 $self->load_scriptlets();
271             die "FATAL: Can't find scriptlet `$name`"
272 22 100       98 unless $self->{'scriptlets'}{$name};
273 21 100       73 if (defined(my $mod = $self->{'scriptlets'}{$name}{'module'})) {
274 6         27 (my $mod_pm = "$mod.pm") =~ s!::!/!g;
275 6         1020 require $mod_pm;
276 18     18   129 no strict 'refs';
  18         43  
  18         13929  
277 6 50       12 ${"$mod\::SCRIPTLET"} or die "Package $mod does not define \$SCRIPTLET";
  6         34  
278 6         7 ${"$mod\::SCRIPTLET"}->{code};
  6         38  
279             } else {
280 15         80 $self->{'scriptlets'}{$name}{'code'};
281             }
282             }
283              
284             sub load_scriptlets {
285 22     22 0 42 my ($self) = @_;
286 22   66     174 $self->{'scriptlets'} //= $self->find_scriptlets();
287             }
288              
289             sub find_scriptlets {
290 20     20 0 42 my ($self) = @_;
291 20         37 my $res = {};
292              
293 20         5990 require File::Slurper;
294 20         164932 require Module::List::Tiny;
295             {
296 20         235 my $mods = Module::List::Tiny::list_modules(
  20         152  
297             'App::perlmv::scriptlet::',
298             {list_modules=>1, recurse=>1, return_path=>1});
299 20         9850 for my $mod (sort keys %$mods) {
300 60         4054 my $name = $mod;
301 60         284 $name =~ s/\AApp::perlmv::scriptlet:://;
302 60         119 $name =~ s!::!/!g;
303 60         182 $name =~ s!_!-!g;
304             $res->{$name} = {
305             module => $mod,
306 60         208 code => File::Slurper::read_text($mods->{$mod}),
307             from => "App::perlmv::scriptlet::*",
308             };
309             }
310             }
311              
312 20         1724 eval { require App::perlmv::scriptlets::std };
  20         4650  
313 20 50       82 if (%App::perlmv::scriptlets::std::scriptlets) {
314             $res->{$_} = { code => $App::perlmv::scriptlets::std::scriptlets{$_},
315             from => "App::perlmv::scriptlets::std" }
316 20         348 for keys %App::perlmv::scriptlets::std::scriptlets;
317             }
318              
319 20         46 eval { require App::perlmv::scriptlets };
  20         2035  
320 20 50       104 if (%App::perlmv::scriptlets::scriptlets) {
321             $res->{$_} = { code => $App::perlmv::scriptlets::scriptlets{$_},
322             from => "App::perlmv::scriptlets" }
323 0         0 for keys %App::perlmv::scriptlets::scriptlets;
324             }
325              
326 20 50       194 if (-d "/usr/share/perlmv/scriptlets") {
327 0         0 local $/;
328 0         0 for (glob "/usr/share/perlmv/scriptlets/*") {
329 0         0 my $name = $_; $name =~ s!.+/!!;
  0         0  
330 0         0 open my($fh), $_;
331 0         0 my $code = <$fh>;
332 0 0       0 $res->{$name} = { code => $code, from => $_ }
333             if $code;
334             }
335             }
336              
337 20 100       397 if (-d "$self->{homedir}/.perlmv/scriptlets") {
338 2         10 local $/;
339 2         94 for (glob "$self->{homedir}/.perlmv/scriptlets/*") {
340 1         4 my $name = $_; $name =~ s!.+/!!;
  1         11  
341 1         66 open my($fh), $_;
342 1         26 my $code = <$fh>;
343 1         17 ($code) = $code =~ /(.*)/s; # untaint
344 1 50       18 $res->{$name} = { code => $code, from => $_ }
345             if $code;
346             }
347             }
348              
349 20         120 $res;
350             }
351              
352             sub valid_scriptlet_name {
353 3     3 0 10 my ($self, $name) = @_;
354 3         41 $name =~ m!\A([A-Za-z0-9_][0-9A-Za-z_-]*/)*[A-Za-z0-9_][0-9A-Za-z_-]*\z!;
355             }
356              
357             sub store_scriptlet {
358 3     3 0 126 my ($self, $name, $code) = @_;
359 3 50       18 die "FATAL: Invalid scriptlet name `$name`\n"
360             unless $self->valid_scriptlet_name($name);
361 3 50       22 die "FATAL: Code not specified\n" unless $code;
362 3         9 my $path = "$self->{homedir}/.perlmv";
363 3 100       38 unless (-d $path) {
364 1 50       67 mkdir $path or die "FATAL: Can't mkdir `$path`: $!\n";
365             }
366 3         11 $path .= "/scriptlets";
367 3 100       42 unless (-d $path) {
368 1 50       36 mkdir $path or die "FATAL: Can't mkdir `$path: $!\n";
369             }
370 3         13 $path .= "/$name";
371 3 100 100     54 if ((-e $path) && !$self->{'overwrite'}) {
372 1         25 die "FATAL: Can't overwrite `$path (use -o)\n";
373             } else {
374 2         134 open my($fh), ">", $path;
375 2         35 print $fh $code;
376 2 50       172 close $fh or die "FATAL: Can't write to $path: $!\n";
377             }
378             }
379              
380             sub delete_user_scriptlet {
381 1     1 0 27 my ($self, $name) = @_;
382 1         1115 unlink "$self->{homedir}/.perlmv/scriptlets/$name";
383             }
384              
385             # keep sync with run_code(), run_code_for_cleaning()
386             sub compile_code {
387 31     31 0 115 my ($self, $code) = @_;
388 18     18   120 no strict;
  18         32  
  18         539  
389 18     18   84 no warnings;
  18         357  
  18         2237  
390 31         67 local $_ = "-TEST";
391 31         49 local $App::perlmv::code::PERLMV = $self;
392 31         91 local $App::perlmv::code::TESTING = 1;
393 31         58 local $App::perlmv::code::COMPILING = 1;
394 31         58 local $App::perlmv::code::ARGS = $self->{'args'};
395 31     4   2567 eval "package App::perlmv::code; $code";
  4         831  
  4         10667  
  4         123  
396 31 50       16153 die "FATAL: Code doesn't compile: code=$code, errmsg=$@\n" if $@;
397             }
398              
399             # keep sync with run_code(), compile_code()
400             sub run_code_for_cleaning {
401 0     0 0 0 my ($self, $code) = @_;
402 18     18   100 no strict;
  18         49  
  18         460  
403 18     18   84 no warnings;
  18         27  
  18         2171  
404 0         0 local $_ = "-CLEAN";
405 0         0 local $App::perlmv::code::PERLMV = $self;
406 0         0 local $App::perlmv::code::CLEANING = 1;
407 0         0 local $App::perlmv::code::ARGS = $self->{'args'};
408 0 0       0 if (ref $code eq 'CODE') {
409 0         0 $code->();
410             } else {
411 0         0 eval "package App::perlmv::code; $code";
412 0 0       0 die "FATAL: Code doesn't run (cleaning): code=$code, errmsg=$@\n" if $@;
413             }
414             }
415              
416             # keep sync with run_code_for_cleaning(), compile_code()
417             sub run_code {
418 107     107 0 166 my ($self, $code) = @_;
419 18     18   102 no strict;
  18         26  
  18         366  
420 18     18   70 no warnings;
  18         22  
  18         26656  
421 107         126 my $orig_ = $_;
422 107         136 local $App::perlmv::code::PERLMV = $self;
423 107         136 local $App::perlmv::code::TESTING = 0;
424 107         137 local $App::perlmv::code::COMPILING = 0;
425 107         148 local $App::perlmv::code::ARGS = $self->{'args'};
426 107         146 my $res;
427 107 100       211 if (ref $code eq 'CODE') {
428 18         37 $res = $code->();
429             } else {
430 89     4   6005 $res = eval "package App::perlmv::code; $code";
  4         22  
  4         6  
  4         78  
431 89 50       724 die "FATAL: Code doesn't compile: code=$code, errmsg=$@\n" if $@;
432             }
433 107 100 66     645 if (defined($res) && length($res) && $_ eq $orig_) { $_ = $res }
  56   100     126  
434             }
435              
436             sub _sort {
437 33     33   60 my $self = shift;
438             $self->{sort_mode} == -1 ? (reverse sort @_) :
439 33 100       216 $self->{sort_mode} == 1 ? (sort @_) :
    100          
440             @_;
441             }
442              
443             sub process_items {
444 36     36 0 93 my ($self, $code, $code_is_final, $items) = @_;
445 36         57 my $i = 0;
446 36         123 while ($i < @$items) {
447 107         180 my $item = $items->[$i];
448 107         157 $i++;
449 107 50       206 if ($item->{cwd}) {
450 0 0       0 chdir $item->{cwd} or die "Can't chdir to `$item->{cwd}`: $!";
451             }
452 107 50 33     260 next if !$self->{'process_symlink'} && (-l $item->{real_name});
453 107 50       1079 if (-d $item->{real_name}) {
454 0 0       0 next unless $self->{'process_dir'};
455 0 0       0 if ($self->{'recursive'}) {
456 0         0 my $cwd = getcwd();
457 0 0       0 if (chdir $item->{real_name}) {
458             print "INFO: chdir `$cwd/$item->{real_name}` ...\n"
459 0 0       0 if $self->{'verbose'};
460 0         0 local *D;
461 0         0 opendir D, ".";
462             my @subitems =
463             $self->_sort(
464 0         0 map { {name_for_script => $_, real_name => $_} }
465 0 0       0 grep { $_ ne '.' && $_ ne '..' }
  0         0  
466             readdir D
467             );
468 0         0 closedir D;
469 0         0 $self->process_items($code, $code_is_final, \@subitems);
470 0         0 splice @$items, $i-1, 0, @subitems;
471 0         0 $i += scalar(@subitems);
472 0         0 $subitems[0]{cwd} = "$cwd/$item->{real_name}";
473 0 0       0 chdir $cwd or die "FATAL: Can't go back to `$cwd`: $!\n";
474             } else {
475 0         0 warn "WARN: Can't chdir to `$cwd/$item->{real_name}`, ".
476             "skipped\n";
477             }
478             }
479             }
480 107         438 $self->process_item($code, $code_is_final, $item, $items, $i-1);
481             }
482             }
483              
484             sub process_item {
485 107     107 0 245 my ($self, $code, $code_is_final, $item, $items, $item_num) = @_;
486              
487             local $App::perlmv::code::FILES =
488 107 50       258 [map {ref($_) ? $_->{name_for_script} : $_} @$items];
  449         1021  
489 107         249 local $App::perlmv::code::FILENUM = $item_num;
490 107         222 local $_ = $item->{name_for_script};
491              
492 107         185 my $old = $item->{real_name};
493 107         439 my $aold = l_abs_path($old);
494 107 50       2495 die "Invalid path $old" unless defined($aold);
495 107         1441 my ($oldvol,$olddir,$oldfile)=File::Spec->splitpath($aold);
496 107         334 my ($olddirvol,$olddirdir,$olddirfile) = File::Spec->splitpath(
497             l_abs_path($olddir));
498 107         3961 my $aolddir = File::Spec->catpath($olddirvol, $olddirdir, '');
499 107         224 local $App::perlmv::code::DIR = $olddir;
500 107         150 local $App::perlmv::code::FILE = $oldfile;
501 107         116 local $App::perlmv::code::PARENT = $olddirfile;
502              
503 107         327 $self->run_code($code);
504              
505 107         139 my $new = $_;
506             # we use rel2abs instead of l_abs_path because path might not exist (yet)
507             # and we don't want to check for actual existence
508 107         2596 my $anew = File::Spec->rel2abs($new);
509              
510 107 100       1396 $self->{_exists}{$aold}++ if (-e $aold);
511 107 100       324 return if $aold eq $anew;
512              
513 101         199 $item->{name_for_script} = $new;
514 101 100       178 unless ($code_is_final) {
515 9         10 push @{ $item->{intermediates} }, $new;
  9         22  
516 9         51 return;
517             }
518              
519 92         110 my $action;
520 92 100 66     289 if (!defined($self->{mode}) || $self->{mode} =~ /^(rename|r)$/) {
    50          
    100          
    100          
    50          
521 83         173 $action = "rename";
522             } elsif ($self->{mode} =~ /^(move|mv|m)$/) {
523 0         0 $action = "move";
524             } elsif ($self->{mode} =~ /^(copy|cp|c)$/) {
525 3         4 $action = "copy";
526             } elsif ($self->{mode} =~ /^(symlink|sym|s)$/) {
527 3         4 $action = "symlink";
528             } elsif ($self->{mode} =~ /^(hardlink|h|link|l)$/) {
529 3         5 $action = "link";
530             } else {
531 0         0 die "Unknown mode $self->{mode}, please use one of: ".
532             "move (m), rename (r), copy (c), symlink (s), or link (l).";
533             }
534              
535 92         127 my $orig_new = $new;
536 92 100       175 unless ($self->{'overwrite'}) {
537 89         103 my $i = 1;
538 89         95 while (1) {
539 103 100 33     1543 if ((-e $new) || defined($anew) && exists $self->{_exists}{$anew}) {
      66        
540 14         77 $new = "$orig_new.$i";
541 14         52 $anew = l_abs_path($new);
542 14         359 $i++;
543             } else {
544 89         220 last;
545             }
546             }
547             }
548 92         234 $self->{_exists}{$anew}++;
549 92 100 66     355 delete $self->{_exists}{$aold} if $action eq 'rename' || $action eq 'move';
550 92 100       273 print "DRYRUN: " if $self->{dry_run};
551             print "$action " . join(" -> ",
552 10   50     127 map {"`$_`"} $old, @{ $item->{intermediates} // []}, $new)."\n"
  5         36  
553 92 100       213 if $self->{verbose};
554 92 100       202 unless ($self->{dry_run}) {
555 89         130 my $res;
556              
557 89 100       156 if ($self->{'parents'}) {
558 1         18 my ($vol, $dir, $file) = File::Spec->splitpath($new);
559 1 50       8 unless (-e $dir) {
560 1         249 make_path($dir, {error => \my $err});
561 1         4 for (@$err) {
562 0         0 my ($file, $message) = %$_;
563 0 0       0 warn "ERROR: Can't mkdir `$dir`: $message" .
564             ($file eq '' ? '' : " ($file)") . "\n";
565             }
566 1 50       8 return if @$err;
567             }
568             }
569              
570 89         121 my $err = "";
571 89 50       246 if ($action eq 'move') {
    100          
    100          
    100          
    50          
572 0         0 $res = File::Copy::move($old, $new);
573 0 0       0 $err = $! unless $res;
574             } elsif ($action eq 'rename') {
575 80         1835 $res = rename $old, $new;
576 80 100       247 $err = $! unless $res;
577             } elsif ($action eq 'copy') {
578 3         8 $res = copy $old, $new;
579 3 50       572 $err = $! unless $res;
580             # XXX copy mtime, ctime, etc
581             } elsif ($action eq 'symlink') {
582 3         71 $res = symlink $old, $new;
583 3 50       15 $err = $! unless $res;
584             } elsif ($action eq 'link') {
585 3         58 $res = link $old, $new;
586 3 50       9 $err = $! unless $res;
587             }
588 89 100       620 warn "ERROR: $action failed `$old` -> `$new`: $err\n" unless $res;
589             }
590             }
591              
592             sub format_scriptlet_source {
593 0     0 0 0 my ($self, $name) = @_;
594 0         0 $self->load_scriptlets();
595             die "FATAL: Scriptlet `$name` not found\n"
596 0 0       0 unless $self->{scriptlets}{$name};
597             "### Name: $name (from ", $self->{scriptlets}{$name}{from}, ")\n" .
598             $self->{scriptlets}{$name}{code} .
599 0 0       0 ($self->{scriptlets}{$name}{code} =~ /\n\z/ ? "" : "\n");
600             }
601              
602             sub rename {
603 33     33 0 952 my ($self, @args) = @_;
604 33         60 my @items;
605 33 50       84 if (@args) {
606 33         78 @items = @args;
607             } else {
608 0   0     0 @items = @{ $self->{'items'} // [] };
  0         0  
609             }
610              
611 33         161 @items = map { {real_name=>$_, name_for_script=>$_} } $self->_sort(@items);
  98         373  
612              
613 33 50       133 if ($self->{_compiled}) {
614             # another run, clean first
615 0         0 $self->run_code_for_cleaning($_) for @{ $self->{'codes'} };
  0         0  
616             }
617 33         109 $self->{_exists} = {};
618 33         88 local $self->{'recursive'} = $self->{'recursive'};
619 33         71 for (my $i=0; $i < @{ $self->{'codes'} }; $i++) {
  69         202  
620 36         68 my $code = $self->{'codes'}[$i];
621 36 100 66     341 $self->compile_code($code) unless $self->{'compiled'} || ref $code eq 'CODE';
622 36 50       104 next if $self->{'check'};
623 36         92 my $code_is_final = ($i == @{ $self->{'codes'} }-1);
  36         114  
624 36 100       96 $self->{'recursive'} = 0 if $i;
625             #print STDERR "DEBUG: items (before): ".dump(@items)."\n";
626 36         136 $self->process_items($code, $code_is_final, \@items);
627             #print STDERR "DEBUG: items (after): ".dump(@items)."\n";
628             }
629 33         564 $self->{'compiled'}++;
630             }
631              
632             1;
633             # ABSTRACT: Rename files using Perl code
634              
635             __END__