Shell-Perl-0.0023/0000755000175000017500000000000012274030026011717 5ustar ronronShell-Perl-0.0023/Changelog.ini0000644000175000017500000001055512274030023014312 0ustar ronron[Module] Name=Shell::Perl Changelog.Creator=Module::Metadata::Changes V 2.05 Changelog.Parser=Config::IniFiles V 2.82 [V 0.0022] Date=2011-03-12T12:00:00 Comments= < 'Shell::Perl', VERSION_FROM => 'lib/Shell/Perl.pm', EXE_FILES => [ 'bin/pirl' ], PREREQ_PM => { 'Class::Accessor' => 0, 'File::Basename' => 0, 'File::HomeDir' => 0, 'File::Slurp' => 0, 'Getopt::Long' => 0, 'IPC::Cmd' => 0, 'Path::Class' => 0, 'Test::Deep' => 0, 'Test::More' => 0, 'Term::ReadLine' => 0, }, ($] >= 5.005 ? ( ABSTRACT_FROM => 'lib/Shell/Perl.pm', AUTHOR => 'A. R. Ferreira ', ) : ()), ($EUMM_VERSION >= 6.31 ? ( LICENSE => 'perl', ) : ()), ($EUMM_VERSION > 6.4501 ? ( META_MERGE => { recommends => { # optional tests 'Test::Pod' => 0, 'Test::Pod::Coverage' => 0, 'Test::Script' => 0, 'Test::Expect' => 0, }, resources => { repository => 'http://github.com/aferreira/pirl', }, }, ) : ()), ); # recommended: # # - -one_of: [ YAML::Syck, YAML ] # - Data::Dump # - Data::Dumper # - Data::Dump::Streamer # Shell-Perl-0.0023/README0000644000175000017500000000151512273306065012611 0ustar ronronShell-Perl version 0.0013 =========================== This is the implementation of a simple command-line interpreter for Perl. After installing the module, you invoke it like this $ pirl Welcome to the Perl shell. Type ':help' for more information pirl @> or in Windows > pirl INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES This module requires these other modules and libraries: Term:ReadLine Data::Dumper YAML or YAML::Syck Data::Dump Class::Accessor and recommends Test::Pod 1.18 Test::Pod::Coverage 1.04 Test::Script COPYRIGHT AND LICENCE Copyright (C) 2007 by Adriano R. Ferreira This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Shell-Perl-0.0023/.gitignore0000644000175000017500000000007212273306065013716 0ustar ronron.gitconfig .*.swp Makefile blib pm_to_blib Shell-Perl-*/ Shell-Perl-0.0023/META.yml0000644000175000017500000000144012274030026013167 0ustar ronron--- abstract: 'A read-eval-print loop in Perl' author: - 'A. R. Ferreira ' build_requires: ExtUtils::MakeMaker: 0 configure_requires: ExtUtils::MakeMaker: 0 dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 6.86, CPAN::Meta::Converter version 2.133380' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Shell-Perl no_index: directory: - t - inc recommends: Test::Expect: 0 Test::Pod: 0 Test::Pod::Coverage: 0 Test::Script: 0 requires: Class::Accessor: 0 File::Basename: 0 File::HomeDir: 0 File::Slurp: 0 Getopt::Long: 0 IPC::Cmd: 0 Path::Class: 0 Term::ReadLine: 0 Test::Deep: 0 Test::More: 0 resources: repository: http://github.com/aferreira/pirl version: 0.0023 Shell-Perl-0.0023/MANIFEST0000644000175000017500000000057312274030026013055 0ustar ronron.gitignore bin/pirl Changelog.ini Changes lib/Shell/Perl.pm lib/Shell/Perl/Dumper.pm Makefile.PL MANIFEST This list of files META.yml Module meta-data (added by MakeMaker) README t/01use.t t/02basic.t t/10compile.t t/11version.t t/20expect_quit.t t/50isolated.t t/90pod.t t/98pod-coverage.t META.json Module JSON meta-data (added by MakeMaker) Shell-Perl-0.0023/bin/0000755000175000017500000000000012274030026012467 5ustar ronronShell-Perl-0.0023/bin/pirl0000755000175000017500000000306012273306065013372 0ustar ronron#!/usr/bin/perl use strict; use warnings; # /Id: pirl 1124 2007-01-25 19:36:07Z me / # don't erase that for now # $Id: /iperl/bin/pirl 2321 2008-03-09T16:47:36.686169Z a.r.ferreira@gmail.com $ our $VERSION = '0.0023'; use Shell::Perl (); Shell::Perl->run_with_args; __END__ =pod =encoding utf-8 =head1 NAME pirl - A read-eval-print loop in Perl (see Shell::Perl) =head1 SYNOPSIS pirl pirl --noornaments pirl --version pirl -v =head1 EXAMPLE SESSION $ pirl Welcome to the Perl shell. Type ':help' for more information pirl @> 1+1 2 pirl @> use YAML qw(Load Dump); () pirl @> $data = Load("--- { a: 1, b: [ 1, 2, 3] }\n"); { a => 1, b => [1, 2, 3] } pirl @> $var = 'a 1 2 3'; $var =~ /(\w+) (\d+) (\d+)/ ("a", 1, 2) pirl @> :q =head1 DESCRIPTION This script is the command-line interface to C which does it all. By now, read the fine details at C documentation. =head1 OPTIONS --ornaments - turn on terminal ornaments (default) --noornaments - turn off terminal organments --version, -v - prints version info and exits with 0 =head1 SEE ALSO Shell::Perl =head1 BUGS Please report bugs via CPAN RT L. =head1 AUTHOR Adriano R. Ferreira, Eferreira@cpan.orgE Caio Marcelo, Ecmarcelo@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007–2011 by Adriano R. Ferreira This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut Shell-Perl-0.0023/t/0000755000175000017500000000000012274030026012162 5ustar ronronShell-Perl-0.0023/t/01use.t0000755000175000017500000000020212273306065013311 0ustar ronron#!perl -T use Test::More tests => 1; use_ok('Shell::Perl'); diag( "Testing Shell::Perl $Shell::Perl::VERSION, Perl $], $^X" ); Shell-Perl-0.0023/t/10compile.t0000644000175000017500000000025312273306735014154 0ustar ronron use Test::More; eval "use Test::Script"; plan skip_all => "Test::Script required for testing" if $@; plan( tests => 1 ); script_compiles( 'bin/pirl', 'pirl compiles'); Shell-Perl-0.0023/t/20expect_quit.t0000644000175000017500000000105712273306065015056 0ustar ronron use Test::More; eval "use Test::Expect"; plan skip_all => "Test::Expect required for testing" if $@; # test pirl and its many quit commands plan( tests => 2*6 ); for my $quit_command ( ':quit', ':q', ':exit', ':x', 'exit', 'quit' ) { expect_run( command => "$^X -Mblib blib/script/pirl --noornaments", prompt => 'pirl @> ', quit => $quit_command, ); expect_like( qr/\A (?: Using .*? blib \n )? # cope with noisy 5.6 blib Welcome /msx, "welcome message" ); } Shell-Perl-0.0023/t/50isolated.t0000644000175000017500000000106312273306065014330 0ustar ronron#perl -T use Test::More 'no_plan'; BEGIN { use_ok('Shell::Perl'); } my $sh = Shell::Perl->new; $_ = ' $_ = 1000 '; my $val = $sh->eval($_); is($val, 1000); $_ = ' $_ '; my $val2 = $sh->eval($_); TODO: { local $TODO = 'needs separating the REPL and the interpreter states'; is($val2, 1000); } # this test script touches at a very sensitive # issue in the implementation of a REPL - # the state of the loop must be kept separate # from the state of the running interpreter. # By state, we mean those global variables like # $_ and everything else. Shell-Perl-0.0023/t/90pod.t0000755000175000017500000000025312273306065013315 0ustar ronron#!perl -T use strict; use Test::More; eval "use Test::Pod 1.18"; plan skip_all => "Test::Pod 1.18 required for testing POD" if $@; all_pod_files_ok(all_pod_files(".")); Shell-Perl-0.0023/t/11version.t0000644000175000017500000000116512273306065014211 0ustar ronron use Test::More tests => 8; my @pirl = ( $^X, '-Mblib', 'blib/script/pirl' ); use IPC::Cmd qw( run ); use Test::Deep; for my $switch ( '-v', '--version' ) { my ( $ok, $err, $full_buf, $out_buf, $err_buf ) = run( command => [ @pirl, $switch ] ); ok( $ok, "'pirl $switch' run ok" ); ok( !$err, 'no error'); cmp_deeply( $out_buf, [ re(qr/\AThis is pirl/) ], 'printed version info' ); my $NO_STDERR_OUTPUT = ($] < 5.008) ? [re(qr/Using .* lib \n/msx)] # cope with noisy 5.6 blib : []; cmp_deeply( $err_buf, $NO_STDERR_OUTPUT, 'no output to STDERR' ) or diag("err_buf= (@$err_buf)"); } Shell-Perl-0.0023/t/98pod-coverage.t0000644000175000017500000000025512273306065015115 0ustar ronron#perl -T use Test::More; eval "use Test::Pod::Coverage 1.04"; plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; all_pod_coverage_ok(); Shell-Perl-0.0023/t/02basic.t0000755000175000017500000000025012273306065013602 0ustar ronron#!perl -T use Test::More tests => 3; BEGIN { use_ok('Shell::Perl'); } my $sh = Shell::Perl->new(); ok($sh, 'defined return of new()'); isa_ok($sh, 'Shell::Perl'); Shell-Perl-0.0023/META.json0000644000175000017500000000267712274030026013354 0ustar ronron{ "abstract" : "A read-eval-print loop in Perl", "author" : [ "A. R. Ferreira " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 6.86, CPAN::Meta::Converter version 2.133380", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Shell-Perl", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "recommends" : { "Test::Expect" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Script" : "0" }, "requires" : { "Class::Accessor" : "0", "File::Basename" : "0", "File::HomeDir" : "0", "File::Slurp" : "0", "Getopt::Long" : "0", "IPC::Cmd" : "0", "Path::Class" : "0", "Term::ReadLine" : "0", "Test::Deep" : "0", "Test::More" : "0" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "http://github.com/aferreira/pirl" } }, "version" : "0.0023" } Shell-Perl-0.0023/lib/0000755000175000017500000000000012274030026012465 5ustar ronronShell-Perl-0.0023/lib/Shell/0000755000175000017500000000000012274030026013534 5ustar ronronShell-Perl-0.0023/lib/Shell/Perl/0000755000175000017500000000000012274030026014436 5ustar ronronShell-Perl-0.0023/lib/Shell/Perl/Dumper.pm0000644000175000017500000002125312273306065016243 0ustar ronronpackage Shell::Perl::Dumper; use strict; use warnings; # $Id$ our $VERSION = '0.0023'; use base qw(Class::Accessor); # to get a new() for free package Shell::Perl::Dumper::Plain; our @ISA = qw(Shell::Perl::Dumper); # to get a new() for free sub is_available { return 1; # always available - no dependency but Perl } sub dump_scalar { shift; return "$_[0]" . "\n"; } sub dump_list { shift; local $" = "\t"; return "@_" . "\n"; } package Shell::Perl::Data::Dump; our @ISA = qw(Shell::Perl::Dumper); # to get a new() for free # XXX make a Data::Dump object an instance variable sub _dump_code_filter { my ($ctx, $object_ref) = @_; return undef unless $ctx->is_code; require B::Deparse; my $code = 'sub ' . (B::Deparse->new)->coderef2text($object_ref); return { dump => $code }; } sub is_available { return eval { require Data::Dump::Filtered; 1 }; } sub dump_scalar { shift; require Data::Dump::Filtered; return Data::Dump::Filtered::dump_filtered(shift, \&_dump_code_filter) . "\n"; } sub dump_list { shift; require Data::Dump::Filtered; return Data::Dump::Filtered::dump_filtered(@_, \&_dump_code_filter) . "\n"; } package Shell::Perl::Data::Dumper; our @ISA = qw(Shell::Perl::Dumper); # XXX make a Data::Dumper object an instance variable # but OO Data::Dumper is very annoying sub is_available { return eval { require Data::Dumper; 1 }; } sub dump_scalar { shift; require Data::Dumper; local $Data::Dumper::Deparse = 1; return Data::Dumper->Dump([shift], [qw($var)]); } sub dump_list { #goto &dump_scalar if @_==2; # fallback to dump_scalar if only one shift; require Data::Dumper; local $Data::Dumper::Deparse = 1; return Data::Dumper->Dump([[@_]], [qw(*var)]); } package Shell::Perl::Dumper::YAML; our @ISA = qw(Shell::Perl::Dumper); sub _require_one_of { my @modules = @_; for (@modules) { my $ret = eval "require $_; 1"; warn "pirl: $_ loaded ok\n" if $ret; # XXX return $_ if $ret; } return undef } our $YAML_PACKAGE; sub is_available { #return eval { require YAML; 1 }; $YAML_PACKAGE = _require_one_of(qw(YAML::Syck YAML)); if ($YAML_PACKAGE) { $YAML_PACKAGE->import(qw(Dump)); do { no strict 'refs'; ${ $YAML_PACKAGE . '::DumpCode' } = 1 }; return 1 } else { return undef; } } sub dump_scalar { shift; #require YAML; # done by &is_available return Dump(shift); } sub dump_list { # XXX shift; #require YAML; # done by &is_available return Dump(@_); } package Shell::Perl::Data::Dump::Streamer; our @ISA = qw(Shell::Perl::Dumper); sub is_available { return eval { require Data::Dump::Streamer; 1 }; } sub dump_scalar { shift; require Data::Dump::Streamer; return Data::Dump::Streamer::Dump(shift)->Names('$var')->Out; } sub dump_list { #goto &dump_scalar if @_==2; # fallback to dump_scalar if only one shift; require Data::Dump::Streamer; return Data::Dump::Streamer::Dump([@_])->Names('*var')->Out; } 1; # svn:keywords Id # svn:eol-style LF __END__ =pod =encoding utf-8 =head1 NAME Shell::Perl::Dumper - Dumpers for Shell::Perl =head1 SYNOPSYS use Shell::Perl::Dumper; $dumper = Shell::Perl::Dumper::Plain->new; print $dumper->dump_scalar($scalar); print $dumper->dump_list(@list); =head1 DESCRIPTION In C, the result of the evaluation is transformed into a string to be printed. As this result may be a pretty complex data structure, the shell provides a hook for you to pretty-print these answers just the way you want. By default, C will try to convert the results via C. That means the output will be Perl code that may be run to get the data structure again. Alternatively, the shell may use C or C with almost the same result with respect to the representation as Perl code. (But the output of the modules differ enough for sufficiently complex data.) Other options are to set the output to produce YAML or a plain simple-minded solution which basically turns the result to string via simple interpolation. All of these are implemented via I. Dumpers are meant to be used like that: $dumper = Some::Dumper::Class->new; # build a dumper $s = $dumper->dump_scalar($scalar); # from scalar to string $s = $dumper->dump_list(@list); # from list to string =head2 METHODS The following methods compose the expected API of a dumper, as used by L. =over 4 =item B $dumper = $class->new(@args); Constructs a dumper. =item B $s = $dumper->dump_scalar($scalar); Turns a scalar into a string representation. =item B $s = $dumper->dump_list(@list); Turns a list into a string representation. =item B $ok = $class->is_available This is an I class method. If it exists, it means that the class has external dependencies (like C depends on C) and whether these may be loaded when needed. If they can, this method returns true. Otherwise, returning false means that a dumper instance of this class probably cannot work. This is typically because the dependency is not installed or cannot be loaded due to an installation problem. This is the algorithm used by L XXX XXX XXX 1. =back =head1 THE STANDARD DUMPERS L provides four standard dumpers: * Shell::Perl::Data::Dump * Shell::Perl::Data::Dumper * Shell::Perl::Data::Dump::Streamer * Shell::Perl::Dumper::YAML * Shell::Perl::Dumper::Plain which corresponds to the four options of the command C< :set out >: "D", "DD", "DDS", "Y", and "P" respectively. =head2 Data::Dump The package C implements a dumper which uses L to turn Perl variables into a string representation. It is used like this: use Shell::Perl::Dumper; if (!Shell::Perl::Data::Dump->is_available) { die "the dumper cannot be loaded correctly" } $dumper = Shell::Perl::Data::Dump->new; print $dumper->dump_scalar($scalar); print $dumper->dump_list(@list); Examples of its output: pirl > :set out D pirl > { a => 3 } #scalar { a => 3 } pirl > (1, 2, "a") #list (1, 2, "a") =head2 Data::Dumper The package C implements a dumper which uses L to turn Perl variables into a string representation. It is used like this: use Shell::Perl::Dumper; if (!Shell::Perl::Data::Dumper->is_available) { die "the dumper cannot be loaded correctly" } $dumper = Shell::Perl::Data::Dumper->new; print $dumper->dump_scalar($scalar); print $dumper->dump_list(@list); Examples of its output: pirl > :set out DD pirl > { a => 3 } #scalar @var = ( { 'a' => 3 } ); pirl > (1, 2, "a") #list @var = ( 1, 2, 'a' ); =head2 YAML The package C implements a dumper which uses L or L to turn Perl variables into a string representation. It is used like this: use Shell::Perl::Dumper; if (!Shell::Perl::Dumper::YAML->is_available) { die "the dumper cannot be loaded correctly" } $dumper = Shell::Perl::Dumper::YAML->new; print $dumper->dump_scalar($scalar); print $dumper->dump_list(@list); Examples of its output: pirl > :set out Y pirl @> { a => 3 } #scalar --- a: 3 pirl @> (1, 2, "a") #list --- 1 --- 2 --- a When loading, C is preferred to C. If it is not avaiable, the C module is the second option. =head2 Data::Dump::Streamer The documentation is yet to be written. =head2 Plain Dumper The package C implements a dumper which uses string interpolation to turn Perl variables into strings. It is used like this: use Shell::Perl::Dumper; $dumper = Shell::Perl::Dumper::Plain->new; print $dumper->dump_scalar($scalar); print $dumper->dump_list(@list); Examples of its output: pirl > :set out P pirl > { a => 3 } #scalar HASH(0x1094d2c0) pirl > (1, 2, "a") #list 1 2 a =head1 SEE ALSO Shell::Perl =head1 BUGS Please report bugs via CPAN RT L or L. =head1 AUTHORS Adriano R. Ferreira, Eferreira@cpan.orgE Caio Marcelo, Ecmarcelo@gmail.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007–2011 by Adriano R. Ferreira This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut Shell-Perl-0.0023/lib/Shell/Perl.pm0000644000175000017500000004470012273306065015011 0ustar ronronpackage Shell::Perl; use strict; use warnings; # /Id: Perl.pm 1131 2007-01-27 17:43:35Z me / # don't erase that for now # $Id: /iperl/lib/Shell/Perl.pm 2317 2008-03-09T16:22:00.577930Z a.r.ferreira@gmail.com $ our $VERSION = '0.0023'; use base qw(Class::Accessor); # soon use base qw(Shell::Base); Shell::Perl->mk_accessors(qw( out_type dumper context package term ornaments )); # XXX use_strict use Term::ReadLine; use Shell::Perl::Dumper; # out_type defaults to one of 'D', 'DD', 'Y', 'P'; # dumper XXX # context defaults to 'list' # package defaults to __PACKAGE__ . '::sandbox' # XXX use_strict defaults to 0 sub new { my $self = shift; my $sh = $self->SUPER::new({ context => 'list', # print context @_ }); $sh->_init; return $sh; } my %dumper_for = ( 'D' => 'Shell::Perl::Data::Dump', 'DD' => 'Shell::Perl::Data::Dumper', 'Y' => 'Shell::Perl::Dumper::YAML', 'Data::Dump' => 'Shell::Perl::Data::Dump', 'Data::Dumper' => 'Shell::Perl::Data::Dumper', 'YAML' => 'Shell::Perl::Dumper::YAML', 'DDS' => 'Shell::Perl::Data::Dump::Streamer', 'P' => 'Shell::Perl::Dumper::Plain', 'plain' => 'Shell::Perl::Dumper::Plain', ); sub _init { my $self = shift; # loop until you find one available alternative for dump format my $dumper_class; for my $format ( qw(D DD DDS Y P) ) { if ($dumper_for{$format}->is_available) { #$self->print("format: $format\n"); $self->set_out($format); last } # XXX this is not working 100% - and I have no clue about it } $self->set_package( __PACKAGE__ . '::sandbox' ); } sub _shell_name { require File::Basename; return File::Basename::basename($0); } sub print { my $self = shift; print {$self->term->OUT} @_; } ## # XXX remove: code and docs ## sub out { ## my $self = shift; ## ## # XXX I want to improve this: preferably with an easy way to add dumpers ## if ($self->context eq 'scalar') { ## $self->print($self->dumper->dump_scalar(shift), "\n"); ## } else { # list ## $self->print($self->dumper->dump_list(@_), "\n"); ## } ## } # XXX I want to improve this: preferably with an easy way to add dumpers =begin private =item B<_print_scalar> $sh->_print_scalar($answer); That corresponds to the 'print' in the read-eval-print loop (in scalar context). It outputs the evaluation result after passing it through the current dumper. =end private =cut sub _print_scalar { # XXX make public, document my $self = shift; $self->print($self->dumper->dump_scalar(shift)); } =begin private =item B<_print_scalar> $sh->_print_list(@answers); That corresponds to the 'print' in the read-eval-print loop (in list context). It outputs the evaluation result after passing it through the current dumper. =end private =cut sub _print_list { # XXX make public, document my $self = shift; $self->print($self->dumper->dump_list(@_)); } sub _warn { shift; my $shell_name = _shell_name; warn "$shell_name: ", @_, "\n"; } sub set_out { my $self = shift; my $type = shift; my $dumper_class = $dumper_for{$type}; if (!defined $dumper_class) { $self->_warn("unknown dumper $type"); return; } if ($dumper_class->is_available) { $self->dumper($dumper_class->new); $self->out_type($type); } else { $self->_warn("can't load dumper $dumper_class"); } } sub _ctx { my $context = shift; if ($context =~ /^(s|scalar|\$)$/i) { return 'scalar'; } elsif ($context =~ /^(l|list|@)$/i) { return 'list'; } elsif ($context =~ /^(v|void|_)$/i) { return 'void'; } else { return undef; } } sub set_ctx { my $self = shift; my $context = _ctx shift; if ($context) { $self->context($context); } else { $self->_warn("unknown context $context"); } } sub set_package { my $self = shift; my $package = shift; if ($package =~ /( [a-zA-Z_] \w* :: )* [a-zA-Z_] \w* /x) { $self->package($package); no strict 'refs'; *{ "${package}::quit" } = sub { $self->quit }; } else { $self->_warn("bad package name $package"); } } use constant HELP => <<'HELP'; Shell commands: (begin with ':') :e(x)it or :q(uit) - leave the shell :set out (D|DD|DDS|Y|P) - setup the output format :set ctx (scalar|list|void|s|l|v|$|@|_) - setup the eval context :set package - set package in which shell eval statements :reset - reset the environment :dump history - (experimental) print the history to STDOUT or a file :h(elp) - get this help screen HELP sub help { print HELP; } # :reset is a nice idea - but I wanted more like CPAN reload # I retreated the current implementation of :reset # because %main:: is used as the evaluation package # and %main:: = () is too severe by now sub reset { my $self = shift; my $package = $self->package; return if $package eq 'main'; # XXX don't reset %main:: no strict 'refs'; %{"${package}::"} = (); #%main:: = (); # this segfaults at my machine } sub prompt_title { my $self = shift; my $shell_name = _shell_name; my $sigil = { scalar => '$', list => '@', void => '' }->{$self->{context}}; return "$shell_name $sigil> "; } sub _readline { my $self = shift; return $self->term->readline($self->prompt_title); } sub _history_file { # XXX require Path::Class; require File::HomeDir; return Path::Class::file( File::HomeDir->my_home, '.pirl-history' )->stringify; } sub _read_history { # XXX belongs to Shell::Perl::ReadLine my $term = shift; my $h = _history_file; #warn "read history from $h\n"; # XXX if ( $term->Features->{readHistory} ) { $term->ReadHistory( $h ); } elsif ( $term->Features->{setHistory} ) { if ( -e $h ) { require File::Slurp; my @h = File::Slurp::read_file( $h ); chomp @h; $term->SetHistory( @h ); } } else { # warn "Your ReadLine doesn't support setHistory\n"; } } sub _write_history { # XXX belongs to Shell::Perl::ReadLine my $term = shift; my $h = _history_file; #warn "write history to $h\n"; # XXX if ( $term->Features->{writeHistory} ) { $term->WriteHistory( $h ); } elsif ( $term->Features->{getHistory} ) { require File::Slurp; my @h = map { "$_\n" } $term->GetHistory; File::Slurp::write_file( $h, @h ); } else { # warn "Your ReadLine doesn't support getHistory\n"; } } sub _new_term { my $self = shift; my $name = shift; my $term = Term::ReadLine->new( $name ); _read_history( $term ); return $term; } sub run { my $self = shift; my $shell_name = _shell_name; $self->term( my $term = $self->_new_term( $shell_name ) ); $term->ornaments($self->ornaments); # XXX my $prompt = "$shell_name > "; print "Welcome to the Perl shell. Type ':help' for more information\n\n"; REPL: while ( defined ($_ = $self->_readline) ) { # trim s/^\s+//g; s/\s+$//g; # Shell commands start with ':' followed by something else # which is not ':', so we can use things like '::my_subroutine()'. if (/^:[^:]/) { last REPL if /^:(exit|quit|q|x)/; $self->set_out($1) if /^:set out (\S+)/; $self->set_ctx($1) if /^:set ctx (\S+)/; $self->set_package($1) if /^:set package (\S+)/; $self->reset if /^:reset/; $self->help if /^:h(elp)?/; $self->dump_history($1) if /^:dump history(?:\s+(\S*))?/; # unknown shell command ?! next REPL; } my $context; $context = _ctx($1) if s/#(s|scalar|\$|l|list|\@|v|void|_)\z//; $context = $self->context unless $context; if ( $context eq 'scalar' ) { my $out = $self->eval($_); if ($@) { warn "ERROR: $@"; next } $self->_print_scalar($out); } elsif ( $context eq 'list' ) { my @out = $self->eval($_); if ($@) { warn "ERROR: $@"; next } $self->_print_list(@out); } elsif ( $context eq 'void' ) { $self->eval($_); if ($@) { warn "ERROR: $@"; next } } else { # XXX should not happen } } $self->quit; } # $shell->eval($exp) sub eval { my $self = shift; my $exp = shift; my $package = $self->package; # XXX gotta restore $_, etc. return eval <term ); $self->print( "Bye.\n" ); # XXX exit; } sub run_with_args { my $self = shift; # XXX do something with @ARGV (Getopt) my %options = ( ornaments => 1 ); if ( @ARGV ) { # only require Getopt::Long if there are actually command line arguments require Getopt::Long; Getopt::Long::GetOptions( \%options, 'ornaments!', 'version|v' ); } my $shell = Shell::Perl->new(%options); if ( $options{version} ) { $shell->_show_version; } else { $shell->run; } } sub _show_version { my $self = shift; printf "This is %s, version %s (%s, using Shell::Perl %s)\n", _shell_name, $main::VERSION, $0, $Shell::Perl::VERSION; exit 0; } sub dump_history { my $self = shift; my $file = shift; if ( !$self->term->Features->{getHistory} ) { print "Your Readline doesn't support getHistory\n"; return; } if ( $file ) { open( my $fh, ">>", $file ) or do { warn "Couldn't open '$file' for history dump\n"; return; }; for ( $self->term->GetHistory ) { print $fh $_, "\n"; } close $fh; print "Dumped history to '$file'\n\n"; } else { print $_, "\n" for($self->{term}->GetHistory); print "\n"; } return 1; } 1; # OUTPUT Data::Dump, Data::Dumper, YAML, others # document: use a different package when eval'ing # reset the environment # implement shell commands (:quit, :set, :exit, etc.) # how to implement array contexts? # IDEA: command ":set ctx scalar | list | void" # terminators "#s" "#l" "#v" "#$" #@ #_ # allow multiline entries. how? ##sub set {} # sets up the instance variables of the shell ## ##sub run {} # run the read-eval-print loop ## ##sub read {} # read a chunk ## ##sub readline {} # read a line ## ##sub eval {} ## ##sub print {} ## ##sub warn {} ## ##sub help { shift->print(HELP) } ## ##sub out { ? } # svn:keywords Id # svn:eol-style LF __END__ =pod =encoding utf-8 =head1 NAME Shell::Perl - A read-eval-print loop in Perl =head1 SYNOPSYS use Shell::Perl; Shell::Perl->run_with_args; =head1 DESCRIPTION This is the implementation of a command-line interpreter for Perl. I wrote this because I was tired of using B when needing a calculator with a real language within. Ah, that and because it was damn easy to write it. This module is the heart of the B script provided with B distribution, along with this module. =head2 EXAMPLE SESSION $ pirl Welcome to the Perl shell. Type ':help' for more information pirl @> 1+1 2 pirl @> use YAML qw(Load Dump); () pirl @> $data = Load("--- { a: 1, b: [ 1, 2, 3] }\n"); { a => 1, b => [1, 2, 3] } pirl @> $var = 'a 1 2 3'; $var =~ /(\w+) (\d+) (\d+)/ ("a", 1, 2) pirl @> :q =head2 COMMANDS Most of the time, the shell reads Perl statements, evaluates them and outputs the result. There are a few commands (started by ':') that are handled by the shell itself. =over 4 =item :h(elp) Handy for remembering what the shell commands are. =item :q(uit) Leave the shell. The Perl statement C will work too. SYNONYMS: :exit, :x =item :set out (D|DD|DDS|Y|P) Changes the dumper for the expression results used before output. The current supported are: =over 4 =item D C =item DD C, the good and old core module =item DDS C =item Y C =item P a plain dumper ("$ans" or "@ans") =back When creating the shell, the dump format is searched among the available ones in the order "D", "DD", "DDS", "Y" and "P". That means L is preferred and will be used if available/installed. Otherwise, L is tried, and so on. Read more about dumpers at L. =item :set ctx (scalar|list|void|s|l|v|$|@|_) Changes the default context used to evaluate the entered expression. The default is C<'list'>. Intuitively, 'scalar', 's' and '$' are synonyms, just like 'list', 'l', and '@' or 'void', 'v', '_'. There is a nice way to override the default context in a given expression. Just a '#' followed by one of 'scalar|list|void|s|l|v|$|@|_' at the end of the expression. pirl @> $var = 'a 1 2 3'; $var =~ /(\w+) (\d+) (\d+)/ ("a", 1, 2) pirl @> $var = 'a 1 2 3'; $var =~ /(\w+) (\d+) (\d+)/ #scalar 1 =item :reset Resets the environment, erasing the symbols created at the current evaluation package. See the section L<"ABOUT EVALUATION">. =back =head2 METHODS Remember this is an alpha version, so the API may change and that includes the methods documented here. So consider this section as implementation notes for a while. In later versions, some of these information may be promoted to a public status. Others may be hidden or changed and even disappear without further notice. =over 4 =item B $sh = Shell::Version->new; The constructor. =item B Shell::Perl->run_with_args; Starts the read-eval-print loop after reading options from C<@ARGV>. It is a class method. If an option B<-v> or B<--version> is provided, instead of starting the REPL, it prints the script identification and exits with 0. $ pirl -v This is pirl, version 0.0017 (bin/pirl, using Shell::Perl 0.0017) =item B $sh->run; The same as C but with no code for interpreting command-line arguments. It is an instance method, so that Crun_with_args> is kind of: Shell::Perl->new->run; =item B $answer = $sh->eval($exp); @answer = $sh->eval($exp); Evaluates the user input given in C<$exp> as Perl code and returns the result. That is the 'eval' part of the read-eval-print loop. =item B $sh->print(@args); Prints a list of args at the output stream currently used by the shell. =item B $sh->help; Outputs the help as provided by the command ":help". =item B $sh->reset; Does nothing by now, but it will. =item B $sh->dump_history(); $sh->dump_history($file); Prints the readline history to C or the optional file. Used to implement experimental command ":dump history". This is experimental code and should change in the future. More control should be added and integrated with other terminal features. =item B $sh->set_ctx($context); Assigns to the current shell context. The argument must be one of C< ( 'scalar', 'list', 'void', 's', 'l', 'v', '$', '@', '_' ) >. =item B $sh->set_package($package); Changes current evaluation package. Doesn't change if the new package name is malformed. =item B $sh->set_out($dumper); Changes the current dumper used for printing the evaluation results. Actually must be one of "D" (for Data::Dump), "DD" (for Data::Dumper), "DDS" (for Data::Dump::Streamer), "Y" (for YAML) or "P" (for plain string interpolation). =item B $prompt = $sh->prompt_title; Returns the current prompt which changes with executable name and context. For example, "pirl @>", "pirl $>", and "pirl >". =item B $sh->quit; This method is invoked when these commands and statements are parsed by the REPL: :q :quit :x :exit quit exit It runs the shutdown procedures for a smooth termination of the shell. For example, it saves the terminal history file. =back =head1 GORY DETAILS =head2 ABOUT EVALUATION When the statement read is evaluated, this is done at a different package, which is C by default. So: $ perl -Mlib=lib bin/pirl Welcome to the Perl shell. Type ':help' for more information pirl @> $a = 2; 2 pirl @> :set out Y # output in YAML pirl @> \%Shell::Perl::sandbox:: --- BEGIN: !!perl/glob: PACKAGE: Shell::Perl::sandbox NAME: BEGIN a: !!perl/glob: PACKAGE: Shell::Perl::sandbox NAME: a SCALAR: 2 This package serves as an environment for the current shell session and :reset can wipe it away. pirl @> :reset pirl @> \%Shell::Perl::sandbox:: --- BEGIN: !!perl/glob: PACKAGE: Shell::Perl::sandbox NAME: BEGIN =head1 TO DO There is a lot to do, as always. Some of the top priority tasks are: =over 4 =item * Accept multiline statements;. =item * Refactor the code to promote easy customization of features. =back =head1 SEE ALSO This project is hosted at Google Code: http://code.google.com/p/iperl/ To know about interactive Perl interpreters, there are two FAQS contained in L which are good starting points. Those are How can I use Perl interactively? http://perldoc.perl.org/perlfaq3.html#How-can-I-use-Perl-interactively%3f Is there a Perl shell? http://perldoc.perl.org/perlfaq3.html#How-can-I-use-Perl-interactively%3f An extra list of Perl shells can be found here: http://www.focusresearch.com/gregor/document/psh-1.1.html#other_perl_shells =head1 BUGS It is a one-line evaluator by now. I don't know what happens if you eval within an eval. I don't expect good things to come. (Lorn who prodded me about this will going to find it out and then I will tell you.) There are some quirks with Term::Readline (at least on Windows). There are more bugs. I am lazy to collect them all and list them now. Please report bugs via CPAN RT L or L. =head1 AUTHORS Adriano R. Ferreira, Eferreira@cpan.orgE Caio Marcelo, Ecmarcelo@gmail.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007–2011 by Adriano R. Ferreira This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut # Local variables: # c-indentation-style: bsd # c-basic-offset: 4 # indent-tabs-mode: nil # End: # vim: expandtab shiftwidth=4: