#!perl

use 5.010001;
use strict;
use warnings;

use File::Which;
use Getopt::Long;
use ShellQuote::Any::Tiny;

our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2024-09-24'; # DATE
our $DIST = 'App-findsort'; # DIST
our $VERSION = '0.004'; # VERSION

my %Opts = (
    sort  => undef,
);

Getopt::Long::Configure(
    'pass_through', 'no_auto_abbrev',
    #'permute'
);
GetOptions(
    'help|h|?' => sub {
        print <<'_';
Usage:
  % findsort [findsort-options] [find-options and arguments]
  % findsort -help
  % findsort -version
findsort options:
  -help           Show this message and exit.
  -version        Show program version and exit.
  -sort=SORT      Pick a sorting order. Available: mtime, -mtime, ctime, -ctime,
                  size, -size, du, -du. The "-" prefix means descending order.

All the other options will be passed to find.
_
        exit 0;
    },
    'version' => sub {
        no warnings 'once';
        print "findsort version ", ($main::VERSION || "dev"),
            ($main::DATE ? " ($main::DATE)" : ""), "\n";
        exit 0;
    },
    'sort=s'  => \$Opts{sort},
);

my $cmd = "find" . (@ARGV ? " " : "") .
    join(" ", map { ShellQuote::Any::Tiny::shell_quote($_) } @ARGV);
open my $findh, "$cmd |" or die "findsort: Can't run find: $!\n"; ## no critic: InputOutput::ProhibitTwoArgOpen

my @files;
while (!eof($findh)) {
    no warnings 'uninitialized'; # "Use of uninitialized value in subroutine entry" at line below
    defined(my $line = <$findh>)
        or die "findsort: getline() failed: $!\n";

    chomp $line;
    push @files, $line;
}

close $findh;
my $exit = $? >> 8;

{
    no warnings 'uninitialized';
    if (!$Opts{sort}) {
        # no sorting needed
    } elsif ($Opts{sort} eq 'mtime') {
        @files = sort { (-M $b) <=> (-M $a) } @files;
    } elsif ($Opts{sort} eq '-mtime') {
        @files = sort { (-M $a) <=> (-M $b) } @files;
    } elsif ($Opts{sort} eq 'ctime') {
        @files = sort { (-C $b) <=> (-C $a) } @files;
    } elsif ($Opts{sort} eq '-ctime') {
        @files = sort { (-C $a) <=> (-C $b) } @files;
    } elsif ($Opts{sort} eq 'size') {
        @files = sort { (-s $a) <=> (-s $b) } @files;
    } elsif ($Opts{sort} eq '-size') {
        @files = sort { (-s $b) <=> (-s $a) } @files;
    } elsif ($Opts{sort} eq 'du' || $Opts{sort} eq '-du') {
        require Filesys::DiskUsage;
        my @sizes = map { Filesys::DiskUsage::du($_) } @files;
        if ($Opts{sort} eq 'du') {
            @files = map { $files[$_] } sort { $sizes[$a] <=> $sizes[$b] } 0 .. $#files;
        } else {
            @files = map { $files[$_] } sort { $sizes[$b] <=> $sizes[$a] } 0 .. $#files;
        }
    } else {
        die "findsort: Unknown -sort value: $Opts{sort}\n";
    }
}

print map { "$_\n" } @files;
exit $exit;

# ABSTRACT: Unix find wrapper that has sorting option
# PODNAME: findsort

__END__

=pod

=encoding UTF-8

=head1 NAME

findsort - Unix find wrapper that has sorting option

=head1 VERSION

This document describes version 0.004 of findsort (from Perl distribution App-findsort), released on 2024-09-24.

=head1 SYNOPSIS

Use like you would use B<find>, but with optional C<-sort> option:

 % findsort -sort=-mtime -type d -name '*.git'

=head1 DESCRIPTION

Despite Unix toolbox philosophy being in general useful, sometimes it's
convenient to merge several Unix utilities into one. This is one of such cases.
Either because B<sort> by default does not offer sorting entries as files, or
because in some situation (e.g. with ssh and Bourne shell account) the syntax to
combine commands is limited.

=head1 OPTIONS

=head2 -help

=head2 -version

=head2 -sort

Specify sort order. Choices:

=over

=item * mtime

Sort by oldest modification time first.

=item * -mtime

Sort by newest modification time first.

=item * ctime

Sort by oldest creation time first.

=item * -ctime

Sort by newest modification time first.

=item * size

Sort by smallest file first. Size is from `stat()`, so for directory this won't
total the total contents inside.

=item * -size

Sort by largest file first. Size is from `stat()`, so for directory this won't
total the total contents inside.

=item * du

Sort by smallest disk usage first. Size is from L<Filesys::DiskUsage>, so for
directory this will total the total contents inside.

=item * -du

Sort by largest disk usage first. Size is from L<Filesys::DiskUsage>, so for
directory this will total the total contents inside.

=back

=head1 ENVIRONMENT

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-findsort>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-findsort>.

=head1 SEE ALSO

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 CONTRIBUTING


To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.

Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:

 % prove -l

If you want to build the distribution (e.g. to try to install it locally on your
system), you can install L<Dist::Zilla>,
L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
that are considered a bug and can be reported to me.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2024 by perlancar <perlancar@cpan.org>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-findsort>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=cut
