File Coverage

blib/lib/Term/ReadLine/Tiny.pm
Criterion Covered Total %
statement 19 352 5.4
branch 0 92 0.0
condition 0 18 0.0
subroutine 6 42 14.2
pod 18 22 81.8
total 43 526 8.1


line stmt bran cond sub pod time code
1             package Term::ReadLine::Tiny;
2             =head1 NAME
3              
4             Term::ReadLine::Tiny - Tiny implementation of ReadLine
5              
6             =head1 VERSION
7              
8             version 1.08
9              
10             =head1 SYNOPSIS
11              
12             use Term::ReadLine::Tiny;
13            
14             $term = Term::ReadLine::Tiny->new();
15             while ( defined($_ = $term->readline("Prompt: ")) )
16             {
17             print "$_\n";
18             }
19             print "\n";
20            
21             $s = "";
22             while ( defined($_ = $term->readkey(1)) )
23             {
24             $s .= $_;
25             }
26             print "\n$s\n";
27              
28             =head1 DESCRIPTION
29              
30             This package is a native perls implementation of ReadLine that doesn't need any library such as 'Gnu ReadLine'.
31             Also fully supports UTF-8, details in L.
32              
33             =head2 Keys
34              
35             B or C<^J> or C<^M>:> Gets input line. Returns the line unless C or aborting or error, otherwise undef.
36              
37             B or C<^H> or C<^?>:> Deletes one character behind cursor.
38              
39             B:> Changes line to previous history line.
40              
41             B:> Changes line to next history line.
42              
43             B:> Moves cursor forward to one character.
44              
45             B:> Moves cursor back to one character.
46              
47             B or C<^A>:> Moves cursor to the start of the line.
48              
49             B or C<^E>:> Moves cursor to the end of the line.
50              
51             B:> Change line to first line of history.
52              
53             B:> Change line to latest line of history.
54              
55             B:> Switch typing mode between insert and overwrite.
56              
57             B:> Deletes one character at cursor. Does nothing if no character at cursor.
58              
59             B or C<^I>:> Completes line automatically by history.
60              
61             B:> Aborts the operation. Returns C.
62              
63             =cut
64 1     1   15596 use strict;
  1         2  
  1         27  
65 1     1   4 use warnings;
  1         2  
  1         22  
66 1     1   12 use v5.10.1;
  1         7  
67 1     1   5 use feature qw(switch);
  1         2  
  1         95  
68 1     1   498 no if ($] >= 5.018), 'warnings' => 'experimental';
  1         11  
  1         5  
