File Coverage

blib/lib/App/perlsh.pm
Criterion Covered Total %
statement 37 83 44.5
branch 8 32 25.0
condition 0 9 0.0
subroutine 9 13 69.2
pod 0 4 0.0
total 54 141 38.3


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2015 -- leonerd@leonerd.org.uk
5              
6             package App::perlsh;
7              
8 2     2   19638 use strict;
  2         4  
  2         45  
9 2     2   10 use warnings;
  2         2  
  2         89  
10              
11             our $VERSION = '0.01';
12              
13 2     2   17 use List::Util qw( reduce );
  2         4  
  2         195  
14 2     2   1532 use Term::ReadLine;
  2         5939  
  2         52  
15 2     2   1183 use Term::Size;
  2         65435  
  2         206  
16              
17 2     2   2274 use Lexical::Persistence 0.98;
  2         39628  
  2         3028  
18              
19             my $COLS = Term::Size::chars \*STDOUT;
20              
21             $SIG{WINCH} = sub {
22             $COLS = Term::Size::chars \*STDOUT;
23             };
24              
25             =head1 NAME
26              
27             C - a simple F REPL based on C
28              
29             =head1 SYNOPSIS
30              
31             use App::perlsh;
32              
33             App::perlsh->run
34              
35             =head1 DESCRIPTION
36              
37             This module implements an application that provides a simple perl REPL
38             ("read-execute-print loop"); that is, an interactive shell-like program that
39             repeatedly reads perl code from input, executes it, and prints the result in a
40             readable manner.
41              
42             Being based on L allows it to accumulate variables along
43             the session, letting the user reuse them in later lines.
44              
45             $ perlsh
46             eval: my $one = 1;
47             '1'
48              
49             eval: my $two = 2;
50             '2'
51              
52             eval: $one + $two
53             '3'
54              
55             eval:
56              
57             =cut
58              
59             sub new
60             {
61 1     1 0 2 my $class = shift;
62 1         6 return bless {}, $class;
63             }
64              
65             # function
66 0 0   0   0 sub _longest { reduce { length $a > length $b ? $a : $b } @_ }
  0     0   0  
67              
68             # function
69             sub _repr
70             {
71 1     1   2 my ( $v, $leader, $leader2 ) = @_;
72              
73 1 50       5 $leader = "" if !defined $leader;
74 1 50       3 $leader2 = $leader if !defined $leader2;
75              
76 1 50       5 if( !defined $v ) {
    50          
    0          
    0          
    0          
77 0         0 return "${leader}undef";
78             }
79             elsif( !ref $v ) {
80 1 50       5 if( $v =~ m/[^\x20-\x7e]/ ) {
81 0         0 $v =~ s{(["\\])}{\\$1}g;
82 0         0 $v =~ s{\e}{\\e}g;
83 0         0 $v =~ s{\t}{\\t}g;
84 0         0 $v =~ s{\n}{\\n}g;
85 0         0 $v =~ s{\r}{\\r}g;
86 0         0 $v =~ s{([^\x20-\x7e])}{"\\x" . sprintf( "%02x", ord $1 ) }eg;
  0         0  
87 0         0 return $leader . qq{"$v"};
88             }
89             else {
90 1         3 $v =~ s{(['\\])}{\\$1}g;
91 1         7 return $leader . qq{'$v'};
92             }
93             }
94             elsif( ref $v eq "SCALAR" ) {
95 0         0 my $rv = $$v;
96 0         0 return "\\" . _repr( $rv );
97             }
98             elsif( ref $v eq "ARRAY" ) {
99 0         0 my @slots = map { _repr( $_ ) } @$v;
  0         0  
100              
101             # See if we're going to use oneline representation
102 0         0 my $oneline = 1;
103 0   0     0 $_ =~ m/\n/ and $oneline = 0, last foreach @slots;
104              
105 0 0       0 if( $oneline ) {
106 0         0 my $ret = $leader . "[ " . join( ", ", @slots ) . " ]";
107 0 0       0 return $ret if length $ret < $COLS;
108             }
109              
110 0         0 my ( $first, @rest ) = @$v;
111             return $leader . "[ " .
112             join( ",\n$leader2 ", _repr( $first, $leader, "$leader2 " ),
113 0         0 map { _repr( $_, $leader2, "$leader2 " ) } @rest )
  0         0  
114             . " ]";
115             }
116             elsif( ref $v eq "HASH" ) {
117 0         0 my @keys = sort keys %$v;
118 0         0 my @values = map { _repr( $v->{$_} ) } @keys;
  0         0  
119              
120 0         0 my $oneline = 1;
121 0   0     0 $_ =~ m/\n/ and $oneline = 0, last foreach @values;
122              
123 0 0       0 if( $oneline ) {
124 0         0 my $ret = "{ " . join( ", ", map { "$keys[$_] => $values[$_]" } ( 0 .. $#keys ) ) . " }";
  0         0  
125 0 0       0 return $ret if length $ret < $COLS;
126             }
127              
128 0         0 my $keylen = length _longest @keys;
129              
130 0         0 my $firstkey = shift @keys;
131             return $leader . "{ " .
132             join( ",\n$leader2 ", sprintf( '%-*s => %s', $keylen, $firstkey, _repr( $v->{$firstkey} ) ),
133 0         0 map { sprintf( '%-*s => %s', $keylen, $_, _repr( $v->{$_} ) ) } @keys )
  0         0  
134             . " }";
135             }
136             else {
137 0         0 return "(Cannot represent ref ".(ref $v).")";
138             }
139             }
140              
141             sub read
142             {
143 0     0 0 0 my $self = shift;
144              
145 0   0     0 my $term = $self->{term} ||= Term::ReadLine->new("perlsh");
146              
147 0         0 return $term->readline( "eval: " );
148             }
149              
150             sub print
151             {
152 0     0 0 0 my $self = shift;
153 0         0 print @_;
154             }
155              
156             sub run
157             {
158 1 50   1 0 26 my $self = ref $_[0] ? shift : shift->new;
159              
160 1         10 my $lp = Lexical::Persistence->new();
161              
162 1         24 while ( defined( my $line = $self->read ) ) {
163 1         9 my $sub = $lp->compile( $line );
164 1 50       79 if( !defined $sub ) {
165 0         0 $self->print( "Compile error: $@\n" );
166 0         0 next;
167             }
168              
169 1         1 my $result = eval { $lp->call( $sub ) };
  1         6  
170              
171 1 50       58 if( $@ ) {
172 0         0 $self->print( "ERR: $@\n" );
173 0         0 next;
174             }
175              
176 1         4 $self->print( _repr( $result ) . "\n\n" );
177             }
178              
179 1         15 $self->print( "\n" ); # Final linefeed
180             }
181              
182             =head1 AUTHOR
183              
184             Paul Evans
185              
186             =cut
187              
188             0x55AA;