#!/usr/bin/perl

=begin metadata

Name: find
Description: search directory tree for files matching a pattern
Author: Greg Snow, snow@biostat.washington.edu
Author: Tom Christiansen, tchrist@perl.com
Author: Larry Wall, larry@wall.org
License: perl

=end metadata

=cut


# find - actually a pass through front end for find2perl

use strict;

use Config qw(%Config);
use File::Basename;
use File::Spec::Functions;
use File::Temp qw/ tempfile /;
use FindBin;

sub find_find2perl {
	return $ENV{FIND2PERL} if defined $ENV{FIND2PERL};
	my @candidates =
		grep { -e }
		map { catfile( $_, 'find2perl' ) }
			dirname($Config{perlpath}),
			split( /$Config{path_sep}/, $ENV{PATH} )
			;

	push @candidates, catfile( $ENV{PERL_LOCAL_LIB_ROOT}, 'bin', 'find2perl' )
		if defined $ENV{PERL_LOCAL_LIB_ROOT};

	return defined $candidates[0] ? $candidates[0] : ();
	}

my $find2perl  = find_find2perl();

die <<"HEREDOC" unless -e $find2perl;
Did not find the find2perl program. Install App::find2perl or set the
FIND2PERL environment variable with the path.

This program looked for find2perl in:

	@{[ defined $find2perl ? $find2perl : '<not found>' ]}

find2perl was distributed with perl up to v5.18 and became a separate
module in v5.20. Older versions of find2perl assumed that the program
would be next to the perl binary but installed something like
/usr/bin/find2perl that would dispatch to the Standard Library
version.
HEREDOC

# Temp files to capture find2perl output and error.
my ($out_fh, $out_file) = tempfile();
my ($err_fh, $err_file) = tempfile();

END {
    unlink $out_file;
    unlink $err_file;
}

# Save STDOUT and STDERR.  Redirect to temp files.

open SAVE_OUT, '>&', STDOUT or die "Can't save STDOUT: $!";
die unless defined fileno SAVE_OUT;

open STDOUT, '>&', $out_fh or die "Can't dup STDOUT to $out_file: $!";
$|++, select $_ for select STDOUT;

open SAVE_ERR, '>&', STDERR or die "Can't save STDERR: $!";
die unless defined fileno SAVE_ERR;

open STDERR, '>&', $err_fh or die "Can't dup STDERR to $err_file: $!";
$|++, select $_ for select STDERR;

# Run find2perl to convert find command to Perl script.

system $^X, $find2perl, @ARGV;

# Check for errors.

my $child_error = $?;
my $acm_min_error;

open STDERR, '>&', SAVE_ERR or die "Can't restore STDERR: $!";

seek $err_fh, 0, 0 or die "Can't rewind $err_file: $!";

my $error = do { local $/; <$err_fh> };

close $err_fh;

if ($error) {
    if ($error =~ /^Unrecognized switch: -[acm]min\s*$/) {
        # Tried to use -amin, -cmin or -mmin, which find2perl doesn't
        # support.
        $acm_min_error++;

        # So change to -atime, -ctime or -mtime, and append an
        # identifier to its argument (fortunately, find2perl does not
        # validate the argument).
        for my $i (0..$#ARGV) {
            $ARGV[$i + 1] .= '-ppt-minutes'
                if $ARGV[$i] =~ s/^(-[acm])min$/${1}time/;
        }

        system $find2perl, @ARGV;
        $child_error = $?;
    } else {
        warn $error;
    }
}

die "Can't run $find2perl (wait status == $child_error)" if $child_error;

die "Empty program" unless -s $out_fh && -s $out_file;

# Get "find" Perl script from output.

seek $out_fh, 0, 0 or die "Can't rewind $out_file: $!";

my $program = do { local $/; <$out_fh> };

close $out_fh;

open STDOUT, '>&', SAVE_OUT or die "Can't restore STDOUT: $!";

# Convert fake -atime, -ctime and -mtime conditionals, such as:
#
#    (int(-C _) > 60-ppt-minutes) &&
#    (int(-M _) < 15-ppt-minutes)
#
# To -amin, -cmin and -mmin conditionals, such as:
#
#    (int(1440 * -C _) > 60) &&
#    (int(1440 * -M _) < 15)
#
if ($acm_min_error) {
    my $minutes_per_day = 24 * 60;
    $program =~ s/(\bint\()(-[AMC] .*)-ppt-minutes/$1$minutes_per_day * $2/g;
}

# Run "find" Perl script.

eval qq{
    no strict;
    local \$^W = 0;
    $program;
};

die "Can't compile and execute $find2perl: $@\n" if $@;

exit 0;

__END__

=head1 NAME

find - search directory tree for files matching a pattern

=head1 SYNOPSIS

B<find>
[ -HdhXxW ]
[ F<Directory> ]
[ expression ]

=head1 DESCRIPTION

This is actually a front end for B<find2perl>, it automatically
converts your request to perl and executes it (unless something goes
wrong, you probably will not see a difference).  If you want to do
something fancier, or are going to do the same search often, you
should give the commands straight to B<find2perl> and run the perl code
yourself (after possible modification).

B<find> searches a directory tree for files matching given criteria,
then executes user specified commands on those files.

 Examples:

find and print a list of all files on the system (disk) with a F<.pm>
extension (perl modules).

C<find / -name "*.pl" -print>

find and delete all files in the current directory and subdirectories
that end with F<.bak> and have not been accessed in the last 10 days
(using unix rm command to do the deleting).

C<find . -name "*.bak" -atime +10 -exec rm {};>

=head1 SEE ALSO

find

File/Find.pm

=head1 RESTRICTIONS

I<find2perl> may not cover all the options that various versions of
find implement.  It is only a wrapper to find2perl and so has all the
same restrictions (some have said that find2perl needs updating).

=head1 BUGS

This manpage should probably include the entire
I<find> manpage, and perhaps that of I<find2perl> as well.

=head1 AUTHOR

This front-end written by Greg Snow, I<snow@biostat.washington.edu>,
with many things "borrowed" from the I<awk> front-end by Tom
Christiansen, I<tchrist@perl.com>.  The I<find2perl> translator was
written by Larry Wall, I<larry@wall.org>, author of Perl.

=head1 COPYRIGHT and LICENSE

This program is copyright (c) Gregory L. Snow 1999
(with parts "borrowed" from things copyright (c) Tom Christiansen 1999).

This program is free and open software. You may use, modify, distribute,
and sell this program (and any modified variants) in any way you wish,
provided you do not restrict others from doing the same.