69             require utf8;
70             require PerlIO;
71             require Term::ReadLine;
72             require Term::ReadKey;
73              
74              
75             BEGIN
76             {
77 1     1   121 require Exporter;
78 1         2 our $VERSION = '1.08';
79 1         7 our @ISA = qw(Exporter);
80 1         2 our @EXPORT = qw();
81 1         3111 our @EXPORT_OK = qw();
82             }
83              
84              
85             =head1 Standard Methods and Functions
86              
87             =cut
88              
89             =head2 ReadLine()
90              
91             returns the actual package that executes the commands. If this package is used, the value is C.
92              
93             =cut
94             sub ReadLine
95             {
96 0     0 1   return __PACKAGE__;
97             }
98              
99             =head2 new([$appname[, IN[, OUT]]])
100              
101             returns the handle for subsequent calls to following functions.
102             Argument I is the name of the application B.
103             Optionally can be followed by two arguments for IN and OUT filehandles. These arguments should be globs.
104              
105             This routine may also get called via Cnew()> if you have $ENV{PERL_RL} set to 'Tiny'.
106              
107             =cut
108             sub new
109             {
110 0     0 1   my $class = shift;
111 0           my ($appname, $IN, $OUT) = @_;
112 0           my $self = {};
113 0           bless $self, $class;
114              
115 0           $self->{readmode} = '';
116 0           $self->{history} = [];
117              
118 0           $self->{features} = {};
119             #$self->{features}->{appname} = $appname;
120 0           $self->{features}->{addhistory} = 1;
121 0           $self->{features}->{minline} = 1;
122 0           $self->{features}->{autohistory} = 1;
123 0           $self->{features}->{gethistory} = 1;
124 0           $self->{features}->{sethistory} = 1;
125 0           $self->{features}->{changehistory} = 1;
126 0           $self->{features}->{utf8} = 1;
127              
128 0           $self->newTTY($IN, $OUT);
129              
130 0           return $self;
131             }
132              
133             sub DESTROY
134             {
135 0     0     my $self = shift;
136 0 0         if ($self->{readmode})
137             {
138 0           Term::ReadKey::ReadMode('restore', $self->{IN});
139 0           $self->{readmode} = '';
140             }
141             }
142              
143             =head2 readline([$prompt[, $default]])
144              
145             interactively gets an input line. Trailing newline is removed.
146              
147             Returns C on C.
148              
149             =cut
150             sub readline
151             {
152 0     0 1   my $self = shift;
153 0           my ($prompt, $default) = @_;
154 0 0         $prompt = "" unless defined($prompt);
155 0 0         $default = "" unless defined($default);
156             my ($in, $out, $history, $minline, $changehistory) =
157 0           ($self->{IN}, $self->{OUT}, $self->{history}, $self->{features}->{minline}, $self->{features}->{changehistory});
158 0 0         unless (-t $in)
159             {
160 0           my $line = <$in>;
161 0 0         chomp $line if defined $line;
162 0           return $line;
163             }
164 0           local $\ = undef;
165              
166 0           $self->{readmode} = 'cbreak';
167 0           Term::ReadKey::ReadMode($self->{readmode}, $self->{IN});
168              
169 0           my @line;
170 0           my ($line, $index) = ("", 0);
171 0           my $history_index;
172 0           my $ins_mode = 0;
173              
174             my $write = sub {
175 0     0     my ($text, $ins) = @_;
176 0           my $s;
177 0           my @a = @line[$index..$#line];
178 0           my $a = substr($line, $index);
179 0           @line = @line[0..$index-1];
180 0           $line = substr($line, 0, $index);
181 0           print $out " ";
182 0           print $out "\e[D";
183 0           print $out "\e[J";
184 0           for my $c (split("", $text))
185             {
186 0           $s = encode_controlchar($c);
187 0 0         unless ($ins)
188             {
189 0           print $out $s;
190 0           push @line, $s;
191 0           $line .= $c;
192             } else
193             {
194 0           my $i = $index-length($line);
195 0           $a[$i] = $s;
196 0           substr($a, $i, 1) = $c;
197             }
198 0           $index++;
199             }
200 0 0         unless ($ins)
201             {
202 0           $s = join("", @a);
203 0           print $out $s;
204 0           print $out "\e[D" x length($s);
205             } else
206             {
207 0           $s = join("", @a);
208 0           print $out $s;
209 0           print $out "\e[D" x (length($s) - length(join("", @a[0..length($text)-1])));
210             }
211 0           push @line, @a;
212 0           $line .= $a;
213 0 0         if ($index >= length($line))
214             {
215 0           print $out " ";
216 0           print $out "\e[D";
217 0           print $out "\e[J";
218             }
219 0           };
220             my $print = sub {
221 0     0     my ($text) = @_;
222 0           $write->($text, $ins_mode);
223 0           };
224             my $set = sub {
225 0     0     my ($text) = @_;
226 0           print $out "\e[D" x length(join("", @line[0..$index-1]));
227 0           print $out "\e[J";
228 0           @line = ();
229 0           $line = "";
230 0           $index = 0;
231 0           $write->($text);
232 0           };
233             my $backspace = sub {
234 0 0   0     return if $index <= 0;
235 0           my @a = @line[$index..$#line];
236 0           my $a = substr($line, $index);
237 0           $index--;
238 0           print $out "\e[D" x length($line[$index]);
239 0           @line = @line[0..$index-1];
240 0           $line = substr($line, 0, $index);
241 0           $write->($a);
242 0           print $out "\e[D" x length(join("", @a));
243 0           $index -= scalar(@a);
244 0           };
245             my $delete = sub {
246 0     0     my @a = @line[$index+1..$#line];
247 0           my $a = substr($line, $index+1);
248 0           @line = @line[0..$index-1];
249 0           $line = substr($line, 0, $index);
250 0           $write->($a);
251 0           print $out "\e[D" x length(join("", @a));
252 0           $index -= scalar(@a);
253 0           };
254             my $home = sub {
255 0     0     print $out "\e[D" x length(join("", @line[0..$index-1]));
256 0           $index = 0;
257 0           };
258             my $end = sub {
259 0     0     my @a = @line[$index..$#line];
260 0           my $a = substr($line, $index);
261 0           @line = @line[0..$index-1];
262 0           $line = substr($line, 0, $index);
263 0           $write->($a);
264 0           };
265             my $left = sub {
266 0 0   0     return if $index <= 0;
267 0           print $out "\e[D" x length($line[$index-1]);
268 0           $index--;
269 0           };
270             my $right = sub {
271 0 0   0     return if $index >= length($line);
272 0           print $out $line[$index];
273 0           $index++;
274 0 0         if ($index >= length($line))
275             {
276 0           print $out " ";
277 0           print $out "\e[D";
278 0           print $out "\e[J";
279             } else
280             {
281 0           print $out $line[$index];
282 0           print $out "\e[D" x length($line[$index]);
283             }
284 0           };
285             my $up = sub {
286 0 0   0     return if $history_index <= 0;
287 0 0         $history->[$history_index] = $line if $changehistory;
288 0           $history_index--;
289 0           $set->($history->[$history_index]);
290 0           };
291             my $down = sub {
292 0 0   0     return if $history_index >= $#$history;
293 0 0         $history->[$history_index] = $line if $changehistory;
294 0           $history_index++;
295 0           $set->($history->[$history_index]);
296 0           };
297             my $pageup = sub {
298 0 0   0     return if $history_index <= 0;
299 0 0         $history->[$history_index] = $line if $changehistory;
300 0           $history_index = 0;
301 0           $set->($history->[$history_index]);
302 0           };
303             my $pagedown = sub {
304 0 0   0     return if $history_index >= $#$history;
305 0 0         $history->[$history_index] = $line if $changehistory;
306 0           $history_index = $#$history;
307 0           $set->($history->[$history_index]);
308 0           };
309              
310 0           print $prompt;
311 0           $set->($default);
312 0           push @$history, $line;
313 0           $history_index = $#$history;
314              
315 0           my $result = undef;
316 0           my ($char, $esc) = ("", undef);
317 0           while (defined($char = getc($in)))
318             {
319 0 0         unless (defined($esc))
320             {
321 0           given ($char)
322             {
323             when (/\e/)
324 0           {
325 0           $esc = "";
326             }
327             when (/\x01/) # ^A
328 0           {
329 0           $home->();
330             }
331             when (/\x04/) # ^D
332 0           {
333 0           $result = undef;
334 0           last;
335             }
336             when (/\x05/) # ^E
337 0           {
338 0           $end->();
339             }
340             when (/\t/) # ^I
341 0           {
342 0           for (my $i = $history_index; $i >= 0; $i--)
343             {
344 0 0         if ($history->[$i] =~ /^$line/)
345             {
346 0           $set->($history->[$i]);
347 0           last;
348             }
349             }
350             }
351             when (/\n|\r/)
352 0           {
353 0           print $out $char;
354 0           $history->[$#$history] = $line;
355 0 0 0       pop @$history unless defined($minline) and length($line) >= $minline;
356 0           $result = $line;
357 0           last;
358             }
359             when (/[\b]|\x7F/)
360 0           {
361 0           $backspace->();
362             }
363             when (/[\x00-\x1F]|\x7F/)
364 0           {
365 0           $print->($char);
366             }
367             default
368 0           {
369 0           $print->($char);
370             }
371             }
372 0           next;
373             }
374 0           $esc .= $char;
375 0 0         if ($esc =~ /^.\d?\D/)
376             {
377 0           given ($esc)
378             {
379             when (/^(\[|O)(A|0A)/)
380 0           {
381 0           $up->();
382             }
383             when (/^(\[|O)(B|0B)/)
384 0           {
385 0           $down->();
386             }
387             when (/^(\[|O)(C|0C)/)
388 0           {
389 0           $right->();
390             }
391             when (/^(\[|O)(D|0D)/)
392 0           {
393 0           $left->();
394             }
395             when (/^(\[|O)(F|0F)/)
396 0           {
397 0           $end->();
398             }
399             when (/^(\[|O)(H|0H)/)
400 0           {
401 0           $home->();
402             }
403             when (/^\[(\d)~/)
404 0           {
405 0           given ($1)
406             {
407             when (1)
408 0           {
409 0           $home->();
410             }
411             when (2)
412 0           {
413 0           $ins_mode = not $ins_mode;
414             }
415             when (3)
416 0           {
417 0           $delete->();
418             }
419             when (4)
420 0           {
421 0           $end->();
422             }
423             when (5)
424 0           {
425 0           $pageup->();
426             }
427             when (6)
428 0           {
429 0           $pagedown->();
430             }
431             when (7)
432 0           {
433 0           $home->();
434             }
435             when (8)
436 0           {
437 0           $end->();
438             }
439             default
440 0           {
441             #$print->("\e$esc");
442             }
443             }
444             }
445             default
446 0           {
447             #$print->("\e$esc");
448             }
449             }
450 0           $esc = undef;
451             }
452             }
453 0 0 0       utf8::encode($result) if defined($result) and utf8::is_utf8($result) and $self->{features}->{utf8};
      0        
454              
455 0           Term::ReadKey::ReadMode('restore', $self->{IN});
456 0           $self->{readmode} = '';
457 0           return $result;
458             }
459              
460             =head2 addhistory($line1[, $line2[, ...]])
461              
462             B
463              
464             adds lines to the history of input.
465              
466             =cut
467             sub addhistory
468             {
469 0     0 1   my $self = shift;
470 0 0         if (grep(":utf8", PerlIO::get_layers($self->{IN})))
471             {
472 0           for (my $i = 0; $i < @_; $i++)
473             {
474 0           utf8::decode($_[$i]);
475             }
476             }
477 0           push @{$self->{history}}, @_;
  0            
478 0           return (@_);
479             }
480             sub AddHistory
481             {
482 0     0 0   return addhistory(@_);
483             }
484              
485             =head2 IN()
486              
487             returns the filehandle for input.
488              
489             =cut
490             sub IN
491             {
492 0     0 1   my $self = shift;
493 0           return $self->{IN};
494             }
495              
496             =head2 OUT()
497              
498             returns the filehandle for output.
499              
500             =cut
501             sub OUT
502             {
503 0     0 1   my $self = shift;
504 0           return $self->{OUT};
505             }
506              
507             =head2 MinLine([$minline])
508              
509             B
510              
511             If argument is specified, it is an advice on minimal size of line to be included into history.
512             C means do not include anything into history (autohistory off).
513              
514             Returns the old value.
515              
516             =cut
517             sub MinLine
518             {
519 0     0 1   my $self = shift;
520 0           my ($minline) = @_;
521 0           my $result = $self->{features}->{minline};
522 0 0         $self->{features}->{minline} = $minline if @_ >= 1;
523 0           $self->{features}->{autohistory} = defined($self->{features}->{minline});
524 0           return $result;
525             }
526             sub minline
527             {
528 0     0 0   return MinLine(@_);
529             }
530              
531             =head2 findConsole()
532              
533             returns an array with two strings that give most appropriate names for files for input and output using conventions C<"<$in">, C<">out">.
534              
535             =cut
536             sub findConsole
537             {
538 0     0 1   return (Term::ReadLine::Stub::findConsole(@_));
539             }
540              
541             =head2 Attribs()
542              
543             returns a reference to a hash which describes internal configuration of the package. B
544              
545             =cut
546             sub Attribs
547             {
548 0     0 1   return {};
549             }
550              
551             =head2 Features()
552              
553             Returns a reference to a hash with keys being features present in current implementation.
554             This features are present:
555              
556             =over
557              
558             =item *
559              
560             I is not present and is the name of the application. B
561              
562             =item *
563              
564             I is present, always C.
565              
566             =item *
567              
568             I is present, default 1. See C method.
569              
570             =item *
571              
572             I is present. C if minline is C. See C method.
573              
574             =item *
575              
576             I is present, always C.
577              
578             =item *
579              
580             I is present, always C.
581              
582             =item *
583              
584             I is present, default C. See C method.
585              
586             =item *
587              
588             I is present, default C. See C method.
589              
590             =back
591              
592             =cut
593             sub Features
594             {
595 0     0 1   my $self = shift;
596 0           my %features = %{$self->{features}};
  0            
597 0           return \%features;
598             }
599              
600             =head1 Additional Methods and Functions
601              
602             =cut
603              
604             =head2 newTTY([$IN[, $OUT]])
605              
606             takes two arguments which are input filehandle and output filehandle. Switches to use these filehandles.
607              
608             =cut
609             sub newTTY
610             {
611 0     0 1   my $self = shift;
612 0           my ($IN, $OUT) = @_;
613              
614 0           my ($console, $consoleOUT) = findConsole();
615 0   0       my $console_utf8 = defined($ENV{LANG}) && $ENV{LANG} =~ /\.UTF\-?8$/i;
616 0           my $console_layers = "";
617 0 0         $console_layers .= " :utf8" if $console_utf8;
618              
619 0           my $in;
620 0 0         $in = $IN if ref($IN) eq "GLOB";
621 0 0         $in = \$IN if ref(\$IN) eq "GLOB";
622 0 0         open($in, "<$console_layers", $console) unless defined($in);
623 0 0         $in = \*STDIN unless defined($in);
624 0           $self->{IN} = $in;
625              
626 0           my $out;
627 0 0         $out = $OUT if ref($OUT) eq "GLOB";
628 0 0         $out = \$OUT if ref(\$OUT) eq "GLOB";
629 0 0         open($out, ">$console_layers", $consoleOUT) unless defined($out);
630 0 0         $out = \*STDOUT unless defined($out);
631 0           $self->{OUT} = $out;
632              
633 0           return ($self->{IN}, $self->{OUT});
634             }
635              
636             =head2 ornaments
637              
638             This is void implementation. Ornaments is B.
639              
640             =cut
641             sub ornaments
642             {
643 0     0 1   return;
644             }
645              
646             =head2 gethistory()
647              
648             B
649              
650             Returns copy of the history in Array.
651              
652             =cut
653             sub gethistory
654             {
655 0     0 1   my $self = shift;
656 0           my @result = @{$self->{history}};
  0            
657 0 0         if ($self->{features}->{utf8})
658             {
659 0           for (my $i = 0; $i < @result; $i++)
660             {
661 0 0         utf8::encode($result[$i]) if utf8::is_utf8($result[$i]);
662             }
663             }
664 0           return @result;
665             }
666             sub GetHistory
667             {
668 0     0 0   return gethistory(@_);
669             }
670              
671             =head2 sethistory($line1[, $line2[, ...]])
672              
673             B
674              
675             rewrites all history by argument values.
676              
677             =cut
678             sub sethistory
679             {
680 0     0 1   my $self = shift;
681 0 0         if (grep(":utf8", PerlIO::get_layers($self->{IN})))
682             {
683 0           for (my $i = 0; $i < @_; $i++)
684             {
685 0           utf8::decode($_[$i]);
686             }
687             }
688 0           @{$self->{history}} = @_;
  0            
689 0           return 1;
690             }
691             sub SetHistory
692             {
693 0     0 0   return sethistory(@_);
694             }
695              
696             =head2 changehistory([$changehistory])
697              
698             If argument is specified, it allows to change history lines when argument value is true.
699              
700             Returns the old value.
701              
702             =cut
703             sub changehistory
704             {
705 0     0 1   my $self = shift;
706 0           my ($changehistory) = @_;
707 0           my $result = $self->{features}->{changehistory};
708 0 0         $self->{features}->{changehistory} = $changehistory if @_ >= 1;
709 0           return $result;
710             }
711              
712             =head1 Other Methods and Functions
713              
714             =cut
715              
716             =head2 readkey([$echo])
717              
718             reads a key from input and echoes if I argument is C.
719              
720             Returns C on C.
721              
722             =cut
723             sub readkey
724             {
725 0     0 1   my $self = shift;
726 0           my ($echo) = @_;
727             my ($in, $out) =
728 0           ($self->{IN}, $self->{OUT});
729 0 0         unless (-t $in)
730             {
731 0           return getc($in);
732             }
733 0           local $\ = undef;
734              
735 0           $self->{readmode} = 'cbreak';
736 0           Term::ReadKey::ReadMode($self->{readmode}, $self->{IN});
737              
738 0           my $result;
739 0           my ($char, $esc) = ("", undef);
740 0           while (defined($char = getc($in)))
741             {
742 0 0         unless (defined($esc))
743             {
744 0           given ($char)
745             {
746             when (/\e/)
747 0           {
748 0           $esc = "";
749             }
750             when (/\x04/)
751 0           {
752 0           $result = undef;
753 0           last;
754             }
755             default
756 0           {
757 0 0         print $out encode_controlchar($char) if $echo;
758 0           $result = $char;
759 0           last;
760             }
761             }
762 0           next;
763             }
764 0           $esc .= $char;
765 0 0         if ($esc =~ /^.\d?\D/)
766             {
767 0           $result = "\e$esc";
768 0           $esc = undef;
769 0           last;
770             }
771             }
772 0 0 0       utf8::encode($result) if defined($result) and utf8::is_utf8($result) and $self->{features}->{utf8};
      0        
773              
774 0           Term::ReadKey::ReadMode('restore', $self->{IN});
775 0           $self->{readmode} = '';
776 0           return $result;
777             }
778              
779             =head2 utf8([$enable])
780              
781             If C<$enable> is C, all read methods return that binary encoded UTF-8 string as possible.
782              
783             Returns the old value.
784              
785             =cut
786             sub utf8
787             {
788 0     0 1   my $self = shift;
789 0           my ($enable) = @_;
790 0           my $result = $self->{features}->{utf8};
791 0 0         $self->{features}->{utf8} = $enable if @_ >= 1;
792 0           return $result;
793             }
794              
795             =head2 encode_controlchar($c)
796              
797             encodes if argument C<$c> is a control character, otherwise returns argument C.
798              
799             =cut
800             sub encode_controlchar
801             {
802 0     0 1   my ($c) = @_;
803 0           $c = substr($c, 0, 1);
804 0           my $s;
805 0           given ($c)
806             {
807             when (/[\x00-\x1F]/)
808 0           {
809 0           $s = "^".chr(0x40+ord($c));
810             }
811             when ($c =~ /[\x7F]/)
812 0           {
813 0           $s = "^".chr(0x3F);
814             }
815             default
816 0           {
817 0           $s = $c;
818             }
819             }
820 0           return $s;
821             }
822              
823              
824             1;
825             __END__