File Coverage

blib/lib/Term/ReadLine/Tiny.pm
Criterion Covered Total %
statement 19 355 5.3
branch 0 94 0.0
condition 0 20 0.0
subroutine 6 44 13.6
pod 19 23 82.6
total 44 536 8.2


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