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 = '2023-06-30'; # DATE
7             our $DIST = 'App-perlmv'; # DIST
8             our $VERSION = '0.607'; # VERSION
9              
10 18     18   363080 use 5.010001;
  18         72  
11 18     18   97 use strict;
  18         42  
  18         418  
12 18     18   89 use warnings;
  18         44  
  18         756  
13             #use Log::ger;
14              
15 18     18   134 use Cwd qw(abs_path getcwd);
  18         34  
  18         1229  
16             #use Data::Dump qw(dump);
17 18     18   9261 use File::Copy;
  18         43114  
  18         1075  
18 18     18   140 use File::Find;
  18         57  
  18         956  
19 18     18   9323 use File::MoreUtil qw(l_abs_path);
  18         47666  
  18         1325  
20 18     18   138 use File::Path qw(make_path);
  18         44  
  18         874  
21 18     18   151 use File::Spec;
  18         36  
  18         552  
22 18     18   9112 use Getopt::Long::Complete qw(GetOptionsWithCompletion);
  18         19481  
  18         29725  
23              
24             sub new {
25 39     39 0 1432392 my ($class) = @_;
26              
27             # determine home
28 39         111 my $homedir;
29              
30 39 50       234 if ( $ENV{'TESTING_HOME'} ) {
31 39         239 $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         778 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         220 bless $self, $class;
57              
58 39         164 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 500 my ( $self, $name ) = @_;
270 22         108 $self->load_scriptlets();
271             die "FATAL: Can't find scriptlet `$name`"
272 22 100       157 unless $self->{'scriptlets'}{$name};
273 21 100       106 if (defined(my $mod = $self->{'scriptlets'}{$name}{'module'})) {
274 6         58 (my $mod_pm = "$mod.pm") =~ s!::!/!g;
275 6         1455 require $mod_pm;
276 18     18   157 no strict 'refs';
  18         51  
  18         19319  
277 6 50       80 ${"$mod\::SCRIPTLET"} or die "Package $mod does not define \$SCRIPTLET";
  6         53  
278 6         14 ${"$mod\::SCRIPTLET"}->{code};
  6         52  
279             } else {
280 15         87 $self->{'scriptlets'}{$name}{'code'};
281             }
282             }
283              
284             sub load_scriptlets {
285 22     22 0 63 my ($self) = @_;
286 22   66     254 $self->{'scriptlets'} //= $self->find_scriptlets();
287             }
288              
289             sub find_scriptlets {
290 20     20 0 56 my ($self) = @_;
291 20         48 my $res = {};
292              
293 20         7447 require File::Slurper;
294 20         220577 require Module::List::Tiny;
295             {
296 20         335 my $mods = Module::List::Tiny::list_modules(
  20         218  
297             'App::perlmv::scriptlet::',
298             {list_modules=>1, recurse=>1, return_path=>1});
299 20         13508 for my $mod (sort keys %$mods) {
300 40         3222 my $name = $mod;
301 40         281 $name =~ s/\AApp::perlmv::scriptlet:://;
302 40         147 $name =~ s!::!/!g;
303 40         195 $name =~ s!_!-!g;
304             $res->{$name} = {
305             module => $mod,
306 40         220 code => File::Slurper::read_text($mods->{$mod}),
307             from => "App::perlmv::scriptlet::*",
308             };
309             }
310             }
311              
312 20         2533 eval { require App::perlmv::scriptlets::std };
  20         6124  
313 20 50       119 if (%App::perlmv::scriptlets::std::scriptlets) {
314             $res->{$_} = { code => $App::perlmv::scriptlets::std::scriptlets{$_},
315             from => "App::perlmv::scriptlets::std" }
316 20         456 for keys %App::perlmv::scriptlets::std::scriptlets;
317             }
318              
319 20         64 eval { require App::perlmv::scriptlets };
  20         3068  
320 20 50       140 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       278 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       516 if (-d "$self->{homedir}/.perlmv/scriptlets") {
338 2         16 local $/;
339 2         174 for (glob "$self->{homedir}/.perlmv/scriptlets/*") {
340 1         18 my $name = $_; $name =~ s!.+/!!;
  1         19  
341 1         51 open my($fh), $_;
342 1         56 my $code = <$fh>;
343 1         19 ($code) = $code =~ /(.*)/s; # untaint
344 1 50       57 $res->{$name} = { code => $code, from => $_ }
345             if $code;
346             }
347             }
348              
349 20         160 $res;
350             }
351              
352             sub valid_scriptlet_name {
353 3     3 0 18 my ($self, $name) = @_;
354 3         98 $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 190 my ($self, $name, $code) = @_;
359 3 50       26 die "FATAL: Invalid scriptlet name `$name`\n"
360             unless $self->valid_scriptlet_name($name);
361 3 50       36 die "FATAL: Code not specified\n" unless $code;
362 3         21 my $path = "$self->{homedir}/.perlmv";
363 3 100       79 unless (-d $path) {
364 1 50       113 mkdir $path or die "FATAL: Can't mkdir `$path`: $!\n";
365             }
366 3         13 $path .= "/scriptlets";
367 3 100       88 unless (-d $path) {
368 1 50       81 mkdir $path or die "FATAL: Can't mkdir `$path: $!\n";
369             }
370 3         18 $path .= "/$name";
371 3 100 100     129 if ((-e $path) && !$self->{'overwrite'}) {
372 1         30 die "FATAL: Can't overwrite `$path (use -o)\n";
373             } else {
374 2         313 open my($fh), ">", $path;
375 2         61 print $fh $code;
376 2 50       328 close $fh or die "FATAL: Can't write to $path: $!\n";
377             }
378             }
379              
380             sub delete_user_scriptlet {
381 1     1 0 32 my ($self, $name) = @_;
382 1         1421 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 187 my ($self, $code) = @_;
388 18     18   142 no strict;
  18         45  
  18         707  
389 18     18   124 no warnings;
  18         410  
  18         2873  
390 31         135 local $_ = "-TEST";
391 31         75 local $App::perlmv::code::PERLMV = $self;
392 31         70 local $App::perlmv::code::TESTING = 1;
393 31         60 local $App::perlmv::code::COMPILING = 1;
394 31         72 local $App::perlmv::code::ARGS = $self->{'args'};
395 31     4   3277 eval "package App::perlmv::code; $code";
  4         1169  
  4         13285  
  4         137  
396 31 50       20716 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   166 no strict;
  18         35  
  18         514  
403 18     18   108 no warnings;
  18         49  
  18         2710  
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 245 my ($self, $code) = @_;
419 18     18   140 no strict;
  18         54  
  18         706  
420 18     18   104 no warnings;
  18         37  
  18         35752  
421 107         201 my $orig_ = $_;
422 107         176 local $App::perlmv::code::PERLMV = $self;
423 107         238 local $App::perlmv::code::TESTING = 0;
424 107         188 local $App::perlmv::code::COMPILING = 0;
425 107         236 local $App::perlmv::code::ARGS = $self->{'args'};
426 107         172 my $res;
427 107 100       287 if (ref $code eq 'CODE') {
428 18         57 $res = $code->();
429             } else {
430 89     4   8830 $res = eval "package App::perlmv::code; $code";
  4         33  
  4         7  
  4         134  
431 89 50       1186 die "FATAL: Code doesn't compile: code=$code, errmsg=$@\n" if $@;
432             }
433 107 100 66     973 if (defined($res) && length($res) && $_ eq $orig_) { $_ = $res }
  56   100     194  
434             }
435              
436             sub _sort {
437 33     33   93 my $self = shift;
438             $self->{sort_mode} == -1 ? (reverse sort @_) :
439 33 100       286 $self->{sort_mode} == 1 ? (sort @_) :
    100          
440             @_;
441             }
442              
443             sub process_items {
444 36     36 0 141 my ($self, $code, $code_is_final, $items) = @_;
445 36         82 my $i = 0;
446 36         161 while ($i < @$items) {
447 107         231 my $item = $items->[$i];
448 107         206 $i++;
449 107 50       294 if ($item->{cwd}) {
450 0 0       0 chdir $item->{cwd} or die "Can't chdir to `$item->{cwd}`: $!";
451             }
452 107 50 33     335 next if !$self->{'process_symlink'} && (-l $item->{real_name});
453 107 50       1555 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         662 $self->process_item($code, $code_is_final, $item, $items, $i-1);
481             }
482             }
483              
484             sub process_item {
485 107     107 0 384 my ($self, $code, $code_is_final, $item, $items, $item_num) = @_;
486              
487             local $App::perlmv::code::FILES =
488 107 50       288 [map {ref($_) ? $_->{name_for_script} : $_} @$items];
  449         1497  
489 107         374 local $App::perlmv::code::FILENUM = $item_num;
490 107         272 local $_ = $item->{name_for_script};
491              
492 107         241 my $old = $item->{real_name};
493 107         647 my $aold = l_abs_path($old);
494 107 50       3743 die "Invalid path $old" unless defined($aold);
495 107         2162 my ($oldvol,$olddir,$oldfile)=File::Spec->splitpath($aold);
496 107         446 my ($olddirvol,$olddirdir,$olddirfile) = File::Spec->splitpath(
497             l_abs_path($olddir));
498 107         6069 my $aolddir = File::Spec->catpath($olddirvol, $olddirdir, '');
499 107         326 local $App::perlmv::code::DIR = $olddir;
500 107         202 local $App::perlmv::code::FILE = $oldfile;
501 107         181 local $App::perlmv::code::PARENT = $olddirfile;
502              
503 107         353 $self->run_code($code);
504              
505 107         197 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         4092 my $anew = File::Spec->rel2abs($new);
509              
510 107 100       2193 $self->{_exists}{$aold}++ if (-e $aold);
511 107 100       483 return if $aold eq $anew;
512              
513 101         268 $item->{name_for_script} = $new;
514 101 100       260 unless ($code_is_final) {
515 9         19 push @{ $item->{intermediates} }, $new;
  9         44  
516 9         84 return;
517             }
518              
519 92         149 my $action;
520 92 100 66     419 if (!defined($self->{mode}) || $self->{mode} =~ /^(rename|r)$/) {
    50          
    100          
    100          
    50          
521 83         242 $action = "rename";
522             } elsif ($self->{mode} =~ /^(move|mv|m)$/) {
523 0         0 $action = "move";
524             } elsif ($self->{mode} =~ /^(copy|cp|c)$/) {
525 3         7 $action = "copy";
526             } elsif ($self->{mode} =~ /^(symlink|sym|s)$/) {
527 3         6 $action = "symlink";
528             } elsif ($self->{mode} =~ /^(hardlink|h|link|l)$/) {
529 3         6 $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         232 my $orig_new = $new;
536 92 100       300 unless ($self->{'overwrite'}) {
537 89         162 my $i = 1;
538 89         140 while (1) {
539 103 100 33     2439 if ((-e $new) || defined($anew) && exists $self->{_exists}{$anew}) {
      66        
540 14         98 $new = "$orig_new.$i";
541 14         66 $anew = l_abs_path($new);
542 14         536 $i++;
543             } else {
544 89         321 last;
545             }
546             }
547             }
548 92         538 $self->{_exists}{$anew}++;
549 92 100 66     517 delete $self->{_exists}{$aold} if $action eq 'rename' || $action eq 'move';
550 92 100       346 print "DRYRUN: " if $self->{dry_run};
551             print "$action " . join(" -> ",
552 10   50     355 map {"`$_`"} $old, @{ $item->{intermediates} // []}, $new)."\n"
  5         42  
553 92 100       250 if $self->{verbose};
554 92 100       303 unless ($self->{dry_run}) {
555 89         152 my $res;
556              
557 89 100       222 if ($self->{'parents'}) {
558 1         29 my ($vol, $dir, $file) = File::Spec->splitpath($new);
559 1 50       28 unless (-e $dir) {
560 1         546 make_path($dir, {error => \my $err});
561 1         9 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       18 return if @$err;
567             }
568             }
569              
570 89         233 my $err = "";
571 89 50       329 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         2486 $res = rename $old, $new;
576 80 100       419 $err = $! unless $res;
577             } elsif ($action eq 'copy') {
578 3         12 $res = copy $old, $new;
579 3 50       790 $err = $! unless $res;
580             # XXX copy mtime, ctime, etc
581             } elsif ($action eq 'symlink') {
582 3         109 $res = symlink $old, $new;
583 3 50       22 $err = $! unless $res;
584             } elsif ($action eq 'link') {
585 3         68 $res = link $old, $new;
586 3 50       13 $err = $! unless $res;
587             }
588 89 100       912 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 1176 my ($self, @args) = @_;
604 33         73 my @items;
605 33 50       120 if (@args) {
606 33         114 @items = @args;
607             } else {
608 0   0     0 @items = @{ $self->{'items'} // [] };
  0         0  
609             }
610              
611 33         286 @items = map { {real_name=>$_, name_for_script=>$_} } $self->_sort(@items);
  98         510  
612              
613 33 50       197 if ($self->{_compiled}) {
614             # another run, clean first
615 0         0 $self->run_code_for_cleaning($_) for @{ $self->{'codes'} };
  0         0  
616             }
617 33         94 $self->{_exists} = {};
618 33         93 local $self->{'recursive'} = $self->{'recursive'};
619 33         87 for (my $i=0; $i < @{ $self->{'codes'} }; $i++) {
  69         270  
620 36         90 my $code = $self->{'codes'}[$i];
621 36 100 66     532 $self->compile_code($code) unless $self->{'compiled'} || ref $code eq 'CODE';
622 36 50       151 next if $self->{'check'};
623 36         133 my $code_is_final = ($i == @{ $self->{'codes'} }-1);
  36         183  
624 36 100       157 $self->{'recursive'} = 0 if $i;
625             #print STDERR "DEBUG: items (before): ".dump(@items)."\n";
626 36         215 $self->process_items($code, $code_is_final, \@items);
627             #print STDERR "DEBUG: items (after): ".dump(@items)."\n";
628             }
629 33         738 $self->{'compiled'}++;
630             }
631              
632             1;
633             # ABSTRACT: Rename files using Perl code
634              
635             __END__