| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package IO::Prompt::Simple; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 12 |  |  | 12 |  | 361703 | use strict; | 
|  | 12 |  |  |  |  | 31 |  | 
|  | 12 |  |  |  |  | 436 |  | 
| 4 | 12 |  |  | 12 |  | 66 | use warnings; | 
|  | 12 |  |  |  |  | 25 |  | 
|  | 12 |  |  |  |  | 347 |  | 
| 5 | 12 |  |  | 12 |  | 323 | use 5.006001; | 
|  | 12 |  |  |  |  | 51 |  | 
|  | 12 |  |  |  |  | 963 |  | 
| 6 | 12 |  |  | 12 |  | 67 | use base 'Exporter'; | 
|  | 12 |  |  |  |  | 24 |  | 
|  | 12 |  |  |  |  | 1236 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | BEGIN { | 
| 9 | 12 | 50 |  | 12 |  | 55362 | $ENV{ANSI_COLORS_DISABLED} = 1 if $^O eq 'MSWin32'; | 
| 10 |  |  |  |  |  |  | } | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | our $VERSION = '0.06'; | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | our @EXPORT = 'prompt'; | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | sub prompt { | 
| 17 | 43 |  |  | 43 | 1 | 52757 | my ($message, $opts) = @_; | 
| 18 | 43 | 100 |  |  |  | 146 | _croak('Usage: prompt($message, [$default_or_opts])') unless defined $message; | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 42 |  |  |  |  | 60 | my $default; | 
| 21 | 42 | 100 |  |  |  | 132 | if (ref $opts eq 'HASH') { | 
| 22 | 32 |  |  |  |  | 75 | $default = $opts->{default}; | 
| 23 |  |  |  |  |  |  | } | 
| 24 |  |  |  |  |  |  | else { | 
| 25 | 10 |  |  |  |  | 22 | ($default, $opts) = ($opts, {}); | 
| 26 |  |  |  |  |  |  | } | 
| 27 | 42 | 100 |  |  |  | 112 | my $display_default = defined $default ? "[$default]" : ''; | 
| 28 | 42 | 100 |  |  |  | 101 | $default = defined $default ? $default : ''; | 
| 29 |  |  |  |  |  |  |  | 
| 30 | 42 |  |  |  |  | 111 | my $stash = { message => $message }; | 
| 31 | 42 |  |  |  |  | 104 | _parse_option($opts, $stash); | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 42 |  |  |  |  | 42933 | $stash->{message} .= " $display_default"; | 
| 34 | 42 | 100 |  |  |  | 117 | if (my $color = $opts->{color}) { | 
| 35 | 3 |  |  |  |  | 17 | require Term::ANSIColor; | 
| 36 | 3 | 100 |  |  |  | 10 | $color = [$color] unless ref $color eq 'ARRAY'; | 
| 37 | 3 |  |  |  |  | 12 | $stash->{message} = Term::ANSIColor::colored($color, $stash->{message}); | 
| 38 |  |  |  |  |  |  | } | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 42 |  |  |  |  | 222 | my ($in, $out) = @$stash{qw/in out/}; | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | # autoflush and reset format for output | 
| 43 | 42 |  |  |  |  | 614 | my $org_out = select $out; | 
| 44 | 42 |  |  |  |  | 152 | local $| = 1; | 
| 45 | 42 |  |  |  |  | 85 | local $\; | 
| 46 | 42 |  |  |  |  | 105 | select $org_out; | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 42 | 100 |  |  |  | 108 | my $ignore_case = $opts->{ignore_case} ? 1 : 0; | 
| 49 | 42 |  |  |  |  | 107 | my $isa_tty     = _isa_tty($in, $out); | 
| 50 | 42 |  |  |  |  | 124 | my $answer; | 
| 51 |  |  |  |  |  |  | my @answers; # for multi | 
| 52 | 42 |  |  |  |  | 47 | while (1) { | 
| 53 | 52 | 100 |  |  |  | 137 | print {$out} $stash->{choices}, "\n" if defined $stash->{choices}; | 
|  | 8 |  |  |  |  | 19 |  | 
| 54 | 52 |  |  |  |  | 62 | print {$out} $stash->{message}, ': '; | 
|  | 52 |  |  |  |  | 172 |  | 
| 55 | 52 | 100 | 100 |  |  | 389 | if ($ENV{PERL_IOPS_USE_DEFAULT} || $opts->{use_default} || (!$isa_tty && eof $in)) { | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 56 | 4 |  |  |  |  | 25 | print {$out} "$default\n"; | 
|  | 4 |  |  |  |  | 12 |  | 
| 57 | 4 |  |  |  |  | 9 | $answer = $default; | 
| 58 | 4 |  |  |  |  | 8 | last; | 
| 59 |  |  |  |  |  |  | } | 
| 60 | 48 |  |  |  |  | 728 | $answer = <$in>; | 
| 61 | 48 | 100 |  |  |  | 238 | if (defined $answer) { | 
| 62 | 45 |  |  |  |  | 82 | chomp $answer; | 
| 63 | 45 | 100 |  |  |  | 110 | print {$out} "$answer\n" unless $isa_tty; | 
|  | 1 |  |  |  |  | 3 |  | 
| 64 |  |  |  |  |  |  | } | 
| 65 |  |  |  |  |  |  | else { | 
| 66 | 3 |  |  |  |  | 4 | print {$out} "\n"; | 
|  | 3 |  |  |  |  | 8 |  | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 48 | 100 | 100 |  |  | 218 | $answer = $default if !defined $answer || $answer eq ''; | 
| 70 | 48 | 100 |  |  |  | 1478 | $answer = $stash->{encoder}->decode($answer) if defined $stash->{encoder}; | 
| 71 | 48 | 100 |  |  |  | 371 | if (my $exclusive_map = $stash->{exclusive_map}) { | 
|  |  | 100 |  |  |  |  |  | 
| 72 | 31 | 100 |  |  |  | 56 | if ($stash->{want_multi}) { | 
| 73 | 3 | 50 |  |  |  | 7 | $answer = $ignore_case ? lc $answer : $answer; | 
| 74 | 3 |  |  |  |  | 3 | my $has_error; | 
| 75 | 3 |  |  |  |  | 9 | for my $ans (split /\s+/, $answer) { | 
| 76 | 5 | 100 |  |  |  | 12 | unless (exists $exclusive_map->{$ans}) { | 
| 77 | 1 |  |  |  |  | 1 | $has_error = 1; | 
| 78 | 1 |  |  |  |  | 2 | last; | 
| 79 |  |  |  |  |  |  | } | 
| 80 | 4 |  |  |  |  | 10 | push @answers, $exclusive_map->{$ans}; | 
| 81 |  |  |  |  |  |  | } | 
| 82 | 3 | 100 |  |  |  | 10 | $has_error = 1 unless @answers; | 
| 83 | 3 | 100 |  |  |  | 9 | last unless $has_error; | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  | else { | 
| 86 | 28 | 100 |  |  |  | 85 | if (exists $exclusive_map->{$ignore_case ? lc $answer : $answer}) { | 
|  |  | 100 |  |  |  |  |  | 
| 87 | 20 | 100 |  |  |  | 42 | $answer = $exclusive_map->{$ignore_case ? lc $answer : $answer}; | 
| 88 | 20 |  |  |  |  | 28 | last; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  | } | 
| 91 | 9 |  |  |  |  | 17 | @answers = (); | 
| 92 | 9 |  |  |  |  | 12 | $answer  = undef; | 
| 93 | 9 |  |  |  |  | 12 | print {$out} $stash->{hint}; | 
|  | 9 |  |  |  |  | 26 |  | 
| 94 | 9 |  |  |  |  | 18 | next; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  | elsif (my $regexp = $stash->{regexp}) { | 
| 97 | 5 | 100 |  |  |  | 37 | last if $answer =~ $regexp; | 
| 98 | 1 |  |  |  |  | 1 | $answer = undef; | 
| 99 | 1 |  |  |  |  | 3 | print {$out} $stash->{hint}; | 
|  | 1 |  |  |  |  | 3 |  | 
| 100 | 1 |  |  |  |  | 2 | next; | 
| 101 |  |  |  |  |  |  | } | 
| 102 | 12 |  |  |  |  | 21 | last; | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  |  | 
| 105 | 42 | 100 |  |  |  | 891 | return $stash->{want_multi} ? @answers : $answer; | 
| 106 |  |  |  |  |  |  | } | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | sub _parse_option { | 
| 109 | 42 |  |  | 42 |  | 66 | my ($opts, $stash) = @_; | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 42 | 100 |  |  |  | 134 | $stash->{in}  = _is_fh($opts->{input})  ? $opts->{input}  : *STDIN; | 
| 112 | 42 | 100 |  |  |  | 129 | $stash->{out} = _is_fh($opts->{output}) ? $opts->{output} : *STDOUT; | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 42 | 100 |  |  |  | 117 | if ($opts->{yn}) { | 
| 115 | 5 |  |  |  |  | 16 | $opts->{anyone}       = \[y => 1, n => 0]; | 
| 116 | 5 | 100 |  |  |  | 17 | $opts->{ignore_case}  = 1 unless exists $opts->{ignore_case}; | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 42 |  | 100 |  |  | 165 | $opts->{anyone} ||= $opts->{choices}; | 
| 120 | 42 | 100 |  |  |  | 114 | if ($opts->{anyone}) { | 
|  |  | 100 |  |  |  |  |  | 
| 121 | 24 |  |  |  |  | 63 | $stash->{exclusive_map} = _make_exclusive_map($opts, $stash); | 
| 122 | 24 | 100 |  |  |  | 91 | $stash->{want_multi}    = $opts->{multi} ? 1 : 0; | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  | elsif ($opts->{regexp}) { | 
| 125 | 5 |  |  |  |  | 14 | $stash->{regexp} = _make_regexp($opts, $stash); | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 42 | 100 |  |  |  | 122 | if ($opts->{encode}) { | 
| 129 | 1 |  |  |  |  | 9 | require Encode; | 
| 130 | 1 |  |  |  |  | 12 | $stash->{encoder} = Encode::find_encoding($opts->{encode}); | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub _make_exclusive_map { | 
| 135 | 24 |  |  | 24 |  | 32 | my ($opts, $stash) = @_; | 
| 136 | 24 |  |  |  |  | 39 | my $anyone = $opts->{anyone}; | 
| 137 | 24 |  |  |  |  | 37 | my $exclusive_map = {}; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 24 | 100 |  |  |  | 63 | my $ignore_case = $opts->{ignore_case} ? 1 : 0; | 
| 140 | 24 |  |  |  |  | 62 | my ($message, $hint, $choices) = @$stash{qw/message hint choices/}; | 
| 141 | 24 |  | 50 |  |  | 52 | my $type = _anyone_type($anyone) || return; | 
| 142 | 24 | 100 | 66 |  |  | 139 | if ($type eq 'ARRAY') { | 
|  |  | 50 | 33 |  |  |  |  | 
| 143 | 6 |  |  |  |  | 16 | my @stuffs = _uniq(@$anyone); | 
| 144 | 6 |  |  |  |  | 12 | for my $stuff (@stuffs) { | 
| 145 | 12 | 100 |  |  |  | 585 | $exclusive_map->{$ignore_case ? lc $stuff : $stuff} = $stuff; | 
| 146 |  |  |  |  |  |  | } | 
| 147 | 6 |  |  |  |  | 42 | $hint     = sprintf "# Please answer %s\n", join ' or ', map qq{`$_`}, @stuffs; | 
| 148 | 6 |  |  |  |  | 56 | $message .= sprintf ' (%s)', join '/', @stuffs; | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  | elsif ($type eq 'HASH' || $type eq 'REFARRAY' || $type eq 'Hash::MultiValue') { | 
| 151 | 9 |  |  |  |  | 26 | my @keys = | 
| 152 |  |  |  |  |  |  | $type eq 'HASH'             ? sort { $a cmp $b } keys %$anyone : | 
| 153 | 18 | 0 |  |  |  | 590 | $type eq 'REFARRAY'         ? do { my $i = 0; grep { ++$i % 2 == 1 } @{$$anyone} } : | 
|  | 8 | 50 |  |  |  | 11 |  | 
|  | 8 | 100 |  |  |  | 11 |  | 
|  | 32 |  |  |  |  | 79 |  | 
|  | 8 |  |  |  |  | 15 |  | 
| 154 |  |  |  |  |  |  | $type eq 'Hash::MultiValue' ? $anyone->keys : (); | 
| 155 | 18 |  |  |  |  | 30 | my $max = 0; | 
| 156 | 18 |  |  |  |  | 23 | my $idx = 1; | 
| 157 | 18 |  |  |  |  | 35 | for my $key (@keys) { | 
| 158 | 35 | 100 |  |  |  | 77 | $max = length $key > $max ? length $key : $max; | 
| 159 | 35 | 100 |  |  |  | 125 | $exclusive_map->{$ignore_case ? lc $key : $key} = | 
|  |  | 100 |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | $type eq 'REFARRAY' ? $$anyone->[$idx] : $anyone->{$key}; | 
| 161 | 35 |  |  |  |  | 78 | $idx += 2; | 
| 162 |  |  |  |  |  |  | } | 
| 163 | 18 |  |  |  |  | 115 | $hint = sprintf "# Please answer %s\n", join ' or ',map qq{`$_`}, @keys; | 
| 164 | 18 | 100 |  |  |  | 63 | if ($opts->{verbose}) { | 
| 165 | 6 |  |  |  |  | 7 | my $idx = -1; | 
| 166 | 11 |  |  |  |  | 12 | $choices = join "\n", map { | 
| 167 | 6 |  |  |  |  | 12 | $idx += 2; | 
| 168 | 11 | 100 |  |  |  | 54 | sprintf "# %-*s => %s", $max, $_, | 
| 169 |  |  |  |  |  |  | $type eq 'REFARRAY' ? $$anyone->[$idx] : $anyone->{$_}; | 
| 170 |  |  |  |  |  |  | } @keys; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  | else { | 
| 173 | 12 |  |  |  |  | 45 | $message .= sprintf ' (%s)', join '/', @keys; | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 | 24 |  |  |  |  | 75 | @$stash{qw/message hint choices/} = ($message, $hint, $choices); | 
| 178 | 24 |  |  |  |  | 75 | return $exclusive_map; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | sub _anyone_type { | 
| 182 | 24 |  |  | 24 |  | 35 | my $anyone = shift; | 
| 183 |  |  |  |  |  |  | my $type = | 
| 184 |  |  |  |  |  |  | ref $anyone eq 'ARRAY' && @$anyone ? 'ARRAY' : | 
| 185 |  |  |  |  |  |  | ref $anyone eq 'HASH'  && %$anyone ? 'HASH'  : | 
| 186 |  |  |  |  |  |  | ref $anyone eq 'REF'   && ref $$anyone eq 'ARRAY' && @{$$anyone} | 
| 187 |  |  |  |  |  |  | ? 'REFARRAY' : | 
| 188 | 24 | 0 | 66 |  |  | 750 | do { | 
|  |  | 50 | 66 |  |  |  |  | 
|  |  | 100 | 33 |  |  |  |  | 
|  |  | 100 | 0 |  |  |  |  | 
| 189 |  |  |  |  |  |  | require Scalar::Util; | 
| 190 |  |  |  |  |  |  | Scalar::Util::blessed($anyone) || '' | 
| 191 |  |  |  |  |  |  | } eq 'Hash::MultiValue' && %$anyone | 
| 192 |  |  |  |  |  |  | ? 'Hash::MultiValue' : ''; | 
| 193 | 24 |  |  |  |  | 575 | return $type; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub _make_regexp { | 
| 197 | 5 |  |  | 5 |  | 6 | my ($opts, $stash) = @_; | 
| 198 | 5 | 100 |  |  |  | 58 | my $regexp = ref $opts->{regexp} eq 'Regexp' ? $opts->{regexp} | 
|  |  | 100 |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | : $opts->{ignore_case} ? qr/$opts->{regexp}/i : qr/$opts->{regexp}/; | 
| 200 | 5 |  |  |  |  | 24 | $stash->{hint} = sprintf "# Please answer pattern %s\n", $regexp; | 
| 201 | 5 |  |  |  |  | 71 | $regexp = qr/\A $regexp \Z/x; | 
| 202 | 5 |  |  |  |  | 23 | return $regexp; | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | # using IO::Interactive::is_interactive() ? | 
| 206 |  |  |  |  |  |  | sub _isa_tty { | 
| 207 | 0 |  |  | 0 |  | 0 | my ($in, $out) = @_; | 
| 208 | 0 | 0 | 0 |  |  | 0 | return -t $in && (-t $out || !(-f $out || -c $out)) ? 1 : 0; ## no critic | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # taken from Test::Builder | 
| 212 |  |  |  |  |  |  | sub _is_fh { | 
| 213 | 84 |  |  | 84 |  | 108 | my $maybe_fh = shift; | 
| 214 | 84 | 100 |  |  |  | 248 | return 0 unless defined $maybe_fh; | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 64 | 50 |  |  |  | 338 | return 1 if ref $maybe_fh  eq 'GLOB'; # its a glob ref | 
| 217 | 0 | 0 |  |  |  | 0 | return 1 if ref \$maybe_fh eq 'GLOB'; # its a glob | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | return eval { $maybe_fh->isa('IO::Handle') } | 
| 220 | 0 |  | 0 |  |  | 0 | || eval { tied($maybe_fh)->can('TIEHANDLE') }; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | sub _uniq { | 
| 224 | 6 |  |  | 6 |  | 7 | my %h; | 
| 225 | 6 |  |  |  |  | 37 | grep !$h{$_}++, @_; | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | sub _croak { | 
| 229 | 1 |  |  | 1 |  | 12 | require Carp; | 
| 230 | 1 |  |  |  |  | 405 | Carp::croak(@_); | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | 1; | 
| 234 |  |  |  |  |  |  | __END__ |