File Coverage

blib/lib/Shell/Perl.pm
Criterion Covered Total %
statement 76 230 33.0
branch 7 86 8.1
condition 0 6 0.0
subroutine 22 45 48.8
pod 14 14 100.0
total 119 381 31.2


line stmt bran cond sub pod time code
1             package Shell::Perl;
2              
3 4     4   115158 use strict;
  4         7  
  4         94  
4 4     4   14 use warnings;
  4         3  
  4         132  
5              
6             our $VERSION = '0.0026';
7              
8 4     4   14 use base qw(Class::Accessor); # soon use base qw(Shell::Base);
  4         6  
  4         1822  
9             Shell::Perl->mk_accessors(qw(
10             out_type
11             dumper
12             context
13             package
14             perl_version
15             term
16             ornaments
17             library
18             )); # XXX use_strict
19              
20 4     4   6604 use lib ();
  4         1860  
  4         100  
21 4     4   2423 use Getopt::Long 2.43 qw(:config no_auto_abbrev no_ignore_case bundling_values);
  4         31808  
  4         75  
22 4     4   2095 use version 0.77;
  4         5041  
  4         19  
23              
24 4     4   1977 use Term::ReadLine;
  4         8244  
  4         106  
25 4     4   1477 use Shell::Perl::Dumper;
  4         6  
  4         23  
