File Coverage

blib/lib/Term/Prompt.pm
Criterion Covered Total %
statement 18 271 6.6
branch 0 196 0.0
condition 0 74 0.0
subroutine 6 16 37.5
pod 2 10 20.0
total 26 567 4.5


line stmt bran cond sub pod time code
1             package Term::Prompt;
2            
3 1     1   25227 use 5.006001;
  1         3  
  1         36  
4 1     1   5 use strict;
  1         2  
  1         31  
5 1     1   5 use warnings;
  1         5  
  1         133  
6            
7             require Exporter;
8            
9             our @ISA = qw (Exporter);
10             our @EXPORT_OK = qw (rangeit legalit typeit menuit exprit yesit coderefit termwrap);
11             our @EXPORT = qw (prompt);
12             our $VERSION = '1.04';
13            
14             our $DEBUG = 0;
15             our $MULTILINE_INDENT = "\t";
16            
17 1     1   6 use Carp;
  1         2  
  1         98  
18 1     1   1001 use Text::Wrap;
  1         3887  
  1         64  
19 1         3921 use Term::ReadKey qw (GetTerminalSize
20 1     1   1142 ReadMode);
  1         5687  
21            
22             my %menu = (
23             order => 'down',
24             return_base => 0,
25             display_base => 1,
26             accept_multiple_selections => 0,
27             accept_empty_selection => 0,
28             title => '',
29             prompt => '>',
30             separator => '[^0-9]+',
31             ignore_whitespace => 0,
32             ignore_empties => 0
33             );
34            
35             # Preloaded methods go here.
36            
37             sub prompt ($$$$;@) {
38            
39 0     0 1   my($mopt, $prompt, $prompt_options, $default, @things) =
40             ('','','',undef,());
41 0           my($repl, $match_options, $case, $low, $high, $before, $regexp, $coderef) =
42             ('','','','','','','','');
43 0           my $prompt_full = '';
44            
45             # Figure out just what we are doing here
46 0           $mopt = $_[0];
47 0 0         print "mopt is: $mopt\n" if $DEBUG;
48            
49             # check the size of the match option, it should just have one char.
50 0 0 0       if (length($mopt) == 1
      0        
51             or $mopt =~ /\-n/i
52             or $mopt =~ /\+-n/i) {
53 0           my $dummy = 'mopt is ok';
54             } else {
55 0           croak "Illegal call of prompt; $mopt is more than one character; stopped";
56             }
57            
58 0           my $type = 0;
59 0           my $menu = 0;
60 0           my $legal = 0;
61 0           my $range = 0;
62 0           my $expr = 0;
63 0           my $code = 0;
64 0           my $yn = 0;
65 0           my $uc = 0;
66 0           my $passwd = 0;
67            
68 0 0         if ($mopt ne lc($mopt)) {
69 0           $uc = 1;
70 0           $mopt = lc($mopt);
71             }
72            
73 0 0 0       if ($mopt eq 'x' || $mopt eq 'a' || ($mopt =~ /n$/) || $mopt eq 'f') {
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
74             # More efficient this way - Allen
75 0           ($mopt, $prompt, $prompt_options, $default) = @_;
76 0           $type = 1;
77             } elsif ($mopt eq 'm') {
78 0           ($mopt, $prompt, $prompt_options, $default) = @_;
79 0           $menu = 1;
80             } elsif ($mopt eq 'c' || $mopt eq 'i') {
81 0           ($mopt, $prompt, $prompt_options, $default, @things) = @_;
82 0           $legal = 1;
83             } elsif ($mopt eq 'r') {
84 0           ($mopt, $prompt, $prompt_options, $default, $low, $high) = @_;
85 0           $range = 1;
86             } elsif ($mopt eq 'e') {
87 0           ($mopt, $prompt, $prompt_options, $default, $regexp) = @_;
88 0           $expr = 1;
89             } elsif ($mopt eq 's') {
90 0           ($mopt, $prompt, $prompt_options, $default, $coderef) = @_;
91 0 0         ref($coderef) eq 'CODE' || die('No valid code reference supplied');
92 0           $code = 1;
93             } elsif ($mopt eq 'y') {
94 0           ($mopt, $prompt, $prompt_options, $default) = @_;
95 0           $yn = 1;
96 0 0 0       unless (defined($prompt_options) && length($prompt_options)) {
97 0 0         if ($uc) {
98 0           $prompt_options = 'Enter y or n';
99             } else {
100 0           $prompt_options = 'y or n';
101             }
102             }
103            
104 0 0         if (defined($default)) {
105 0 0         unless ($default =~ m/^[ynYN]/) {
106 0 0         if ($default) {
107 0           $default = 'y';
108             } else {
109 0           $default = 'n';
110             }
111             }
112             } else {
113 0           $default = 'n';
114             }
115             } elsif ($mopt eq 'p') {
116 0           ($mopt, $prompt, $prompt_options, $default) = @_;
117 0           $passwd = 1;
118             } else {
119 0           croak "prompt type $mopt not recognized";
120             }
121            
122 0           my $ok = 0;
123            
124 0           $mopt = lc($mopt);
125            
126 0           while (1) {
127            
128 0 0         if (!$menu) {
129            
130             # print out the prompt string in all its gore
131 0           $prompt_full = "$prompt ";
132            
133             } else {
134            
135             ## We're working on a menu
136 0           @menu{sort keys %{$prompt}} = @{$prompt}{sort keys %{$prompt}};
  0            
  0            
  0            
137            
138 0           $prompt_full = "$menu{'prompt'} ";
139            
140 0           my @menu_items = @{$menu{'items'}};
  0            
141 0           my $number_menu_items = scalar(@menu_items);
142            
143 0           $menu{'low'} = $menu{'display_base'};
144 0           $menu{'high'} = $number_menu_items+$menu{'display_base'}-1;
145            
146 0           my $digits_in_menu_item = (int(log($menu{'high'})/log(10)) + 1);
147            
148 0           my $entry_length = 0;
149 0           my $item_length = 0;
150 0           for (@menu_items) {
151 0 0         $entry_length = length($_)
152             if length($_) > $entry_length;
153             }
154 0           $item_length = $entry_length;
155 0           $entry_length += ( $digits_in_menu_item ## Max number of digits in a selection
156             +
157             3 ## two for ') ', at least one for a column separator
158             );
159            
160 0           my $gw = get_width();
161            
162 0 0         my $num_cols = (defined($menu{'cols'})
163             ? $menu{'cols'}
164             : int($gw/$entry_length));
165 0   0       $num_cols ||= 1; # Could be zero if longest entry in a
166             # list is wider than the screen
167 0 0         my $num_rows = (defined($menu{'rows'})
168             ? $menu{'rows'}
169             : int($number_menu_items/$num_cols)+1) ;
170            
171 0           my $data_fmt = "%${digits_in_menu_item}d) %-${item_length}.${item_length}s";
172 0           my $column_end_fmt = ("%s ");
173 0           my $line_end_fmt = ("%s\n");
174 0           my @menu_out = ();
175 0           my $row = 0;
176 0           my $col = 0;
177 0           my $idx = 0;
178            
179 0 0         if ($menu{order} =~ /ACROSS/i) {
180             ACROSS_LOOP:
181 0           for ($row = 0; $row < $num_rows; $row++) {
182 0           for ($col = 0; $col < $num_cols; $col++) {
183 0           $menu_out[$row][$col] = sprintf($data_fmt,$idx+$menu{'display_base'},$menu_items[$idx++]);
184             last ACROSS_LOOP
185 0 0         if $idx eq scalar(@menu_items);
186             }
187             }
188             } else {
189             DOWN_LOOP:
190 0           for ($col = 0; $col < $num_cols; $col++) {
191 0           for ($row = 0; $row < $num_rows; $row++) {
192 0           $menu_out[$row][$col] = sprintf($data_fmt,$idx+$menu{'display_base'},$menu_items[$idx++]);
193             last DOWN_LOOP
194 0 0         if $idx eq scalar(@menu_items);
195             }
196             }
197             }
198            
199 0 0         if (length($menu{'title'})) {
200 0           print $menu{'title'},"\n",'-' x length($menu{'title'}),"\n";
201             }
202            
203 0           for ($row = 0;$row < $num_rows;$row++) {
204 0           for ($col = 0;$col < $num_cols-1;$col++) {
205 0 0         printf($column_end_fmt,$menu_out[$row][$col])
206             if defined($menu_out[$row][$col]);
207             }
208 0 0         if (defined($menu_out[$row][$num_cols-1])) {
209 0           printf($line_end_fmt,$menu_out[$row][$num_cols-1])
210             } else {
211 0           print "\n";
212             }
213             }
214            
215 0 0         if ($number_menu_items != ($num_rows)*($num_cols)) {
216 0           print "\n";
217             }
218            
219 0 0 0       unless (defined($prompt_options) && length($prompt_options)) {
220 0           $prompt_options = "$menu{'low'} - $menu{'high'}";
221 0 0         if ($menu{'accept_multiple_selections'}) {
222 0           $prompt_options .= ', separate multiple entries with spaces';
223             }
224             }
225             }
226            
227 0 0 0       unless ($before || $uc || ($prompt_options eq '')) {
      0        
228 0           $prompt_full .= "($prompt_options) ";
229             }
230            
231 0 0 0       if (defined($default) and $default ne '') {
232 0           $prompt_full .= "[default $default] ";
233             }
234            
235 0           print termwrap($prompt_full);
236 0           my $old_divide = undef;
237            
238 0 0         if (defined($/)) {
239 0           $old_divide = $/;
240             }
241            
242 0           $/ = "\n";
243            
244 0 0         ReadMode('noecho') if($passwd);
245 0           $repl = scalar(readline(*STDIN));
246 0 0         ReadMode('restore') if($passwd);
247            
248 0 0         if (defined($old_divide)) {
249 0           $/ = $old_divide;
250             } else {
251 0           undef($/);
252             }
253            
254 0           chomp($repl); # nuke the
255            
256 0           $repl =~ s/^\s*//; # ignore leading white space
257 0           $repl =~ s/\s*$//; # ignore trailing white space
258            
259 0 0         $repl = $default if $repl eq '';
260            
261 0 0 0       if (!$menu && ($repl eq '') && (! $uc)) {
      0        
262             # so that a simple return can be an end of a series of prompts - Allen
263 0           print "Invalid option\n";
264 0           next;
265             }
266            
267 0 0         print termwrap("Reply: '$repl'\n") if $DEBUG;
268            
269             # Now here is where things get real interesting
270 0           my @menu_repl = ();
271 0 0 0       if ($uc && ($repl eq '')) {
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
272 0           $ok = 1;
273             } elsif ($type || $passwd) {
274 0           $ok = typeit($mopt, $repl, $DEBUG, $uc);
275             } elsif ($menu) {
276 0           $ok = menuit(\@menu_repl, $repl, $DEBUG, $uc);
277             } elsif ($legal) {
278 0           ($ok,$repl) = legalit($mopt, $repl, $uc, @things);
279             } elsif ($range) {
280 0           $ok = rangeit($repl, $low, $high, $uc);
281             } elsif ($expr) {
282 0           $ok = exprit($repl, $regexp, $prompt_options, $uc, $DEBUG);
283             } elsif ($code) {
284 0           $ok = coderefit($repl, $coderef, $prompt_options, $uc, $DEBUG);
285             } elsif ($yn) {
286 0           ($ok,$repl) = yesit($repl, $uc, $DEBUG);
287             } else {
288 0           croak "No subroutine known for prompt type $mopt.";
289             }
290            
291 0 0 0       if ($ok) {
    0          
292 0 0         if ($menu) {
293 0 0         if ($menu{'accept_multiple_selections'}) {
294 0 0         return (wantarray ? @menu_repl : \@menu_repl);
295             } else {
296 0           return $menu_repl[0];
297             }
298             } else {
299 0           return $repl;
300             }
301             } elsif (defined($prompt_options) && length($prompt_options)) {
302 0 0         if ($uc) {
303 0           print termwrap("$prompt_options\n");
304             } else {
305 0 0         if (!$menu) {
306 0           print termwrap("Options are: $prompt_options\n");
307             }
308 0           $before = 1;
309             }
310             }
311             }
312             }
313            
314             sub rangeit ($$$$ ) {
315             # this routine makes sure that the reply is within a given range
316            
317 0     0 0   my($repl, $low, $high, $uc) = @_;
318            
319 0 0 0       if ( $low <= $repl && $repl <= $high ) {
    0          
320 0           return 1;
321             } elsif (!$uc) {
322 0           print 'Invalid range value. ';
323             }
324 0           return 0;
325             }
326            
327             sub legalit ($$$@) {
328             # this routine checks to see if a repl is one of a set of 'things'
329             # it checks case based on c = case check, i = ignore case
330            
331 0     0 0   my($mopt, $repl, $uc, @things) = @_;
332 0           my(@match) = ();
333            
334 0 0         if (grep {$_ eq $repl} (@things)) {
  0            
335 0           return 1, $repl; # save time
336             }
337            
338 0           my $quote_repl = quotemeta($repl);
339            
340 0 0         if ($mopt eq 'i') {
341 0           @match = grep {$_ =~ m/^$quote_repl/i} (@things);
  0            
342             } else {
343 0           @match = grep {$_ =~ m/^$quote_repl/} (@things);
  0            
344             }
345            
346 0 0         if (scalar(@match) == 1) {
347 0           return 1, $match[0];
348             } else {
349 0 0         if (! $uc) {
350 0           print 'Invalid. ';
351             }
352 0           return 0, '';
353             }
354             }
355            
356             sub typeit ($$$$ ) {
357             # this routine does checks based on the following:
358             # x = no checks, a = alpha only, n = numeric only
359 0     0 0   my ($mopt, $repl, $dbg, $uc) = @_;
360 0 0         print "inside of typeit\n" if $dbg;
361            
362 0 0 0       if ( $mopt eq 'x' or $mopt eq 'p' ) {
    0          
    0          
    0          
    0          
    0          
363 0           return 1;
364             } elsif ( $mopt eq 'a' ) {
365 0 0         if ( $repl =~ /^[a-zA-Z]*$/ ) {
    0          
366 0           return 1;
367             } elsif (! $uc) {
368 0           print 'Invalid type value. ';
369             }
370             } elsif ( $mopt eq 'n' ) {
371 0 0         if ( $repl =~/^[0-9]*$/ ) {
    0          
372 0           return 1;
373             } elsif (! $uc) {
374 0           print 'Invalid numeric value. Must be a positive integer or 0. ';
375             }
376             } elsif ( $mopt eq '-n' ) {
377 0 0         if ( $repl =~/^-[0-9]*$/ ) {
    0          
378 0           return 1;
379             } elsif (! $uc) {
380 0           print 'Invalid numeric value. Must be a negative integer or 0. ';
381             }
382             } elsif ( $mopt eq '+-n' ) {
383 0 0         if ( $repl =~/^-?[0-9]*$/ ) {
    0          
384 0           return 1;
385             } elsif (! $uc) {
386 0           print 'Invalid numeric value. Must be an integer. ';
387             }
388             } elsif ( $mopt eq 'f' ) {
389 0 0         if ( $repl =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d)?([Ee]([+-]?\d+))?$/) {
    0          
390 0           return 1;
391             } elsif (! $uc) {
392 0           print 'Invalid floating point value. ';
393             }
394             } else {
395 0           croak "typeit called with unknown prompt type $mopt; stopped";
396             }
397            
398 0           return 0;
399             }
400            
401             sub menuit (\@$$$ ) {
402 0     0 0   my ($ra_repl, $repl, $dbg, $uc) = @_;
403 0 0         print "inside of menuit\n" if $dbg;
404            
405 0           my @msgs = ();
406            
407             ## Parse for multiple values. Strip all whitespace if requested or
408             ## just strip leading and trailing whitespace to avoid a being
409             ## interpreted as separating empty choices.
410            
411 0 0         if($menu{'ignore_whitespace'}) {
412 0           $repl =~ s/\s+//g;
413             } else {
414 0           $repl =~ s/^(?:\s+)//;
415 0           $repl =~ s/(?:\s+)$//;
416             }
417            
418 0           my @repls = split(/$menu{'separator'}/,$repl);
419 0 0         if($menu{ignore_empties}) {
420 0           @repls = grep{length($_)} @repls;
  0            
421             }
422            
423             ## Validations
424 0 0 0       if ( scalar(@repls) > 1
    0 0        
425             &&
426             !$menu{'accept_multiple_selections'} ) {
427 0           push @msgs, 'Multiple choices not allowed.';
428             } elsif (!scalar(@repls)
429             &&
430             !$menu{'accept_empty_selection'}) {
431 0           push @msgs, 'You must make a selection.';
432             } else {
433 0           for (@repls) {
434 0 0         if ( !rangeit($_,$menu{'low'},$menu{'high'},1)) {
435 0           push @msgs, "$_ is an invalid choice.";
436             }
437             }
438             }
439            
440             ## Print errors or return values
441 0 0         if (scalar(@msgs)) {
442 0           print "\n",join("\n",@msgs),"\n\n";
443 0           return 0;
444             } else {
445 0           @{$ra_repl} = map {$_ - $menu{'display_base'} + $menu{'return_base'}} @repls;
  0            
  0            
446 0           return 1;
447             }
448            
449             }
450            
451             sub exprit ($$$$$ ) {
452             # This routine does checks based on whether something
453             # matches a supplied regexp - Allen
454 0     0 0   my($repl, $regexp, $prompt_options, $uc, $dbg) = @_;
455 0 0         print "inside of exprit\n" if $dbg;
456            
457 0 0 0       if ( $repl =~ /^$regexp$/ ) {
    0          
458 0           return 1;
459             } elsif ((!$uc) ||
460             (!defined($prompt_options)) || (!length($prompt_options))) {
461 0           print termwrap("Reply needs to match regular expression /^$regexp$/.\n");
462             }
463 0           return 0;
464             }
465            
466             sub coderefit ($$$$$ ) {
467             # Execute supplied code reference with reply as argument and examine
468             # sub-routine's return value
469 0     0 0   my($repl, $coderef, $prompt_options, $uc, $dbg) = @_;
470 0 0         print "inside of coderefit\n" if $dbg;
471            
472 0 0 0       if ( &$coderef($repl) ) {
    0          
473 0           return 1;
474             } elsif ((!$uc) ||
475             (!defined($prompt_options)) || (!length($prompt_options))) {
476 0           print termwrap("Reply is invalid.\n");
477             }
478 0           return 0;
479             }
480            
481             sub yesit ($$$ ) {
482             # basic yes or no - Allen
483 0     0 0   my ($repl, $uc, $dbg) = @_;
484 0 0         print "inside of yesit\n" if $dbg;
485            
486 0 0         if ($repl =~ m/^[0nN]/) {
    0          
    0          
487 0           return 1,0;
488             } elsif ($repl =~ m/^[1yY]/) {
489 0           return 1,1;
490             } elsif (! $uc) {
491 0           print 'Invalid yes or no response. ';
492             }
493 0           return 0,0;
494             }
495            
496             sub termwrap ($;@) {
497 0     0 1   my($message) = '';
498 0 0         if ($#_ > 0) {
499 0 0         if (defined($,)) {
500 0           $message = join($,,@_);
501             } else {
502 0           $message = join(' ',@_);
503             }
504             } else {
505 0           $message = $_[0];
506             }
507            
508 0           my $width = get_width();
509            
510 0 0 0       if (defined($width) && $width) {
511 0           $Text::Wrap::Columns = $width;
512             }
513            
514 0 0         if ($message =~ m/\n\Z/) {
515 0           $message = wrap('', $MULTILINE_INDENT, $message);
516 0           $message =~ s/\n*\Z/\n/;
517 0           return $message;
518             } else {
519 0           $message = wrap('', $MULTILINE_INDENT, $message);
520 0           $message =~ s/\n*\Z//;
521 0           return $message;
522             }
523             }
524            
525             sub get_width {
526            
527             ## The 'use strict' added above caused the calls
528             ## GetTerminalSize(STDOUT) and GetTerminalSize(STDERR) to fail in
529             ## compilation. The fix as to REMOVE the parens. It seems as if
530             ## this call works the same way as 'print' - if you need to
531             ## specify the filehandle, you don't use parens (and don't put a
532             ## comma after the filehandle, although that is irrelevant here.)
533            
534             ## SO DON'T PUT THEM BACK! :-)
535            
536             my($width) = eval {
537             local($SIG{__DIE__});
538             (GetTerminalSize(select))[0];
539             } || eval {
540             if (-T STDOUT) {
541             local($SIG{__DIE__});
542             return (GetTerminalSize STDOUT )[0];
543             } else {
544             return 0;
545             }
546             } || eval {
547             if (-T STDERR) {
548             local($SIG{__DIE__});
549             return (GetTerminalSize STDERR )[0];
550             } else {
551             return 0;
552             }
553             } || eval {
554             local($SIG{__DIE__});
555             (GetTerminalSize STDOUT )[0];
556 0   0 0 0   } || eval {
557             local($SIG{__DIE__});
558             (GetTerminalSize STDERR )[0];
559             };
560 0           return $width;
561             }
562            
563             1;
564            
565             # Autoload methods go after =cut, and are processed by the autosplit program.
566            
567             __END__