Hi,
I have the following code that does a smart copy from a folder that has bin and lib directories to bin and lib directories at a different location.
When you run this code, it gives you the conflict between the source and the target locations. This part works fine, but the problem is if there are too many conflicts the user has to approve all of them one by one.
The change that I am looking for is to show the conflicting files to the user all at once and prompt to the user to take an action to either continue even though there is conflict and over write the files or to quit the process.
BTW, this is a follow up question from:
https://www.experts-exchange.com/questions/28319462/How-to-do-a-smart-copy-of-the-files-between-directories-in-Perl.html?anchorAnswerId=39751638#a39751638
If you have any questions, please check with me before you spent time instead of making assumptions.
Please see the code below:
use strict;
use warnings;
use File::Copy qw(cp);
use File::Find;
use File::Path qw(mkpath);
## taken from Term::Prompt
use Carp;
use Text::Wrap;
use Term::ReadKey qw (GetTerminalSize ReadMode);
our $DEBUG = 0;
our $MULTILINE_INDENT = "\t";
my %menu = (
order => 'down',
return_base => 0,
display_base => 1,
accept_multiple_selections => 0,
accept_empty_selection => 0,
title => '',
prompt => '>',
separator => '[^0-9]+',
ignore_whitespace => 0,
ignore_empties => 0
);
sub prompt ($$$$;@);
# End Term::Prompt
my $dir = shift or die "Usage: $0 base_dir prod_dirs\n";
my %chk = map { $_ => 1 } @ARGV;
chdir $dir or die "could not cd to $dir: $!";
opendir DIR, '.' or die "could not open $dir: $!";
my @dirs = grep { -d $_ and exists($chk{$_}) and $_ !~ m{^(?:bin|lib|\.+)$}o }
readdir DIR;
closedir DIR;
die "Usage: $0 base_dir prod_dirs\n e.g release2Production.pl . rel1.3\n" unless @dirs;
print "base dir $dir\n";
print "checking @dirs...\n";
my ($base, %files, %multi, %dest);
foreach my $dir (qw(bin lib)) {
File::Find::find({wanted => \&base}, $dir) if (-d $dir);
}
foreach my $dir (sort @dirs) {
$base = $dir;
File::Find::find({wanted => \&search}, $base);
}
if (%multi) {
foreach my $file (sort keys %multi) {
my $val = prompt('m', { prompt => 'which file?', title => 'conflict',
items => [ 'quit', map { "$_/$file" } @{$files{$file}} ],
cols => 1, display_base => 0 }, '', undef);
exit unless $val;
$files{$file} = [$files{$file}[$val-1]];
}
} else {
my $val = prompt('m', { prompt => 'what to do?', title => 'no conflicts',
items => ['quit', 'no conflicts - continue'], cols => 1,
display_base => 0 }, '', undef);
exit unless $val;
}
foreach my $file (keys %files) {
die "woa!! found conflict for $file after resolutions\n"
unless (@{$files{$file}} == 1);
# print "cp $files{$file}[0]/$file\n";
my $dir = $file;
$dir =~ s{/[^/]+$}{};
mkpath($dir) unless (-d $dir);
cp "$files{$file}[0]/$file", $file
or die "could not copy $files{$file}[0]/$file: $!";
}
sub base {
return unless -f $_;
my $name = $File::Find::name;
$dest{$name}++;
}
sub search {
return unless -f $_;
my $name = $File::Find::name;
$name =~ s{^$base/}{};
if (exists $files{$name}) {
$multi{$name}++;
push @{$files{$name}}, $base;
} elsif (exists $dest{$name}) {
$multi{$name}++;
push @{$files{$name}}, '.', $base;
} else {
push @{$files{$name}}, $base;
}
}
# BEGIN main part of Term::Prompt
# Preloaded methods go here.
sub prompt ($$$$;@) {
my($mopt, $prompt, $prompt_options, $default, @things) =
('','','',undef,());
my($repl, $match_options, $case, $low, $high, $before, $regexp, $coderef) =
('','','','','','','','');
my $prompt_full = '';
# Figure out just what we are doing here
$mopt = $_[0];
print "mopt is: $mopt\n" if $DEBUG;
# check the size of the match option, it should just have one char.
if (length($mopt) == 1
or $mopt =~ /\-n/i
or $mopt =~ /\+-n/i) {
my $dummy = 'mopt is ok';
} else {
croak "Illegal call of prompt; $mopt is more than one character; stopped";
}
my $type = 0;
my $menu = 0;
my $legal = 0;
my $range = 0;
my $expr = 0;
my $code = 0;
my $yn = 0;
my $uc = 0;
my $passwd = 0;
if ($mopt ne lc($mopt)) {
$uc = 1;
$mopt = lc($mopt);
}
if ($mopt eq 'm') {
($mopt, $prompt, $prompt_options, $default) = @_;
$menu = 1;
} else {
croak "prompt type $mopt not recognized";
}
my $ok = 0;
$mopt = lc($mopt);
while (1) {
if ($menu) {
## We're working on a menu
@menu{sort keys %{$prompt}} = @{$prompt}{sort keys %{$prompt}};
$prompt_full = "$menu{'prompt'} ";
my @menu_items = @{$menu{'items'}};
my $number_menu_items = scalar(@menu_items);
$menu{'low'} = $menu{'display_base'};
$menu{'high'} = $number_menu_items+$menu{'display_base'}-1;
my $digits_in_menu_item = (int(log($menu{'high'})/log(10)) + 1);
my $entry_length = 0;
my $item_length = 0;
for (@menu_items) {
$entry_length = length($_)
if length($_) > $entry_length;
}
$item_length = $entry_length;
$entry_length += ( $digits_in_menu_item ## Max number of digits in a selection
+
3 ## two for ') ', at least one for a column separator
);
my $gw = get_width();
my $num_cols = (defined($menu{'cols'})
? $menu{'cols'}
: int($gw/$entry_length));
$num_cols ||= 1; # Could be zero if longest entry in a
# list is wider than the screen
my $num_rows = (defined($menu{'rows'})
? $menu{'rows'}
: int($number_menu_items/$num_cols)+1) ;
my $data_fmt = "%${digits_in_menu_item}d) %-${item_length}.${item_length}s";
my $column_end_fmt = ("%s ");
my $line_end_fmt = ("%s\n");
my @menu_out = ();
my $row = 0;
my $col = 0;
my $idx = 0;
if ($menu{order} =~ /ACROSS/i) {
ACROSS_LOOP:
for ($row = 0; $row < $num_rows; $row++) {
for ($col = 0; $col < $num_cols; $col++) {
$menu_out[$row][$col] = sprintf($data_fmt,$idx+$menu{'display_base'},$menu_items[$idx++]);
last ACROSS_LOOP
if $idx eq scalar(@menu_items);
}
}
} else {
DOWN_LOOP:
for ($col = 0; $col < $num_cols; $col++) {
for ($row = 0; $row < $num_rows; $row++) {
$menu_out[$row][$col] = sprintf($data_fmt,$idx+$menu{'display_base'},$menu_items[$idx++]);
last DOWN_LOOP
if $idx eq scalar(@menu_items);
}
}
}
if (length($menu{'title'})) {
print $menu{'title'},"\n",'-' x length($menu{'title'}),"\n";
}
for ($row = 0;$row < $num_rows;$row++) {
for ($col = 0;$col < $num_cols-1;$col++) {
printf($column_end_fmt,$menu_out[$row][$col])
if defined($menu_out[$row][$col]);
}
if (defined($menu_out[$row][$num_cols-1])) {
printf($line_end_fmt,$menu_out[$row][$num_cols-1])
} else {
print "\n";
}
}
if ($number_menu_items != ($num_rows)*($num_cols)) {
print "\n";
}
unless (defined($prompt_options) && length($prompt_options)) {
$prompt_options = "$menu{'low'} - $menu{'high'}";
if ($menu{'accept_multiple_selections'}) {
$prompt_options .= ', separate multiple entries with spaces';
}
}
}
unless ($before || $uc || ($prompt_options eq '')) {
$prompt_full .= "($prompt_options) ";
}
if (defined($default) and $default ne '') {
$prompt_full .= "[default $default] ";
}
print termwrap($prompt_full);
my $old_divide = undef;
if (defined($/)) {
$old_divide = $/;
}
$/ = "\n";
ReadMode('noecho') if($passwd);
$repl = scalar(readline(*STDIN));
ReadMode('restore') if($passwd);
if (defined($old_divide)) {
$/ = $old_divide;
} else {
undef($/);
}
chomp($repl); # nuke the <CR>
$repl =~ s/^\s*//; # ignore leading white space
$repl =~ s/\s*$//; # ignore trailing white space
$repl = $default if $repl eq '';
print termwrap("Reply: '$repl'\n") if $DEBUG;
# Now here is where things get real interesting
my @menu_repl = ();
if ($menu) {
$ok = menuit(\@menu_repl, $repl, $DEBUG, $uc);
} else {
croak "No subroutine known for prompt type $mopt.";
}
if ($ok) {
if ($menu) {
if ($menu{'accept_multiple_selections'}) {
return (wantarray ? @menu_repl : \@menu_repl);
} else {
return $menu_repl[0];
}
}
} elsif (defined($prompt_options) && length($prompt_options)) {
if ($uc) {
print termwrap("$prompt_options\n");
} else {
if (!$menu) {
print termwrap("Options are: $prompt_options\n");
}
$before = 1;
}
}
}
}
sub rangeit ($$$$ ) {
# this routine makes sure that the reply is within a given range
my($repl, $low, $high, $uc) = @_;
if ( $low <= $repl && $repl <= $high ) {
return 1;
} elsif (!$uc) {
print 'Invalid range value. ';
}
return 0;
}
sub menuit (\@$$$ ) {
my ($ra_repl, $repl, $dbg, $uc) = @_;
print "inside of menuit\n" if $dbg;
my @msgs = ();
## Parse for multiple values. Strip all whitespace if requested or
## just strip leading and trailing whitespace to avoid a being
## interpreted as separating empty choices.
if($menu{'ignore_whitespace'}) {
$repl =~ s/\s+//g;
} else {
$repl =~ s/^(?:\s+)//;
$repl =~ s/(?:\s+)$//;
}
my @repls = split(/$menu{'separator'}/,$repl);
if($menu{ignore_empties}) {
@repls = grep{length($_)} @repls;
}
## Validations
if ( scalar(@repls) > 1
&&
!$menu{'accept_multiple_selections'} ) {
push @msgs, 'Multiple choices not allowed.';
} elsif (!scalar(@repls)
&&
!$menu{'accept_empty_selection'}) {
push @msgs, 'You must make a selection.';
} else {
for (@repls) {
if ( !rangeit($_,$menu{'low'},$menu{'high'},1)) {
push @msgs, "$_ is an invalid choice.";
}
}
}
## Print errors or return values
if (scalar(@msgs)) {
print "\n",join("\n",@msgs),"\n\n";
return 0;
} else {
@{$ra_repl} = map {$_ - $menu{'display_base'} + $menu{'return_base'}} @repls;
return 1;
}
}
sub termwrap ($;@) {
my($message) = '';
if ($#_ > 0) {
if (defined($,)) {
$message = join($,,@_);
} else {
$message = join(' ',@_);
}
} else {
$message = $_[0];
}
my $width = get_width();
if (defined($width) && $width) {
$Text::Wrap::Columns = $width;
}
if ($message =~ m/\n\Z/) {
$message = wrap('', $MULTILINE_INDENT, $message);
$message =~ s/\n*\Z/\n/;
return $message;
} else {
$message = wrap('', $MULTILINE_INDENT, $message);
$message =~ s/\n*\Z//;
return $message;
}
}
sub get_width {
## The 'use strict' added above caused the calls
## GetTerminalSize(STDOUT) and GetTerminalSize(STDERR) to fail in
## compilation. The fix as to REMOVE the parens. It seems as if
## this call works the same way as 'print' - if you need to
## specify the filehandle, you don't use parens (and don't put a
## comma after the filehandle, although that is irrelevant here.)
## SO DON'T PUT THEM BACK! :-)
my($width) = eval {
local($SIG{__DIE__});
(GetTerminalSize(select))[0];
} || eval {
if (-T STDOUT) {
local($SIG{__DIE__});
return (GetTerminalSize STDOUT )[0];
} else {
return 0;
}
} || eval {
if (-T STDERR) {
local($SIG{__DIE__});
return (GetTerminalSize STDERR )[0];
} else {
return 0;
}
} || eval {
local($SIG{__DIE__});
(GetTerminalSize STDOUT )[0];
} || eval {
local($SIG{__DIE__});
(GetTerminalSize STDERR )[0];
};
return $width;
}
Select all Open in new window
Thanks,
Open in new window