################################################################################
#
################################################################################
package Shell::EZ;
use strict;
use warnings;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
require Exporter;
@ISA       = qw(Exporter);
@EXPORT    = qw( );
@EXPORT_OK = qw ();
$VERSION   = '0.10';
########################
use IO::File;
use File::Temp qw/tempfile/;
use Carp;
use File::Basename;
my $PGM = basename $::0;
########################



##################################################
#
##################################################
sub new
{
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $s = {};
    bless $s,$class;
    $s->reset(@_);
    return $s;
}



#
# p_stderr_mode  => separate, together
# p_return_mode  => cref, pass, stdout, stderr
# p_exit_on_fail => <boolean>
# p_with_filter  => <sub> or <string>
# p_log_filename => <string>, temp has special meaning.
# p_log_fh       => NA
#
##################################################
#
##################################################
sub reset
{
    my $s = shift;

    $s->{p_stderr_mode}  = 'together';  
    $s->{p_return_mode}  = 'pass'; 
    $s->{p_exit_on_fail} = 0;
    $s->{p_with_filter}  = undef;
    $s->{p_log_filename} = '';
    $s->{p_log_fh}       = '';

    $s->{cmd}    = undef;
    $s->{stderr} = undef;
    $s->{stdout} = undef;
    $s->{rval}   = undef;
    $s->{rsig}   = undef;
    $s->{pass}   = undef;

    if ($s->{p_log_filename}) {
        my ($okay,$init_text,$tmp);
        if ('temp' eq $s->{p_log_filename}) {
            ($s->{p_log_fh}, $s->{p_log_filename}) = tempfile();
            $tmp = 'Opens';
            $okay = $s->{p_log_fh};
        } else {
            $tmp = -f $s->{p_log_filename} ? 'Reopens' : 'Opens';
            $s->{p_log_fh} = IO::File->new;
            $okay = $s->{p_log_fh}->open(">> " . $s->{p_log_filename});
        }
        if ($okay) {
            select((select($s->{p_log_fh}), $| = 1)[0]);
            $s->log( title => "$PGM: $tmp log.", );
        } else {
            carp 'Warning: Could not open log for shell commands.';
            undef $s->{p_log_fh};
            undef $s->{p_log_filename};
        }
    }
}


##################################################
#
##################################################
sub log 
{
    my $s = shift;
    my %h = @_;
    my $fh = $s->{p_log_fh};
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
        = localtime();

    my $mmddyy = sprintf('%2d/%02d/%02d', $mon+1,$mday,$year%100);
    my $hhmmss = sprintf('%2d:%02d:%02d', $hour,$min,$sec);
    
    if ( $h{title} ) {
        print $fh "$mmddyy $hhmmss: $s->{title}\n";
    } else {
        my $x = $s->{success} ? 'pass' : 'fail';
        print $fh "$mmddyy $hhmmss: [$s->{cmd}]\n";
        my ($hh,$mm,$ss) = (int($s->{duration}/3600),
                            int(($s->{duration}%3600)/60),
                            $s->{duration}%60);
        $hhmmss = sprintf('%02d:%02d:%02d',$hh,$mm,$ss);
        print $fh "             : rval=$s->{rval} ($x)\n";
        print $fh "             : duration=$hhmmss (hh:mm:ss)\n";
        print $fh "             : $s->{n_stdout} lines stdout.\n";
        print $fh "             : $s->{n_stderr} lines stderr.\n";
        if (! $s->{p_log_quiet}) {
            my $str='stdxxx';
            my $i=2;
            if ('together' eq $s->{p_stderr_mode}) {
                foreach ( @{$s->{ra_stdout}} ) {
                    print $fh "  $str>> $_";
                    $str = sprintf('%5d', $i++);
                }
            } elsif ('separate' eq $s->{p_stderr_mode}) {
                $str='stdout';
                foreach ( @{$s->{ra_stdout}} ) {
                    print $fh "  $str>> $_";
                    $str = sprintf('%5d', $i++);
                }
                $str='err';
                $i=2;
                foreach ( @{$s->{ra_stderr}} ) {
                    print $fh "  $str>> $_";
                    $str = sprintf('%5d', $i++);
                }
            } else {
                die;
            }
        }
    }
}


