405 lines
9.4 KiB
Plaintext
405 lines
9.4 KiB
Plaintext
|
#!/usr/bin/perl
|
||
|
package IPL::Shell;
|
||
|
use strict;
|
||
|
|
||
|
BEGIN {
|
||
|
use vars qw{$VERSION $RCSID};
|
||
|
|
||
|
$VERSION = do { my @r = (q$Revision: 1.3 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
|
||
|
$RCSID = q$Id: ipl,v 1.3 2004/11/11 00:51:38 deveiant Exp $;
|
||
|
|
||
|
use base qw{Shell::Base};
|
||
|
|
||
|
use Carp qw{carp croak confess};
|
||
|
use Data::Dumper qw{};
|
||
|
use Term::ANSIColor qw{color};
|
||
|
use Cwd qw{cwd};
|
||
|
use File::Basename qw(basename);
|
||
|
use Net::Domain qw(hostfqdn);
|
||
|
use String::Format qw(stringf);
|
||
|
use Sys::Hostname qw(hostname);
|
||
|
use Devel::Symdump qw{};
|
||
|
|
||
|
$Shell::Base::RE_QUIT = '(?i)^\s*(exit|q(uit)?|logout)';
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#####################################################################
|
||
|
### C L A S S G L O B A L S
|
||
|
#####################################################################
|
||
|
|
||
|
# Default configuration values
|
||
|
our %Defaults = (
|
||
|
prompt_fmt => '%{bold cyan}C%p%{white}C %!> ',
|
||
|
);
|
||
|
|
||
|
|
||
|
|
||
|
#####################################################################
|
||
|
### I N S T A N C E M E T H O D S
|
||
|
#####################################################################
|
||
|
|
||
|
### Shell initialization
|
||
|
sub init {
|
||
|
my ( $self, $args ) = @_;
|
||
|
|
||
|
# Merge defaults
|
||
|
%$args = ( %Defaults, %$args );
|
||
|
|
||
|
# Add instance vars
|
||
|
$self->{result_history} = [];
|
||
|
$self->{package} = 'main';
|
||
|
$self->{prompt_fmt} = $args->{prompt_fmt};
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: init_completions()
|
||
|
### Initialize the internal COMPLETIONS list.
|
||
|
sub init_completions {
|
||
|
my $self = shift;
|
||
|
$self->SUPER::init_completions;
|
||
|
$self->completions( map { ":$_" } $self->completions );
|
||
|
}
|
||
|
|
||
|
|
||
|
### Intro/outro message
|
||
|
sub intro { return qq{IPL: Interactive Perl Shell $VERSION} }
|
||
|
sub outro { "" }
|
||
|
|
||
|
|
||
|
### Prompt callback
|
||
|
sub prompt {
|
||
|
my $self = shift;
|
||
|
my $fmt = $self->{prompt_fmt};
|
||
|
|
||
|
my $prompt = stringf $fmt => {
|
||
|
'!' => $self->term->{history_length},
|
||
|
'$' => $$,
|
||
|
'0' => $self->progname,
|
||
|
'C' => sub { color($_[0]) },
|
||
|
'H' => hostfqdn,
|
||
|
'W' => basename(cwd),
|
||
|
'c' => ref($self),
|
||
|
'g' => scalar getgrgid($(),
|
||
|
'h' => hostname,
|
||
|
'p' => $self->{package},
|
||
|
'u' => scalar getpwuid($<),
|
||
|
'w' => cwd,
|
||
|
};
|
||
|
|
||
|
# Append a reset to clean up if there are color escapes in the prompt
|
||
|
$prompt .= color( 'reset' ) if $prompt =~ m{\e\[};
|
||
|
|
||
|
return "\n$prompt";
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: print( $output )
|
||
|
### Output method.
|
||
|
sub print {
|
||
|
my $self = shift;
|
||
|
my $output = join( "", @_ );
|
||
|
|
||
|
my $nlcount = $output =~ tr/\n/\n/;
|
||
|
|
||
|
if ( $nlcount >= $self->{ROWS} - 3 ) {
|
||
|
my $pager = $self->pager;
|
||
|
open my $P, "|$pager" or carp "Can't open $pager: $!";
|
||
|
CORE::print $P $output;
|
||
|
close $P;
|
||
|
} else {
|
||
|
CORE::print $output;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: parseline( $input )
|
||
|
### Command-parsing method: parse the specified I<input>, run the specified
|
||
|
### command, and output the result.
|
||
|
sub parseline {
|
||
|
my ( $self, $input ) = @_;
|
||
|
|
||
|
if ( $input =~ m{^:(\S+.*)} ) {
|
||
|
return $self->SUPER::parseline( $1 );
|
||
|
}
|
||
|
|
||
|
else {
|
||
|
return ( "eval", {}, $input );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: prettyPrint( $value )
|
||
|
### Return the given I<value> as a pretty-printed string.
|
||
|
sub prettyPrint {
|
||
|
my ( $self, $val ) = @_;
|
||
|
|
||
|
local $Data::Dumper::Terse = 1;
|
||
|
local $Data::Dumper::Indent = 1;
|
||
|
local $Data::Dumper::Pad = " ";
|
||
|
|
||
|
my $rval = Data::Dumper->Dumpxs( [$val] );
|
||
|
$rval =~ s{^\s+}{};
|
||
|
$rval = " => $rval ";
|
||
|
|
||
|
return $rval;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#####################################################################
|
||
|
### C O M M A N D M E T H O D S
|
||
|
#####################################################################
|
||
|
|
||
|
### METHOD: do_shellobj( undef )
|
||
|
### Implementation of the 'shellobj' command
|
||
|
sub do_shellobj {
|
||
|
my $self = shift;
|
||
|
return Data::Dumper->Dumpxs( [$self], [qw{self}] );
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: help_shellobj( undef )
|
||
|
### Callback for help on the 'shellobj' command.
|
||
|
sub help_shellobj {
|
||
|
return q{Show a dump of the IPL shell object itself.};
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: do_eval( @args )
|
||
|
### Implementation of the 'eval' command.
|
||
|
sub do_eval {
|
||
|
my ( $self, @args ) = @_;
|
||
|
|
||
|
my (
|
||
|
$input,
|
||
|
$res,
|
||
|
$rval,
|
||
|
);
|
||
|
|
||
|
$input = join( ' ', @args );
|
||
|
#$self->print( "[Evaluating '$input']\n" );
|
||
|
$res = eval qq{
|
||
|
package $self->{package};
|
||
|
no strict;
|
||
|
$input ;
|
||
|
};
|
||
|
|
||
|
# Show error message for errors
|
||
|
if ( $@ ) {
|
||
|
( $rval = $@ ) =~ s{at \S+ line \d+\..*}{};
|
||
|
}
|
||
|
|
||
|
# Dump everything else
|
||
|
else {
|
||
|
$rval = $self->prettyPrint( $res );
|
||
|
}
|
||
|
|
||
|
return $rval;
|
||
|
}
|
||
|
|
||
|
### METHOD: help_eval( undef )
|
||
|
### Provide help text for the 'eval' command.
|
||
|
sub help_eval {
|
||
|
return q{Evaluate the input as Perl and display the result.};
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: do_rl( $subcmd, @args )
|
||
|
### Implementation of the 'readline' command.
|
||
|
sub do_rl {
|
||
|
my ( $self, $subcmd, @args ) = @_;
|
||
|
|
||
|
my $rval = '';
|
||
|
|
||
|
if ( $subcmd =~ m{features} ) {
|
||
|
$rval = $self->prettyPrint( $self->term->Features );
|
||
|
}
|
||
|
|
||
|
elsif ( $subcmd =~ m{module} ) {
|
||
|
$rval = "Readline features provided by: " .
|
||
|
$self->term->ReadLine;
|
||
|
}
|
||
|
|
||
|
elsif ( $subcmd =~ m{completions} ) {
|
||
|
$rval = "Tab-completions: \n" .
|
||
|
$self->prettyPrint( [sort $self->completions] );
|
||
|
}
|
||
|
|
||
|
else {
|
||
|
$rval = $self->prettyPrint( $self->term );
|
||
|
}
|
||
|
|
||
|
return $rval;
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: help_rl( undef )
|
||
|
### Return help text for the 'rl' (readline) command.
|
||
|
sub help_rl {q{
|
||
|
rl [<subcommand>]
|
||
|
|
||
|
Provide information about the readline implementation used by the shell. The
|
||
|
<subcommand> can be one of:
|
||
|
|
||
|
features Dump the hash of features understood by the implementation.
|
||
|
module Show the name of the actual module that is providing readline
|
||
|
features.
|
||
|
completions Dump the list of tab-completions.
|
||
|
|
||
|
}}
|
||
|
|
||
|
|
||
|
### METHOD: do_perldoc( @args )
|
||
|
### Implementation of the 'perldoc' command.
|
||
|
sub do_perldoc {
|
||
|
my ( $self, @args ) = @_;
|
||
|
|
||
|
if ( @args ) {
|
||
|
system 'perldoc', @args;
|
||
|
return undef;
|
||
|
} else {
|
||
|
return "'perldoc' requires at least one argument.";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: help_perldoc()
|
||
|
### Help method for the 'perldoc' command.
|
||
|
sub help_perldoc {q{
|
||
|
Run the 'perldoc' program.
|
||
|
}}
|
||
|
|
||
|
|
||
|
### METHOD: do_package( $newpkg )
|
||
|
### Change the package future evals will take place in.
|
||
|
sub do_package {
|
||
|
my ( $self, $package ) = @_;
|
||
|
|
||
|
if ( $package ) {
|
||
|
return "Invalid package name '$package'"
|
||
|
unless $package =~ m{^[a-z]\w+(?:::[a-z]\w+)*$}i;
|
||
|
$self->{package} = $package;
|
||
|
return "Set eval package to: '$package'.";
|
||
|
} else {
|
||
|
return "Current package is: '$self->{package}'.";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
### METHOD: help_package()
|
||
|
### Help method for the 'package' command.
|
||
|
sub help_package {q{
|
||
|
package [<newpackage>]
|
||
|
|
||
|
Set the package future evaluations will occur in to 'newpackage' if the optional
|
||
|
argument is given, else just print the name of the current package.
|
||
|
}}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
### METHOD: do_reload()
|
||
|
### Implementation of the 'reload' command.
|
||
|
sub do_reload {
|
||
|
my $self = shift;
|
||
|
|
||
|
if ( my $h = $self->histfile ) {
|
||
|
$self->term->WriteHistory( $h );
|
||
|
}
|
||
|
|
||
|
$self->print( "Re-execing shell...\n" );
|
||
|
local $ENV{IPL_RELOAD} = 1;
|
||
|
exec $0;
|
||
|
return "Hmmm... something funky happened.";
|
||
|
}
|
||
|
|
||
|
sub help_reload {q{
|
||
|
reload
|
||
|
|
||
|
Reload the shell.
|
||
|
}}
|
||
|
|
||
|
|
||
|
### METHOD: do_inspect( [$package] )
|
||
|
### Implementation of the 'inspect' command.
|
||
|
sub do_inspect {
|
||
|
my $self = shift;
|
||
|
my $package = shift || $self->{package};
|
||
|
|
||
|
my $inspector = new Devel::Symdump $package;
|
||
|
|
||
|
no strict;
|
||
|
my @out = ( "Symbol table for $package" );
|
||
|
my ( %scalars, %arrays, %hashes, %functions, %packages, %filehandles );
|
||
|
|
||
|
# Grab entries for each type from the symbol table for the inspected
|
||
|
# package.
|
||
|
%scalars = map { $_ => ${*{$_}{SCALAR}} } $inspector->scalars;
|
||
|
%arrays = map { $_ => *{$_}{ARRAY} } $inspector->arrays;
|
||
|
%hashes = map { $_ => *{$_}{HASH} } $inspector->hashes;
|
||
|
%functions = map { $_ => *{$_}{CODE} } $inspector->functions;
|
||
|
%packages = map { $_ => undef } $inspector->packages;
|
||
|
%filehandles = map { $_ => *{$_}{IO} } $inspector->filehandles;
|
||
|
|
||
|
# Build displays for each entry in the symbol table, sorted by type,
|
||
|
# prepending the sigil for each one. In part stolen from Matthew Simon
|
||
|
# Cavalletto's Term::ShellKit::Dev
|
||
|
foreach my $output (
|
||
|
[ 'Scalars', \%scalars, '$', ],
|
||
|
[ 'Arrays', \%arrays, '@'],
|
||
|
[ 'Hashes', \%hashes, '%'],
|
||
|
[ 'Subs', \%functions, 'sub '],
|
||
|
[ 'Packages', \%packages, '::'],
|
||
|
[ 'Filehandles', \%filehandles, '*'],
|
||
|
) {
|
||
|
next unless scalar keys %{$output->[1]};
|
||
|
push @out, "$output->[0]:", '-' x length($output->[0]);
|
||
|
foreach ( sort keys %{$output->[1]} ) {
|
||
|
if ( defined $output->[1]{$_} ) {
|
||
|
push @out, qq{ $output->[2]$_ = "$output->[1]{$_}"};
|
||
|
} else {
|
||
|
push @out, qq{ $output->[2]$_};
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return join "\n", @out;
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
sub help_inspect {q{
|
||
|
inspect [<package>]
|
||
|
|
||
|
Inspect the contents of the specified package, or the current package if no
|
||
|
package is given.
|
||
|
}}
|
||
|
|
||
|
|
||
|
|
||
|
#####################################################################
|
||
|
### R U N T I M E P A C K A G E
|
||
|
#####################################################################
|
||
|
package ipl;
|
||
|
use strict;
|
||
|
|
||
|
BEGIN {
|
||
|
use Getopt::Long qw{GetOptions};
|
||
|
$GetOpt::Long::Bundling = 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( $0 eq __FILE__ ) {
|
||
|
my %opts = (
|
||
|
HISTFILE => glob("~/.ipl_history"),
|
||
|
RCFILES => [glob("~/.iplrc"), ".iplrc"],
|
||
|
);
|
||
|
|
||
|
IPL::Shell->new( %opts )->run;
|
||
|
}
|
||
|
|
||
|
|