#!/usr/bin/perl -w

use strict;

my $obj = PSH->new;
$obj->cmdloop;

package PSH;
use base qw(Term::Shell);
use Data::Dumper;
use Cwd;

sub precmd
{
    my $o    = shift;
    my $hnd  = shift;
    my $cmd  = shift;
    my $args = shift;
    @$args = expand(@$args);
}

sub expand
{
    for (@_)
    {
        $_ =~ s[^~][$ENV{HOME}];
        $_ =~ s[\$([_A-Za-z0-9]+)][$ENV{$1} || '']eg;
    }
    @_;
}

sub prompt_str
{
    my $cwd = cwd;
    $cwd =~ s[^\Q$ENV{HOME}\E][~];
    "psh:$cwd> ";
}

sub smry_eval { "how to evaluate Perl code" }

sub help_eval
{
    <<'END';
You can evaluate snippets of Perl code just by putting them on a line
beginning with !:

    psh:~> ! print "$_\n" for keys %ENV

END
}

#=============================================================================
# External commands
#=============================================================================
{
    my $eval_num = "000001";

    sub catch_run
    {
        my ( $o, $command, @args ) = @_;

        # Evaluate perl code if it's a ! line.
        if ( $command =~ s/^!// )
        {
            ( my $code = $o->line ) =~ s/^!//;
            my $really_long_string = <<END;
package PSH::namespace_$eval_num;
{
    no strict;
    eval "no warnings";
    local \$^W = 0;
    $code;
}
END
            {
                local *_;
                my ( $eval_num, $o, $command, @args, $code );
                eval $really_long_string;
            }
            print "$@\n" if $@;
            $eval_num++;
        }

        # Real external commands.
        else
        {
            system( $command, @args );
        }
    }
}

sub catch_comp
{
    my ( $o, $action, $word, $line, $start ) = @_;

    # Complete environment variables (not working)
    if ( $word =~ /^\$/ )
    {
        return $o->completions( $word, [ keys %ENV ] );
    }
    my @files = glob("$word*");
    return $o->completions( $word, \@files );
}

sub comp_
{
    my ( $o, $word, $line, $start ) = @_;
    my @exes;
    use Config;
    for my $part ( split /\Q$Config{path_sep}\E/, $ENV{PATH} )
    {
        next unless -d $part;
        opendir( PART, $part ) or die "can't opendir $part: $!";
        while ( my $entry = readdir(PART) )
        {
            my $file = "$part/$entry";
            push @exes, $entry if -f $file and -x $file;
        }
        closedir(PART) or die "can't closedir $part: $!";
    }
    my @comp = grep { length($_) } $o->possible_actions( $word, 'run', 1 );
    push @comp, $o->completions( $word, \@exes );
    @comp = sort @comp;
    @comp;
}

#=============================================================================
# Shell Builtins
#=============================================================================
sub smry_echo { 'output the args' }

sub help_echo
{
    <<'END';
echo: echo [arg ...]
    Output the args.
END
}

sub run_echo
{
    my ( $o, @args ) = @_;
    my @exp = expand(@args);
    defined $_ or $_ = '' for @exp;
    print "@exp\n" if @exp;
}

sub smry_set { 'set environment variables' }

sub help_set
{
    <<'END';
set: set [ name[=value] ... ]
    set lets you manipulate environment variables. You can view environment
    variables using 'set'. To view specific variables, use 'set name'. To set
    environment variables, use 'set foo=bar'.
END
}

sub run_set
{
    my ( $o, @args ) = @_;
    if (@args)
    {
        for my $arg (@args)
        {
            my ( $key, $val ) = split /=/, $arg;
            if ( defined $val )
            {
                $ENV{$key} = $val;
            }
            else
            {
                $val = $ENV{$key} || '';
                print "$key=$val\n";
            }
        }
    }
    else
    {
        my ( $key, $val );
        while ( ( $key, $val ) = each %ENV )
        {
            print "$key=$val\n";
        }
    }
}

sub smry_cd { 'change working directory' }

sub help_cd
{
    <<'END';
cd: cd [dir]
    Change the current directory to DIR.  The variable $HOME is the default
    DIR.
END
}

sub run_cd
{
    my ( $o, $dir ) = @_;
    $dir = $ENV{HOME} unless defined $dir;
    chdir $dir or do
    {
        print "$0: $dir: $!\n";
        return;
    };
    $ENV{PWD} = $dir;
}

__END__

# Not working yet...

sub smry_alias { 'view or set command aliases' }
sub help_alias {
    <<'END';
alias: [ name[=value] ... ]
    'alias' with no arguments prints the list of aliases in the form
    NAME=VALUE on standard output. An alias is defined for each NAME whose
    VALUE is given.
END
}
sub run_alias {
    my $o = shift;
    if (@_) {
	for my $a (@_) {
	    my ($key, $val) = split /=/, $a;
	    if (defined $val) {
		$o->{SHELL}{alias}{$key} = $val;
	    }
	    else {
		$val = $o->{SHELL}{alias}{$key};
		print "alias $key=$val\n" if defined $val;
		print "alias: `$key' not found\n" if not defined $val;
	    }
	}
    }
    else {
	my ($key, $val);
	while (($key, $val) = each %{$o->{SHELL}{alias}}) {
	    print "alias $key=$val\n";
	}
    }
}
