App-FindUtils
view release on metacpan or search on metacpan
lib/App/FindUtils.pm view on Meta::CPAN
pos => 0,
slurpy => 1,
},
#case_insensitive => {
# schema => 'bool*',
# cmdline_aliases=>{i=>{}},
#},
detail => {
summary => 'Instead of just listing duplicate names, return all the location of duplicates',
schema => 'bool*',
cmdline_aliases => {l=>{}},
},
eval => {
#schema => 'code_from_str::local_topic*', # not yet available
schema => 'str*',
description => <<'MARKDOWN',
Process filename through this code. Code will receive filename in `$_` and is
expected to change and return a new "name" that will be compared for duplicate
instead of the original name. You can use this e.g. to find duplicate in some
part of the filename. As an alternative, see the `--regex` option.
MARKDOWN
cmdline_aliases => {e=>{}},
},
regex => {
schema => 're_from_str*',
description => <<'MARKDOWN',
Specify a regex with a capture to get part of the filename. The first capture
`$1` will be used to compare for duplicate instead of the original name. You can
use this to find duplicate in some part of the filename. As an alternative, see
the `--eval` option.
If regex does not match or if there is no capture, the filename will be used
instead as the fallback.
MARKDOWN
cmdline_aliases => {r=>{}},
},
exclude_filename_regex => {
schema => 're_from_str*',
summary => 'Filename regex to exclude',
cmdline_aliases => {x=>{}},
},
exclude_directories => {
schema => 'bool*',
summary => 'Exclude directories',
cmdline_aliases => {D=>{}},
},
},
examples => [
{
summary => "Find duplicate filenames under the current directory",
test => 0,
'x.doc.show_result' => 0,
src => '[[prog]]',
src_plang => 'bash',
},
{
summary => "Find duplicate receipts by order ID (filenames are named receipt-order=12345.pdf), exclude backup files",
test => 0,
'x.doc.show_result' => 0,
src => q{[[prog]] -x '/\\.bak$/' -r '/order=(\\d+)/' --debug},
src_plang => 'bash',
},
],
args_rels => {
choose_one => ['eval', 'regex'],
},
};
sub find_duplicate_filenames {
require Cwd;
require File::Find;
my %args = @_;
$args{dirs} //= ["."];
my $eval;
if (defined $args{eval}) {
my $code = "no strict; no warnings; package main; sub { local \$_=\$_; " . $args{eval} . "; return \$_ }";
$eval = eval $code or return [400, "Can't compile code in eval: $@"]; ## no critic: BuiltinFunctions::ProhibitStringyEval
} elsif (defined $args{regex}) {
$eval = sub { /$args{regex}/; $1 // $_ };
}
#my $ci = $args{case_insensitive};
my %names; # filename (or name) => {realpath1=>1, ...}. if hash has >1 keys than it's duplicate
File::Find::find(
sub {
no warnings 'once'; # for $File::find::dir
if ($args{exclude_directories} && (-d)) {
log_info "Excluding directory: $_";
return;
}
# XXX inefficient
my $realpath = Cwd::realpath($_);
log_debug "Found path $realpath";
unless ($realpath) {
log_warn "Can't find real path for $_: $!, skipped";
return;
}
if ($args{exclude_filename_regex}) {
if ($_ =~ $args{exclude_filename_regex}) {
log_info "$_ excluded (matches --exclude-filename-regex: $args{exclude_filename_regex})";
return;
}
}
my $name;
if ($eval) {
$name = $eval->();
unless (defined $name) {
log_warn "Eval code produced undef for $_, skipped";
return;
}
} else {
( run in 2.054 seconds using v1.01-cache-2.11-cpan-c966e8aa7e8 )