26              
27             # out_type defaults to one of 'D', 'DD', 'Y', 'P';
28             # dumper XXX
29             # context defaults to 'list'
30             # package defaults to __PACKAGE__ . '::sandbox'
31             # XXX use_strict defaults to 0
32              
33             sub new {
34 2     2 1 71353 my $self = shift;
35 2         64 my $sh = $self->SUPER::new({
36             context => 'list', # print context
37             perl_version => $],
38             @_ });
39 2         29 $sh->_init;
40 2         7 return $sh;
41             }
42              
43             my %dumper_for = (
44             'D' => 'Shell::Perl::Data::Dump',
45             'DD' => 'Shell::Perl::Data::Dumper',
46             'Y' => 'Shell::Perl::Dumper::YAML',
47             'Data::Dump' => 'Shell::Perl::Data::Dump',
48             'Data::Dumper' => 'Shell::Perl::Data::Dumper',
49             'YAML' => 'Shell::Perl::Dumper::YAML',
50             'DDS' => 'Shell::Perl::Data::Dump::Streamer',
51              
52             'P' => 'Shell::Perl::Dumper::Plain',
53             'plain' => 'Shell::Perl::Dumper::Plain',
54             );
55              
56             sub _init {
57 2     2   3 my $self = shift;
58              
59             # loop until you find one available alternative for dump format
60 2         3 my $dumper_class;
61 2         4 for my $format ( qw(D DD DDS Y P) ) {
62 4 100       30 if ($dumper_for{$format}->is_available) {
63             #$self->print("format: $format\n");
64 2         11 $self->set_out($format);
65             last
66 2         16 } # XXX this is not working 100% - and I have no clue about it
67             }
68              
69             # Set library paths
70 2 50       8 if ($self->library) {
71 0         0 warn "Setting library paths (@{$self->library})\n";
  0         0  
72 0         0 lib->import(@{ $self->library });
  0         0  
73             }
74              
75 2         45 $self->set_package( __PACKAGE__ . '::sandbox' );
76              
77             }
78              
79             sub _shell_name {
80 0     0   0 require File::Basename;
81 0         0 return File::Basename::basename($0);
82             }
83              
84             sub print {
85 0     0 1 0 my $self = shift;
86 0         0 print {$self->term->OUT} @_;
  0         0  
87             }
88              
89             ## # XXX remove: code and docs
90             ## sub out {
91             ## my $self = shift;
92             ##
93             ## # XXX I want to improve this: preferably with an easy way to add dumpers
94             ## if ($self->context eq 'scalar') {
95             ## $self->print($self->dumper->dump_scalar(shift), "\n");
96             ## } else { # list
97             ## $self->print($self->dumper->dump_list(@_), "\n");
98             ## }
99             ## }
100              
101             # XXX I want to improve this: preferably with an easy way to add dumpers
102              
103             =begin private
104              
105             =item B<_print_scalar>
106              
107             $sh->_print_scalar($answer);
108              
109             That corresponds to the 'print' in the read-eval-print
110             loop (in scalar context). It outputs the evaluation result
111             after passing it through the current dumper.
112              
113             =end private
114              
115             =cut
116              
117             sub _print_scalar { # XXX make public, document
118 0     0   0 my $self = shift;
119 0         0 $self->print($self->dumper->dump_scalar(shift));
120             }
121              
122             =begin private
123              
124             =item B<_print_scalar>
125              
126             $sh->_print_list(@answers);
127              
128             That corresponds to the 'print' in the read-eval-print
129             loop (in list context). It outputs the evaluation result
130             after passing it through the current dumper.
131              
132             =end private
133              
134             =cut
135              
136             sub _print_list { # XXX make public, document
137 0     0   0 my $self = shift;
138 0         0 $self->print($self->dumper->dump_list(@_));
139             }
140              
141             sub _warn {
142 0     0   0 shift;
143 0         0 my $shell_name = _shell_name;
144 0         0 warn "$shell_name: ", @_, "\n";
145             }
146              
147             sub set_out {
148 2     2 1 4 my $self = shift;
149 2         3 my $type = shift;
150 2         5 my $dumper_class = $dumper_for{$type};
151 2 50       8 if (!defined $dumper_class) {
152 0         0 $self->_warn("unknown dumper $type");
153 0         0 return;
154             }
155 2 50       13 if ($dumper_class->is_available) {
156 2         21 $self->dumper($dumper_class->new);
157 2         71 $self->out_type($type);
158             } else {
159 0         0 $self->_warn("can't load dumper $dumper_class");
160             }
161             }
162              
163             sub _ctx {
164 0     0   0 my $context = shift;
165              
166 0 0       0 if ($context =~ /^(s|scalar|\$)$/i) {
    0          
    0          
167 0         0 return 'scalar';
168             } elsif ($context =~ /^(l|list|@)$/i) {
169 0         0 return 'list';
170             } elsif ($context =~ /^(v|void|_)$/i) {
171 0         0 return 'void';
172             } else {
173 0         0 return undef;
174             }
175             }
176              
177             sub set_ctx {
178 0     0 1 0 my $self = shift;
179 0         0 my $context = _ctx shift;
180              
181 0 0       0 if ($context) {
182 0         0 $self->context($context);
183             } else {
184 0         0 $self->_warn("unknown context $context");
185             }
186             }
187              
188             sub set_package {
189 2     2 1 4 my $self = shift;
190 2         4 my $package = shift;
191              
192 2 50       21 if ($package =~ /( [a-zA-Z_] \w* :: )* [a-zA-Z_] \w* /x) {
193 2         10 $self->package($package);
194              
195 4     4   2166 no strict 'refs';
  4         5  
  4         937  
196 2     0   22 *{ "${package}::quit" } = sub { $self->quit };
  2         16  
  0         0  
197             } else {
198 0         0 $self->_warn("bad package name $package");
199             }
200             }
201              
202             # $err = _check_perl_version($version);
203             sub _check_perl_version {
204 0     0   0 my $version = shift;
205 0         0 my $ver = eval { version->parse($version) };
  0         0  
206 0 0       0 if ($@) {
207 0         0 (my $err = $@) =~ s/at \S+ line \d+.$//;
208 0         0 return $err;
209             }
210             # Current perl
211 0   0     0 my $v = $^V || version->parse($]);
212 0 0       0 if ($ver > $v) {
213 0         0 return "This is only $v";
214             }
215 0         0 return undef; # good
216             }
217              
218             sub set_perl_version {
219 0     0 1 0 my $self = shift;
220 0         0 my $version = shift;
221              
222 0 0 0     0 if (!defined $version) {
    0          
223 0         0 $self->perl_version($]);
224             }
225             elsif ($version eq q{''} || $version eq q{""}) {
226 0         0 $self->perl_version('');
227             }
228             else {
229 0         0 my $err = _check_perl_version($version);
230 0 0       0 if ($err) {
231 0         0 $self->_warn("bad perl_version ($version): $err");
232             }
233             else {
234 0         0 $self->perl_version($version);
235             }
236             }
237             }
238              
239 4         355 use constant HELP =>
240 4     4   24 <<'HELP';
  4         4  
