use v5.16;
package PerlShell {
use Moo;
use constant { RO => 'ro', RW => 'rw', LAZY => 'lazy', RWP => 'rwp' };
use Types::Standard -types;
use Types::Path::Tiny -types;
use Term::ANSIColor ();
use Term::ReadLine::Tiny ();
use List::Util qw(uniq);
use namespace::autoclean;
has cwd => (
is => RW,
isa => Dir,
lazy => 1,
builder => sub { Path->class->cwd },
);
has term => (
is => LAZY,
isa => InstanceOf['Term::ReadLine::Tiny'],
builder => sub {
my $self = shift;
my $term = 'Term::ReadLine::Tiny'->new;
# $term->autocomplete(sub { $self->handle_autocomplete(@_) });
$term;
},
);
has util_namespace => (
is => LAZY,
isa => Str, # package name really
builder => sub { 'PerlShell::Functions' },
);
sub prompt {
my $self = shift;
Term::ANSIColor::colored(['bold white'], '> ');
}
sub prepare_environment {
my $self = shift;
my ($ns) = @_;
no strict 'refs';
my @our;
for my $var (keys %ENV) {
${"$ns\::$var"} = $ENV{$var};
push @our, "\$$var";
}
${"$ns\::SHELL"} = $self; push @our, "\$SHELL";
${"$ns\::TERM"} = $self->term; push @our, "\$TERM";
${"$ns\::CWD"} = $self->cwd; push @our, "\$CWD";
sprintf('our (%s);', join q[,], uniq @our);
}
sub format_for_output {
my $self = shift;
"$_[0]";
}
sub run {
my $self = shift;
my $term = $self->term;
my $package = $self->util_namespace;
while (defined(my $line = $term->readline($self->prompt))) {
local $@;
my $prefix = $self->prepare_environment($package);
my @output = eval qq{ package $package; no strict; no warnings; $prefix; $line };
if ($@) {
say {$term->OUT} Term::ANSIColor::colored(['bold red'], $@);
}
else {
say {$term->OUT} $self->format_for_output($_) for @output;
}
}
say "";
say {$term->OUT} Term::ANSIColor::colored(['bold green'], 'Bye!');
}
}
package PerlShell::Util {
use Ref::Util ();
sub croak {
my $fmt = shift;
die sprintf("$fmt\n", @_);
}
sub parse_arguments {
my %options;
my @arguments;
my $seendashdash = 0;
if (Ref::Util::is_plain_hashref($_[0])) {
%options = %{ +shift };
}
while (@_) {
if (not Ref::Util::is_ref($_[0]) and not $seendashdash and $_[0] =~ /\A-([\w-]+)\z/) {
my $str = $1;
shift;
if ($str eq '-') {
$seendashdash = 1;
}
else {
$options{$str} = 1;
}
next;
}
push @arguments, shift;
}
return (\%options, \@arguments);
}
}
package PerlShell::Functions {
sub args {
require Data::Dumper;
Data::Dumper::Dumper(PerlShell::Util::parse_arguments(@_));
}
sub ls {
my ($options, $args) = PerlShell::Util::parse_arguments(@_);
my @dirs = map Path::Tiny::->new($_), @$args;
@dirs = our $CWD unless @dirs;
map { $_->children } @dirs;
}
sub cd {
my ($options, $args) = PerlShell::Util::parse_arguments(@_);
PerlShell::Util::croak('cd expects 1 argument, not %d', scalar(@$args))
unless @$args == 1;
my $new;
our ($CWD, $SHELL);
if ($args->[0] eq '..') {
$new = $CWD->parent;
}
elsif ($args->[0] eq '.') {
$new = $CWD;
}
else {
$new = Path::Tiny::->new($args->[0]);
if ($new->is_relative) {
$new = $CWD->child($new);
}
}
$SHELL->cwd( $CWD = $new );
}
}
PerlShell->new->run;
####
> cd('bin')
> ls()
> cd('..')
####
> cd bin
> ls
> cd '..'