| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 36 |  |  | 36 |  | 2940034 | use 5.010; | 
|  | 36 |  |  |  |  | 488 |  | 
| 2 |  |  |  |  |  |  | package IO::Prompter; | 
| 3 | 36 |  |  | 36 |  | 22261 | use utf8; | 
|  | 36 |  |  |  |  | 536 |  | 
|  | 36 |  |  |  |  | 190 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 36 |  |  | 36 |  | 1188 | use warnings; | 
|  | 36 |  |  |  |  | 73 |  | 
|  | 36 |  |  |  |  | 1397 |  | 
| 6 | 36 |  |  | 36 |  | 22857 | no if $] >= 5.018000, warnings => 'experimental'; | 
|  | 36 |  |  |  |  | 486 |  | 
|  | 36 |  |  |  |  | 222 |  | 
| 7 | 36 |  |  | 36 |  | 3258 | use strict; | 
|  | 36 |  |  |  |  | 82 |  | 
|  | 36 |  |  |  |  | 782 |  | 
| 8 | 36 |  |  | 36 |  | 178 | use Carp; | 
|  | 36 |  |  |  |  | 69 |  | 
|  | 36 |  |  |  |  | 2499 |  | 
| 9 | 36 |  |  | 36 |  | 32734 | use Contextual::Return qw< PUREBOOL BOOL SCALAR METHOD VOID LIST RETOBJ >; | 
|  | 36 |  |  |  |  | 665667 |  | 
|  | 36 |  |  |  |  | 222 |  | 
| 10 | 36 |  |  | 36 |  | 60611 | use Scalar::Util qw< openhandle looks_like_number >; | 
|  | 36 |  |  |  |  | 90 |  | 
|  | 36 |  |  |  |  | 2036 |  | 
| 11 | 36 |  |  | 36 |  | 16822 | use Symbol       qw< qualify_to_ref >; | 
|  | 36 |  |  |  |  | 29432 |  | 
|  | 36 |  |  |  |  | 2385 |  | 
| 12 | 36 |  |  | 36 |  | 15788 | use match::smart qw< match >; | 
|  | 36 |  |  |  |  | 206899 |  | 
|  | 36 |  |  |  |  | 317 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | our $VERSION = '0.005001'; | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | my $fake_input;     # Flag that we're faking input from the source | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | my $DEFAULT_TERM_WIDTH   = 80; | 
| 19 |  |  |  |  |  |  | my $DEFAULT_VERBATIM_KEY = "\cV"; | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | # Completion control... | 
| 22 |  |  |  |  |  |  | my $COMPLETE_DISPLAY_FIELDS = 4;  #...per line | 
| 23 |  |  |  |  |  |  | my $COMPLETE_DISPLAY_GAP    = 3;  #...spaces | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | my $COMPLETE_KEY  = $ENV{IO_PROMPTER_COMPLETE_KEY} // qq{\t}; | 
| 26 |  |  |  |  |  |  | my $COMPLETE_HIST = $ENV{IO_PROMPTER_HISTORY_KEY}  // qq{\cR}; | 
| 27 |  |  |  |  |  |  | my $COMPLETE_NEXT = qq{\cN}; | 
| 28 |  |  |  |  |  |  | my $COMPLETE_PREV = qq{\cP}; | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | my $COMPLETE_INIT  = qr{ [\Q$COMPLETE_KEY$COMPLETE_HIST\E] }xms; | 
| 31 |  |  |  |  |  |  | my $COMPLETE_CYCLE = qr{ [$COMPLETE_NEXT$COMPLETE_PREV] }xms; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | my %COMPLETE_MODE = ( | 
| 34 |  |  |  |  |  |  | $COMPLETE_KEY | 
| 35 |  |  |  |  |  |  | => [split /\s+/, $ENV{IO_PROMPTER_COMPLETE_MODES}//q{list+longest  full}], | 
| 36 |  |  |  |  |  |  | $COMPLETE_HIST | 
| 37 |  |  |  |  |  |  | => [split /\s+/, $ENV{IO_PROMPTER_HISTORY_MODES} // q{full}], | 
| 38 |  |  |  |  |  |  | ); | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | my $FAKE_ESC    = "\e"; | 
| 41 |  |  |  |  |  |  | my $FAKE_INSERT = "\cF"; | 
| 42 |  |  |  |  |  |  | my $MENU_ESC    = "\e"; | 
| 43 |  |  |  |  |  |  | my $MENU_MK     = '__M_E_N_U__'; | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | my %EDIT = ( | 
| 46 |  |  |  |  |  |  | BACK    => qq{\cB}, | 
| 47 |  |  |  |  |  |  | FORWARD => qq{\cF}, | 
| 48 |  |  |  |  |  |  | START   => qq{\cA}, | 
| 49 |  |  |  |  |  |  | END     => qq{\cE}, | 
| 50 |  |  |  |  |  |  | ); | 
| 51 |  |  |  |  |  |  | my $EDIT_KEY = '['.join(q{},values %EDIT).']'; | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | # Extracting key letters... | 
| 54 |  |  |  |  |  |  | my $KL_EXTRACT = qr{ (?| \[  ( [[:alnum:]]++ )  \] | 
| 55 |  |  |  |  |  |  | | \(  ( [[:alnum:]]++ )  \) | 
| 56 |  |  |  |  |  |  | | \<  ( [[:alnum:]]++ )  \> | 
| 57 |  |  |  |  |  |  | | \{  ( [[:alnum:]]++ )  \} | 
| 58 |  |  |  |  |  |  | ) | 
| 59 |  |  |  |  |  |  | }xms; | 
| 60 |  |  |  |  |  |  | my $KL_DEF_EXTRACT = qr{ \[  ( [[:alnum:]]++ )  \] }xms; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | # Auxiliary prompts for -Yes => N construct... | 
| 64 |  |  |  |  |  |  | my @YESNO_PROMPTS = ( | 
| 65 |  |  |  |  |  |  | q{Really?}, | 
| 66 |  |  |  |  |  |  | q{You're quite certain?}, | 
| 67 |  |  |  |  |  |  | q{Definitely?}, | 
| 68 |  |  |  |  |  |  | q{You mean it?}, | 
| 69 |  |  |  |  |  |  | q{You truly mean it?}, | 
| 70 |  |  |  |  |  |  | q{You're sure?}, | 
| 71 |  |  |  |  |  |  | q{Have you thought this through?}, | 
| 72 |  |  |  |  |  |  | q{You understand the consequences?}, | 
| 73 |  |  |  |  |  |  | ); | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # Remember returned values for history completion... | 
| 77 |  |  |  |  |  |  | my %history_cache; | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | # Track lexically-scoped default options and wrapper subs... | 
| 80 |  |  |  |  |  |  | my @lexical_options  = []; | 
| 81 |  |  |  |  |  |  | my @lexical_wrappers = []; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # Export the prompt() sub... | 
| 84 |  |  |  |  |  |  | sub import { | 
| 85 | 42 |  |  | 42 |  | 1520 | my (undef, $config_data, @other_args) = @_; | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | # Handle -argv requests... | 
| 88 | 42 | 50 | 66 |  |  | 393 | if (defined $config_data && $config_data eq '-argv') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 89 | 0 |  |  |  |  | 0 | scalar prompt(-argv, @other_args); | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | # Handle lexical options... | 
| 93 |  |  |  |  |  |  | elsif (ref $config_data eq 'ARRAY') { | 
| 94 | 3 |  |  |  |  | 6 | push @lexical_options, $config_data; | 
| 95 | 3 |  |  |  |  | 14 | $^H{'IO::Prompter::scope_number'} = $#lexical_options; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | # Handle lexical wrappers... | 
| 99 |  |  |  |  |  |  | elsif (ref $config_data eq 'HASH') { | 
| 100 | 2 |  |  |  |  | 4 | push @lexical_options, []; | 
| 101 | 2 |  |  |  |  | 5 | $lexical_wrappers[ $#lexical_options ] = $config_data; | 
| 102 | 2 |  |  |  |  | 8 | $^H{'IO::Prompter::scope_number'} = $#lexical_options; | 
| 103 | 2 |  |  |  |  | 3 | for my $subname (keys %{$config_data}) { | 
|  | 2 |  |  |  |  | 8 |  | 
| 104 | 2 |  |  |  |  | 3 | my @args = @{$config_data->{$subname}}; | 
|  | 2 |  |  |  |  | 5 |  | 
| 105 | 36 |  |  | 36 |  | 27395 | no strict 'refs'; | 
|  | 36 |  |  |  |  | 134 |  | 
|  | 36 |  |  |  |  | 1499 |  | 
| 106 | 36 |  |  | 36 |  | 297 | no warnings 'redefine'; | 
|  | 36 |  |  |  |  | 84 |  | 
|  | 36 |  |  |  |  | 6138 |  | 
| 107 | 2 |  |  |  |  | 7 | *{caller().'::'.$subname} = sub { | 
| 108 | 3 |  |  | 3 |  | 2648 | my $scope_number = (caller 0)[10]{'IO::Prompter::scope_number'}; | 
| 109 | 3 |  | 50 |  |  | 63 | return prompt(@{$lexical_wrappers[$scope_number]{$subname}//[]}, @_); | 
|  | 3 |  |  |  |  | 16 |  | 
| 110 | 2 |  |  |  |  | 9 | }; | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | # Handler faked input specifications... | 
| 115 |  |  |  |  |  |  | elsif (defined $config_data) { | 
| 116 | 2 |  |  |  |  | 4 | $fake_input = $config_data; | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 36 |  |  | 36 |  | 312 | no strict 'refs'; | 
|  | 36 |  |  |  |  | 85 |  | 
|  | 36 |  |  |  |  | 12437 |  | 
| 120 | 42 |  |  |  |  | 147 | *{caller().'::prompt'} = \&prompt; | 
|  | 42 |  |  |  |  | 189 |  | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | # Prompt for, read, vet, and return input... | 
| 124 |  |  |  |  |  |  | sub prompt { | 
| 125 |  |  |  |  |  |  | # Reclaim full control of print statements while prompting... | 
| 126 | 99 |  |  | 99 | 1 | 63796 | local $\ = ''; | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # Locate any lexical default options... | 
| 129 | 99 |  | 100 |  |  | 341 | my $hints_hash = (caller 0)[10] // {}; | 
| 130 | 99 |  | 100 |  |  | 2819 | my $scope_num = $hints_hash->{'IO::Prompter::scope_number'} // 0; | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | # Extract and sanitize configuration arguments... | 
| 133 | 99 |  |  |  |  | 173 | my $opt_ref = _decode_args(@{$lexical_options[$scope_num]}, @_); | 
|  | 99 |  |  |  |  | 346 |  | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | _warn( void => 'Useless use of prompt() in void context' ) | 
| 136 | 97 | 100 | 100 |  |  | 382 | if VOID && !$opt_ref->{-void}; | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | # Set up yesno prompts if required... | 
| 139 |  |  |  |  |  |  | my @yesno_prompts | 
| 140 | 97 | 50 | 100 |  |  | 2820 | = ($opt_ref->{-yesno}{count}//0) > 1 ? @YESNO_PROMPTS : (); | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | # Work out where the prompts go, and where the input comes from... | 
| 143 | 97 |  | 66 |  |  | 361 | my $in_filehandle  = $opt_ref->{-in}  // _open_ARGV(); | 
| 144 | 97 |  | 66 |  |  | 629 | my $out_filehandle = $opt_ref->{-out} // qualify_to_ref(select); | 
| 145 | 97 | 50 |  |  |  | 2881 | if (!openhandle $in_filehandle) { | 
| 146 | 0 | 0 |  |  |  | 0 | open my $fh, '<', $in_filehandle | 
| 147 |  |  |  |  |  |  | or _opt_err('Unacceptable', '-in', 'valid filehandle or filename'); | 
| 148 | 0 |  |  |  |  | 0 | $in_filehandle = $fh; | 
| 149 |  |  |  |  |  |  | } | 
| 150 | 97 | 50 |  |  |  | 364 | if (!openhandle $out_filehandle) { | 
| 151 | 0 | 0 |  |  |  | 0 | open my $fh, '>', $out_filehandle | 
| 152 |  |  |  |  |  |  | or _opt_err('Unacceptable', '-out', 'valid filehandle or filename'); | 
| 153 | 0 |  |  |  |  | 0 | $out_filehandle = $fh; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | # Track timeouts... | 
| 157 | 36 |  | 50 | 36 |  | 290 | my $in_pos = do { no warnings;  tell $in_filehandle } // 0; | 
|  | 36 |  |  |  |  | 78 |  | 
|  | 36 |  |  |  |  | 35662 |  | 
|  | 97 |  |  |  |  | 160 |  | 
|  | 97 |  |  |  |  | 445 |  | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | # Short-circuit if not valid handles... | 
| 160 | 97 | 50 | 33 |  |  | 532 | return if !openhandle($in_filehandle) || !openhandle($out_filehandle); | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | # Work out how they're arriving and departing... | 
| 163 | 97 | 50 | 33 |  |  | 560 | my $outputter_ref = -t $in_filehandle && -t $out_filehandle | 
| 164 |  |  |  |  |  |  | ? _std_printer_to($out_filehandle, $opt_ref) | 
| 165 |  |  |  |  |  |  | : _null_printer() | 
| 166 |  |  |  |  |  |  | ; | 
| 167 | 97 |  |  |  |  | 273 | my $inputter_ref = _generate_unbuffered_reader_from( | 
| 168 |  |  |  |  |  |  | $in_filehandle, $outputter_ref, $opt_ref | 
| 169 |  |  |  |  |  |  | ); | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | # Clear the screen if requested to... | 
| 172 | 97 | 50 |  |  |  | 343 | if ($opt_ref->{-wipe}) { | 
| 173 | 0 |  |  |  |  | 0 | $outputter_ref->(-nostyle => "\n" x 1000); | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | # Handle menu structures... | 
| 177 | 97 |  |  |  |  | 143 | my $input; | 
| 178 |  |  |  |  |  |  | eval { | 
| 179 |  |  |  |  |  |  | REPROMPT_YESNO: | 
| 180 | 97 | 100 |  |  |  | 274 | if ($opt_ref->{-menu}) { | 
| 181 |  |  |  |  |  |  | # Remember top of (possibly nested) menu... | 
| 182 | 1 |  |  |  |  | 3 | my @menu = ( $opt_ref->{-menu} ); | 
| 183 | 1 |  |  |  |  | 3 | my $top_prompt = $opt_ref->{-prompt}; | 
| 184 | 1 |  |  |  |  | 17 | $top_prompt =~ s{$MENU_MK}{$opt_ref->{-menu}{prompt}}xms; | 
| 185 | 1 |  |  |  |  | 4 | $menu[-1]{prompt} = $top_prompt; | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | MENU: | 
| 188 | 1 |  |  |  |  | 2 | while (1) { | 
| 189 |  |  |  |  |  |  | # Track the current level... | 
| 190 | 1 |  |  |  |  | 4 | $opt_ref->{-menu_curr_level} = $menu[-1]{value_for}; | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | # Show menu and retreive choice... | 
| 193 | 1 |  |  |  |  | 14 | $outputter_ref->(-style => $menu[-1]{prompt}); | 
| 194 | 1 |  |  |  |  | 4 | my $tag = $inputter_ref->($menu[-1]{constraint}); | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | # Handle a failure by exiting the loop... | 
| 197 | 1 | 50 |  |  |  | 4 | last MENU if !defined $tag; | 
| 198 | 1 |  |  |  |  | 7 | $tag =~ s{\A\s*(\S*).*}{$1}xms; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | # Handle  by moving up menu stack... | 
| 201 | 1 | 50 |  |  |  | 4 | if ($tag eq $MENU_ESC) { | 
| 202 | 0 |  |  |  |  | 0 | $input = undef; | 
| 203 | 0 | 0 |  |  |  | 0 | last MENU if @menu <= 1; | 
| 204 | 0 |  |  |  |  | 0 | pop @menu; | 
| 205 | 0 |  |  |  |  | 0 | next MENU; | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | # Handle defaults by selecting and ejecting... | 
| 209 | 1 | 50 | 33 |  |  | 8 | if ($tag =~ /\A\R?\Z/ && exists $opt_ref->{-def}) { | 
| 210 | 1 |  |  |  |  | 10 | $input = $tag; | 
| 211 | 1 |  |  |  |  | 5 | last MENU; | 
| 212 |  |  |  |  |  |  | } | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | # Otherwise, retrieve value for selected tag and exit if not a nested menu... | 
| 215 | 0 |  |  |  |  | 0 | $input = $menu[-1]{value_for}{$tag}; | 
| 216 | 0 | 0 |  |  |  | 0 | last MENU if !ref $input; | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | # Otherwise, go down the menu one level... | 
| 219 |  |  |  |  |  |  | push @menu, | 
| 220 |  |  |  |  |  |  | _build_menu($input, | 
| 221 |  |  |  |  |  |  | "Select from $menu[-1]{key_for}{$tag}: ", | 
| 222 |  |  |  |  |  |  | $opt_ref->{-number} || $opt_ref->{-integer} | 
| 223 | 0 |  | 0 |  |  | 0 | ); | 
| 224 | 0 |  |  |  |  | 0 | $menu[-1]{prompt} .= '> '; | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | # Otherwise, simply ask and ye shall receive... | 
| 229 |  |  |  |  |  |  | else { | 
| 230 | 96 |  |  |  |  | 345 | $outputter_ref->(-style => $opt_ref->{-prompt}); | 
| 231 | 96 |  |  |  |  | 255 | $input = $inputter_ref->(); | 
| 232 |  |  |  |  |  |  | } | 
| 233 | 97 |  |  |  |  | 313 | 1; | 
| 234 |  |  |  |  |  |  | } | 
| 235 | 97 |  | 33 |  |  | 228 | // do { | 
| 236 |  |  |  |  |  |  | # Supply the missing newline if requested... | 
| 237 |  |  |  |  |  |  | $outputter_ref->(-echostyle => $opt_ref->{-return}(q{})) | 
| 238 | 0 | 0 |  |  |  | 0 | if exists $opt_ref->{-return}; | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | # Rethrow any other exception... | 
| 241 | 0 |  |  |  |  | 0 | my $error = $@; | 
| 242 | 0 | 0 |  |  |  | 0 | die $@ unless ref($error) eq 'IO::Prompter::Cancellation'; | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | # Return failure on cancellation... | 
| 245 | 0 | 0 |  |  |  | 0 | return if $opt_ref->{-verbatim}; | 
| 246 | 0 |  |  | 0 |  | 0 | return PUREBOOL { 0 } | 
| 247 | 0 |  |  | 0 |  | 0 | BOOL { 0 } | 
| 248 | 0 |  |  | 0 |  | 0 | SCALAR { ${$error}  } | 
|  | 0 |  |  |  |  | 0 |  | 
| 249 | 0 |  |  | 0 |  | 0 | METHOD { defaulted => sub { 0 }, timedout  => sub { 0 } }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 250 |  |  |  |  |  |  | }; | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | # Provide default value if available and necessary... | 
| 253 | 97 |  |  |  |  | 171 | my $defaulted = 0; | 
| 254 | 97 | 50 | 100 |  |  | 654 | if (defined $input && $input =~ /\A\R?\Z/ && exists $opt_ref->{-def}) { | 
|  |  |  | 66 |  |  |  |  | 
| 255 | 5 |  |  |  |  | 12 | $input = $opt_ref->{-def}; | 
| 256 | 5 |  |  |  |  | 26 | $defaulted = 1; | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | # The input line is usually chomped before being returned... | 
| 260 | 97 | 100 | 100 |  |  | 450 | if (defined $input && !$opt_ref->{-line}) { | 
| 261 | 82 |  |  |  |  | 149 | chomp $input; | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | # Check for a value indicating failure... | 
| 265 | 97 | 100 | 100 |  |  | 296 | if (exists $opt_ref->{-fail}   && match($input, $opt_ref->{-fail})) { | 
| 266 | 2 |  |  |  |  | 227 | $input = undef; | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | # Setting @ARGV is a special case; process it like a command-line... | 
| 270 | 97 | 100 |  |  |  | 345 | if ($opt_ref->{-argv}) { | 
| 271 | 5 |  |  |  |  | 11 | @ARGV = map { _shell_expand($_) } | 
| 272 | 1 |  |  |  |  | 17 | grep {defined} | 
|  | 15 |  |  |  |  | 24 |  | 
| 273 |  |  |  |  |  |  | $input =~ m{ | 
| 274 |  |  |  |  |  |  | ( '  [^'\\]* (?: \\. [^'\\]* )* ' ) | 
| 275 |  |  |  |  |  |  | |   ( "  [^"\\]* (?: \\. [^"\\]* )* " ) | 
| 276 |  |  |  |  |  |  | |   (?: ^ | \s)  ( [^\s"'] \S*        ) | 
| 277 |  |  |  |  |  |  | }gxms; | 
| 278 | 1 |  |  |  |  | 19 | return 1; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | # "Those who remember history are enabled to repeat it"... | 
| 282 | 96 | 100 | 66 |  |  | 436 | if (defined $input and $opt_ref->{-history} ne 'NONE') { | 
| 283 | 84 |  | 100 |  |  | 399 | my $history_set = $history_cache{ $opt_ref->{-history} } //= [] ; | 
| 284 | 84 |  |  |  |  | 151 | @{ $history_set } = ($input, grep { $_ ne $input } @{ $history_set }); | 
|  | 84 |  |  |  |  | 284 |  | 
|  | 179 |  |  |  |  | 379 |  | 
|  | 84 |  |  |  |  | 185 |  | 
| 285 |  |  |  |  |  |  | } | 
| 286 |  |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | # If input timed out insert the default, if any... | 
| 288 | 36 |  | 50 | 36 |  | 296 | my $timedout = $in_pos == do{ no warnings; tell $in_filehandle } // 0; | 
|  | 36 |  |  |  |  | 88 |  | 
|  | 36 |  |  |  |  | 43730 |  | 
|  | 96 |  |  |  |  | 195 |  | 
|  | 96 |  |  |  |  | 395 |  | 
| 289 | 96 | 50 | 66 |  |  | 255 | if ($timedout && exists $opt_ref->{-def}) { | 
| 290 | 0 |  |  |  |  | 0 | $input = $opt_ref->{-def}; | 
| 291 | 0 |  |  |  |  | 0 | $defaulted = 1; | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | # A defined input is a successful input... | 
| 295 | 96 |  |  |  |  | 197 | my $succeeded = defined $input; | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | # The -yesno variants also need a 'y' to be successful... | 
| 298 | 96 | 100 |  |  |  | 262 | if ($opt_ref->{-yesno}{count}) { | 
| 299 | 27 |  | 66 |  |  | 152 | $succeeded &&= $input =~ m{\A \s* y}ixms; | 
| 300 | 27 | 50 | 66 |  |  | 87 | if ($succeeded && $opt_ref->{-yesno}{count} > 1) { | 
| 301 | 0 |  |  |  |  | 0 | my $count = --$opt_ref->{-yesno}{count}; | 
| 302 |  |  |  |  |  |  | $opt_ref->{-prompt} | 
| 303 | 0 | 0 |  |  |  | 0 | = @yesno_prompts ? shift(@yesno_prompts) . q{ } | 
|  |  | 0 |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | : $count > 1     ? qq{Please confirm $count more times } | 
| 305 |  |  |  |  |  |  | :                   q{Please confirm one last time } | 
| 306 |  |  |  |  |  |  | ; | 
| 307 | 0 |  |  |  |  | 0 | goto REPROMPT_YESNO;    # Gasp, yes goto is the cleanest way! | 
| 308 |  |  |  |  |  |  | } | 
| 309 |  |  |  |  |  |  | } | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | # Verbatim return doesn't do fancy tricks... | 
| 312 | 96 | 100 |  |  |  | 268 | if ($opt_ref->{-verbatim}) { | 
| 313 | 6 |  | 66 |  |  | 138 | return $input // (); | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | # Failure in a list context returns nothing... | 
| 317 | 90 | 100 | 100 |  |  | 331 | return if LIST && !$succeeded; | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | # Otherwise, be context sensitive... | 
| 320 |  |  |  |  |  |  | return | 
| 321 | 43 |  |  | 43 |  | 19250 | PUREBOOL { $_ = RETOBJ; next handler;      } | 
|  | 43 |  |  |  |  | 1082 |  | 
| 322 | 75 |  |  | 75 |  | 17434 | BOOL { $succeeded;                     } | 
| 323 | 48 |  |  | 48 |  | 19731 | SCALAR { $input;                         } | 
| 324 |  |  |  |  |  |  | METHOD { | 
| 325 | 0 |  |  |  |  | 0 | defaulted => sub { $defaulted  }, | 
| 326 |  |  |  |  |  |  | timedout  => sub { | 
| 327 | 0 | 0 |  |  |  | 0 | return q{} if !$timedout; | 
| 328 |  |  |  |  |  |  | return "timed out after $opt_ref->{-timeout} second" | 
| 329 | 0 | 0 |  |  |  | 0 | . ($opt_ref->{-timeout} == 1 ? q{} : q{s}); | 
| 330 |  |  |  |  |  |  | }, | 
| 331 | 86 |  |  | 0 |  | 2122 | }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 332 |  |  |  |  |  |  | } | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | # Simulate a command line expansion for the -argv option... | 
| 336 |  |  |  |  |  |  | sub _shell_expand { | 
| 337 | 5 |  |  | 5 |  | 12 | my ($text) = @_; | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | # Single-quoted text is literal... | 
| 340 | 5 | 100 |  |  |  | 17 | if ($text =~ m{\A ' (.*) ' \z}xms) { | 
| 341 | 1 |  |  |  |  | 6 | return $1; | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | # Everything else has shell variables expanded... | 
| 345 | 4 |  |  |  |  | 80 | my $ENV_PAT = join '|', reverse sort keys %ENV; | 
| 346 | 4 |  |  |  |  | 204 | $text =~ s{\$ ($ENV_PAT)}{$ENV{$1}}gxms; | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | # Double-quoted text isn't globbed... | 
| 349 | 4 | 100 |  |  |  | 56 | if ($text =~ m{\A " (.*) " \z}xms) { | 
| 350 | 2 |  |  |  |  | 6 | return $1; | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | # Everything else is... | 
| 354 | 2 |  |  |  |  | 72 | return glob($text); | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | # No completion is the default... | 
| 358 |  |  |  |  |  |  | my $DEFAULT_COMPLETER = sub { q{} }; | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | # Translate std constraints... | 
| 361 |  |  |  |  |  |  | my %STD_CONSTRAINT = ( | 
| 362 |  |  |  |  |  |  | positive  => sub { $_ > 0      }, | 
| 363 |  |  |  |  |  |  | negative  => sub { $_ < 0      }, | 
| 364 |  |  |  |  |  |  | zero      => sub { $_ == 0     }, | 
| 365 |  |  |  |  |  |  | even      => sub { $_ % 2 == 0 }, | 
| 366 |  |  |  |  |  |  | odd       => sub { $_ % 2 != 0 }, | 
| 367 |  |  |  |  |  |  | ); | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | # Create abbreviations... | 
| 370 |  |  |  |  |  |  | $STD_CONSTRAINT{pos} = $STD_CONSTRAINT{positive}; | 
| 371 |  |  |  |  |  |  | $STD_CONSTRAINT{neg} = $STD_CONSTRAINT{negative}; | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | # Create antitheses... | 
| 374 |  |  |  |  |  |  | for my $constraint (keys %STD_CONSTRAINT) { | 
| 375 |  |  |  |  |  |  | my $implementation = $STD_CONSTRAINT{$constraint}; | 
| 376 |  |  |  |  |  |  | $STD_CONSTRAINT{"non$constraint"} | 
| 377 |  |  |  |  |  |  | = sub { ! $implementation->(@_) }; | 
| 378 |  |  |  |  |  |  | } | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | # Special style specifications require decoding... | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | sub _decode_echo { | 
| 383 | 1 |  |  | 1 |  | 3 | my $style = shift; | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | # Not a special style... | 
| 386 | 1 | 50 | 33 |  |  | 14 | return $style if ref $style || $style !~ m{/}; | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | # A slash means yes/no echoes... | 
| 389 | 0 |  |  |  |  | 0 | my ($yes, $no) = split m{/}, $style; | 
| 390 | 0 | 0 |  | 0 |  | 0 | return sub{ /y/i ? $yes : $no }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 391 |  |  |  |  |  |  | } | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | sub _decode_echostyle { | 
| 394 | 0 |  |  | 0 |  | 0 | my $style = shift; | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | # Not a special style... | 
| 397 | 0 | 0 | 0 |  |  | 0 | return $style if ref $style || $style !~ m{/}; | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | # A slash means yes/no styles... | 
| 400 | 0 |  |  |  |  | 0 | my ($yes, $no) = split m{/}, $style; | 
| 401 | 0 | 0 |  | 0 |  | 0 | return sub{ /y/i ? $yes : $no }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 402 |  |  |  |  |  |  | } | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | sub _decode_style { | 
| 405 |  |  |  |  |  |  | # No special prompt styles (yet)... | 
| 406 | 0 |  |  | 0 |  | 0 | return shift; | 
| 407 |  |  |  |  |  |  | } | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | # Generate safe closure around active sub... | 
| 410 |  |  |  |  |  |  | sub _gen_wrapper_for { | 
| 411 | 1 |  |  | 1 |  | 4 | my ($arg) = @_; | 
| 412 |  |  |  |  |  |  | return ref $arg ne 'CODE' | 
| 413 | 0 |  |  | 0 |  | 0 | ? sub { $arg } | 
| 414 | 36 | 50 | 0 | 36 |  | 334 | : sub { eval { for (shift) { no warnings; return $arg->($_) // $_ } } }; | 
|  | 36 |  |  | 0 |  | 150 |  | 
|  | 36 |  |  |  |  | 117499 |  | 
|  | 1 |  |  |  |  | 9 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 415 |  |  |  |  |  |  | } | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | # Create recognizer... | 
| 418 |  |  |  |  |  |  | my $STD_CONSTRAINT | 
| 419 |  |  |  |  |  |  | = '^(?:' . join('|', reverse sort keys %STD_CONSTRAINT) . ')'; | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | # Translate name constraints to implementations... | 
| 422 |  |  |  |  |  |  | sub _standardize_constraint { | 
| 423 | 3 |  |  | 3 |  | 10 | my ($option_type, $constraint_spec) = @_; | 
| 424 |  |  |  |  |  |  |  | 
| 425 | 3 | 100 |  |  |  | 17 | return ("be an acceptable $option_type", $constraint_spec) | 
| 426 |  |  |  |  |  |  | if ref $constraint_spec; | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 1 |  |  |  |  | 6 | my @constraint_names = split /\s+/, $constraint_spec; | 
| 429 |  |  |  |  |  |  | my @constraints = | 
| 430 | 1 |  | 33 |  |  | 4 | map { $STD_CONSTRAINT{$_} | 
|  | 2 |  |  |  |  | 8 |  | 
| 431 |  |  |  |  |  |  | // _opt_err('invalid',-$option_type,'"pos", "neg", "even", etc.', qq{"$_"}) | 
| 432 |  |  |  |  |  |  | } @constraint_names; | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | return ( | 
| 435 |  |  |  |  |  |  | 'be ' . join(' and ', @constraint_names), | 
| 436 |  |  |  |  |  |  | sub { | 
| 437 | 4 |  |  | 4 |  | 168 | my ($compare_val) = @_; | 
| 438 | 4 |  |  |  |  | 8 | for my $constraint (@constraints) { | 
| 439 | 6 | 100 |  |  |  | 12 | return 0 if !$constraint->($compare_val); | 
| 440 |  |  |  |  |  |  | } | 
| 441 | 1 |  |  |  |  | 4 | return 1; | 
| 442 |  |  |  |  |  |  | } | 
| 443 | 1 |  |  |  |  | 8 | ); | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | # Convert args to prompt + options hash... | 
| 448 |  |  |  |  |  |  | sub _decode_args { | 
| 449 |  |  |  |  |  |  | my %option = ( | 
| 450 |  |  |  |  |  |  | -prompt    => undef, | 
| 451 |  |  |  |  |  |  | -complete  => $DEFAULT_COMPLETER, | 
| 452 |  |  |  |  |  |  | -must      => {}, | 
| 453 |  |  |  |  |  |  | -history   => 'DEFAULT', | 
| 454 | 0 |  |  | 0 |  | 0 | -style     => sub{ q{} }, | 
| 455 | 0 |  |  | 0 |  | 0 | -nostyle   => sub{ q{} }, | 
| 456 | 0 |  |  | 0 |  | 0 | -echostyle => sub{ q{} }, | 
| 457 | 30 | 50 |  | 30 |  | 169 | -echo      => sub { my $char = shift; $char eq "\t" ? q{ } : $char }, | 
|  | 30 |  |  |  |  | 399 |  | 
| 458 | 0 |  |  | 0 |  | 0 | -return    => sub { "\n" }, | 
| 459 | 99 |  |  | 99 |  | 1243 | ); | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | DECODING: | 
| 462 | 99 |  |  |  |  | 403 | while (defined(my $arg = shift @_)) { | 
| 463 | 220 | 50 |  |  |  | 545 | if (my $type = ref $arg) { | 
| 464 | 0 |  |  |  |  | 0 | _warn( reserved => | 
| 465 |  |  |  |  |  |  | 'prompt(): Unexpected argument (' . lc($type) . ' ref) ignored' | 
| 466 |  |  |  |  |  |  | ); | 
| 467 |  |  |  |  |  |  | } | 
| 468 |  |  |  |  |  |  | else { | 
| 469 | 220 |  |  |  |  | 290 | state $already_wiped; | 
| 470 | 220 |  |  |  |  | 282 | my $redo; | 
| 471 |  |  |  |  |  |  | # The sound of one hand clapping... | 
| 472 | 220 | 100 |  |  |  | 5155 | if ($arg =~ /^-_/) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 473 | 6 |  |  |  |  | 22 | $redo = 1; | 
| 474 |  |  |  |  |  |  | } | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | # Non-chomping option... | 
| 477 |  |  |  |  |  |  | elsif ($arg =~ /^-line$/) { | 
| 478 | 1 |  |  |  |  | 3 | $option{-line}++; | 
| 479 |  |  |  |  |  |  | } | 
| 480 |  |  |  |  |  |  | elsif ($arg =~ /^-l/) { | 
| 481 | 4 |  |  |  |  | 16 | $option{-line}++; | 
| 482 | 4 |  |  |  |  | 8 | $redo = 1; | 
| 483 |  |  |  |  |  |  | } | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | # The -yesno variants... | 
| 486 |  |  |  |  |  |  | elsif ($arg =~ /^-YesNo$/) { | 
| 487 | 4 | 50 | 33 |  |  | 13 | my $count = @_ && looks_like_number($_[0]) ? shift @_ : 1; | 
| 488 |  |  |  |  |  |  | $option{-yesno} = { | 
| 489 | 4 |  |  |  |  | 55 | must => { '[YN]' => qr{\A \s* [YN] }xms }, | 
| 490 |  |  |  |  |  |  | count  => $count, | 
| 491 |  |  |  |  |  |  | }; | 
| 492 |  |  |  |  |  |  | } | 
| 493 |  |  |  |  |  |  | elsif ($arg =~ /^-YN/) { | 
| 494 |  |  |  |  |  |  | $option{-yesno} = { | 
| 495 | 1 |  |  |  |  | 16 | must => { '[YN]' => qr{\A \s* [YN] }xms }, | 
| 496 |  |  |  |  |  |  | count  => 1, | 
| 497 |  |  |  |  |  |  | }; | 
| 498 | 1 |  |  |  |  | 3 | $redo = 2; | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  | elsif ($arg =~ /^-yesno$/) { | 
| 501 | 4 | 50 | 33 |  |  | 17 | my $count = @_ && looks_like_number($_[0]) ? shift @_ : 1; | 
| 502 |  |  |  |  |  |  | $option{-yesno} = { | 
| 503 | 4 |  |  |  |  | 28 | must => { '[yn]' => qr{\A \s* [YN] }ixms }, | 
| 504 |  |  |  |  |  |  | count  => $count, | 
| 505 |  |  |  |  |  |  | }; | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  | elsif ($arg =~ /^-yn/) { | 
| 508 |  |  |  |  |  |  | $option{-yesno} = { | 
| 509 | 4 |  |  |  |  | 49 | must => { '[yn]' => qr{\A \s* [YN] }ixms }, | 
| 510 |  |  |  |  |  |  | count  => 1, | 
| 511 |  |  |  |  |  |  | }; | 
| 512 | 4 |  |  |  |  | 11 | $redo = 2; | 
| 513 |  |  |  |  |  |  | } | 
| 514 |  |  |  |  |  |  | elsif ($arg =~ /^-Yes$/) { | 
| 515 | 7 | 50 | 33 |  |  | 29 | my $count = @_ && looks_like_number($_[0]) ? shift @_ : 1; | 
| 516 |  |  |  |  |  |  | $option{-yesno} = { | 
| 517 | 7 |  |  |  |  | 38 | must => { '[Y for yes]' => qr{\A \s* (?: [^y] | \Z) }xms }, | 
| 518 |  |  |  |  |  |  | count  => $count, | 
| 519 |  |  |  |  |  |  | }; | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  | elsif ($arg =~ /^-Y/) { | 
| 522 |  |  |  |  |  |  | $option{-yesno} = { | 
| 523 | 1 |  |  |  |  | 10 | must => { '[Y for yes]' => qr{\A \s* (?: [^y] | \Z) }xms }, | 
| 524 |  |  |  |  |  |  | count  => 1, | 
| 525 |  |  |  |  |  |  | }; | 
| 526 | 1 |  |  |  |  | 2 | $redo = 1; | 
| 527 |  |  |  |  |  |  | } | 
| 528 |  |  |  |  |  |  | elsif ($arg =~ /^-yes$/) { | 
| 529 | 5 | 50 | 33 |  |  | 18 | my $count = @_ && looks_like_number($_[0]) ? shift @_ : 1; | 
| 530 | 5 |  |  |  |  | 15 | $option{-yesno} = { count  => $count }; | 
| 531 |  |  |  |  |  |  | } | 
| 532 |  |  |  |  |  |  | elsif ($arg =~ /^-y/) { | 
| 533 | 1 |  |  |  |  | 6 | $option{-yesno} = { count  => 1 }; | 
| 534 | 1 |  |  |  |  | 3 | $redo = 1; | 
| 535 |  |  |  |  |  |  | } | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | # Load @ARGV... | 
| 538 |  |  |  |  |  |  | elsif ($arg =~ /^-argv$/) { | 
| 539 | 1 |  |  |  |  | 3 | $option{-argv} = 1; | 
| 540 |  |  |  |  |  |  | } | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | elsif ($arg =~ /^-a/) { | 
| 543 | 0 |  |  |  |  | 0 | $option{-argv} = 1; | 
| 544 | 0 |  |  |  |  | 0 | $redo = 1; | 
| 545 |  |  |  |  |  |  | } | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | # Clear screen before prompt... | 
| 548 |  |  |  |  |  |  | elsif ($arg =~ /^-wipe(first)?$/) { | 
| 549 | 0 | 0 |  |  |  | 0 | $option{-wipe} = $1 ? !$already_wiped : 1; | 
| 550 | 0 |  |  |  |  | 0 | $already_wiped = 1; | 
| 551 |  |  |  |  |  |  | } | 
| 552 |  |  |  |  |  |  | elsif ($arg =~ /^-w/) { | 
| 553 | 0 |  |  |  |  | 0 | $option{-wipe} = 1; | 
| 554 | 0 |  |  |  |  | 0 | $already_wiped = 1; | 
| 555 | 0 |  |  |  |  | 0 | $redo = 1; | 
| 556 |  |  |  |  |  |  | } | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | # Specify a failure condition... | 
| 559 |  |  |  |  |  |  | elsif ($arg =~ /^-fail$/) { | 
| 560 | 3 | 50 |  |  |  | 7 | _opt_err('Missing', -fail, 'failure condition') if !@_; | 
| 561 | 3 |  |  |  |  | 8 | $option{-fail} = shift @_; | 
| 562 |  |  |  |  |  |  | } | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | # Specify an immediate failure condition... | 
| 565 |  |  |  |  |  |  | elsif ($arg =~ /^-cancel/) { | 
| 566 | 0 | 0 |  |  |  | 0 | _opt_err('Missing', -cancel, 'cancellation condition') if !@_; | 
| 567 | 0 |  |  |  |  | 0 | $option{-cancel} = shift @_; | 
| 568 |  |  |  |  |  |  | } | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | # Specify a file request... | 
| 571 |  |  |  |  |  |  | elsif ($arg =~ /^-f(?:ilenames?)?$/) { | 
| 572 | 0 |  |  | 0 |  | 0 | $option{-must}{'0: be an existing file'} = sub { -e $_[0] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 573 | 0 |  |  | 0 |  | 0 | $option{-must}{'1: be readable'}         = sub { -r $_[0] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 574 | 0 |  |  |  |  | 0 | $option{-complete}                       = 'filenames'; | 
| 575 |  |  |  |  |  |  | } | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | # Specify prompt echoing colour/style... | 
| 578 |  |  |  |  |  |  | elsif ($arg =~ /^-style/) { | 
| 579 | 0 | 0 |  |  |  | 0 | _opt_err('Missing -style specification') if !@_; | 
| 580 | 0 |  |  |  |  | 0 | my $style = _decode_style(shift @_); | 
| 581 | 0 |  |  |  |  | 0 | $option{-style} = _gen_wrapper_for($style); | 
| 582 |  |  |  |  |  |  | } | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | # Specify input colour/style... | 
| 585 |  |  |  |  |  |  | elsif ($arg =~ /^-echostyle/) { | 
| 586 | 0 | 0 |  |  |  | 0 | _opt_err('Missing -echostyle specification') if !@_; | 
| 587 | 0 |  |  |  |  | 0 | my $style = _decode_echostyle(shift @_); | 
| 588 | 0 |  |  |  |  | 0 | $option{-echostyle} = _gen_wrapper_for($style); | 
| 589 |  |  |  |  |  |  | } | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | # Specify input and output filehandles... | 
| 593 | 0 |  |  |  |  | 0 | elsif ($arg =~ /^-stdio$/) { $option{-in}  = *STDIN; | 
| 594 | 0 |  |  |  |  | 0 | $option{-out} = *STDOUT; | 
| 595 |  |  |  |  |  |  | } | 
| 596 | 17 |  |  |  |  | 43 | elsif ($arg =~ /^-in$/)    { $option{-in}  = shift @_; } | 
| 597 | 6 |  |  |  |  | 27 | elsif ($arg =~ /^-out$/)   { $option{-out} = shift @_; } | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  |  | 
| 600 |  |  |  |  |  |  | # Specify integer and number return value... | 
| 601 |  |  |  |  |  |  | elsif ($arg =~ /^-integer$/)       { | 
| 602 | 6 |  |  |  |  | 13 | $option{-integer} = 1; | 
| 603 | 6 | 100 | 100 |  |  | 176 | if (@_ && (ref $_[0] || $_[0] =~ $STD_CONSTRAINT)) { | 
|  |  |  | 66 |  |  |  |  | 
| 604 | 3 |  |  |  |  | 10 | my ($errmsg, $constraint) | 
| 605 |  |  |  |  |  |  | = _standardize_constraint('integer',shift); | 
| 606 | 3 |  |  |  |  | 10 | $option{-must}{$errmsg} = $constraint; | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  | } | 
| 609 |  |  |  |  |  |  | elsif ($arg =~ /^-num(?:ber)?$/)   { | 
| 610 | 2 |  |  |  |  | 6 | $option{-number}  = 1; | 
| 611 | 2 | 50 | 33 |  |  | 170 | if (@_ && (ref $_[0] || $_[0] =~ $STD_CONSTRAINT)) { | 
|  |  |  | 33 |  |  |  |  | 
| 612 | 0 |  |  |  |  | 0 | my ($errmsg, $constraint) | 
| 613 |  |  |  |  |  |  | = _standardize_constraint('number',shift); | 
| 614 | 0 |  |  |  |  | 0 | $option{-must}{$errmsg} = $constraint; | 
| 615 |  |  |  |  |  |  | } | 
| 616 |  |  |  |  |  |  | } | 
| 617 | 1 |  |  |  |  | 3 | elsif ($arg =~ /^-i/)              { $option{-integer} = 1; $redo = 1; } | 
|  | 1 |  |  |  |  | 2 |  | 
| 618 | 1 |  |  |  |  | 3 | elsif ($arg =~ /^-n/)              { $option{-number}  = 1; $redo = 1; } | 
|  | 1 |  |  |  |  | 2 |  | 
| 619 |  |  |  |  |  |  |  | 
| 620 |  |  |  |  |  |  | # Specify void context is okay... | 
| 621 | 1 |  |  |  |  | 3 | elsif ($arg =~ /^-void$/)          { $option{-void} = 1;               } | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | # Specify verbatim return value... | 
| 624 | 3 |  |  |  |  | 10 | elsif ($arg =~ /^-verb(?:atim)?$/) { $option{-verbatim} = 1;           } | 
| 625 | 3 |  |  |  |  | 8 | elsif ($arg =~ /^-v/)              { $option{-verbatim} = 1; $redo = 1;} | 
|  | 3 |  |  |  |  | 6 |  | 
| 626 |  |  |  |  |  |  |  | 
| 627 |  |  |  |  |  |  | # Specify single character return... | 
| 628 | 1 |  |  |  |  | 4 | elsif ($arg =~ /^-sing(?:le)?$/)   { $option{-single} = 1;             } | 
| 629 | 5 |  |  |  |  | 13 | elsif ($arg =~ /^-[s1]/)           { $option{-single} = 1; $redo = 1;  } | 
|  | 5 |  |  |  |  | 8 |  | 
| 630 |  |  |  |  |  |  |  | 
| 631 |  |  |  |  |  |  | # Specify a default... | 
| 632 |  |  |  |  |  |  | elsif ($arg =~ /^-DEF(?:AULT)?/) { | 
| 633 | 0 | 0 |  |  |  | 0 | _opt_err('Missing', '-DEFAULT', 'string') if !@_; | 
| 634 | 0 |  |  |  |  | 0 | $option{-def} = shift @_; | 
| 635 | 0 |  |  |  |  | 0 | $option{-def_nocheck} = 1; | 
| 636 |  |  |  |  |  |  | _opt_err('Invalid', '-DEFAULT', 'string', 'reference') | 
| 637 | 0 | 0 |  |  |  | 0 | if ref($option{-def}); | 
| 638 |  |  |  |  |  |  | } | 
| 639 |  |  |  |  |  |  | elsif ($arg =~ /^-def(?:ault)?/) { | 
| 640 | 5 | 50 |  |  |  | 31 | _opt_err('Missing', '-default', 'string') if !@_; | 
| 641 | 5 |  |  |  |  | 16 | $option{-def} = shift @_; | 
| 642 |  |  |  |  |  |  | _opt_err('Invalid', '-default', 'string', 'reference') | 
| 643 | 5 | 50 |  |  |  | 15 | if ref($option{-def}); | 
| 644 |  |  |  |  |  |  | } | 
| 645 | 2 |  |  |  |  | 7 | elsif ($arg =~ /^-d(.+)$/)   { $option{-def} = $1; } | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | # Specify a timeout... | 
| 648 |  |  |  |  |  |  | elsif ($arg =~ /^-t(\d+)/)   { | 
| 649 | 1 |  |  |  |  | 5 | $option{-timeout} = $1; | 
| 650 | 1 |  |  |  |  | 6 | $arg =~ s{\d+}{}xms; | 
| 651 | 1 |  |  |  |  | 2 | $redo = 1; | 
| 652 |  |  |  |  |  |  | } | 
| 653 |  |  |  |  |  |  | elsif ($arg =~ /^-timeout$/) { | 
| 654 | 2 | 100 |  |  |  | 9 | _opt_err('Missing', -timeout, 'number of seconds') if !@_; | 
| 655 | 1 |  |  |  |  | 4 | $option{-timeout} = shift @_; | 
| 656 |  |  |  |  |  |  | _opt_err('Invalid', -timeout,'number of seconds', qq{'$option{-timeout}'}) | 
| 657 | 1 | 50 |  |  |  | 11 | if !looks_like_number($option{-timeout}); | 
| 658 |  |  |  |  |  |  | } | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | # Specify a set of input constraints... | 
| 661 |  |  |  |  |  |  | elsif ($arg =~ /^-g.*/) { | 
| 662 | 8 | 50 |  |  |  | 32 | _opt_err('Missing', -guarantee, 'input restriction') if !@_; | 
| 663 | 8 |  |  |  |  | 13 | my $restriction = shift @_; | 
| 664 | 8 |  |  |  |  | 17 | my $restriction_type = ref $restriction; | 
| 665 |  |  |  |  |  |  |  | 
| 666 | 8 |  |  |  |  | 19 | $option{-must}{'be a valid input'} = $restriction; | 
| 667 |  |  |  |  |  |  |  | 
| 668 |  |  |  |  |  |  | # Hashes restrict input to their keys... | 
| 669 | 8 | 100 |  |  |  | 20 | if ($restriction_type eq 'HASH') { | 
| 670 | 2 |  |  |  |  | 4 | $restriction_type = 'ARRAY'; | 
| 671 | 2 |  |  |  |  | 3 | $restriction = [ keys %{$restriction} ]; | 
|  | 2 |  |  |  |  | 8 |  | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  | # Arrays of strings matched (and completed) char-by-char... | 
| 674 | 8 | 100 |  |  |  | 21 | if ($restriction_type eq 'ARRAY') { | 
|  |  | 50 |  |  |  |  |  | 
| 675 | 7 |  |  |  |  | 11 | my @restrictions = @{$restriction}; | 
|  | 7 |  |  |  |  | 22 |  | 
| 676 |  |  |  |  |  |  | $option{-guarantee} | 
| 677 |  |  |  |  |  |  | = '\A(?:' | 
| 678 |  |  |  |  |  |  | . join('|', map { | 
| 679 | 7 |  |  |  |  | 18 | join(q{}, map { "(?:\Q$_\E" } split(q{}, $_)) | 
|  | 30 |  |  |  |  | 67 |  | 
|  | 34 |  |  |  |  | 121 |  | 
| 680 |  |  |  |  |  |  | . ')?' x length($_) | 
| 681 |  |  |  |  |  |  | } @restrictions) | 
| 682 |  |  |  |  |  |  | . ')\z' | 
| 683 |  |  |  |  |  |  | ; | 
| 684 | 7 | 50 |  |  |  | 23 | if ($option{-complete} == $DEFAULT_COMPLETER) { | 
| 685 | 7 |  |  |  |  | 17 | $option{-complete} = \@restrictions; | 
| 686 |  |  |  |  |  |  | } | 
| 687 |  |  |  |  |  |  | } | 
| 688 |  |  |  |  |  |  | # Regexes matched as-is... | 
| 689 |  |  |  |  |  |  | elsif ($restriction_type eq 'Regexp') { | 
| 690 | 1 |  |  |  |  | 3 | $option{-guarantee} = $restriction; | 
| 691 |  |  |  |  |  |  | } | 
| 692 |  |  |  |  |  |  | else { | 
| 693 | 0 |  |  |  |  | 0 | _opt_err( 'Invalid', -guarantee, | 
| 694 |  |  |  |  |  |  | 'array or hash reference, or regex' | 
| 695 |  |  |  |  |  |  | ); | 
| 696 |  |  |  |  |  |  | } | 
| 697 |  |  |  |  |  |  | } | 
| 698 |  |  |  |  |  |  |  | 
| 699 |  |  |  |  |  |  | # Specify a set of key letters... | 
| 700 |  |  |  |  |  |  | elsif ($arg =~ '-keyletters_implement') { | 
| 701 |  |  |  |  |  |  | # Extract all keys and default keys... | 
| 702 | 4 |  |  |  |  | 56 | my @keys  = ($option{-prompt} =~ m{$KL_EXTRACT}gxms); | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | # Convert default to a -default... | 
| 705 | 4 |  |  |  |  | 23 | my @defaults = ($option{-prompt} =~ m{$KL_DEF_EXTRACT}gxms); | 
| 706 | 4 | 50 |  |  |  | 14 | if (@defaults > 1) { | 
|  |  | 100 |  |  |  |  |  | 
| 707 | 0 |  |  |  |  | 0 | _warn( ambiguous => | 
| 708 |  |  |  |  |  |  | "prompt(): -keyletters found too many defaults" | 
| 709 |  |  |  |  |  |  | ) | 
| 710 |  |  |  |  |  |  | } | 
| 711 |  |  |  |  |  |  | elsif (@defaults) { | 
| 712 | 2 |  |  |  |  | 8 | push @_, -default => $defaults[0]; | 
| 713 |  |  |  |  |  |  | } | 
| 714 |  |  |  |  |  |  |  | 
| 715 |  |  |  |  |  |  | # Convert key letters to a -guarantee... | 
| 716 | 4 |  |  |  |  | 11 | @keys = ( map({uc} @keys), map({lc} @keys) ); | 
|  | 10 |  |  |  |  | 23 |  | 
|  | 10 |  |  |  |  | 25 |  | 
| 717 | 4 | 100 |  |  |  | 10 | if (@defaults == 1) { | 
| 718 | 2 |  |  |  |  | 4 | push @keys, q{}; | 
| 719 |  |  |  |  |  |  | } | 
| 720 | 4 |  |  |  |  | 13 | push @_, -guarantee => \@keys; | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | } | 
| 723 |  |  |  |  |  |  | elsif ($arg =~ /^-key(?:let(?:ter)?)(?:s)?/) { | 
| 724 | 2 |  |  |  |  | 6 | push @_, '-keyletters_implement'; | 
| 725 |  |  |  |  |  |  | } | 
| 726 |  |  |  |  |  |  | elsif ($arg =~ /^-k/) { | 
| 727 | 2 |  |  |  |  | 8 | push @_, '-keyletters_implement'; | 
| 728 | 2 |  |  |  |  | 4 | $redo = 1; | 
| 729 |  |  |  |  |  |  | } | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | # Specify a set of return constraints... | 
| 732 |  |  |  |  |  |  | elsif ($arg =~ /^-must$/) { | 
| 733 | 6 | 50 |  |  |  | 21 | _opt_err('Missing', -must, 'constraint hash') if !@_; | 
| 734 | 6 |  |  |  |  | 11 | my $must = shift @_; | 
| 735 | 6 | 50 |  |  |  | 16 | _opt_err('Invalid', -must, 'hash reference') | 
| 736 |  |  |  |  |  |  | if ref($must) ne 'HASH'; | 
| 737 | 6 |  |  |  |  | 10 | for my $errmsg (keys %{$must}) { | 
|  | 6 |  |  |  |  | 23 |  | 
| 738 | 6 |  |  |  |  | 19 | $option{-must}{$errmsg} = $must->{$errmsg}; | 
| 739 |  |  |  |  |  |  | } | 
| 740 |  |  |  |  |  |  | } | 
| 741 |  |  |  |  |  |  |  | 
| 742 |  |  |  |  |  |  | # Specify a history set... | 
| 743 |  |  |  |  |  |  | elsif ($arg =~ /^-history/) { | 
| 744 |  |  |  |  |  |  | $option{-history} | 
| 745 | 0 | 0 | 0 |  |  | 0 | = @_ && $_[0] !~ /^-/ ? shift @_ | 
| 746 |  |  |  |  |  |  | :                       undef; | 
| 747 |  |  |  |  |  |  | _opt_err('Invalid', -history, 'history set name', qq{'$option{-history}'}) | 
| 748 | 0 | 0 |  |  |  | 0 | if ref($option{-history}); | 
| 749 |  |  |  |  |  |  | } | 
| 750 | 0 | 0 |  |  |  | 0 | elsif ($arg =~ /^-h(.*)/)   { $option{-history} = length($1) ? $1 : undef; } | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  | # Specify completions... | 
| 753 |  |  |  |  |  |  | elsif ($arg =~ /^-comp(?:lete)?/) { | 
| 754 | 0 | 0 |  |  |  | 0 | _opt_err('Missing', -complete, 'completions') if !@_; | 
| 755 | 0 |  |  |  |  | 0 | my $comp_spec = shift @_; | 
| 756 | 0 |  | 0 |  |  | 0 | my $comp_type = ref($comp_spec) || $comp_spec || '???'; | 
| 757 | 0 | 0 |  |  |  | 0 | if ($comp_type =~ m{\A(?: file\w* | dir\w* | ARRAY | HASH | CODE )\Z}xms) { | 
| 758 | 0 |  |  |  |  | 0 | $option{-complete} = $comp_spec; | 
| 759 |  |  |  |  |  |  | } | 
| 760 |  |  |  |  |  |  | else { | 
| 761 | 0 |  |  |  |  | 0 | _opt_err( 'Invalid', -complete, | 
| 762 |  |  |  |  |  |  | '"filenames", "dirnames", or reference to array, hash, or subroutine'); | 
| 763 |  |  |  |  |  |  | } | 
| 764 |  |  |  |  |  |  | } | 
| 765 |  |  |  |  |  |  |  | 
| 766 |  |  |  |  |  |  | # Specify what to echo when a character is keyed... | 
| 767 |  |  |  |  |  |  | elsif ($arg =~ /^-(echo|ret(?:urn)?)$/) { | 
| 768 | 0 | 0 |  |  |  | 0 | my $flag = $1 eq 'echo' ? '-echo' : '-return'; | 
| 769 | 36 | 0 | 0 | 36 |  | 356 | if ($flag eq '-echo' && !eval { no warnings 'deprecated'; require Term::ReadKey }) { | 
|  | 36 |  |  |  |  | 98 |  | 
|  | 36 |  |  |  |  | 6780 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 770 | 0 |  |  |  |  | 0 | _warn( bareword => "Warning: next input will be in plaintext\n"); | 
| 771 |  |  |  |  |  |  | } | 
| 772 | 0 | 0 | 0 |  |  | 0 | my $arg = @_ && $_[0] !~ /^-/ ? shift(@_) | 
|  |  | 0 |  |  |  |  |  | 
| 773 |  |  |  |  |  |  | : $flag eq '-echo'    ? q{} | 
| 774 |  |  |  |  |  |  | :                       qq{\n}; | 
| 775 | 0 |  |  |  |  | 0 | $option{$flag} = _gen_wrapper_for(_decode_echo($arg)); | 
| 776 |  |  |  |  |  |  | } | 
| 777 |  |  |  |  |  |  | elsif ($arg =~ /^-e(.*)/) { | 
| 778 | 36 | 50 |  | 36 |  | 309 | if (!eval { no warnings 'deprecated'; require Term::ReadKey }) { | 
|  | 36 |  |  |  |  | 81 |  | 
|  | 36 |  |  |  |  | 80171 |  | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 141 |  | 
| 779 | 1 |  |  |  |  | 7 | _warn( bareword => "Warning: next input will be in plaintext\n"); | 
| 780 |  |  |  |  |  |  | } | 
| 781 | 1 |  |  |  |  | 11 | my $arg = $1; | 
| 782 | 1 |  |  |  |  | 7 | $option{-echo} = _gen_wrapper_for(_decode_echo($arg)); | 
| 783 |  |  |  |  |  |  | } | 
| 784 |  |  |  |  |  |  | elsif ($arg =~ /^-r(.+)/) { | 
| 785 | 0 |  |  |  |  | 0 | my $arg = $1; | 
| 786 | 0 |  |  |  |  | 0 | $option{-return} = _gen_wrapper_for(_decode_echo($arg)); | 
| 787 |  |  |  |  |  |  | } | 
| 788 |  |  |  |  |  |  | elsif ($arg =~ /^-r/) { | 
| 789 | 0 |  |  | 0 |  | 0 | $option{-return} = sub{ "\n" }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 790 |  |  |  |  |  |  | } | 
| 791 |  |  |  |  |  |  |  | 
| 792 |  |  |  |  |  |  | # Specify an initial input... | 
| 793 |  |  |  |  |  |  | elsif ($arg =~ /^-prefill/) { | 
| 794 | 0 | 0 |  |  |  | 0 | _opt_err('Missing', '-prefill', 'string') if !@_; | 
| 795 | 0 |  |  |  |  | 0 | $option{-prefill} = shift @_; | 
| 796 |  |  |  |  |  |  | _opt_err('Invalid', '-prefill', 'string', 'reference') | 
| 797 | 0 | 0 |  |  |  | 0 | if ref($option{-prefill}); | 
| 798 |  |  |  |  |  |  | } | 
| 799 |  |  |  |  |  |  |  | 
| 800 |  |  |  |  |  |  | # Explicit prompt replaces implicit prompts... | 
| 801 |  |  |  |  |  |  | elsif ($arg =~ /^-prompt$/) { | 
| 802 | 0 | 0 |  |  |  | 0 | _opt_err('Missing', '-prompt', 'prompt string') if !@_; | 
| 803 | 0 |  |  |  |  | 0 | $option{-prompt} = shift @_; | 
| 804 |  |  |  |  |  |  | _opt_err('Invalid', '-prompt', 'string', 'reference') | 
| 805 | 0 | 0 |  |  |  | 0 | if ref($option{-prompt}); | 
| 806 |  |  |  |  |  |  | } | 
| 807 |  |  |  |  |  |  | elsif ($arg =~ /^-p(\S*)$/) { | 
| 808 | 0 |  |  |  |  | 0 | $option{-prompt} = $1; | 
| 809 |  |  |  |  |  |  | } | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  | # Menus inject a placeholder in the prompt string... | 
| 812 |  |  |  |  |  |  | elsif ($arg =~ /^-menu$/) { | 
| 813 | 1 | 50 |  |  |  | 5 | _opt_err('Missing', '-menu', 'menu specification') if !@_; | 
| 814 | 1 | 50 |  |  |  | 6 | $option{-menu}         = ref $_[0] ? shift(@_) : \shift(@_); | 
| 815 | 1 |  |  |  |  | 3 | $option{-prompt}      .= $MENU_MK; | 
| 816 | 1 |  |  |  |  | 2 | $option{-def_nocheck}  = 1; | 
| 817 |  |  |  |  |  |  | } | 
| 818 |  |  |  |  |  |  |  | 
| 819 |  |  |  |  |  |  | # A monitoring sub is called on every input character... | 
| 820 |  |  |  |  |  |  | elsif ($arg =~ /^-monitor/) { | 
| 821 | 0 | 0 | 0 |  |  | 0 | _opt_err('Missing', '-monitor', 'a monitor subref') | 
| 822 |  |  |  |  |  |  | if !@_ || ref $_[0] ne 'CODE'; | 
| 823 | 0 |  |  |  |  | 0 | $option{-monitor} = shift(@_); | 
| 824 |  |  |  |  |  |  | } | 
| 825 |  |  |  |  |  |  |  | 
| 826 |  |  |  |  |  |  | # Anything else of the form '-...' is a misspelt option... | 
| 827 | 2 |  |  |  |  | 8 | elsif ($arg =~ /^-\w+$/) { _warn(misc => "prompt(): Unknown option $arg ignored"); } | 
| 828 |  |  |  |  |  |  |  | 
| 829 |  |  |  |  |  |  | # Anything else is part fo the prompt... | 
| 830 | 96 |  |  |  |  | 288 | else { $option{-prompt} .= $arg; } | 
| 831 |  |  |  |  |  |  |  | 
| 832 |  |  |  |  |  |  | # Handle option bundling... | 
| 833 | 218 | 100 | 100 |  |  | 2380 | redo DECODING if $redo && $arg =~ s{\A -.{$redo} (?=.)}{-}xms; | 
| 834 |  |  |  |  |  |  | } | 
| 835 |  |  |  |  |  |  | } | 
| 836 |  |  |  |  |  |  |  | 
| 837 |  |  |  |  |  |  | # Precompute top-level menu, if menuing... | 
| 838 | 97 | 100 |  |  |  | 270 | if (exists $option{-menu}) { | 
| 839 |  |  |  |  |  |  | $option{-menu} = _build_menu($option{-menu}, | 
| 840 |  |  |  |  |  |  | undef, | 
| 841 |  |  |  |  |  |  | $option{-number}||$option{-integer} | 
| 842 | 1 |  | 33 |  |  | 6 | ); | 
| 843 |  |  |  |  |  |  | } | 
| 844 |  |  |  |  |  |  |  | 
| 845 |  |  |  |  |  |  | # Handle return magic on -single... | 
| 846 | 97 | 100 | 50 |  |  | 329 | if (defined $option{-single} && length($option{-echo}('X')//'echoself')) { | 
|  |  |  | 66 |  |  |  |  | 
| 847 | 6 |  | 50 | 0 |  | 28 | $option{-return} //= sub{ "\n" }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 848 |  |  |  |  |  |  | } | 
| 849 |  |  |  |  |  |  |  | 
| 850 |  |  |  |  |  |  | # Adjust prompt as necessary... | 
| 851 | 97 | 100 |  |  |  | 614 | if ($option{-argv}) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 852 | 1 |  | 33 |  |  | 4 | my $progname = $option{-prompt} // $0; | 
| 853 | 1 |  |  |  |  | 2 | $progname =~ s{^.*/}{}xms; | 
| 854 |  |  |  |  |  |  |  | 
| 855 | 1 |  |  |  |  | 3 | my $HINT = '[enter command line args here]'; | 
| 856 | 1 |  |  |  |  | 5 | $option{-prompt} = "> $progname  $HINT\r> $progname "; | 
| 857 |  |  |  |  |  |  |  | 
| 858 | 1 |  |  |  |  | 2 | $option{-complete} = 'filenames'; | 
| 859 |  |  |  |  |  |  |  | 
| 860 | 1 |  |  |  |  | 2 | my $not_first; | 
| 861 |  |  |  |  |  |  | $option{-echo}   = sub{ | 
| 862 | 0 |  |  | 0 |  | 0 | my $char = shift; | 
| 863 | 0 |  |  |  |  | 0 | $option{-prompt} = "> $progname ";  # Sneaky resetting to handle completions | 
| 864 | 0 | 0 |  |  |  | 0 | return $char if $not_first++; | 
| 865 | 0 |  |  |  |  | 0 | return "\r> $progname  " . (q{ } x length $HINT) . "\r> $progname $char"; | 
| 866 |  |  |  |  |  |  | } | 
| 867 | 1 |  |  |  |  | 18 | } | 
| 868 |  |  |  |  |  |  | elsif (!defined $option{-prompt}) { | 
| 869 | 3 |  |  |  |  | 15 | $option{-prompt} = '> '; | 
| 870 |  |  |  |  |  |  | } | 
| 871 |  |  |  |  |  |  | elsif ($option{-prompt} =~ m{ \S \z}xms) { | 
| 872 |  |  |  |  |  |  | # If prompt doesn't end in whitespace, make it so... | 
| 873 | 79 |  |  |  |  | 220 | $option{-prompt} .= ' '; | 
| 874 |  |  |  |  |  |  | } | 
| 875 |  |  |  |  |  |  | elsif ($option{-prompt} =~ m{ (.*) \n \z}xms) { | 
| 876 |  |  |  |  |  |  | # If prompt ends in a newline, remove it... | 
| 877 | 0 |  |  |  |  | 0 | $option{-prompt} = $1; | 
| 878 |  |  |  |  |  |  | } | 
| 879 |  |  |  |  |  |  |  | 
| 880 |  |  |  |  |  |  | # Steal history set name if -h given without a specification... | 
| 881 | 97 |  | 33 |  |  | 285 | $option{-history} //= $option{-prompt}; | 
| 882 |  |  |  |  |  |  |  | 
| 883 |  |  |  |  |  |  | # Verify any default satisfies any constraints... | 
| 884 | 97 | 100 | 100 |  |  | 301 | if (exists $option{-def} && !$option{-def_nocheck}) { | 
| 885 | 6 | 50 |  |  |  | 21 | if (!_verify_input_constraints(\q{},undef,undef,\%option)) { | 
| 886 | 0 |  |  |  |  | 0 | _warn( misc => | 
| 887 |  |  |  |  |  |  | 'prompt(): -default value does not satisfy -must constraints' | 
| 888 |  |  |  |  |  |  | ); | 
| 889 |  |  |  |  |  |  | } | 
| 890 |  |  |  |  |  |  | } | 
| 891 |  |  |  |  |  |  |  | 
| 892 | 97 |  |  |  |  | 254 | return \%option; | 
| 893 |  |  |  |  |  |  | } | 
| 894 |  |  |  |  |  |  |  | 
| 895 |  |  |  |  |  |  | #====[ Error Handlers ]========================================= | 
| 896 |  |  |  |  |  |  |  | 
| 897 |  |  |  |  |  |  | sub _opt_err { | 
| 898 | 2 |  |  | 2 |  | 7 | my ($problem, $option, $expectation, $found) = @_; | 
| 899 | 2 | 100 |  |  |  | 6 | if (@_ > 3) { | 
| 900 | 1 |  |  |  |  | 8 | Carp::croak "prompt(): $problem value for $option (expected $expectation, but found $found)"; | 
| 901 |  |  |  |  |  |  | } | 
| 902 |  |  |  |  |  |  | else { | 
| 903 | 1 |  |  |  |  | 8 | Carp::croak "prompt(): $problem value for $option (expected $expectation)"; | 
| 904 |  |  |  |  |  |  | } | 
| 905 |  |  |  |  |  |  | } | 
| 906 |  |  |  |  |  |  |  | 
| 907 |  |  |  |  |  |  | sub _warn { | 
| 908 | 5 |  |  | 5 |  | 46 | my ($category, @message) = @_; | 
| 909 |  |  |  |  |  |  |  | 
| 910 | 5 | 100 |  |  |  | 124 | return if !warnings::enabled($category); | 
| 911 |  |  |  |  |  |  |  | 
| 912 | 3 |  |  |  |  | 526 | my $message = join(q{},@message); | 
| 913 | 3 | 100 |  |  |  | 99 | warn $message =~ /\n$/ ? $message : Carp::shortmess($message); | 
| 914 |  |  |  |  |  |  | } | 
| 915 |  |  |  |  |  |  |  | 
| 916 |  |  |  |  |  |  |  | 
| 917 |  |  |  |  |  |  | #====[ Utility subroutines ]==================================== | 
| 918 |  |  |  |  |  |  |  | 
| 919 |  |  |  |  |  |  | # Return the *ARGV filehandle, "magic-opening" it if necessary... | 
| 920 |  |  |  |  |  |  | sub _open_ARGV { | 
| 921 | 80 | 50 |  | 80 |  | 398 | if (!openhandle \*ARGV) { | 
| 922 | 0 |  | 0 |  |  | 0 | $ARGV = shift @ARGV // '-'; | 
| 923 | 0 | 0 |  |  |  | 0 | open *ARGV or Carp::croak(qq{prompt(): Can't open *ARGV: $!}); | 
| 924 |  |  |  |  |  |  | } | 
| 925 | 80 |  |  |  |  | 252 | return \*ARGV; | 
| 926 |  |  |  |  |  |  | } | 
| 927 |  |  |  |  |  |  |  | 
| 928 |  |  |  |  |  |  | my $INTEGER_PAT = qr{ \A \s*+ [+-]?+ \d++ (?: [Ee] \+? \d+ )? \s*+ \Z }xms; | 
| 929 |  |  |  |  |  |  |  | 
| 930 |  |  |  |  |  |  | my $NUMBER_PAT  = qr{ | 
| 931 |  |  |  |  |  |  | \A \s*+ [+-]?+ | 
| 932 |  |  |  |  |  |  | (?: | 
| 933 |  |  |  |  |  |  | \d++ (?: [.,] \d*+ )? | 
| 934 |  |  |  |  |  |  | |   [.,] \d++ | 
| 935 |  |  |  |  |  |  | ) | 
| 936 |  |  |  |  |  |  | (?: [eE] [+-]?+ \d++ )? | 
| 937 |  |  |  |  |  |  | \s*+ \Z | 
| 938 |  |  |  |  |  |  | }xms; | 
| 939 |  |  |  |  |  |  |  | 
| 940 |  |  |  |  |  |  | # Verify interactive constraints... | 
| 941 |  |  |  |  |  |  | sub _verify_input_constraints { | 
| 942 | 121 |  |  | 121 |  | 329 | my ($input_ref, $local_fake_input_ref, $outputter_ref, $opt_ref, $extras) | 
| 943 |  |  |  |  |  |  | = @_; | 
| 944 |  |  |  |  |  |  |  | 
| 945 |  |  |  |  |  |  | # Use default if appropriate (but short-circuit checks if -DEFAULT set)... | 
| 946 | 121 |  |  |  |  | 193 | my $input = ${$input_ref}; | 
|  | 121 |  |  |  |  | 269 |  | 
| 947 | 121 | 50 | 66 |  |  | 211 | if (${$input_ref} =~ m{^\R?$}xms && exists $opt_ref->{-def}) { | 
|  | 121 |  |  |  |  | 709 |  | 
| 948 | 11 | 100 |  |  |  | 30 | return 1 if $opt_ref->{-def_nocheck}; | 
| 949 |  |  |  |  |  |  | $input = $opt_ref->{-def} | 
| 950 | 10 |  |  |  |  | 20 | } | 
| 951 | 120 |  |  |  |  | 279 | chomp $input; | 
| 952 |  |  |  |  |  |  |  | 
| 953 | 120 |  |  |  |  | 187 | my $failed; | 
| 954 |  |  |  |  |  |  | # Integer constraint is hard-coded... | 
| 955 | 120 | 100 | 100 |  |  | 511 | if ($opt_ref->{-integer} && $input !~ $INTEGER_PAT) { | 
| 956 | 4 |  |  |  |  | 12 | $failed = $opt_ref->{-prompt} . "(must be an integer) "; | 
| 957 |  |  |  |  |  |  | } | 
| 958 |  |  |  |  |  |  |  | 
| 959 |  |  |  |  |  |  | # Numeric constraint is hard-coded... | 
| 960 | 120 | 100 | 100 |  |  | 678 | if (!$failed && $opt_ref->{-number} && $input !~ $NUMBER_PAT) { | 
|  |  |  | 100 |  |  |  |  | 
| 961 | 2 |  |  |  |  | 6 | $failed = $opt_ref->{-prompt} . "(must be a number) "; | 
| 962 |  |  |  |  |  |  | } | 
| 963 |  |  |  |  |  |  |  | 
| 964 |  |  |  |  |  |  | # Sort and clean up -must list... | 
| 965 | 120 |  | 50 |  |  | 327 | my $must_ref = $opt_ref->{-must} // {}; | 
| 966 | 120 |  |  |  |  | 186 | my @must_keys     = sort keys %{$must_ref}; | 
|  | 120 |  |  |  |  | 464 |  | 
| 967 | 120 | 50 |  |  |  | 304 | my %clean_key_for = map { $_ => (/^\d+[.:]?\s*(.*)/s ? $1 : $_) } @must_keys; | 
|  | 36 |  |  |  |  | 170 |  | 
| 968 | 120 |  |  |  |  | 233 | my @must_kv_list  = map { $clean_key_for{$_} => $must_ref->{$_} } @must_keys; | 
|  | 36 |  |  |  |  | 93 |  | 
| 969 |  |  |  |  |  |  |  | 
| 970 |  |  |  |  |  |  | # Combine -yesno and -must constraints... | 
| 971 |  |  |  |  |  |  | my %constraint_for = ( | 
| 972 | 120 |  | 50 |  |  | 510 | %{ $extras // {} }, | 
| 973 | 120 |  | 100 |  |  | 178 | %{ $opt_ref->{-yesno}{must} // {} }, | 
|  | 120 |  |  |  |  | 611 |  | 
| 974 |  |  |  |  |  |  | @must_kv_list, | 
| 975 |  |  |  |  |  |  | ); | 
| 976 |  |  |  |  |  |  | my @constraints = ( | 
| 977 | 120 |  | 50 |  |  | 519 | keys %{ $extras // {} }, | 
| 978 | 120 |  | 100 |  |  | 602 | keys %{ $opt_ref->{-yesno}{must} // {} }, | 
| 979 | 120 |  |  |  |  | 359 | @clean_key_for{@must_keys}, | 
| 980 |  |  |  |  |  |  | ); | 
| 981 |  |  |  |  |  |  |  | 
| 982 |  |  |  |  |  |  | # User-specified constraints... | 
| 983 | 120 | 100 | 100 |  |  | 629 | if (!$failed && keys %constraint_for) { | 
| 984 |  |  |  |  |  |  | CONSTRAINT: | 
| 985 | 62 |  |  |  |  | 129 | for my $msg (@constraints) { | 
| 986 | 62 |  |  |  |  | 91 | my $constraint = $constraint_for{$msg}; | 
| 987 | 36 | 100 |  | 36 |  | 383 | next CONSTRAINT if eval { no warnings; local $_ = $input; match($input, $constraint); }; | 
|  | 36 |  |  |  |  | 79 |  | 
|  | 36 |  |  |  |  | 8848 |  | 
|  | 62 |  |  |  |  | 100 |  | 
|  | 62 |  |  |  |  | 116 |  | 
|  | 62 |  |  |  |  | 188 |  | 
| 988 |  |  |  |  |  |  | $failed = $msg =~ m{\A [[:upper:]] }xms ? "$msg " | 
| 989 |  |  |  |  |  |  | : $msg =~ m{\A \W }xms          ? $opt_ref->{-prompt} | 
| 990 |  |  |  |  |  |  | . "$msg " | 
| 991 |  |  |  |  |  |  | :                                 $opt_ref->{-prompt} | 
| 992 | 22 | 100 |  |  |  | 2088 | . "(must $msg) " | 
|  |  | 100 |  |  |  |  |  | 
| 993 |  |  |  |  |  |  | ; | 
| 994 | 22 |  |  |  |  | 54 | last CONSTRAINT; | 
| 995 |  |  |  |  |  |  | } | 
| 996 |  |  |  |  |  |  | } | 
| 997 |  |  |  |  |  |  |  | 
| 998 |  |  |  |  |  |  | # If any constraint not satisfied... | 
| 999 | 120 | 100 |  |  |  | 4161 | if ($failed) { | 
| 1000 |  |  |  |  |  |  | # Return failure if not actually prompting at the moment... | 
| 1001 | 28 | 50 |  |  |  | 74 | return 0 if !$outputter_ref; | 
| 1002 |  |  |  |  |  |  |  | 
| 1003 |  |  |  |  |  |  | # Redraw post-menu prompt with failure message appended... | 
| 1004 | 28 |  |  |  |  | 180 | $failed =~ s{.*$MENU_MK}{}xms; | 
| 1005 | 28 |  |  |  |  | 73 | $outputter_ref->(-style => _wipe_line(), $failed); | 
| 1006 |  |  |  |  |  |  |  | 
| 1007 |  |  |  |  |  |  | # Reset input collector... | 
| 1008 | 28 |  |  |  |  | 50 | ${$input_ref}  = q{}; | 
|  | 28 |  |  |  |  | 52 |  | 
| 1009 |  |  |  |  |  |  |  | 
| 1010 |  |  |  |  |  |  | # Reset faked input, if any... | 
| 1011 | 28 | 50 | 33 |  |  | 89 | if (defined $fake_input && length($fake_input) > 0) { | 
| 1012 | 0 |  |  |  |  | 0 | $fake_input =~ s{ \A (.*) \R? }{}xm; | 
| 1013 | 0 |  |  |  |  | 0 | ${$local_fake_input_ref} = $1; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1014 |  |  |  |  |  |  | } | 
| 1015 |  |  |  |  |  |  |  | 
| 1016 | 36 |  |  | 36 |  | 317 | no warnings 'exiting'; | 
|  | 36 |  |  |  |  | 77 |  | 
|  | 36 |  |  |  |  | 27050 |  | 
| 1017 | 28 |  |  |  |  | 135 | next INPUT; | 
| 1018 |  |  |  |  |  |  | } | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | # Otherwise succeed... | 
| 1021 | 92 |  |  |  |  | 409 | return 1; | 
| 1022 |  |  |  |  |  |  | } | 
| 1023 |  |  |  |  |  |  |  | 
| 1024 |  |  |  |  |  |  | # Build a sub to read from specified filehandle, with or without timeout... | 
| 1025 |  |  |  |  |  |  | sub _generate_buffered_reader_from { | 
| 1026 | 97 |  |  | 97 |  | 227 | my ($in_fh, $outputter_ref, $opt_ref) = @_; | 
| 1027 |  |  |  |  |  |  |  | 
| 1028 |  |  |  |  |  |  | # Set-up for timeouts... | 
| 1029 | 97 |  | 50 |  |  | 410 | my $fileno      = fileno($in_fh) // -1; | 
| 1030 | 97 |  | 66 |  |  | 430 | my $has_timeout = exists $opt_ref->{-timeout} && $fileno >= 0; | 
| 1031 | 97 |  |  |  |  | 179 | my $timeout     = $opt_ref->{-timeout}; | 
| 1032 | 97 |  |  |  |  | 228 | my $readbits    = q{}; | 
| 1033 | 97 | 50 | 33 |  |  | 333 | if ($has_timeout && $fileno >= 0) { | 
| 1034 | 0 |  |  |  |  | 0 | vec($readbits,$fileno,1) = 1; | 
| 1035 |  |  |  |  |  |  | } | 
| 1036 |  |  |  |  |  |  |  | 
| 1037 |  |  |  |  |  |  | # Set up local faked input, if any... | 
| 1038 | 97 |  |  |  |  | 256 | my $local_fake_input; | 
| 1039 |  |  |  |  |  |  | my $orig_fake_input; | 
| 1040 | 97 | 100 | 66 |  |  | 580 | if (defined $fake_input && length($fake_input) > 0) { | 
| 1041 | 4 |  |  |  |  | 50 | $fake_input =~ s{ \A (.*) \R? }{}xm; | 
| 1042 | 4 |  |  |  |  | 22 | $orig_fake_input = $local_fake_input = $1; | 
| 1043 |  |  |  |  |  |  | } | 
| 1044 |  |  |  |  |  |  |  | 
| 1045 |  |  |  |  |  |  | return sub { | 
| 1046 | 97 |  |  | 97 |  | 220 | my ($extra_constraints) = @_; | 
| 1047 |  |  |  |  |  |  |  | 
| 1048 |  |  |  |  |  |  | INPUT: | 
| 1049 | 97 |  |  |  |  | 192 | while (1) { | 
| 1050 | 125 | 50 | 33 |  |  | 476 | if (!$has_timeout || select $readbits, undef, undef, $timeout) { | 
| 1051 | 125 |  |  |  |  | 205 | my $input; | 
| 1052 |  |  |  |  |  |  |  | 
| 1053 |  |  |  |  |  |  | # Real input comes from real filehandles... | 
| 1054 | 125 | 100 |  |  |  | 283 | if (!defined $local_fake_input) { | 
| 1055 | 121 |  |  |  |  | 492 | $input = readline $in_fh; | 
| 1056 |  |  |  |  |  |  | } | 
| 1057 |  |  |  |  |  |  | # Fake input has to be typed... | 
| 1058 |  |  |  |  |  |  | else { | 
| 1059 | 4 |  |  |  |  | 9 | $input = $local_fake_input; | 
| 1060 | 4 |  |  |  |  | 4000845 | sleep 1; | 
| 1061 | 4 |  |  |  |  | 237 | for ($local_fake_input =~ m/\X/g) { | 
| 1062 | 24 |  |  |  |  | 137 | _simulate_typing(); | 
| 1063 | 24 |  |  |  |  | 542 | $outputter_ref->(-echostyle => $opt_ref->{-echo}($_)); | 
| 1064 |  |  |  |  |  |  | } | 
| 1065 | 4 |  |  |  |  | 61 | readline $in_fh; | 
| 1066 |  |  |  |  |  |  |  | 
| 1067 |  |  |  |  |  |  | # Check for simulated EOF... | 
| 1068 | 4 | 50 |  |  |  | 126 | if ($input =~ m{^ \s* (?: \cD | \cZ ) }xms) { | 
| 1069 | 0 |  |  |  |  | 0 | $input = undef; | 
| 1070 |  |  |  |  |  |  | } | 
| 1071 |  |  |  |  |  |  | } | 
| 1072 |  |  |  |  |  |  |  | 
| 1073 | 125 | 50 |  |  |  | 389 | if (exists $opt_ref->{-cancel}) { | 
| 1074 | 0 |  |  |  |  | 0 | for my $nextchar (split q{}, $input) { | 
| 1075 |  |  |  |  |  |  | die bless \$input, 'IO::Prompter::Cancellation' | 
| 1076 | 0 | 0 |  |  |  | 0 | if match($nextchar, $opt_ref->{-cancel}); | 
| 1077 |  |  |  |  |  |  | } | 
| 1078 |  |  |  |  |  |  | } | 
| 1079 |  |  |  |  |  |  |  | 
| 1080 | 125 | 100 |  |  |  | 338 | if (defined $input) { | 
| 1081 | 115 |  |  |  |  | 451 | _verify_input_constraints( | 
| 1082 |  |  |  |  |  |  | \$input, \$local_fake_input, $outputter_ref, $opt_ref, $extra_constraints | 
| 1083 |  |  |  |  |  |  | ); | 
| 1084 |  |  |  |  |  |  | } | 
| 1085 |  |  |  |  |  |  |  | 
| 1086 |  |  |  |  |  |  | return defined $input && $opt_ref->{-single} | 
| 1087 | 97 | 100 | 100 |  |  | 611 | ? substr($input, 0, 1) | 
| 1088 |  |  |  |  |  |  | : $input; | 
| 1089 |  |  |  |  |  |  | } | 
| 1090 |  |  |  |  |  |  | else { | 
| 1091 | 0 |  |  |  |  | 0 | return; | 
| 1092 |  |  |  |  |  |  | } | 
| 1093 |  |  |  |  |  |  | } | 
| 1094 |  |  |  |  |  |  | } | 
| 1095 | 97 |  |  |  |  | 990 | } | 
| 1096 |  |  |  |  |  |  |  | 
| 1097 |  |  |  |  |  |  | sub _autoflush { | 
| 1098 | 0 |  |  | 0 |  | 0 | my ($fh) = @_; | 
| 1099 | 0 |  |  |  |  | 0 | my $prev_selected = select $fh; | 
| 1100 | 0 |  |  |  |  | 0 | $| = 1; | 
| 1101 | 0 |  |  |  |  | 0 | select $prev_selected; | 
| 1102 | 0 |  |  |  |  | 0 | return; | 
| 1103 |  |  |  |  |  |  | } | 
| 1104 |  |  |  |  |  |  |  | 
| 1105 |  |  |  |  |  |  | sub _simulate_typing { | 
| 1106 | 24 |  |  | 24 |  | 65 | state $TYPING_SPEED = 0.07; # seconds per character | 
| 1107 | 24 |  |  |  |  | 920634 | select undef, undef, undef, rand $TYPING_SPEED; | 
| 1108 |  |  |  |  |  |  | } | 
| 1109 |  |  |  |  |  |  |  | 
| 1110 |  |  |  |  |  |  | sub _term_width { | 
| 1111 | 36 |  |  | 36 |  | 340 | my ($term_width) = eval { no warnings 'deprecated'; Term::ReadKey::GetTerminalSize(\*STDERR) }; | 
|  | 36 |  |  | 28 |  | 109 |  | 
|  | 36 |  |  |  |  | 36874 |  | 
|  | 28 |  |  |  |  | 45 |  | 
|  | 28 |  |  |  |  | 323 |  | 
| 1112 | 28 |  | 33 |  |  | 265 | return $term_width // $DEFAULT_TERM_WIDTH; | 
| 1113 |  |  |  |  |  |  | } | 
| 1114 |  |  |  |  |  |  |  | 
| 1115 |  |  |  |  |  |  | sub _wipe_line { | 
| 1116 | 28 |  |  | 28 |  | 65 | return qq{\r} . q{ } x (_term_width()-1) . qq{\r}; | 
| 1117 |  |  |  |  |  |  | } | 
| 1118 |  |  |  |  |  |  |  | 
| 1119 |  |  |  |  |  |  | # Convert a specification into a list of possible completions... | 
| 1120 |  |  |  |  |  |  | sub _current_completions_for { | 
| 1121 | 0 |  |  | 0 |  | 0 | my ($input_text, $opt_ref) = @_; | 
| 1122 | 0 |  |  |  |  | 0 | my $completer = $opt_ref->{-complete}; | 
| 1123 |  |  |  |  |  |  |  | 
| 1124 |  |  |  |  |  |  | # Isolate the final whitespace-separated word... | 
| 1125 | 0 |  |  |  |  | 0 | my ($prefix, $lastword) | 
| 1126 |  |  |  |  |  |  | = $input_text =~ m{ | 
| 1127 |  |  |  |  |  |  | (?| ^ (.*\s+) (.*) | 
| 1128 |  |  |  |  |  |  | | ^ ()      (.*) | 
| 1129 |  |  |  |  |  |  | ) | 
| 1130 |  |  |  |  |  |  | }xms; | 
| 1131 |  |  |  |  |  |  |  | 
| 1132 |  |  |  |  |  |  | # Find candidates... | 
| 1133 | 0 |  |  |  |  | 0 | my @candidates; | 
| 1134 | 0 |  | 0 |  |  | 0 | for my $completer_type (ref($completer) || $completer // q{}) { | 
|  |  |  | 0 |  |  |  |  | 
| 1135 |  |  |  |  |  |  | # If completer is sub, recursively call it with input words... | 
| 1136 | 0 | 0 |  |  |  | 0 | if ($completer_type eq 'CODE') { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1137 |  |  |  |  |  |  | ($prefix, @candidates) | 
| 1138 |  |  |  |  |  |  | = _current_completions_for( | 
| 1139 |  |  |  |  |  |  | $input_text, | 
| 1140 | 0 |  |  |  |  | 0 | { %{$opt_ref}, | 
|  | 0 |  |  |  |  | 0 |  | 
| 1141 |  |  |  |  |  |  | -complete => $completer->(split /\s+/, $input_text, -1) | 
| 1142 |  |  |  |  |  |  | } | 
| 1143 |  |  |  |  |  |  | ); | 
| 1144 |  |  |  |  |  |  | } | 
| 1145 |  |  |  |  |  |  |  | 
| 1146 |  |  |  |  |  |  | # If completer is array, grep the appropriate elements... | 
| 1147 |  |  |  |  |  |  | elsif ($completer_type eq 'ARRAY') { | 
| 1148 | 0 |  |  |  |  | 0 | @candidates = grep { /\A\Q$lastword\E/ } @{$completer}; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1149 |  |  |  |  |  |  | } | 
| 1150 |  |  |  |  |  |  |  | 
| 1151 |  |  |  |  |  |  | # If completer is hash, grep the appropriate keys... | 
| 1152 |  |  |  |  |  |  | elsif ($completer_type eq 'HASH') { | 
| 1153 | 0 |  |  |  |  | 0 | @candidates = grep { /\A\Q$lastword\E/ } keys %{$completer}; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1154 |  |  |  |  |  |  | } | 
| 1155 |  |  |  |  |  |  |  | 
| 1156 |  |  |  |  |  |  | # If completer is 'file...', glob up the appropriate filenames... | 
| 1157 |  |  |  |  |  |  | elsif ($completer_type eq /^file\w*$/) { | 
| 1158 | 0 |  |  |  |  | 0 | @candidates = glob($lastword.'*'); | 
| 1159 |  |  |  |  |  |  | } | 
| 1160 |  |  |  |  |  |  |  | 
| 1161 |  |  |  |  |  |  | # If completer is 'dir...', glob up the appropriate directories... | 
| 1162 |  |  |  |  |  |  | elsif ($completer_type eq /^dir\w*$/) { | 
| 1163 | 0 |  |  |  |  | 0 | @candidates = grep {-d} glob($lastword.'*'); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1164 |  |  |  |  |  |  | } | 
| 1165 |  |  |  |  |  |  | } | 
| 1166 |  |  |  |  |  |  |  | 
| 1167 | 0 |  |  |  |  | 0 | chomp @candidates; | 
| 1168 | 0 |  |  |  |  | 0 | return ($prefix, @candidates); | 
| 1169 |  |  |  |  |  |  | } | 
| 1170 |  |  |  |  |  |  |  | 
| 1171 |  |  |  |  |  |  |  | 
| 1172 |  |  |  |  |  |  | sub _current_history_for { | 
| 1173 | 0 |  |  | 0 |  | 0 | my ($prefix, $opt_ref) = @_; | 
| 1174 |  |  |  |  |  |  |  | 
| 1175 | 0 |  |  |  |  | 0 | my $prefix_len = length($prefix); | 
| 1176 | 0 | 0 |  |  |  | 0 | return q{}, map { /\A (.*?) \R \Z/x ? $1 : $_ } | 
| 1177 | 0 |  |  |  |  | 0 | grep { substr($_,0,$prefix_len) eq $prefix } | 
| 1178 | 0 |  |  |  |  | 0 | @{ $history_cache{$opt_ref->{-history}} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1179 |  |  |  |  |  |  | } | 
| 1180 |  |  |  |  |  |  |  | 
| 1181 |  |  |  |  |  |  | sub _longest_common_prefix_for { | 
| 1182 | 0 |  |  | 0 |  | 0 | my $prefix = shift @_; | 
| 1183 | 0 |  |  |  |  | 0 | for my $comparison (@_) { | 
| 1184 | 0 |  |  |  |  | 0 | ($comparison ^ $prefix) =~ m{ \A (\0*) }xms; | 
| 1185 | 0 |  |  |  |  | 0 | my $common_length = length($1); | 
| 1186 | 0 | 0 |  |  |  | 0 | return q{} if !$common_length; | 
| 1187 | 0 |  |  |  |  | 0 | $prefix = substr($prefix, 0, $common_length); | 
| 1188 |  |  |  |  |  |  | } | 
| 1189 | 0 |  |  |  |  | 0 | return $prefix; | 
| 1190 |  |  |  |  |  |  | } | 
| 1191 |  |  |  |  |  |  |  | 
| 1192 |  |  |  |  |  |  | sub _display_completions { | 
| 1193 | 0 |  |  | 0 |  | 0 | my ($input, @candidates) = @_; | 
| 1194 |  |  |  |  |  |  |  | 
| 1195 | 0 | 0 |  |  |  | 0 | return q{} if @candidates <= 1; | 
| 1196 |  |  |  |  |  |  |  | 
| 1197 |  |  |  |  |  |  | # How big is each field in the table? | 
| 1198 | 0 |  |  |  |  | 0 | my $field_width | 
| 1199 |  |  |  |  |  |  | = _term_width() / $COMPLETE_DISPLAY_FIELDS - $COMPLETE_DISPLAY_GAP; | 
| 1200 |  |  |  |  |  |  |  | 
| 1201 |  |  |  |  |  |  | # Crop the possibilities intelligently to that width... | 
| 1202 | 0 |  |  |  |  | 0 | for my $candidate (@candidates) { | 
| 1203 | 0 |  |  |  |  | 0 | substr($candidate, 0, length($input)) =~ s{ \A .* [/\\] }{}xms; | 
| 1204 | 0 |  |  |  |  | 0 | $candidate | 
| 1205 |  |  |  |  |  |  | = sprintf "%-*s", $field_width, substr($candidate,0,$field_width); | 
| 1206 |  |  |  |  |  |  | } | 
| 1207 |  |  |  |  |  |  |  | 
| 1208 |  |  |  |  |  |  | # Collect them into rows... | 
| 1209 | 0 |  |  |  |  | 0 | my $display = "\n"; | 
| 1210 | 0 |  |  |  |  | 0 | my $gap     = q{ } x $COMPLETE_DISPLAY_GAP; | 
| 1211 | 0 |  |  |  |  | 0 | while (@candidates) { | 
| 1212 | 0 |  |  |  |  | 0 | $display .= $gap | 
| 1213 |  |  |  |  |  |  | . join($gap, splice(@candidates, 0, $COMPLETE_DISPLAY_FIELDS)) | 
| 1214 |  |  |  |  |  |  | . "\n"; | 
| 1215 |  |  |  |  |  |  | } | 
| 1216 |  |  |  |  |  |  |  | 
| 1217 | 0 |  |  |  |  | 0 | return $display; | 
| 1218 |  |  |  |  |  |  | } | 
| 1219 |  |  |  |  |  |  |  | 
| 1220 |  |  |  |  |  |  | sub _generate_unbuffered_reader_from { | 
| 1221 | 97 |  |  | 97 |  | 255 | my ($in_fh, $outputter_ref, $opt_ref) = @_; | 
| 1222 |  |  |  |  |  |  |  | 
| 1223 | 36 |  |  | 36 |  | 346 | my $has_readkey = eval { no warnings 'deprecated'; require Term::ReadKey }; | 
|  | 36 |  |  |  |  | 91 |  | 
|  | 36 |  |  |  |  | 13730 |  | 
|  | 97 |  |  |  |  | 179 |  | 
|  | 97 |  |  |  |  | 14087 |  | 
| 1224 |  |  |  |  |  |  |  | 
| 1225 |  |  |  |  |  |  | # If no per-character reads, fall back on buffered input... | 
| 1226 | 97 | 50 | 33 |  |  | 723 | if (!-t $in_fh || !$has_readkey) { | 
| 1227 | 97 |  |  |  |  | 357 | return _generate_buffered_reader_from($in_fh, $outputter_ref, $opt_ref); | 
| 1228 |  |  |  |  |  |  | } | 
| 1229 |  |  |  |  |  |  |  | 
| 1230 |  |  |  |  |  |  | # Adapt to local control characters... | 
| 1231 | 0 |  |  |  |  | 0 | my %ctrl = eval { Term::ReadKey::GetControlChars($in_fh) }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1232 | 0 |  |  |  |  | 0 | delete $ctrl{$_} for grep { $ctrl{$_} eq "\cA" } keys %ctrl; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1233 |  |  |  |  |  |  |  | 
| 1234 | 0 |  | 0 |  |  | 0 | $ctrl{EOF}       //= "\4"; | 
| 1235 | 0 |  | 0 |  |  | 0 | $ctrl{INTERRUPT} //= "\3"; | 
| 1236 | 0 | 0 | 0 |  |  | 0 | $ctrl{ERASE}     //= $^O eq 'MSWin32' ? "\10" : "0177"; | 
| 1237 |  |  |  |  |  |  |  | 
| 1238 | 0 |  |  |  |  | 0 | my $ctrl           = join '|', values %ctrl; | 
| 1239 |  |  |  |  |  |  |  | 
| 1240 | 0 |  | 0 |  |  | 0 | my $VERBATIM_KEY = $ctrl{QUOTENEXT} // $DEFAULT_VERBATIM_KEY; | 
| 1241 |  |  |  |  |  |  |  | 
| 1242 |  |  |  |  |  |  | # Translate timeout for ReadKey (with 32-bit MAXINT workaround for Windows)... | 
| 1243 |  |  |  |  |  |  | my $timeout = !defined $opt_ref->{-timeout} ? 0x7FFFFFFF    # 68 years | 
| 1244 |  |  |  |  |  |  | : $opt_ref->{-timeout} == 0     ? -1 | 
| 1245 |  |  |  |  |  |  | :                                 $opt_ref->{-timeout} | 
| 1246 | 0 | 0 |  |  |  | 0 | ; | 
|  |  | 0 |  |  |  |  |  | 
| 1247 |  |  |  |  |  |  |  | 
| 1248 |  |  |  |  |  |  | return sub { | 
| 1249 | 0 |  |  | 0 |  | 0 | my ($extra_constraints) = @_; | 
| 1250 |  |  |  |  |  |  |  | 
| 1251 |  |  |  |  |  |  | # Short-circuit on unreadable filehandle... | 
| 1252 | 0 | 0 |  |  |  | 0 | return if !openhandle($in_fh); | 
| 1253 |  |  |  |  |  |  |  | 
| 1254 |  |  |  |  |  |  | # Set up direct reading, and prepare to clean up on abnormal exit... | 
| 1255 | 0 |  |  |  |  | 0 | Term::ReadKey::ReadMode('raw', $in_fh); | 
| 1256 | 0 |  |  |  |  | 0 | my $prev_SIGINT = $SIG{INT}; | 
| 1257 | 0 | 0 |  |  |  | 0 | local $SIG{INT} = sub { return if $prev_SIGINT eq 'IGNORE'; | 
| 1258 | 0 |  |  |  |  | 0 | Term::ReadKey::ReadMode('restore', $in_fh); | 
| 1259 | 0 | 0 | 0 |  |  | 0 | exit(1) if !defined $prev_SIGINT | 
| 1260 |  |  |  |  |  |  | || $prev_SIGINT eq 'DEFAULT'; | 
| 1261 |  |  |  |  |  |  | { | 
| 1262 | 0 |  |  |  |  | 0 | package main; | 
| 1263 | 36 |  |  | 36 |  | 318 | no strict 'refs'; | 
|  | 36 |  |  |  |  | 87 |  | 
|  | 36 |  |  |  |  | 154030 |  | 
| 1264 | 0 |  |  |  |  | 0 | $prev_SIGINT->() | 
| 1265 |  |  |  |  |  |  | } | 
| 1266 | 0 |  |  |  |  | 0 | }; | 
| 1267 |  |  |  |  |  |  |  | 
| 1268 |  |  |  |  |  |  | # Set up local faked input, if any... | 
| 1269 | 0 |  |  |  |  | 0 | my $local_fake_input; | 
| 1270 |  |  |  |  |  |  | my $orig_fake_input; | 
| 1271 | 0 | 0 | 0 |  |  | 0 | if (defined $fake_input && length($fake_input) > 0) { | 
| 1272 | 0 |  |  |  |  | 0 | $fake_input =~ s{ \A (.*) \R? }{}xm; | 
| 1273 | 0 |  |  |  |  | 0 | $orig_fake_input = $local_fake_input = $1; | 
| 1274 |  |  |  |  |  |  | } | 
| 1275 |  |  |  |  |  |  |  | 
| 1276 | 0 | 0 |  |  |  | 0 | my $input = exists $opt_ref->{-prefill} ? $opt_ref->{-prefill} : q{}; | 
| 1277 | 0 | 0 |  |  |  | 0 | if (exists $opt_ref->{-prefill}) { | 
| 1278 | 0 | 0 |  |  |  | 0 | if (exists $opt_ref->{-monitor}) { | 
| 1279 |  |  |  |  |  |  | my %opts = ( -cursor_pos => length($input), | 
| 1280 |  |  |  |  |  |  | -prompt     => $opt_ref->{-prompt}, | 
| 1281 |  |  |  |  |  |  | -style      => $opt_ref->{-style}->(), | 
| 1282 | 0 |  |  |  |  | 0 | -echostyle  => $opt_ref->{-echostyle}->(), | 
| 1283 |  |  |  |  |  |  | ); | 
| 1284 | 0 |  |  |  |  | 0 | my $input_copy = $input; | 
| 1285 | 0 |  |  |  |  | 0 | eval { $opt_ref->{-monitor}->($input_copy, \%opts) }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1286 |  |  |  |  |  |  | } | 
| 1287 | 0 |  |  |  |  | 0 | $outputter_ref->( -style => $opt_ref->{-style}, _wipe_line(), $opt_ref->{-prompt}); | 
| 1288 | 0 |  |  |  |  | 0 | $outputter_ref->( -echostyle => join(q{}, map { $opt_ref->{-echo}($_) } $input =~ m/\X/g) ); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1289 |  |  |  |  |  |  | } | 
| 1290 |  |  |  |  |  |  |  | 
| 1291 | 0 |  |  |  |  | 0 | my $insert_offset = 0; | 
| 1292 |  |  |  |  |  |  | INPUT: | 
| 1293 | 0 |  |  |  |  | 0 | while (1) { | 
| 1294 | 0 |  |  |  |  | 0 | state $prev_was_verbatim = 0; | 
| 1295 | 0 |  |  |  |  | 0 | state $completion_level  = 0; | 
| 1296 | 0 |  |  |  |  | 0 | state $completion_type   = q{}; | 
| 1297 |  |  |  |  |  |  |  | 
| 1298 |  |  |  |  |  |  | # Get next character entered... | 
| 1299 | 0 |  |  |  |  | 0 | my $next = Term::ReadKey::ReadKey($timeout, $in_fh); | 
| 1300 |  |  |  |  |  |  |  | 
| 1301 |  |  |  |  |  |  | # Check for cancellation... | 
| 1302 | 0 | 0 | 0 |  |  | 0 | if (exists $opt_ref->{-cancel} && match($next, $opt_ref->{-cancel})) { | 
| 1303 | 0 |  |  |  |  | 0 | Term::ReadKey::ReadMode('restore', $in_fh); | 
| 1304 | 0 |  |  |  |  | 0 | die bless \$input, 'IO::Prompter::Cancellation'; | 
| 1305 |  |  |  |  |  |  | } | 
| 1306 |  |  |  |  |  |  |  | 
| 1307 |  |  |  |  |  |  | # Finished with completion mode? | 
| 1308 | 0 | 0 | 0 |  |  | 0 | if (($next//q{}) !~ m{ $COMPLETE_INIT | $COMPLETE_CYCLE }xms) { | 
| 1309 | 0 |  |  |  |  | 0 | $completion_level = 0; | 
| 1310 | 0 |  |  |  |  | 0 | $completion_type = q{}; | 
| 1311 |  |  |  |  |  |  | } | 
| 1312 |  |  |  |  |  |  |  | 
| 1313 |  |  |  |  |  |  | # Are we faking input? | 
| 1314 | 0 |  |  |  |  | 0 | my $faking = defined $local_fake_input; | 
| 1315 |  |  |  |  |  |  |  | 
| 1316 |  |  |  |  |  |  | # If not EOF... | 
| 1317 | 0 | 0 |  |  |  | 0 | if (defined $next) { | 
| 1318 |  |  |  |  |  |  | # Remember where we were parked... | 
| 1319 | 0 |  |  |  |  | 0 | my $prev_insert_offset = $insert_offset; | 
| 1320 |  |  |  |  |  |  |  | 
| 1321 |  |  |  |  |  |  | # Handle interrupts... | 
| 1322 | 0 | 0 | 0 |  |  | 0 | if ($next eq $ctrl{INTERRUPT}) { | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1323 | 0 |  |  |  |  | 0 | $SIG{INT}(); | 
| 1324 | 0 |  |  |  |  | 0 | next INPUT; | 
| 1325 |  |  |  |  |  |  | } | 
| 1326 |  |  |  |  |  |  |  | 
| 1327 |  |  |  |  |  |  | # Handle verbatim quoter... | 
| 1328 |  |  |  |  |  |  | elsif (!$prev_was_verbatim && $next eq $VERBATIM_KEY) { | 
| 1329 | 0 |  |  |  |  | 0 | $prev_was_verbatim = 1; | 
| 1330 | 0 |  |  |  |  | 0 | next INPUT; | 
| 1331 |  |  |  |  |  |  | } | 
| 1332 |  |  |  |  |  |  |  | 
| 1333 |  |  |  |  |  |  | # Handle completions... | 
| 1334 |  |  |  |  |  |  | elsif (!$prev_was_verbatim | 
| 1335 |  |  |  |  |  |  | && ( $next =~ $COMPLETE_INIT | 
| 1336 |  |  |  |  |  |  | || $completion_level > 0 && $next =~ $COMPLETE_CYCLE | 
| 1337 |  |  |  |  |  |  | ) | 
| 1338 |  |  |  |  |  |  | ) { | 
| 1339 | 0 |  |  |  |  | 0 | state @completion_list;  # ...all candidates for completion | 
| 1340 | 0 |  |  |  |  | 0 | state @completion_ring;  # ..."next" candidate cycle | 
| 1341 | 0 |  |  |  |  | 0 | state $completion_ring_first;  # ...special case first time | 
| 1342 | 0 |  |  |  |  | 0 | state $completion_prefix;      # ...skipped before completing | 
| 1343 |  |  |  |  |  |  |  | 
| 1344 |  |  |  |  |  |  | # Track completion type and level (switch if necessary)... | 
| 1345 | 0 | 0 | 0 |  |  | 0 | if ($next =~ $COMPLETE_INIT && index($completion_type, $next) < 0) { | 
| 1346 | 0 | 0 |  |  |  | 0 | $completion_type = index($COMPLETE_KEY, $next) >= 0 ? $COMPLETE_KEY : $COMPLETE_HIST; | 
| 1347 | 0 |  |  |  |  | 0 | $completion_level = 1; | 
| 1348 |  |  |  |  |  |  | } | 
| 1349 |  |  |  |  |  |  | else { | 
| 1350 | 0 |  |  |  |  | 0 | $completion_level++; | 
| 1351 |  |  |  |  |  |  | } | 
| 1352 |  |  |  |  |  |  |  | 
| 1353 |  |  |  |  |  |  | # If starting completion, cache completions... | 
| 1354 | 0 | 0 |  |  |  | 0 | if ($completion_level == 1) { | 
| 1355 | 0 | 0 |  |  |  | 0 | ($completion_prefix, @completion_list) | 
| 1356 |  |  |  |  |  |  | = index($COMPLETE_KEY, $next) >= 0 | 
| 1357 |  |  |  |  |  |  | ? _current_completions_for($input, $opt_ref) | 
| 1358 |  |  |  |  |  |  | : _current_history_for($input, $opt_ref); | 
| 1359 | 0 |  |  |  |  | 0 | @completion_ring = (@completion_list, q{}); | 
| 1360 | 0 |  |  |  |  | 0 | $completion_ring_first = 1; | 
| 1361 |  |  |  |  |  |  | } | 
| 1362 |  |  |  |  |  |  |  | 
| 1363 |  |  |  |  |  |  | # Can only complete if there are completions to be had... | 
| 1364 | 0 | 0 |  |  |  | 0 | if (@completion_list) { | 
| 1365 |  |  |  |  |  |  | # Select the appropriate mode... | 
| 1366 |  |  |  |  |  |  | my $mode = $COMPLETE_MODE{$completion_type}[$completion_level-1] | 
| 1367 | 0 |  | 0 |  |  | 0 | // $COMPLETE_MODE{$completion_type}[-1]; | 
| 1368 |  |  |  |  |  |  |  | 
| 1369 |  |  |  |  |  |  | # 'longest mode' finds longest consistent prefix... | 
| 1370 | 0 | 0 |  |  |  | 0 | if ($mode =~ /longest/) { | 
|  |  | 0 |  |  |  |  |  | 
| 1371 | 0 |  |  |  |  | 0 | $input | 
| 1372 |  |  |  |  |  |  | = $completion_prefix | 
| 1373 |  |  |  |  |  |  | . _longest_common_prefix_for(@completion_list); | 
| 1374 |  |  |  |  |  |  | } | 
| 1375 |  |  |  |  |  |  | # 'full mode' suggests next full match... | 
| 1376 |  |  |  |  |  |  | elsif ($mode =~ /full/) { | 
| 1377 | 0 | 0 |  |  |  | 0 | if (!$completion_ring_first) { | 
| 1378 | 0 | 0 |  |  |  | 0 | if ($next eq $COMPLETE_PREV) { | 
| 1379 | 0 |  |  |  |  | 0 | unshift @completion_ring, | 
| 1380 |  |  |  |  |  |  | pop @completion_ring; | 
| 1381 |  |  |  |  |  |  | } | 
| 1382 |  |  |  |  |  |  | else { | 
| 1383 | 0 |  |  |  |  | 0 | push @completion_ring, | 
| 1384 |  |  |  |  |  |  | shift @completion_ring; | 
| 1385 |  |  |  |  |  |  | } | 
| 1386 |  |  |  |  |  |  | } | 
| 1387 | 0 |  |  |  |  | 0 | $input = $completion_prefix . $completion_ring[0]; | 
| 1388 | 0 |  |  |  |  | 0 | $completion_ring_first = 0; | 
| 1389 |  |  |  |  |  |  | } | 
| 1390 |  |  |  |  |  |  | # 'list mode' lists all possibilities... | 
| 1391 | 0 | 0 |  |  |  | 0 | my $list_display = $mode =~ /list/ | 
| 1392 |  |  |  |  |  |  | ? _display_completions($input, @completion_list) | 
| 1393 |  |  |  |  |  |  | : q{}; | 
| 1394 |  |  |  |  |  |  |  | 
| 1395 |  |  |  |  |  |  | # Update prompt with selected completion... | 
| 1396 |  |  |  |  |  |  | $outputter_ref->( -style => | 
| 1397 |  |  |  |  |  |  | $list_display, | 
| 1398 |  |  |  |  |  |  | _wipe_line(), | 
| 1399 | 0 |  |  |  |  | 0 | $opt_ref->{-prompt}, $input | 
| 1400 |  |  |  |  |  |  | ); | 
| 1401 |  |  |  |  |  |  |  | 
| 1402 |  |  |  |  |  |  | # If last completion was unique choice, completed... | 
| 1403 | 0 | 0 |  |  |  | 0 | if (@completion_list <= 1) { | 
| 1404 | 0 |  |  |  |  | 0 | $completion_level = 0; | 
| 1405 |  |  |  |  |  |  | } | 
| 1406 |  |  |  |  |  |  | } | 
| 1407 | 0 |  |  |  |  | 0 | next INPUT; | 
| 1408 |  |  |  |  |  |  | } | 
| 1409 |  |  |  |  |  |  |  | 
| 1410 |  |  |  |  |  |  | # Handle erasures (including pushbacks if faking)... | 
| 1411 |  |  |  |  |  |  | elsif (!$prev_was_verbatim && $next eq $ctrl{ERASE}) { | 
| 1412 | 0 | 0 |  |  |  | 0 | if (!length $input) { | 
|  |  | 0 |  |  |  |  |  | 
| 1413 |  |  |  |  |  |  | # Do nothing... | 
| 1414 |  |  |  |  |  |  | } | 
| 1415 |  |  |  |  |  |  | elsif ($insert_offset) { | 
| 1416 |  |  |  |  |  |  | # Can't erase past start of input... | 
| 1417 | 0 | 0 |  |  |  | 0 | next INPUT if $insert_offset >= length($input); | 
| 1418 |  |  |  |  |  |  |  | 
| 1419 |  |  |  |  |  |  | # Erase character just before cursor... | 
| 1420 | 0 |  |  |  |  | 0 | substr($input, -$insert_offset-1, 1, q{}); | 
| 1421 |  |  |  |  |  |  |  | 
| 1422 |  |  |  |  |  |  | # Redraw... | 
| 1423 | 0 |  |  |  |  | 0 | my $input_pre  = substr($input.' ',0,length($input)-$insert_offset+1); | 
| 1424 | 0 |  |  |  |  | 0 | my $input_post = substr($input.' ',length($input)-$insert_offset); | 
| 1425 |  |  |  |  |  |  | my $display_pre | 
| 1426 | 0 |  |  |  |  | 0 | = join q{}, map { $opt_ref->{-echo}($_) } $input_pre =~ m/\X/g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1427 |  |  |  |  |  |  | my $display_post | 
| 1428 | 0 |  |  |  |  | 0 | = join q{}, map { $opt_ref->{-echo}($_) } $input_post =~ m/\X/g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1429 |  |  |  |  |  |  | $outputter_ref->( -echostyle => | 
| 1430 |  |  |  |  |  |  | "\b" x length($display_pre) | 
| 1431 | 0 |  |  |  |  | 0 | . join(q{}, map { $opt_ref->{-echo}($_) } $input =~ m/\X/g) | 
| 1432 | 0 |  |  |  |  | 0 | . q{ } x length($opt_ref->{-echo}(q{ })) | 
| 1433 |  |  |  |  |  |  | . "\b" x length($display_post) | 
| 1434 |  |  |  |  |  |  | ); | 
| 1435 |  |  |  |  |  |  | } | 
| 1436 |  |  |  |  |  |  | else { | 
| 1437 | 0 |  |  |  |  | 0 | my $erased = substr($input, -1, 1, q{}); | 
| 1438 | 0 | 0 |  |  |  | 0 | if ($faking) { | 
| 1439 | 0 |  |  |  |  | 0 | substr($local_fake_input,0,0,$erased); | 
| 1440 |  |  |  |  |  |  | } | 
| 1441 |  |  |  |  |  |  | $outputter_ref->( -nostyle => | 
| 1442 | 0 |  | 0 |  |  | 0 | map { $_ x (length($opt_ref->{-echo}($_)//'X')) } | 
|  | 0 |  |  |  |  | 0 |  | 
| 1443 |  |  |  |  |  |  | "\b", ' ', "\b" | 
| 1444 |  |  |  |  |  |  | ); | 
| 1445 |  |  |  |  |  |  | } | 
| 1446 | 0 |  |  |  |  | 0 | next INPUT; | 
| 1447 |  |  |  |  |  |  | } | 
| 1448 |  |  |  |  |  |  |  | 
| 1449 |  |  |  |  |  |  | # Handle EOF (including cancelling any remaining fake input)... | 
| 1450 |  |  |  |  |  |  | elsif (!$prev_was_verbatim && $next eq $ctrl{EOF}) { | 
| 1451 | 0 |  |  |  |  | 0 | Term::ReadKey::ReadMode('restore', $in_fh); | 
| 1452 | 0 |  |  |  |  | 0 | close $in_fh; | 
| 1453 | 0 |  |  |  |  | 0 | undef $fake_input; | 
| 1454 | 0 | 0 |  |  |  | 0 | return length($input) ? $input : undef; | 
| 1455 |  |  |  |  |  |  | } | 
| 1456 |  |  |  |  |  |  |  | 
| 1457 |  |  |  |  |  |  | # Handle escape from faking... | 
| 1458 |  |  |  |  |  |  | elsif (!$prev_was_verbatim && $faking && $next eq $FAKE_ESC) { | 
| 1459 | 0 |  |  |  |  | 0 | my $lookahead = Term::ReadKey::ReadKey(0, $in_fh); | 
| 1460 |  |  |  |  |  |  |  | 
| 1461 |  |  |  |  |  |  | # Two  implies the current faked line is deferred... | 
| 1462 | 0 | 0 |  |  |  | 0 | if ($lookahead eq $FAKE_ESC) { | 
| 1463 | 0 |  |  |  |  | 0 | $fake_input =~ s{ \A }{$orig_fake_input\n}xm; | 
| 1464 |  |  |  |  |  |  | } | 
| 1465 |  |  |  |  |  |  | # Only one  implies the current faked line is replaced... | 
| 1466 |  |  |  |  |  |  | else { | 
| 1467 | 0 |  |  |  |  | 0 | $in_fh->ungetc(ord($lookahead)); | 
| 1468 |  |  |  |  |  |  | } | 
| 1469 | 0 |  |  |  |  | 0 | undef $local_fake_input; | 
| 1470 | 0 |  |  |  |  | 0 | $faking = 0; | 
| 1471 | 0 |  |  |  |  | 0 | next INPUT; | 
| 1472 |  |  |  |  |  |  | } | 
| 1473 |  |  |  |  |  |  |  | 
| 1474 |  |  |  |  |  |  | # Handle returns... | 
| 1475 |  |  |  |  |  |  | elsif (!$prev_was_verbatim && $next =~ /\A\R\z/) { | 
| 1476 |  |  |  |  |  |  | # Complete faked line, if faked input incomplete... | 
| 1477 | 0 | 0 | 0 |  |  | 0 | if ($faking && length($local_fake_input)) { | 
| 1478 | 0 |  |  |  |  | 0 | for ($local_fake_input =~ m/\X/g) { | 
| 1479 | 0 |  |  |  |  | 0 | _simulate_typing(); | 
| 1480 | 0 |  |  |  |  | 0 | $outputter_ref->(-echostyle => $opt_ref->{-echo}($_)); | 
| 1481 |  |  |  |  |  |  | } | 
| 1482 | 0 |  |  |  |  | 0 | $input .= $local_fake_input; | 
| 1483 |  |  |  |  |  |  | } | 
| 1484 |  |  |  |  |  |  |  | 
| 1485 |  |  |  |  |  |  | # Add newline to the accumulated input string... | 
| 1486 | 0 |  |  |  |  | 0 | $input .= $next; | 
| 1487 |  |  |  |  |  |  |  | 
| 1488 |  |  |  |  |  |  | # Check that input satisfied any constraints... | 
| 1489 | 0 |  |  |  |  | 0 | _verify_input_constraints( | 
| 1490 |  |  |  |  |  |  | \$input, \$local_fake_input, $outputter_ref, | 
| 1491 |  |  |  |  |  |  | $opt_ref, $extra_constraints, | 
| 1492 |  |  |  |  |  |  | ); | 
| 1493 |  |  |  |  |  |  |  | 
| 1494 |  |  |  |  |  |  | # Echo a default value if appropriate... | 
| 1495 | 0 | 0 | 0 |  |  | 0 | if ($input =~ m{\A\R?\Z}xms && defined $opt_ref->{-def}) { | 
| 1496 | 0 |  |  |  |  | 0 | my $def_val = $opt_ref->{-def}; | 
| 1497 |  |  |  |  |  |  |  | 
| 1498 |  |  |  |  |  |  | # Try to find the key, for a menu... | 
| 1499 | 0 | 0 |  |  |  | 0 | if (exists $opt_ref->{-menu_curr_level}) { | 
| 1500 | 0 |  |  |  |  | 0 | for my $key ( keys %{$opt_ref->{-menu_curr_level}}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1501 | 0 | 0 |  |  |  | 0 | if (match($def_val, $opt_ref->{-menu_curr_level}{$key})) { | 
| 1502 | 0 |  |  |  |  | 0 | $def_val = $key; | 
| 1503 | 0 |  |  |  |  | 0 | last; | 
| 1504 |  |  |  |  |  |  | } | 
| 1505 |  |  |  |  |  |  | } | 
| 1506 |  |  |  |  |  |  | } | 
| 1507 |  |  |  |  |  |  |  | 
| 1508 |  |  |  |  |  |  | # Echo it as if it had been typed... | 
| 1509 | 0 |  |  |  |  | 0 | $outputter_ref->(-echostyle => $opt_ref->{-echo}($def_val)); | 
| 1510 |  |  |  |  |  |  | } | 
| 1511 |  |  |  |  |  |  |  | 
| 1512 |  |  |  |  |  |  | # Echo the return (or otherwise, as specified)... | 
| 1513 | 0 |  |  |  |  | 0 | $outputter_ref->(-echostyle => $opt_ref->{-return}($next)); | 
| 1514 |  |  |  |  |  |  |  | 
| 1515 |  |  |  |  |  |  | # Clean up, and return the input... | 
| 1516 | 0 |  |  |  |  | 0 | Term::ReadKey::ReadMode('restore', $in_fh); | 
| 1517 |  |  |  |  |  |  |  | 
| 1518 |  |  |  |  |  |  | # Handle fake EOF... | 
| 1519 | 0 | 0 | 0 |  |  | 0 | if ($faking && $input =~ m{^ (?: \cD | \cZ) }xms) { | 
| 1520 | 0 |  |  |  |  | 0 | return undef; | 
| 1521 |  |  |  |  |  |  | } | 
| 1522 |  |  |  |  |  |  |  | 
| 1523 | 0 |  |  |  |  | 0 | return $input; | 
| 1524 |  |  |  |  |  |  | } | 
| 1525 |  |  |  |  |  |  |  | 
| 1526 |  |  |  |  |  |  | # Handle anything else... | 
| 1527 |  |  |  |  |  |  | elsif ($prev_was_verbatim || $next !~ /$ctrl/) { | 
| 1528 |  |  |  |  |  |  | # If so, get the next fake character... | 
| 1529 | 0 | 0 |  |  |  | 0 | if ($faking) { | 
| 1530 | 0 | 0 |  |  |  | 0 | $next = length($local_fake_input) | 
| 1531 |  |  |  |  |  |  | ? substr($local_fake_input,0,1,q{}) | 
| 1532 |  |  |  |  |  |  | : q{}; | 
| 1533 |  |  |  |  |  |  | } | 
| 1534 |  |  |  |  |  |  |  | 
| 1535 |  |  |  |  |  |  | # Handle editing... | 
| 1536 | 0 | 0 |  |  |  | 0 | if ($next eq $EDIT{BACK}) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1537 | 0 | 0 |  |  |  | 0 | $insert_offset += ($insert_offset < length $input) ? 1 : 0; | 
| 1538 |  |  |  |  |  |  | } | 
| 1539 |  |  |  |  |  |  | elsif ($next eq $EDIT{FORWARD}) { | 
| 1540 | 0 | 0 |  |  |  | 0 | $insert_offset += ($insert_offset > 0) ? -1 : 0; | 
| 1541 |  |  |  |  |  |  | } | 
| 1542 |  |  |  |  |  |  | elsif ($next eq $EDIT{START}) { | 
| 1543 | 0 |  |  |  |  | 0 | $insert_offset = length($input); | 
| 1544 |  |  |  |  |  |  | } | 
| 1545 |  |  |  |  |  |  | elsif ($next eq $EDIT{END}) { | 
| 1546 | 0 |  |  |  |  | 0 | $insert_offset = 0; | 
| 1547 |  |  |  |  |  |  | } | 
| 1548 |  |  |  |  |  |  |  | 
| 1549 |  |  |  |  |  |  | # Handle non-editing... | 
| 1550 |  |  |  |  |  |  | else { | 
| 1551 |  |  |  |  |  |  | # Check for input restrictions... | 
| 1552 | 0 | 0 |  |  |  | 0 | if (exists $opt_ref->{-guarantee}) { | 
| 1553 | 0 | 0 |  |  |  | 0 | next INPUT if ($input.$next) !~ $opt_ref->{-guarantee}; | 
| 1554 |  |  |  |  |  |  | } | 
| 1555 |  |  |  |  |  |  |  | 
| 1556 |  |  |  |  |  |  | # Add the new input char to the accumulated input string... | 
| 1557 | 0 | 0 |  |  |  | 0 | if ($insert_offset) { | 
| 1558 | 0 |  |  |  |  | 0 | substr($input, -$insert_offset, 0) = $next; | 
| 1559 | 0 |  |  |  |  | 0 | $prev_insert_offset++; | 
| 1560 |  |  |  |  |  |  | } | 
| 1561 |  |  |  |  |  |  | else { | 
| 1562 | 0 |  |  |  |  | 0 | $input .= $next; | 
| 1563 |  |  |  |  |  |  | } | 
| 1564 |  |  |  |  |  |  | } | 
| 1565 |  |  |  |  |  |  |  | 
| 1566 |  |  |  |  |  |  | # Display the character (or whatever was specified)... | 
| 1567 |  |  |  |  |  |  |  | 
| 1568 | 0 | 0 | 0 |  |  | 0 | if ($insert_offset || $prev_insert_offset) { | 
|  |  | 0 |  |  |  |  |  | 
| 1569 | 0 |  |  |  |  | 0 | my $input_pre  = substr($input,0,length($input)-$prev_insert_offset); | 
| 1570 | 0 |  |  |  |  | 0 | my $input_post = substr($input,length($input)-$insert_offset); | 
| 1571 |  |  |  |  |  |  | my $display_pre | 
| 1572 | 0 |  |  |  |  | 0 | = join q{}, map { $opt_ref->{-echo}($_) } $input_pre =~ m/\X/g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1573 |  |  |  |  |  |  | my $display_post | 
| 1574 | 0 |  |  |  |  | 0 | = join q{}, map { $opt_ref->{-echo}($_) } $input_post =~ m/\X/g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1575 |  |  |  |  |  |  | $outputter_ref->( -echostyle => | 
| 1576 |  |  |  |  |  |  | "\b" x length($display_pre) | 
| 1577 | 0 |  |  |  |  | 0 | . join(q{}, map { $opt_ref->{-echo}($_) } $input =~ m/\X/g) | 
|  | 0 |  |  |  |  | 0 |  | 
| 1578 |  |  |  |  |  |  | . "\b" x length($display_post) | 
| 1579 |  |  |  |  |  |  | ); | 
| 1580 |  |  |  |  |  |  | } | 
| 1581 |  |  |  |  |  |  | elsif ($next !~ $EDIT_KEY) { | 
| 1582 | 0 |  |  |  |  | 0 | $outputter_ref->(-echostyle => $opt_ref->{-echo}($next)); | 
| 1583 |  |  |  |  |  |  | } | 
| 1584 |  |  |  |  |  |  |  | 
| 1585 |  |  |  |  |  |  | # Not verbatim after this... | 
| 1586 | 0 |  |  |  |  | 0 | $prev_was_verbatim = 0; | 
| 1587 |  |  |  |  |  |  | } | 
| 1588 |  |  |  |  |  |  | else { | 
| 1589 |  |  |  |  |  |  | # Not verbatim after mysterious ctrl input... | 
| 1590 | 0 |  |  |  |  | 0 | $prev_was_verbatim = 0; | 
| 1591 |  |  |  |  |  |  |  | 
| 1592 | 0 |  |  |  |  | 0 | next INPUT; | 
| 1593 |  |  |  |  |  |  | } | 
| 1594 |  |  |  |  |  |  | } | 
| 1595 |  |  |  |  |  |  |  | 
| 1596 | 0 | 0 | 0 |  |  | 0 | if ($opt_ref->{-single} || !defined $next || $input =~ m{\Q$/\E$}) { | 
|  |  |  | 0 |  |  |  |  | 
| 1597 |  |  |  |  |  |  | # Did we get an acceptable value? | 
| 1598 | 0 | 0 |  |  |  | 0 | if (defined $next) { | 
| 1599 | 0 |  |  |  |  | 0 | _verify_input_constraints( | 
| 1600 |  |  |  |  |  |  | \$input, \$local_fake_input, $outputter_ref, | 
| 1601 |  |  |  |  |  |  | $opt_ref, $extra_constraints, | 
| 1602 |  |  |  |  |  |  | ); | 
| 1603 |  |  |  |  |  |  | } | 
| 1604 |  |  |  |  |  |  |  | 
| 1605 |  |  |  |  |  |  | # Reset terminal... | 
| 1606 | 0 |  |  |  |  | 0 | Term::ReadKey::ReadMode('restore', $in_fh); | 
| 1607 |  |  |  |  |  |  |  | 
| 1608 |  |  |  |  |  |  | # Return failure if failed before input or cancelled... | 
| 1609 | 0 | 0 | 0 |  |  | 0 | if (!defined $next && length($input) == 0 | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1610 |  |  |  |  |  |  | ||  exists $opt_ref->{-cancel} && match($next, $opt_ref->{-cancel})) { | 
| 1611 | 0 | 0 |  |  |  | 0 | return if $opt_ref->{-verbatim}; | 
| 1612 | 0 |  |  |  |  | 0 | return PUREBOOL { 0 } | 
| 1613 | 0 |  |  |  |  | 0 | BOOL { 0 } | 
| 1614 | 0 |  |  |  |  | 0 | SCALAR { undef  } | 
| 1615 | 0 |  |  |  |  | 0 | METHOD { defaulted => sub { 0 }, timedout  => sub { 0 } }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1616 |  |  |  |  |  |  | } | 
| 1617 |  |  |  |  |  |  |  | 
| 1618 |  |  |  |  |  |  | # Otherwise supply a final newline if necessary... | 
| 1619 | 0 | 0 | 0 |  |  | 0 | if ( $opt_ref->{-single} | 
|  |  |  | 0 |  |  |  |  | 
| 1620 |  |  |  |  |  |  | &&   exists $opt_ref->{-return} | 
| 1621 |  |  |  |  |  |  | &&   $input !~ /\A\R\z/ ) { | 
| 1622 | 0 |  |  |  |  | 0 | $outputter_ref->(-echostyle => $opt_ref->{-return}(q{})); | 
| 1623 |  |  |  |  |  |  | } | 
| 1624 |  |  |  |  |  |  |  | 
| 1625 | 0 |  |  |  |  | 0 | return $input; | 
| 1626 |  |  |  |  |  |  | } | 
| 1627 |  |  |  |  |  |  | } | 
| 1628 |  |  |  |  |  |  | continue { | 
| 1629 |  |  |  |  |  |  | # Perform monitor (if any) and redraw prompt (if required)... | 
| 1630 | 0 | 0 |  |  |  | 0 | if ($opt_ref->{-monitor}) { | 
| 1631 |  |  |  |  |  |  | my %opts = ( -cursor_pos => length($input) - $insert_offset, | 
| 1632 |  |  |  |  |  |  | -prompt     => $opt_ref->{-prompt}, | 
| 1633 |  |  |  |  |  |  | -style      => $opt_ref->{-style}->(), | 
| 1634 | 0 |  |  |  |  | 0 | -echostyle  => $opt_ref->{-echostyle}->(), | 
| 1635 |  |  |  |  |  |  | ); | 
| 1636 | 0 |  |  |  |  | 0 | my $input_copy = $input; | 
| 1637 | 0 |  |  |  |  | 0 | my $output_pos = $outputter_ref->(-tell); | 
| 1638 | 0 | 0 | 0 |  |  | 0 | if (!defined eval { $opt_ref->{-monitor}->($input_copy, \%opts) } | 
|  | 0 |  |  |  |  | 0 |  | 
| 1639 |  |  |  |  |  |  | ||  $output_pos != $outputter_ref->(-tell)) { | 
| 1640 | 0 |  |  |  |  | 0 | my $input_pre  = substr($input.' ',0,length($input)-$insert_offset+1); | 
| 1641 | 0 |  |  |  |  | 0 | my $input_post = substr($input.' ',length($input)-$insert_offset); | 
| 1642 |  |  |  |  |  |  | my $display_pre | 
| 1643 | 0 |  |  |  |  | 0 | = join q{}, map { $opt_ref->{-echo}($_) } $input_pre =~ m/\X/g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1644 |  |  |  |  |  |  | my $display_post | 
| 1645 | 0 |  |  |  |  | 0 | = join q{}, map { $opt_ref->{-echo}($_) } $input_post =~ m/\X/g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1646 | 0 |  |  |  |  | 0 | $outputter_ref->( -style => $opt_ref->{-style}, _wipe_line(), $opt_ref->{-prompt}); | 
| 1647 |  |  |  |  |  |  | $outputter_ref->( -echostyle => | 
| 1648 | 0 |  |  |  |  | 0 | join(q{}, map { $opt_ref->{-echo}($_) } $input =~ m/\X/g) | 
|  | 0 |  |  |  |  | 0 |  | 
| 1649 |  |  |  |  |  |  | . "\b" x (length($display_post)-1) | 
| 1650 |  |  |  |  |  |  | ); | 
| 1651 |  |  |  |  |  |  | } | 
| 1652 |  |  |  |  |  |  | } | 
| 1653 |  |  |  |  |  |  | } | 
| 1654 |  |  |  |  |  |  | } | 
| 1655 | 0 |  |  |  |  | 0 | } | 
| 1656 |  |  |  |  |  |  |  | 
| 1657 |  |  |  |  |  |  | # Build a menu... | 
| 1658 |  |  |  |  |  |  | sub _build_menu { | 
| 1659 | 1 |  |  | 1 |  | 4 | my ($source_ref, $initial_prompt, $is_numeric) = @_; | 
| 1660 | 1 |  | 50 |  |  | 8 | my $prompt = ($initial_prompt//q{}) . qq{\n}; | 
| 1661 | 1 |  |  |  |  | 2 | my $final = q{}; | 
| 1662 | 1 |  |  |  |  | 3 | my %value_for; | 
| 1663 |  |  |  |  |  |  | my %key_for; | 
| 1664 | 1 |  |  |  |  | 0 | my @selectors; | 
| 1665 |  |  |  |  |  |  |  | 
| 1666 | 1 |  |  |  |  | 3 | my $source_type = ref $source_ref; | 
| 1667 | 1 | 50 |  |  |  | 6 | if ($source_type eq 'HASH') { | 
|  |  | 50 |  |  |  |  |  | 
| 1668 | 0 |  |  |  |  | 0 | my @sorted_keys = sort(keys(%{$source_ref})); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1669 | 0 | 0 |  |  |  | 0 | @selectors = $is_numeric ? (1..@sorted_keys) : ('a'..'z','A'..'Z'); | 
| 1670 | 0 |  |  |  |  | 0 | @key_for{@selectors}   = @sorted_keys; | 
| 1671 | 0 |  |  |  |  | 0 | @value_for{@selectors} = @{$source_ref}{@sorted_keys}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1672 | 0 |  |  |  |  | 0 | $source_ref = \@sorted_keys; | 
| 1673 |  |  |  |  |  |  | } | 
| 1674 |  |  |  |  |  |  | elsif ($source_type eq 'SCALAR') { | 
| 1675 | 0 |  |  |  |  | 0 | $source_ref = [ split "\n", ${$source_ref} ]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1676 |  |  |  |  |  |  | } | 
| 1677 |  |  |  |  |  |  |  | 
| 1678 | 1 |  |  |  |  | 2 | my @source = @{$source_ref}; | 
|  | 1 |  |  |  |  | 4 |  | 
| 1679 | 1 | 50 |  |  |  | 5 | @selectors = $is_numeric ? (1..@source) : ('a'..'z','A'..'Z'); | 
| 1680 | 1 | 50 |  |  |  | 5 | if (!keys %value_for) { | 
| 1681 | 1 |  |  |  |  | 10 | @value_for{@selectors} = @source; | 
| 1682 |  |  |  |  |  |  | } | 
| 1683 |  |  |  |  |  |  |  | 
| 1684 |  |  |  |  |  |  | ITEM: | 
| 1685 | 1 |  |  |  |  | 4 | for my $tag (@selectors) { | 
| 1686 | 10 |  | 50 |  |  | 21 | my $item = shift(@source) // last ITEM; | 
| 1687 | 10 |  |  |  |  | 13 | chomp $item; | 
| 1688 | 10 |  |  |  |  | 24 | $prompt .= sprintf("%4s. $item\n", $tag); | 
| 1689 | 10 |  |  |  |  | 15 | $final = $tag; | 
| 1690 |  |  |  |  |  |  | } | 
| 1691 |  |  |  |  |  |  |  | 
| 1692 | 1 | 50 |  |  |  | 4 | if (@source) { | 
| 1693 | 0 |  |  |  |  | 0 | _warn( misc => | 
| 1694 |  |  |  |  |  |  | "prompt(): Too many menu items. Ignoring the final " . @source | 
| 1695 |  |  |  |  |  |  | ); | 
| 1696 |  |  |  |  |  |  | } | 
| 1697 |  |  |  |  |  |  |  | 
| 1698 | 1 | 0 |  |  |  | 7 | my $constraint = $is_numeric       ? '(?:' . join('|',@selectors) .')' | 
|  |  | 50 |  |  |  |  |  | 
| 1699 |  |  |  |  |  |  | : $final =~ /[A-Z]/ ? "[a-zA-$final]" | 
| 1700 |  |  |  |  |  |  | :                     "[a-$final]"; | 
| 1701 | 1 | 50 |  |  |  | 4 | my $constraint_desc = $is_numeric  ? "[1-$selectors[-1]]" : $constraint; | 
| 1702 | 1 |  |  |  |  | 3 | $constraint = '\A\s*' . $constraint . '\s*\Z'; | 
| 1703 |  |  |  |  |  |  |  | 
| 1704 |  |  |  |  |  |  | return { | 
| 1705 | 1 |  |  |  |  | 64 | data       => $source_ref, | 
| 1706 |  |  |  |  |  |  | key_for    => \%key_for, | 
| 1707 |  |  |  |  |  |  | value_for  => \%value_for, | 
| 1708 |  |  |  |  |  |  | prompt     => "$prompt\n", | 
| 1709 |  |  |  |  |  |  | is_numeric => $is_numeric, | 
| 1710 |  |  |  |  |  |  | constraint => { "Enter $constraint_desc: " => qr/$constraint|$MENU_ESC/ }, | 
| 1711 |  |  |  |  |  |  | }; | 
| 1712 |  |  |  |  |  |  | } | 
| 1713 |  |  |  |  |  |  |  | 
| 1714 |  |  |  |  |  |  | # Vocabulary that _stylize understands... | 
| 1715 |  |  |  |  |  |  | my %synonyms = ( | 
| 1716 |  |  |  |  |  |  | bold      => [qw], | 
| 1717 |  |  |  |  |  |  | dark      => [qw], | 
| 1718 |  |  |  |  |  |  | faint     => [qw], | 
| 1719 |  |  |  |  |  |  | underline => [qw], | 
| 1720 |  |  |  |  |  |  | italic    => [qw], | 
| 1721 |  |  |  |  |  |  | blink     => [qw], | 
| 1722 |  |  |  |  |  |  | reverse   => [qw], | 
| 1723 |  |  |  |  |  |  | concealed => [qw], | 
| 1724 |  |  |  |  |  |  | reset     => [qw], | 
| 1725 |  |  |  |  |  |  | bright_   => [qw< bright\s+ vivid\s+ >], | 
| 1726 |  |  |  |  |  |  | red       => [qw< scarlet vermilion crimson ruby cherry cerise cardinal carmine | 
| 1727 |  |  |  |  |  |  | burgundy claret chestnut copper garnet geranium russet | 
| 1728 |  |  |  |  |  |  | salmon titian coral cochineal rose cinnamon ginger gules >], | 
| 1729 |  |  |  |  |  |  | yellow    => [qw< gold golden lemon cadmium daffodil mustard primrose tawny | 
| 1730 |  |  |  |  |  |  | amber aureate canary champagne citrine citron cream goldenrod honey straw >], | 
| 1731 |  |  |  |  |  |  | green     => [qw< olive jade pea emerald lime chartreuse forest sage vert >], | 
| 1732 |  |  |  |  |  |  | cyan      => [qw< aqua aquamarine teal turquoise ultramarine >], | 
| 1733 |  |  |  |  |  |  | blue      => [qw< azure cerulean cobalt indigo navy sapphire >], | 
| 1734 |  |  |  |  |  |  | magenta   => [qw< amaranthine amethyst lavender lilac mauve mulberry orchid periwinkle | 
| 1735 |  |  |  |  |  |  | plum pomegranate violet purple aubergine cyclamen fuchsia modena puce | 
| 1736 |  |  |  |  |  |  | purpure >], | 
| 1737 |  |  |  |  |  |  | black     => [qw< charcoal ebon ebony jet obsidian onyx raven sable slate >], | 
| 1738 |  |  |  |  |  |  | white     => [qw< alabaster ash chalk ivory milk pearl silver argent >], | 
| 1739 |  |  |  |  |  |  | ); | 
| 1740 |  |  |  |  |  |  |  | 
| 1741 |  |  |  |  |  |  | # Back-mapping to standard terms... | 
| 1742 |  |  |  |  |  |  | my %normalize | 
| 1743 |  |  |  |  |  |  | = map { join('|', map { "$_\\b" } reverse sort @{$synonyms{$_}}) => $_ } | 
| 1744 |  |  |  |  |  |  | keys %synonyms; | 
| 1745 |  |  |  |  |  |  |  | 
| 1746 |  |  |  |  |  |  | my $BACKGROUND = qr{ | 
| 1747 |  |  |  |  |  |  | (\S+) \s+ (?: behind | beneath | below | under(?:neath)? )\b | 
| 1748 |  |  |  |  |  |  | | \b (?:upon|over|on) \s+ (?:an?)? \s+ (.*?) \s+ (?:background|bg|field) \b | 
| 1749 |  |  |  |  |  |  | | \b (?:upon\s+ | over\s+ | (?:(on|upon|over)\s+a\s+)?  (?:background|bg|field) \s+ (?:of\s+|in\s+)? | on\s+) (\S+) | 
| 1750 |  |  |  |  |  |  | }ixms; | 
| 1751 |  |  |  |  |  |  |  | 
| 1752 |  |  |  |  |  |  | # Convert a description to ANSI colour codes... | 
| 1753 |  |  |  |  |  |  | sub _stylize { | 
| 1754 | 0 |  | 0 | 0 |  | 0 | my $spec = shift // q{}; | 
| 1755 |  |  |  |  |  |  |  | 
| 1756 |  |  |  |  |  |  | # Handle arrays and hashes as args... | 
| 1757 | 0 | 0 |  |  |  | 0 | if (ref($spec) eq 'ARRAY') { | 
|  |  | 0 |  |  |  |  |  | 
| 1758 | 0 |  |  |  |  | 0 | $spec = join q{ }, @{$spec}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1759 |  |  |  |  |  |  | } | 
| 1760 |  |  |  |  |  |  | elsif (ref($spec) eq 'HASH') { | 
| 1761 | 0 |  |  |  |  | 0 | $spec = join q{ }, keys %{$spec}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1762 |  |  |  |  |  |  | } | 
| 1763 |  |  |  |  |  |  |  | 
| 1764 |  |  |  |  |  |  | # Ignore punctuation... | 
| 1765 | 0 |  |  |  |  | 0 | $spec =~ s/[^\w\s]//g; | 
| 1766 |  |  |  |  |  |  |  | 
| 1767 |  |  |  |  |  |  | # Handle backgrounds... | 
| 1768 | 0 |  |  |  |  | 0 | $spec =~ s/$BACKGROUND/on_$+/g; | 
| 1769 |  |  |  |  |  |  |  | 
| 1770 |  |  |  |  |  |  | # Apply standard translations... | 
| 1771 | 0 |  |  |  |  | 0 | for my $pattern (keys %normalize) { | 
| 1772 | 0 |  | 0 |  |  | 0 | $spec =~ s{\b(on_|\b) $pattern}{($1//q{}).$normalize{$pattern}}geixms; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1773 |  |  |  |  |  |  | } | 
| 1774 |  |  |  |  |  |  |  | 
| 1775 |  |  |  |  |  |  | # Ignore anything unknown... | 
| 1776 | 0 | 0 | 0 |  |  | 0 | $spec =~ s{((?:on_)?(?:(ansi\d+|rgb\d+)|(\S+)))}{ $2 || exists $synonyms{$3} ? $1 : q{} }gxmse; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1777 |  |  |  |  |  |  |  | 
| 1778 |  |  |  |  |  |  | # Build ANSI terminal codes around text... | 
| 1779 | 0 |  |  |  |  | 0 | my $raw_text = join q{}, @_; | 
| 1780 | 0 |  |  |  |  | 0 | my ($prews, $text, $postws) = $raw_text =~ m{\A (\s*) (.*?) (\s*) \Z}xms; | 
| 1781 | 0 |  |  |  |  | 0 | my @style = split /\s+/, $spec; | 
| 1782 | 0 | 0 |  |  |  | 0 | return $prews | 
| 1783 |  |  |  |  |  |  | . ( @style ? Term::ANSIColor::colored(\@style, $text) : $text ) | 
| 1784 |  |  |  |  |  |  | . $postws; | 
| 1785 |  |  |  |  |  |  | } | 
| 1786 |  |  |  |  |  |  |  | 
| 1787 |  |  |  |  |  |  | # Build a subroutine that prints printable chars to the specified filehandle... | 
| 1788 |  |  |  |  |  |  | sub _std_printer_to { | 
| 1789 | 0 |  |  | 0 |  | 0 | my ($out_filehandle, $opt_ref) = @_; | 
| 1790 | 36 |  |  | 36 |  | 343 | no strict 'refs'; | 
|  | 36 |  |  |  |  | 78 |  | 
|  | 36 |  |  |  |  | 14215 |  | 
| 1791 | 0 |  |  |  |  | 0 | _autoflush($out_filehandle); | 
| 1792 | 0 | 0 |  |  |  | 0 | if (eval { require Term::ANSIColor}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1793 |  |  |  |  |  |  | return sub { | 
| 1794 | 0 |  |  | 0 |  | 0 | my $style = shift; | 
| 1795 | 0 | 0 |  |  |  | 0 | return tell($out_filehandle) if $style eq -tell; | 
| 1796 | 0 |  |  |  |  | 0 | my @loc = (@_); | 
| 1797 | 0 |  |  |  |  | 0 | s{\e}{^}gxms for @loc; | 
| 1798 | 0 |  |  |  |  | 0 | print {$out_filehandle} _stylize($opt_ref->{$style}(@loc), @loc); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1799 | 0 |  |  |  |  | 0 | }; | 
| 1800 |  |  |  |  |  |  | } | 
| 1801 |  |  |  |  |  |  | else { | 
| 1802 |  |  |  |  |  |  | return sub { | 
| 1803 | 0 |  |  | 0 |  | 0 | my $style = shift; | 
| 1804 | 0 | 0 |  |  |  | 0 | return tell($out_filehandle) if $style eq -tell; | 
| 1805 | 0 |  |  |  |  | 0 | my @loc = (@_); | 
| 1806 | 0 |  |  |  |  | 0 | s{\e}{^}gxms for @loc; | 
| 1807 | 0 |  |  |  |  | 0 | print {$out_filehandle} @loc; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1808 | 0 |  |  |  |  | 0 | }; | 
| 1809 |  |  |  |  |  |  | } | 
| 1810 |  |  |  |  |  |  | } | 
| 1811 |  |  |  |  |  |  |  | 
| 1812 |  |  |  |  |  |  | # Build a subroutine that prints to nowhere... | 
| 1813 |  |  |  |  |  |  | sub _null_printer { | 
| 1814 | 97 |  |  | 149 |  | 386 | return sub {}; | 
|  |  |  |  | 97 |  |  |  | 
| 1815 |  |  |  |  |  |  | } | 
| 1816 |  |  |  |  |  |  |  | 
| 1817 |  |  |  |  |  |  | 1; # Magic true value required at end of module | 
| 1818 |  |  |  |  |  |  | __END__ |