File Coverage

blib/lib/App/perlmv.pm
Criterion Covered Total %
statement 240 377 63.6
branch 90 200 45.0
condition 21 55 38.1
subroutine 32 49 65.3
pod 0 19 0.0
total 383 700 54.7


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