#!/usr/bin/perl

use 5.022;
use strict;
use warnings;

use App::Pfind;

our $VERSION = $App::Pfind::VERSION;

App::Pfind::Run(\@ARGV);
exit 0;

# PODNAME: pfind
# ABSTRACT: A Perl based find replacement
 
__DATA__

=pod

=head1 NAME

B<pfind> - A Perl based I<find> replacement

=head1 SYNOPSIS

  pfind dir1 dir2 ... [--exec perl_code]

The program recursively crawls through all the directories listed on its command
line and execute some piece of perl code on each files and directories that
are encountered.

=head1 DESCRIPTION

B<pfind> is a replacement for the standard B<find> command where the countless
flags and options are mostly all replaced by a single option (B<--exec>) that 
can execute arbitrary Perl code.

See examples of B<pfind> in action below, in the L</EXAMPLES> section.

=head1 OPTIONS

All the options below can be abbreviated down to uniqueness and be supplied in
any order. Input files and directories can be mixed in with the options. Options
processing can be stopped with the B<--> flag, all remaining arguments will be
considered input files.

=over 4

=item B<-e> I<code>, B<--exec>

Execute the given piece of code for each file and directory encountered by the
program. The program will C<chdir> into each directory being crawled before
calling your code and the C<$_> variable will contain the base name of the
current file or directory. In addition, the C<$dir> variable will contain the
directory name of the current file and C<$name> will contain the full name of
the file (more or less C<$dir> concatenated with C<$_>).

The code can also call the C<prune> method to skip recursing into the current
directory. This has no effect if called while looking at a file. This cannot be
used if the B<--depth-first> option is passed. This does not interupt the
execution of the code for the current directory or file.

You will typically uses the code to perform tests on the given file and some
sort of actions depending on the result of the tests. See L</EXAMPLES> below.

This option can be passed multiple times. However, multiple pieces of code given
to this option will not be independant: they will share the same variables and
if C<return> is called by a piece of code, no more code will be executed for the
current file. However the keyword C<next> can be used to jump to the next piece
of code to be executed.

One exception is that the C<$_>, C<$dir>, and C<$name> variables are saved and
each piece of coce will initially see the correct values. The variables can be
modified but the next pieces of code executed after the current one will not see
the modification.

=item B<-d>, B<--depth-first>

When this option is passed, the code given to B<--exec> will be called first for
the content of a directory and then for the directory itself (this is a depth
first approach). By default, the code is executed first for a directory and then
for its content.

Using this option might be required if you're planning on changing the name of a
directory.

The opposite option is B<--no-depth-first> (or B<--nod>).

=item B<-t> I<types>, B<--type>

Filter the files on which the actions should be executed. This argument can
receive a list of characters defining type of files to accept. There should be
no separators. Alternatively the option can be passed multiple times.

Valid values for I<types>: C<f>, a regular file; C<d>, a directory; C<l>, a
symbolic link; C<b>, a block special file; C<c>, a character special file; C<p>,
a fifo file (pipe); C<s>, a socket.

Note that these types are not mutually exclusive. If multiple types are passed,
only files that match all the types will be accepted. A type can be given in
upper-case to reverse its meaning (excluding the files of that type).

When a file is filtered, the code given to B<--exec> will not be executed for
that file but, if this file is a directory, the B<--pre> and B<--post> code will
still execute. Filtering directories will also not prevent from recursing in
them (you can use the B<--no-recurse> option for that).

=item B<-f>, B<--follow>

When this option is passed, symlinks are followed (by default they are treated
as files but not followed).

=item B<-ff>, B<--follow-fast>

Same as B<--follow> but faster. However, with this option, it is possible that
some files will be processed twice if the symlinks for some kind of cycles.

The B<--follow> and B<--follow-fast> options are mutually exclusive.

=item B<--chdir>

When this option is set (which is the default), the program will C<chdir> into
each directory being crawled before calling your code.

This behavior can be deactivated with the opposite option B<--no-chdir>. In this
case, during the execution of the code passed to B<--exec>, the C<$_> variable
will contain the full path to the current file (same as the C<$name> variable).
That name will be absolute or relative, depending on whether the starting
directory given on the command line has been given with an absolute or relative
path.

=item B<-p> I<text>, B<--print>

Print the argument of this function after each call of the Perl C<print>
function. This defaults to a new-line. Technically this option is setting the
C<$\> variable in Perl.

=item B<--no-recurse>, B<-nor>

