#!/usr/bin/env perl

use 5.010; # for // operator

use warnings;
use strict;
use Parallel::ForkManager;
use App::Prun;
use Getopt::Long;
use File::Spec;

my $me = (File::Spec->splitpath($0))[2];
my $def_processes = get_parallel(2);

exit main();

sub usage {
    print <<EOT;
$me v$App::Prun::VERSION - run commands in parallel

usage: $me [OPTIONS] [FILE]...

Run commands, one per line, from either FILEs or stdin if no FILEs specified.

Up to the defined number (-p) of commands will be run in parallel by forking
and running a shell for each one.

Each command is passed to perl's system() function to be executed. system()
may use your system's shell to run the command. See perldoc -f system.

If --exit-on-failure is specified, and a command exits with a failed status,
$me will wait for any running children to complete then exit with an error
without running any more commands.

OPTIONS
  -e, --exit-on-failure  exit the first time a command returns a failed status
  -h, -?, --help         display this help
  -p, --processes=<num>  run up to num processes at once (default: $def_processes)
  -r  --report-failed    print a message to STDERR for each command that fails
  -V, --version          print version and exit

NOTES
  Embedded newlines in any command will break this script as it stupidly
  treats each line as a command without any special parsing.

  Empty lines and comments (lines beginning with '#') are ignored.

EXAMPLES
  # Run tkprof against all .trc files in the current directory, run 32
  # of them at a time.
  for F in *.trc; do echo "tkprof \$F \${F%trc}txt"; done | $me -p 32

  # Run all commands in a file (command_file), one command per line. Run
  # the default number of processes in parallel ($def_processes).
  # Ignore any failed processes, but do report to STDOUT any that fail.
  $me -r command_file
EOT
    exit 1;
}

#
# Try to find the number of CPUs on the system
#
sub get_parallel {
    my $def_parallel = shift;
    my $ret;

    eval { require Sys::CpuAffinity };
    unless ($@) {
        $ret = Sys::CpuAffinity::getNumCpus();
        return $ret if $ret;
    }

    no warnings;
    $ret = `nproc 2>/dev/null`;
    use warnings;
    if (defined $ret) {
        chomp $ret;

    } else {
        open my $fh, '<', '/proc/cpuinfo' or return $def_parallel;
        $ret = 0;
        while (<$fh>) {
            ++$ret if /^processor\s*:\s*[0-9]+/;
        }
        close $fh;
    }

    $ret && $ret > 0 ? $ret : $def_parallel;
}

sub get_opts {
    my %opts;

    Getopt::Long::Configure('bundling');

    GetOptions (\%opts,
                'exit_on_failure|exit-on-failure|e',
                'help|h|?',
                'processes|p=i',
                'report_failed|report-failed|r',
                'test_dump|test-dump',
                'version|V',
               ) or usage();

    if ($opts{version}) {
        print "$me v$App::Prun::VERSION\n";
        exit 0;
    }

    usage() if $opts{help};

    $opts{processes} //= $def_processes;

    %opts;
}

sub main {
    my %opts = get_opts();

    my $ar = App::Prun->new(
        pm => Parallel::ForkManager->new($opts{processes}),
        report_failed_procs => $opts{report_failed},
        exit_on_failed_proc => $opts{exit_on_failure},
    );

    $ar->_test_dump if $opts{test_dump};

    $ar->run_command($_) while (<>);
    $ar->done;
}

# ABSTRACT: Provides the prun script as a command line interface to L<Parallel::ForkManager>.
# PODNAME: prun
1;

__END__

=pod

=encoding UTF-8

=head1 NAME

prun - Provides the prun script as a command line interface to L<Parallel::ForkManager>.

=head1 VERSION

version 1.11

=head1 DESCRIPTION

B<prun> allows you to utilize multiple CPUs
for some workloads from the shell more easily.

prun takes a list of commands (stdin and/or from file(s)) and run the commands
in parallel.

prun is a CLI front end to L<Parallel::ForkManager>. It runs commands in
parallel up to a maximum number of processes at once.

=over

=item * prun --help

=item * L<Parallel::ForkManager>

=back

=head1 SYNOPSYS

    for nr in `seq 1..100`; do echo "echo command #$nr" | prun

    prun command_file_to_run_in_parallel

=head1 EXAMPLES

There are also examples available from the command line B<--help>.

Run tkprof against all .trc files in the current directory, run 32
of them at a time.

  for F in *.trc; do echo "tkprof $F ${F%trc}txt"; done | prun -p 32

Run all commands in a file (command_file), one command per line. Run
the default number of processes in parallel ($def_processes).
Ignore any failed processes, but do report to STDOUT any that fail.

  prun -r command_file

Test with the dummy_load script included in the contrib/ directory
of this distribution:

  for F in `seq 1 100`; do echo "contrib/dummy_load"; done | prun

=head1 SEE ALSO

=over

=item L<App::Prun::Scaled>

=item L<Parallel::ForkManager>

=item L<Parallel::ForkManager::Scaled>

=back

=head1 REPOSITORY

The source repository for this module may be found at https://github.com/jmccarv/App-Prun.git

clone it:

  git clone https://github.com/jmccarv/App-Prun.git

=head1 AUTHOR

Jason McCarver <slam@parasite.cc>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2022 by Jason McCarver.

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

=cut