241             Shell commands: (begin with ':')
242             :e(x)it or :q(uit) - leave the shell
243             :set out (D|DD|DDS|Y|P) - setup the output format
244             :set ctx (scalar|list|void|s|l|v|$|@|_) - setup the eval context
245             :set package - set package in which shell eval statements
246             :set perl_version - set perl version to eval statements
247             :reset - reset the environment
248             :dump history - (experimental) print the history to STDOUT or a file
249             :h(elp) - get this help screen
250              
251             HELP
252              
253             sub help {
254 0     0 1 0 print HELP;
255             }
256              
257             # :reset is a nice idea - but I wanted more like CPAN reload
258             # I retreated the current implementation of :reset
259             # because %main:: is used as the evaluation package
260             # and %main:: = () is too severe by now
261              
262             sub reset {
263 0     0 1 0 my $self = shift;
264 0         0 my $package = $self->package;
265 0 0       0 return if $package eq 'main'; # XXX don't reset %main::
266 4     4   21 no strict 'refs';
  4         7  
  4         4212  
267 0         0 %{"${package}::"} = ();
  0         0  
268             #%main:: = (); # this segfaults at my machine
269             }
270              
271             sub prompt_title {
272 0     0 1 0 my $self = shift;
273 0         0 my $shell_name = _shell_name;
274 0         0 my $sigil = { scalar => '$', list => '@', void => '' }->{$self->{context}};
275 0         0 return "$shell_name $sigil> ";
276             }
277              
278             sub _readline {
279 0     0   0 my $self = shift;
280 0         0 return $self->term->readline($self->prompt_title);
281             }
282              
283             sub _history_file { # XXX
284 0     0   0 require Path::Class;
285 0         0 require File::HomeDir;
286 0         0 return Path::Class::file( File::HomeDir->my_home, '.pirl-history-xxx' );
287             }
288              
289             sub _read_history { # XXX belongs to Shell::Perl::ReadLine
290 0     0   0 my $term = shift;
291 0         0 my $h = _history_file;
292             #warn "read history from $h\n"; # XXX
293 0 0       0 if ( $term->Features->{readHistory} ) {
    0          
294 0         0 $term->ReadHistory( "$h" );
295             } elsif ( $term->Features->{setHistory} ) {
296 0 0       0 if ( -e $h ) {
297 0         0 my @h = $h->slurp( chomp => 1 );
298 0         0 $term->SetHistory( @h );
299             }
300             } else {
301             # warn "Your ReadLine doesn't support setHistory\n";
302             }
303              
304             }
305              
306             sub _write_history { # XXX belongs to Shell::Perl::ReadLine
307 0     0   0 my $term = shift;
308 0         0 my $h = _history_file;
309             #warn "write history to $h\n"; # XXX
310 0 0       0 if ( $term->Features->{writeHistory} ) {
    0          
311 0         0 $term->WriteHistory( "$h" );
312             } elsif ( $term->Features->{getHistory} ) {
313 0         0 my @h = $term->GetHistory;
314 0         0 $h->spew_lines(\@h);
315             } else {
316             # warn "Your ReadLine doesn't support getHistory\n";
317             }
318             }
319              
320             sub _new_term {
321 0     0   0 my $self = shift;
322 0         0 my $name = shift;
323 0         0 my $term = Term::ReadLine->new( $name );
324 0         0 _read_history( $term );
325 0         0 return $term;
326             }
327              
328             sub run {
329 0     0 1 0 my $self = shift;
330 0         0 my $shell_name = _shell_name;
331 0         0 $self->term( my $term = $self->_new_term( $shell_name ) );
332 0         0 $term->ornaments($self->ornaments); # XXX
333 0         0 my $prompt = "$shell_name > ";
334              
335 0         0 print "Welcome to the Perl shell. Type ':help' for more information\n\n";
336              
337 0         0 REPL: while ( defined ($_ = $self->_readline) ) {
338              
339             # trim
340 0         0 s/^\s+//g;
341 0         0 s/\s+$//g;
342              
343             # Shell commands start with ':' followed by something else
344             # which is not ':', so we can use things like '::my_subroutine()'.
345 0 0       0 if (/^:[^:]/) {
346 0 0       0 last REPL if /^:(exit|quit|q|x)/;
347 0 0       0 $self->set_out($1) if /^:set out (\S+)/;
348 0 0       0 $self->set_ctx($1) if /^:set ctx (\S+)/;
349 0 0       0 $self->set_package($1) if /^:set package (\S+)/;
350 0 0       0 $self->set_perl_version($1) if /^:set perl_version(?: (\S+))?/;
351 0 0       0 $self->reset if /^:reset/;
352 0 0       0 $self->help if /^:h(elp)?/;
353 0 0       0 $self->dump_history($1) if /^:dump history(?:\s+(\S*))?/;
354             # unknown shell command ?!
355 0         0 next REPL;
356             }
357              
358 0         0 my $context;
359 0 0       0 $context = _ctx($1) if s/#(s|scalar|\$|l|list|\@|v|void|_)\z//;
360 0 0       0 $context = $self->context unless $context;
361 0 0       0 if ( $context eq 'scalar' ) {
    0          
    0          
362 0         0 my $out = $self->eval($_);
363 0 0       0 if ($@) { warn "ERROR: $@"; next }
  0         0  
  0         0  
364 0         0 $self->_print_scalar($out);
365             } elsif ( $context eq 'list' ) {
366 0         0 my @out = $self->eval($_);
367 0 0       0 if ($@) { warn "ERROR: $@"; next }
  0         0  
  0         0  
368 0         0 $self->_print_list(@out);
369             } elsif ( $context eq 'void' ) {
370 0         0 $self->eval($_);
371 0 0       0 if ($@) { warn "ERROR: $@"; next }
  0         0  
  0         0  
372             } else {
373             # XXX should not happen
374             }
375              
376             }
377 0         0 $self->quit;
378              
379             }
380              
381             sub _package_stmt {
382 2     2   5 my $package = shift->package;
383 2         17 ("package $package");
384             }
385              
386             sub _use_perl_stmt {
387 2     2   5 my $perl_version = shift->perl_version;
388 2 50       17 $perl_version ? ("use $perl_version") : ();
389             }
390              
391             # $shell->eval($exp)
392             sub eval {
393 2     2 1 398 my $self = shift;
394 2         2 my $exp = shift;
395              
396 2         5 my $preamble = join ";\n", (
397             $self->_package_stmt,
398             $self->_use_perl_stmt,
399             "no strict qw(vars subs)",
400             "", # for the trailing ;
401             );
402              
403             # XXX gotta restore $_, etc.
404 2     1   112 return eval <
  1     1   18  
  1     1   2  
  1     1   3  
  1         1  
  1         30  
  1         13  
  1         2  
  1         4  
  1         1  
  1         18  
405             $preamble
406             #line 1
407             $exp
408             CHUNK
409             # XXX gotta save $_, etc.
410             }
411              
412             sub quit {
413 0     0 1 0 my $self = shift;
414 0         0 _write_history( $self->term );
415 0         0 $self->print( "Bye.\n" ); # XXX
416 0         0 exit;
417             }
418              
419             sub run_with_args {
420 0     0 1 0 my $self = shift;
421              
422             # XXX do something with @ARGV (Getopt)
423 0         0 my %options = ( ornaments => 1 );
424 0 0       0 if ( @ARGV ) {
425             # only require Getopt::Long if there are actually command line arguments
426 0         0 require Getopt::Long;
427 0         0 Getopt::Long::GetOptions( \%options, 'ornaments!', 'version|v', 'library|I=s@' );
428             }
429              
430 0         0 my $shell = Shell::Perl->new(%options);
431 0 0       0 if ( $options{version} ) {
432 0         0 $shell->_show_version;
433             } else {
434 0         0 $shell->run;
435             }
436             }
437              
438             sub _show_version {
439 0     0   0 my $self = shift;
440 0         0 printf "This is %s, version %s (%s, using Shell::Perl %s)\n",
441             _shell_name,
442             $main::VERSION,
443             $0,
444             $Shell::Perl::VERSION;
445 0         0 exit 0;
446             }
447              
448             sub dump_history {
449 0     0 1 0 my $self = shift;
450 0         0 my $file = shift;
451              
452 0 0       0 if ( !$self->term->Features->{getHistory} ) {
453 0         0 print "Your Readline doesn't support getHistory\n";
454 0         0 return;
455             }
456              
457 0 0       0 if ( $file ) {
458             open( my $fh, ">>", $file )
459 0 0       0 or do { warn "Couldn't open '$file' for history dump\n"; return; };
  0         0  
  0         0  
460 0         0 for ( $self->term->GetHistory ) {
461 0         0 print $fh $_, "\n";
462             }
463 0         0 close $fh;
464              
465 0         0 print "Dumped history to '$file'\n\n";
466             } else {
467 0         0 print $_, "\n" for($self->{term}->GetHistory);
468 0         0 print "\n";
469             }
470 0         0 return 1;
471             }
472              
473             1;
474              
475             # OUTPUT Data::Dump, Data::Dumper, YAML, others
476             # document: use a different package when eval'ing
477             # reset the environment
478             # implement shell commands (:quit, :set, :exit, etc.)
479             # how to implement array contexts?
480             # IDEA: command ":set ctx scalar | list | void"
481             # terminators "#s" "#l" "#v" "#$" #@ #_
482             # allow multiline entries. how?
483              
484             ##sub set {} # sets up the instance variables of the shell
485             ##
486             ##sub run {} # run the read-eval-print loop
487             ##
488             ##sub read {} # read a chunk
489             ##
490             ##sub readline {} # read a line
491             ##
492             ##sub eval {}
493             ##
494             ##sub print {}
495             ##
496             ##sub warn {}
497             ##
498             ##sub help { shift->print(HELP) }
499             ##
500             ##sub out { ? }
501              
502             __END__