Do not recurse into directories given on the command line. These directories are
still processed like normal files but not their content.

This option cannot be used when B<--depth-first> is passed.

=item B<-B> I<code>, B<--BEGIN>

Specify a piece of code that is executed before starting to crawl the
directories. That code can set-up variables and functions to be used later by
the code passed to B<--exec>.

This option can be passed several times. Each piece of code will be called in
order.

=item B<-E> I<code>, B<--END>

Similar to the B<--BEGIN> option, but the passed code will be executed after all
the crawling is done.

=item B<--pre> I<code>, B<--pre-process>

Execute the given piece of code each time a directory is entered. The name of
the current directory is in the variables C<$_> and C<$dir>. If B<--chdir> is in
effect (the default), then the current directory is already set to that
directory.

This option cannot be used together with B<--follow> or B<--follow-fast>.

Note: the matching option in the underlying C<File::Find> Perl library support
modifying which files of the directory will be used. This is not supported by
B<pfind>.

=item B<--post> I<code>, B<--post-process>

Execute the given piece of code just before exiting each processed directory.
The name of the (still) current directory is in the variables C<$_> and C<$dir>.
If B<--chdir> is in effect (the default), then the current directory is still
set to that directory.

This option cannot be used together with B<--follow> or B<--follow-fast>.

=item B<-h>, B<--help>

Print this help message and exits. Note: the help message printed will be much
improved if you have the B<perldoc> program installed (sometimes from a
B<perl-doc> package).

=item B<-v>, B<--verbose>

Print the version of the program and exit.

=item B<--version>

Print the version of the program and exit.

=back

=head1 PERL ENVIRONMENT

In addition to the variables listed above (C<$_>, C<$name>, and C<$dir>), the
Perl environment used to execute the code provided to the B<--exec>, B<--BEGIN>,
B<--END>, B<--pre>, and B<--post > flags will contain the following methods.

=over 4

=item C<cp> and C<mv>

These methods (coming from the L<File::Copy> module) both takes two arguments
(two file names) and copy or move the first file to the second one (in the case
of C<mv>, the second argument can also be a directory name, that must already
exists).

=item C<mkdir> and C<rmdir>

These two functions exist by default in Perl (L<mkdir> and L<rmdir>) but
improved versions that can take more than one arguments are provided.

If no arguments are passed, use C<$_>.

=item C<rm>

This function takes a list of files and directories and delete them. While it is
built on top of the C<remove_tree> function from L<File::Path>, it will only
delete files or empty directories and will recurse in non-empty directories.

If no arguments are passed, use C<$_>.

=item C<rmtree>

An alias to the standand C<remove_tree> function from L<File::Path>. This takes
a lis of files or directories and delete them all recursively. Use with caution!

If no arguments are passed, use C<$_>.

=back

=head1 EXAMPLES

A default invocation of the program without arguments other than directories and
files will as the B<find> program, printing the recursive content of all the
listed directories and files:

  pfind dir1 dir2 dir3

By default, pfind C<chdir> into each directory, so the only the base name of the
files is printed. With the B<--no-chdir> option, the full name of the files is
printed:

  pfind --no-chdir dir1 dir2 dir3

This example will print the name of all the files and directories that it sees
but it will skip the content of hidden directories and hidden directories
themselves:

  pfind -e 'if (/^\..+/) { prune; return }' -e 'print $name' dir...

This example prints the name of all symbolic links whose targets are invalid:

  pfind -e 'print $name if -l && !-e'

Using the B<--type> option, the same example could be written:

  pfind -t l -e 'print $name unless -e'

Rename all the file in the current folder, replacing C<_> by C<-> but do not
recurse into sub-directories:

  pfind -e '$r = s/_/-/gr; system "mv \"$_\" \"$r\""' -e 'prune unless /\./' .

Print the name of all the folders that don't contain a particular file called
F<somefile.ext>:

  pfind -e '$a{$dir}++ if /somefile.ext/' -pre '$a{$_} = 0' \
        -post 'print unless $a{$_}' .

=head1 AUTHOR

This program has been written by L<Mathias Kende|mailto:mathias@cpan.org>.

=head1 LICENCE

Copyright 2019 Mathias Kende

This program is distributed under the MIT (X11) License:
L<http://www.opensource.org/licenses/mit-license.php>

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

=head1 SEE ALSO

L<perl(1)>, L<find(1)>, L<xargs(1)>,
L<File::Find|https://perldoc.perl.org/file/find.html>

=cut
