File Coverage

blib/lib/Term/ReadLine/Tiny.pm
Criterion Covered Total %
statement 19 341 5.5
branch 0 82 0.0
condition 0 6 0.0
subroutine 6 41 14.6
pod 17 21 80.9
total 42 491 8.5


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.07
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   13700 use strict;
  1         2  
  1         26  
65 1     1   4 use warnings;
  1         2  
  1         23  
66 1     1   12 use v5.10.1;
  1         5  
67 1     1   4 use feature qw(switch);
  1         2  
  1         102  
68 1     1   504 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   117 require Exporter;
78 1         3 our $VERSION = '1.07';
79 1         7 our @ISA = qw(Exporter);
80 1         2 our @EXPORT = qw();
81 1         2888 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              
127 0           $self->newTTY($IN, $OUT);
128              
129 0           return $self;
130             }
131              
132             sub DESTROY
133             {
134 0     0     my $self = shift;
135 0 0         if ($self->{readmode})
136             {
137 0           Term::ReadKey::ReadMode('restore', $self->{IN});
138 0           $self->{readmode} = '';
139             }
140             }
141              
142             =head2 readline([$prompt[, $default]])
143              
144             interactively gets an input line. Trailing newline is removed.
145              
146             Returns C on C.
147              
148             =cut
149             sub readline
150             {
151 0     0 1   my $self = shift;
152 0           my ($prompt, $default) = @_;
153 0 0         $prompt = "" unless defined($prompt);
154 0 0         $default = "" unless defined($default);
155             my ($in, $out, $history, $minline, $changehistory) =
156 0           ($self->{IN}, $self->{OUT}, $self->{history}, $self->{features}->{minline}, $self->{features}->{changehistory});
157 0 0         unless (-t $in)
158             {
159 0           my $line = <$in>;
160 0 0         chomp $line if defined $line;
161 0           return $line;
162             }
163 0           local $\ = undef;
164              
165 0           $self->{readmode} = 'cbreak';
166 0           Term::ReadKey::ReadMode($self->{readmode}, $self->{IN});
167              
168 0           my @line;
169 0           my ($line, $index) = ("", 0);
170 0           my $history_index;
171 0           my $ins_mode = 0;
172              
173             my $write = sub {
174 0     0     my ($text, $ins) = @_;
175 0           my $s;
176 0           my @a = @line[$index..$#line];
177 0           my $a = substr($line, $index);
178 0           @line = @line[0..$index-1];
179 0           $line = substr($line, 0, $index);
180 0           print $out " ";
181 0           print $out "\e[D";
182 0           print $out "\e[J";
183 0           for my $c (split("", $text))
184             {
185 0           $s = encode_controlchar($c);
186 0 0         unless ($ins)
187             {
188 0           print $out $s;
189 0           push @line, $s;
190 0           $line .= $c;
191             } else
192             {
193 0           my $i = $index-length($line);
194 0           $a[$i] = $s;
195 0           substr($a, $i, 1) = $c;
196             }
197 0           $index++;
198             }
199 0 0         unless ($ins)
200             {
201 0           $s = join("", @a);
202 0           print $out $s;
203 0           print $out "\e[D" x length($s);
204             } else
205             {
206 0           $s = join("", @a);
207 0           print $out $s;
208 0           print $out "\e[D" x (length($s) - length(join("", @a[0..length($text)-1])));
209             }
210 0           push @line, @a;
211 0           $line .= $a;
212 0 0         if ($index >= length($line))
213             {
214 0           print $out " ";
215 0           print $out "\e[D";
216 0           print $out "\e[J";
217             }
218 0           };
219             my $print = sub {
220 0     0     my ($text) = @_;
221 0           $write->($text, $ins_mode);
222 0           };
223             my $set = sub {
224 0     0     my ($text) = @_;
225 0           print $out "\e[D" x length(join("", @line[0..$index-1]));
226 0           print $out "\e[J";
227 0           @line = ();
228 0           $line = "";
229 0           $index = 0;
230 0           $write->($text);
231 0           };
232             my $backspace = sub {
233 0 0   0     return if $index <= 0;
234 0           my @a = @line[$index..$#line];
235 0           my $a = substr($line, $index);
236 0           $index--;
237 0           print $out "\e[D" x length($line[$index]);
238 0           @line = @line[0..$index-1];
239 0           $line = substr($line, 0, $index);
240 0           $write->($a);
241 0           print $out "\e[D" x length(join("", @a));
242 0           $index -= scalar(@a);
243 0           };
244             my $delete = sub {
245 0     0     my @a = @line[$index+1..$#line];
246 0           my $a = substr($line, $index+1);
247 0           @line = @line[0..$index-1];
248 0           $line = substr($line, 0, $index);
249 0           $write->($a);
250 0           print $out "\e[D" x length(join("", @a));
251 0           $index -= scalar(@a);
252 0           };
253             my $home = sub {
254 0     0     print $out "\e[D" x length(join("", @line[0..$index-1]));
255 0           $index = 0;
256 0           };
257             my $end = sub {
258 0     0     my @a = @line[$index..$#line];
259 0           my $a = substr($line, $index);
260 0           @line = @line[0..$index-1];
261 0           $line = substr($line, 0, $index);
262 0           $write->($a);
263 0           };
264             my $left = sub {
265 0 0   0     return if $index <= 0;
266 0           print $out "\e[D" x length($line[$index-1]);
267 0           $index--;
268 0           };
269             my $right = sub {
270 0 0   0     return if $index >= length($line);
271 0           print $out $line[$index];
272 0           $index++;
273 0 0         if ($index >= length($line))
274             {
275 0           print $out " ";
276 0           print $out "\e[D";
277 0           print $out "\e[J";
278             } else
279             {
280 0           print $out $line[$index];
281 0           print $out "\e[D" x length($line[$index]);
282             }
283 0           };
284             my $up = sub {
285 0 0   0     return if $history_index <= 0;
286 0 0         $history->[$history_index] = $line if $changehistory;
287 0           $history_index--;
288 0           $set->($history->[$history_index]);
289 0           };
290             my $down = sub {
291 0 0   0     return if $history_index >= $#$history;
292 0 0         $history->[$history_index] = $line if $changehistory;
293 0           $history_index++;
294 0           $set->($history->[$history_index]);
295 0           };
296             my $pageup = sub {
297 0 0   0     return if $history_index <= 0;
298 0 0         $history->[$history_index] = $line if $changehistory;
299 0           $history_index = 0;
300 0           $set->($history->[$history_index]);
301 0           };
302             my $pagedown = sub {
303 0 0   0     return if $history_index >= $#$history;
304 0 0         $history->[$history_index] = $line if $changehistory;
305 0           $history_index = $#$history;
306 0           $set->($history->[$history_index]);
307 0           };
308              
309 0           print $prompt;
310 0           $set->($default);
311 0           push @$history, $line;
312 0           $history_index = $#$history;
313              
314 0           my $result = undef;
315 0           my ($char, $esc) = ("", undef);
316 0           while (defined($char = getc($in)))
317             {
318 0 0         unless (defined($esc))
319             {
320 0           given ($char)
321             {
322             when (/\e/)
323 0           {
324 0           $esc = "";
325             }
326             when (/\x01/) # ^A
327 0           {
328 0           $home->();
329             }
330             when (/\x04/) # ^D
331 0           {
332 0           $result = undef;
333 0           last;
334             }
335             when (/\x05/) # ^E
336 0           {
337 0           $end->();
338             }
339             when (/\t/) # ^I
340 0           {
341 0           for (my $i = $history_index; $i >= 0; $i--)
342             {
343 0 0         if ($history->[$i] =~ /^$line/)
344             {
345 0           $set->($history->[$i]);
346 0           last;
347             }
348             }
349             }
350             when (/\n|\r/)
351 0           {
352 0           print $out $char;
353 0           $history->[$#$history] = $line;
354 0 0 0       pop @$history unless defined($minline) and length($line) >= $minline;
355 0           $result = $line;
356 0           last;
357             }
358             when (/[\b]|\x7F/)
359 0           {
360 0           $backspace->();
361             }
362             when (/[\x00-\x1F]|\x7F/)
363 0           {
364 0           $print->($char);
365             }
366             default
367 0           {
368 0           $print->($char);
369             }
370             }
371 0           next;
372             }
373 0           $esc .= $char;
374 0 0         if ($esc =~ /^.\d?\D/)
375             {
376 0           given ($esc)
377             {
378             when (/^\[(A|0A)/)
379 0           {
380 0           $up->();
381             }
382             when (/^\[(B|0B)/)
383 0           {
384 0           $down->();
385             }
386             when (/^\[(C|0C)/)
387 0           {
388 0           $right->();
389             }
390             when (/^\[(D|0D)/)
391 0           {
392 0           $left->();
393             }
394             when (/^\[(H|0H)/)
395 0           {
396 0           $home->();
397             }
398             when (/^\[(F|0F)/)
399 0           {
400 0           $end->();
401             }
402             when (/^\[(\d)~/)
403 0           {
404 0           given ($1)
405             {
406             when (1)
407 0           {
408 0           $home->();
409             }
410             when (2)
411 0           {
412 0           $ins_mode = not $ins_mode;
413             }
414             when (3)
415 0           {
416 0           $delete->();
417             }
418             when (4)
419 0           {
420 0           $end->();
421             }
422             when (5)
423 0           {
424 0           $pageup->();
425             }
426             when (6)
427 0           {
428 0           $pagedown->();
429             }
430             when (7)
431 0           {
432 0           $home->();
433             }
434             when (8)
435 0           {
436 0           $end->();
437             }
438             default
439 0           {
440             #$print->("\e$esc");
441             }
442             }
443             }
444             default
445 0           {
446             #$print->("\e$esc");
447             }
448             }
449 0           $esc = undef;
450             }
451             }
452              
453 0           Term::ReadKey::ReadMode('restore', $self->{IN});
454 0           $self->{readmode} = '';
455 0           return $result;
456             }
457              
458             =head2 addhistory($line1[, $line2[, ...]])
459              
460             B
461              
462             adds lines to the history of input.
463              
464             =cut
465             sub addhistory
466             {
467 0     0 1   my $self = shift;
468 0 0         if ($self->{features}->{utf8})
469             {
470 0           for (my $i = 0; $i < @_; $i++)
471             {
472 0           utf8::decode($_[$i]);
473             }
474             }
475 0           push @{$self->{history}}, @_;
  0            
476 0           return (@_);
477             }
478             sub AddHistory
479             {
480 0     0 0   return addhistory(@_);
481             }
482              
483             =head2 IN()
484              
485             returns the filehandle for input.
486              
487             =cut
488             sub IN
489             {
490 0     0 1   my $self = shift;
491 0           return $self->{IN};
492             }
493              
494             =head2 OUT()
495              
496             returns the filehandle for output.
497              
498             =cut
499             sub OUT
500             {
501 0     0 1   my $self = shift;
502 0           return $self->{OUT};
503             }
504              
505             =head2 MinLine([$minline])
506              
507             B
508              
509             If argument is specified, it is an advice on minimal size of line to be included into history.
510             C means do not include anything into history (autohistory off).
511              
512             Returns the old value.
513              
514             =cut
515             sub MinLine
516             {
517 0     0 1   my $self = shift;
518 0           my ($minline) = @_;
519 0           my $result = $self->{features}->{minline};
520 0 0         $self->{features}->{minline} = $minline if @_ >= 1;
521 0           $self->{features}->{autohistory} = defined($self->{features}->{minline});
522 0           return $result;
523             }
524             sub minline
525             {
526 0     0 0   return MinLine(@_);
527             }
528              
529             =head2 findConsole()
530              
531             returns an array with two strings that give most appropriate names for files for input and output using conventions C<"<$in">, C<">out">.
532              
533             =cut
534             sub findConsole
535             {
536 0     0 1   return (Term::ReadLine::Stub::findConsole(@_));
537             }
538              
539             =head2 Attribs()
540              
541             returns a reference to a hash which describes internal configuration of the package. B
542              
543             =cut
544             sub Attribs
545             {
546 0     0 1   return {};
547             }
548              
549             =head2 Features()
550              
551             Returns a reference to a hash with keys being features present in current implementation.
552             This features are present:
553              
554             =over
555              
556             =item *
557              
558             I is not present and is the name of the application. B
559              
560             =item *
561              
562             I is present, always C.
563              
564             =item *
565              
566             I is present, default 1. See C method.
567              
568             =item *
569              
570             I is present. C if minline is C. See C method.
571              
572             =item *
573              
574             I is present, always C.
575              
576             =item *
577              
578             I is present, always C.
579              
580             =item *
581              
582             I is present, default C. See C method.
583              
584             =item *
585              
586             I is present. C if input file handle has C<:utf8> layer.
587              
588             =back
589              
590             =cut
591             sub Features
592             {
593 0     0 1   my $self = shift;
594 0           my %features = %{$self->{features}};
  0            
595 0           return \%features;
596             }
597              
598             =head1 Additional Methods and Functions
599              
600             =cut
601              
602             =head2 newTTY([$IN[, $OUT]])
603              
604             takes two arguments which are input filehandle and output filehandle. Switches to use these filehandles.
605              
606             =cut
607             sub newTTY
608             {
609 0     0 1   my $self = shift;
610 0           my ($IN, $OUT) = @_;
611              
612 0           my ($console, $consoleOUT) = findConsole();
613 0   0       my $console_utf8 = defined($ENV{LANG}) && $ENV{LANG} =~ /\.UTF\-?8$/i;
614 0           my $console_layers = "";
615 0 0         $console_layers .= " :utf8" if $console_utf8;
616              
617 0           my $in;
618 0 0         $in = $IN if ref($IN) eq "GLOB";
619 0 0         $in = \$IN if ref(\$IN) eq "GLOB";
620 0 0         open($in, "<$console_layers", $console) unless defined($in);
621 0 0         $in = \*STDIN unless defined($in);
622 0           $self->{IN} = $in;
623              
624 0           my $out;
625 0 0         $out = $OUT if ref($OUT) eq "GLOB";
626 0 0         $out = \$OUT if ref(\$OUT) eq "GLOB";
627 0 0         open($out, ">$console_layers", $consoleOUT) unless defined($out);
628 0 0         $out = \*STDOUT unless defined($out);
629 0           $self->{OUT} = $out;
630              
631 0           $self->{features}->{utf8} = grep(":utf8", PerlIO::get_layers($in));
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           return @{$self->{history}};
  0            
657             }
658             sub GetHistory
659             {
660 0     0 0   return gethistory(@_);
661             }
662              
663             =head2 sethistory($line1[, $line2[, ...]])
664              
665             B
666              
667             rewrites all history by argument values.
668              
669             =cut
670             sub sethistory
671             {
672 0     0 1   my $self = shift;
673 0 0         if ($self->{features}->{utf8})
674             {
675 0           for (my $i = 0; $i < @_; $i++)
676             {
677 0           utf8::decode($_[$i]);
678             }
679             }
680 0           @{$self->{history}} = @_;
  0            
681 0           return 1;
682             }
683             sub SetHistory
684             {
685 0     0 0   return sethistory(@_);
686             }
687              
688             =head2 changehistory([$changehistory])
689              
690             If argument is specified, it allows to change history lines when argument value is true.
691              
692             Returns the old value.
693              
694             =cut
695             sub changehistory
696             {
697 0     0 1   my $self = shift;
698 0           my ($changehistory) = @_;
699 0           my $result = $self->{features}->{changehistory};
700 0 0         $self->{features}->{changehistory} = $changehistory if @_ >= 1;
701 0           return $result;
702             }
703              
704             =head1 Other Methods and Functions
705              
706             =cut
707              
708             =head2 readkey([$echo])
709              
710             reads a key from input and echoes if I argument is C.
711              
712             Returns C on C.
713              
714             =cut
715             sub readkey
716             {
717 0     0 1   my $self = shift;
718 0           my ($echo) = @_;
719             my ($in, $out) =
720 0           ($self->{IN}, $self->{OUT});
721 0 0         unless (-t $in)
722             {
723 0           return getc($in);
724             }
725 0           local $\ = undef;
726              
727 0           $self->{readmode} = 'cbreak';
728 0           Term::ReadKey::ReadMode($self->{readmode}, $self->{IN});
729              
730 0           my $result;
731 0           my ($char, $esc) = ("", undef);
732 0           while (defined($char = getc($in)))
733             {
734 0 0         unless (defined($esc))
735             {
736 0           given ($char)
737             {
738             when (/\e/)
739 0           {
740 0           $esc = "";
741             }
742             when (/\x04/)
743 0           {
744 0           $result = undef;
745 0           last;
746             }
747             default
748 0           {
749 0 0         print $out encode_controlchar($char) if $echo;
750 0           $result = $char;
751 0           last;
752             }
753             }
754 0           next;
755             }
756 0           $esc .= $char;
757 0 0         if ($esc =~ /^.\d?\D/)
758             {
759 0           $result = "\e$esc";
760 0           $esc = undef;
761 0           last;
762             }
763             }
764              
765 0           Term::ReadKey::ReadMode('restore', $self->{IN});
766 0           $self->{readmode} = '';
767 0           return $result;
768             }
769              
770             =head2 encode_controlchar($c)
771              
772             encodes if argument C is a control character, otherwise returns argument C.
773              
774             =cut
775             sub encode_controlchar
776             {
777 0     0 1   my ($c) = @_;
778 0           $c = substr($c, 0, 1);
779 0           my $s;
780 0           given ($c)
781             {
782             when (/[\x00-\x1F]/)
783 0           {
784 0           $s = "^".chr(0x40+ord($c));
785             }
786             when ($c =~ /[\x7F]/)
787 0           {
788 0           $s = "^".chr(0x3F);
789             }
790             default
791 0           {
792 0           $s = $c;
793             }
794             }
795 0           return $s;
796             }
797              
798              
799             1;
800             __END__