##################################################
#
##################################################
sub run
{
    my $s = shift;
    #SafeSubs();
    my %h = @_;
    my $cmd = $h{cmd} || '';
    
    $s->{cmd} = $cmd if $cmd;

    croak 'ERROR: No command given to run'
        if (! $s->{cmd});

    my ($fh_err, $f_err);

    if (0) {
    } elsif ( 'separate' eq $s->{p_stderr_mode} ) {
        ($fh_err, $f_err) = tempfile();
        $cmd = $s->{cmd} . " 2>$f_err";
        close $fh_err;
    } elsif ( 'together' eq $s->{p_stderr_mode} ) {
        $cmd = $s->{cmd} . " 2>&1";
    } else {
        die "internal error";
    }
    my $t_start = time;
    my @a;

    if ($s->{p_with_filter} && 
        if ("CODE" eq (ref $s->{p_with_filter} || '')) {
            my $rh_func_context = {};
            map { 
                my $r = &$rf($rh_func_context, $_); 
                push @a,$r if (defined $r);
            } `$cmd`;

        } else {
            @a = grep { /$s->{p_with_filter}/ } `$cmd`;
        }
    } else {
        @a = `$cmd`;
    }

    $s->{duration} = time - $t_start;
    $s->{rval}     = ($? >> 8) & 0xff;
    $s->{rsig}     = $? & 0xff;
    $s->{pass}     = 0 == $s->{rval};
    $s->{n_stdout} = scalar @a;

    if ($s->{p_with_grep}) {
        $s->{stdout} = join('', grep( /$s->{p_with_grep}/, `$cmd`));
    } else {
        $s->{ra_stdout} = \@a;
    }
    $s->{stderr}   = '';

    if ( 'separate' eq $s->{p_stderr_mode} ) {
        $fh_err = IO::File->new;
        if (! $fh_err->open($f_err) && ($? >> 8)) {
            croak "Error: Could not recover stderr";
        }
        binmode $fh_err;
        $s->{stderr} = join('',<$fh_err>);
        close $fh_err;
    } 

    if (0) {
    } elsif (!$s->{pass} && $s->{p_exit_on_fail}) {
        exit( $s->{rval} );
    } elsif ('cref' eq $s->{p_return_mode}) {
        return $s;
    } elsif ('stdout' eq $s->{p_return_mode}) {
        return wantarray ? split(/\n/, $s->{stdout}) : $s->{stdout};
    } elsif ('stderr' eq $s->{p_return_mode}) {
        return wantarray ? map("$_\n",split(/\n/, $s->{stderr})) : $s->{stderr};
    } elsif ('pass' eq $s->{p_return_mode}) {
        return wantarray ? ($s->{pass}, $s->{rval}, $s->{rsig}) : $s->{pass};
    } 
    die "internal error";
}


##################################################
#
##################################################
sub DESTROY
{
    my $s = shift;
    if ( $s->{p_log_fh} ) {
        close $s->{p_log_fh};
    }
    undef $s;
}


##################################################
#
##################################################
package main;
use strict;
use warnings;

my $s = Shell::EX->new;
$s->run(cmd => 'ls');

print @{$s->{ra_stdout}};

__END__

=head1 NAME

Shell::EX - conveniently run shell and backtick commands.

=head1 SYNOPSIS

=head1 DESCRIPTION

=head1 EXAMPLES

=head1 CAVEATS

=head1 AUTHORS

Jim E Quinlan ( jquinlan @ cpan dot org )

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2006 James E. Quinlan  All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
