File Coverage

blib/lib/Prompt/ReadKey.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Prompt::ReadKey;
4 3     3   266119 use Moose;
  0            
  0            
5              
6             use Prompt::ReadKey::Util;
7              
8             use Carp qw(croak);
9             use Term::ReadKey;
10             use List::Util qw(first);
11             use Text::Table;
12             use Text::Sprintf::Named;
13              
14             our $VERSION = "0.04";
15              
16             has default_prompt => (
17             init_arg => "prompt",
18             isa => "Str",
19             is => "rw",
20             );
21              
22             has additional_options => (
23             isa => "ArrayRef[HashRef]",
24             is => "rw",
25             auto_deref => 1,
26             );
27              
28             has auto_help => (
29             isa => "Bool",
30             is => "rw",
31             default => 1,
32             );
33              
34             has help_headings => (
35             isa => "ArrayRef[HashRef[Str]]",
36             is => "rw",
37             default => sub {[
38             { name => "keys", heading => "Key" },
39             { name => "name", heading => "Name" },
40             { name => "doc", heading => "Description" },
41             ]},
42             );
43              
44             has help_header => (
45             isa => "Str",
46             is => "rw",
47             default => "The list of available commands is:",
48             );
49              
50             has help_footer => (
51             isa => "Str",
52             is => "rw",
53             );
54              
55             has help_keys => (
56             isa => "ArrayRef[Str]",
57             is => "rw",
58             auto_deref => 1,
59             default => sub { [qw(h ?)] },
60             );
61              
62             has default_options => (
63             init_arg => "options",
64             isa => "ArrayRef[HashRef]",
65             is => "rw",
66             auto_deref => 1,
67             );
68              
69             has allow_duplicate_names => (
70             isa => "Bool",
71             is => "rw",
72             default => 0,
73             );
74              
75             has readkey_mode => (
76             isa => "Int",
77             is => "rw",
78             default => 0, # normal getc, change to get timed
79             );
80              
81             has readmode => (
82             isa => "Int",
83             is => "rw",
84             default => 3, # cbreak mode
85             );
86              
87             has echo_key => (
88             isa => "Bool",
89             is => "rw",
90             default => 1,
91             );
92              
93             has auto_newline => (
94             isa => "Bool",
95             is => "rw",
96             default => 1,
97             );
98              
99             has return_option => (
100             isa => "Bool",
101             is => "rw",
102             default => 0,
103             );
104              
105             has return_name => (
106             isa => "Bool",
107             is => "rw",
108             default => 1,
109             );
110              
111             has case_insensitive => (
112             isa => "Bool",
113             is => "rw",
114             default => 1,
115             );
116              
117             has repeat_until_valid => (
118             isa => "Bool",
119             is => "rw",
120             default => 1,
121             );
122              
123             has prompt_format => (
124             isa => "Str",
125             is => "rw",
126             default => '%(prompt)s [%(option_keys)s] ',
127             );
128              
129             sub prompt {
130             my ( $self, %args ) = @_;
131              
132             my @options = $self->prepare_options(%args);
133              
134             $self->do_prompt(
135             %args,
136             options => \@options,
137             prompt => $self->format_prompt( %args, options => \@options, option_count => scalar(@options) ),
138             );
139             }
140              
141             sub do_prompt {
142             my ( $self, %args ) = @_;
143              
144             my $repeat = $self->_get_arg_or_default( repeat_until_valid => %args );
145              
146             prompt: {
147             if ( my $opt = $self->prompt_once(%args) ) {
148              
149             if ( $opt->{reprompt_after} ) { # help, etc
150             $self->option_to_return_value(%args, option => $opt); # trigger callback
151             redo prompt;
152             }
153              
154             return $self->option_to_return_value(%args, option => $opt);
155             }
156              
157             redo prompt if $repeat;
158             }
159              
160             return;
161             }
162              
163             sub prompt_once {
164             my ( $self, %args ) = @_;
165              
166             $self->print_prompt(%args);
167             $self->read_option(%args);
168             }
169              
170             sub print_prompt {
171             my ( $self, %args ) = @_;
172             $self->print($self->_get_arg_or_default( prompt => %args ));
173             }
174              
175             sub print {
176             my ( $self, @args ) = @_;
177             local $| = 1;
178             print @args;
179             }
180              
181             sub prepare_options {
182             my ( $self, %args ) = @_;
183              
184             $self->filter_options(
185             %args,
186             options => [
187             $self->sort_options(
188             %args,
189             options => [
190             $self->process_options(
191             %args,
192             options => [ $self->gather_options(%args) ]
193             ),
194             ],
195             ),
196             ],
197             );
198             }
199              
200             sub process_options {
201             my ( $self, @args ) = @_;
202             map { $self->process_option( @args, option => $_ ) } $self->_get_arg_or_default(options => @args);
203             }
204              
205             sub process_option {
206             my ( $self, %args ) = @_;
207             my $opt = $args{option};
208              
209             my @keys = $opt->{key} ? delete($opt->{key}) : @{ $opt->{keys} || [] };
210              
211             unless ( @keys ) {
212             croak "either 'key', 'keys', or 'name' is a required option" unless $opt->{name};
213             @keys = ( substr $opt->{name}, 0, 1 );
214             }
215              
216             $opt->{keys} = \@keys;
217              
218             return $opt;
219             }
220              
221             sub gather_options {
222             my ( $self, %args ) = @_;
223              
224             return (
225             # explicit or default options
226             $self->_get_arg_or_default(options => %args),
227              
228             # static additional options from the object *and* options passed on the arg list
229             $self->additional_options(),
230             _get_arg(additional_options => %args),
231              
232             # the help command
233             $self->create_help_option(%args),
234             );
235             }
236              
237             sub get_help_keys {
238             my ( $self, @args ) = @_;
239              
240             if ( $self->_get_arg_or_default( auto_help => @args ) ) {
241             return $self->_get_arg_or_default( help_keys => @args );
242             }
243             }
244              
245             sub create_help_option {
246             my ( $self, @args ) = @_;
247              
248             if ( my @keys = $self->get_help_keys(@args) ) {
249             return {
250             reprompt_after => 1,
251             doc => "List available commands",
252             name => "help",
253             keys => \@keys,
254             callback => "display_help",
255             is_help => 1,
256             special_option => 1,
257             }
258             }
259              
260             return;
261             }
262              
263             sub display_help {
264             my ( $self, @args ) = @_;
265              
266             my @options = $self->_get_arg_or_default(options => @args);
267              
268             my $help = join("\n\n", grep { defined }
269             $self->_get_arg_or_default(help_header => @args),
270             $self->tabulate_help_text( @args, help_table => [ map { $self->option_to_help_text(@args, option => $_) } @options ] ),
271             $self->_get_arg_or_default(help_footer => @args),
272             );
273              
274             $self->print("\n$help\n\n");
275             }
276              
277             sub tabulate_help_text {
278             my ( $self, %args ) = @_;
279              
280             my @headings = $self->_get_arg_or_default( help_headings => %args );
281              
282             my $table = Text::Table->new( map { $_->{heading}, \" " } @headings );
283              
284             my @rows = _get_arg( help_table => %args );
285              
286             $table->load( map {
287             my $row = $_;
288             [ map { $row->{ $_->{name} } } @headings ];
289             } @rows );
290              
291             $table->body_rule(" ");
292              
293             return $table;
294             }
295              
296             sub option_to_help_text {
297             my ( $self, %args ) = @_;
298             my $opt = $args{option};
299              
300             return {
301             keys => join(", ", grep { /^[[:graph:]]+$/ } @{ $opt->{keys} } ),
302             name => $opt->{name} || "",
303             doc => $opt->{doc} || "",
304             };
305             }
306              
307             sub sort_options {
308             my ( $self, @args ) = @_;
309             $self->_get_arg_or_default(options => @args);
310             }
311              
312             sub filter_options {
313             my ( $self, %args ) = @_;
314              
315             my @options = $self->_get_arg_or_default(options => %args);
316              
317             croak "No more than one default is allowed" if 1 < scalar grep { $_->{default} } @options;
318              
319             foreach my $field ( "keys", ( $self->_get_arg_or_default( allow_duplicate_names => %args ) ? "name" : () ) ) {
320             my %idx;
321              
322             foreach my $option ( @options ) {
323             my $value = $option->{$field};
324             my @values = ref($value) ? @$value : $value;
325             push @{ $idx{$_} ||= [] }, $option for grep { defined } @values;
326             }
327              
328             foreach my $key ( keys %idx ) {
329             delete $idx{$key} if @{ $idx{$key} } == 1;
330             }
331              
332             if ( keys %idx ) {
333             # FIXME this error sucks
334             require Data::Dumper;
335             croak "duplicate value for '$field': " . Data::Dumper::Dumper(\%idx);
336             }
337             }
338              
339             return @options;
340             }
341              
342             sub prompt_string {
343             my ( $self, @args ) = @_;
344             if ( my $string = $self->_get_arg_or_default(prompt => @args) ) {
345             return $self->format_string(
346             @args,
347             format => $string,
348             );
349             } else {
350             croak "'prompt' argument is required";
351             }
352             }
353              
354             sub get_default_option {
355             my ( $self, @args ) = @_;
356              
357             if ( my $default = $self->_get_arg_or_default( default_option => @args ) ) {
358             return $default;
359             } else {
360             return first { $_->{default} } $self->_get_arg_or_default( options => @args );
361             }
362             }
363              
364             sub format_options {
365             my ( $self, %args ) = @_;
366              
367             my $default_option = $self->get_default_option(%args) || {};
368              
369             my @options = grep { not $_->{special_option} } $self->_get_arg_or_default(options => %args);
370              
371             if ( $self->_get_arg_or_default( case_insensitive => %args ) ) {
372             return join "", map {
373             my $default = $default_option == $_;
374             map { $default ? uc : lc } grep { /^[[:graph:]]+$/ } @{ $_->{keys} };
375             } @options;
376             } else {
377             return join "", grep { /^[[:graph:]]+$/ } map { @{ $_->{keys} } } @options;
378             }
379             }
380              
381             sub format_string {
382             my ( $self, %args ) = @_;
383             Text::Sprintf::Named->new({ fmt => $args{format} })->format({ args => \%args })
384             }
385              
386             sub format_prompt {
387             my ( $self, @args ) = @_;
388              
389             my $format = $self->_get_arg_or_default( prompt_format => @args );
390              
391              
392             $self->format_string(
393             @args,
394             format => $format,
395             prompt => $self->prompt_string(@args),
396             option_keys => $self->format_options(@args),
397             );
398             }
399              
400             sub read_option {
401             my ( $self, @args ) = @_;
402              
403             my @options = $self->_get_arg_or_default(options => @args);
404              
405             my %by_key = map {
406             my $opt = $_;
407             map { $_ => $opt } map { $self->process_char( @args, char => $_ ) } @{ $_->{keys} };
408             } @options;
409              
410             my $c = $self->process_char( @args, char => $self->read_key(@args) );
411              
412             if ( defined $c ) {
413             if ( exists $by_key{$c} ) {
414             return $by_key{$c};
415             } elsif ( $c =~ /^\s+$/ ) {
416             if ( my $default = $self->get_default_option(@args) ) {
417             return $default;
418             }
419             }
420             }
421              
422             $self->invalid_choice(@args, char => $c);
423              
424             return;
425             }
426              
427             sub invalid_choice {
428             my ( $self, %args ) = @_;
429              
430             my $output;
431             my $c = $args{char};
432              
433             if ( defined($c) and $c =~ /^[[:graph:]]+$/ ) {
434             $output = "'$c' is not a valid choice, please select one of the options.";
435             } else {
436             $output = "Invalid input, please select one of the options.";
437             }
438              
439             if ( my @keys = $self->get_help_keys(%args) ) {
440             $output .= " Enter '$keys[0]' for help.";
441             }
442              
443             $self->print("$output\n");
444             }
445              
446             sub option_to_return_value {
447             my ( $self, %args ) = @_;
448              
449             my $opt = $args{option};
450              
451             if ( $opt->{special_option} ) {
452             if ( my $cb = $opt->{callback} ) {
453             return $self->$cb(%args);
454             } else {
455             return $opt;
456             }
457             } else {
458             return $opt if $self->_get_arg_or_default(return_option => %args);
459              
460             if ( my $cb = $opt->{callback} ) {
461             return $self->$cb(%args);
462             } else {
463             return (
464             $self->_get_arg_or_default(return_name => %args)
465             ? $opt->{name}
466             : $opt
467             );
468             }
469             }
470             }
471              
472             sub read_key {
473             my ( $self, %args ) = @_;
474              
475             ReadMode( $self->_get_arg_or_default( readmode => %args ) );
476              
477             my $sigint = $SIG{INT} || sub { exit 1 };
478              
479             local $SIG{INT} = sub {
480             ReadMode(0);
481             print "\n" if $self->_get_arg_or_default( auto_newline => %args );
482             $sigint->();
483             };
484              
485             my $readkey_mode = $self->_get_arg_or_default( readkey_mode => %args );
486              
487             my $c = ReadKey($readkey_mode);
488              
489             if ( $c eq chr(0x1b) ) {
490             $c .= ReadKey($readkey_mode);
491             $c .= ReadKey($readkey_mode);
492             }
493              
494             ReadMode(0);
495              
496             die "Error reading key from user: $!" unless defined($c);
497              
498             print $c if $c =~ /^[[:graph:]]+$/ and $self->_get_arg_or_default( echo_key => %args );
499              
500             print "\n" if $c ne "\n" and $self->_get_arg_or_default( auto_newline => %args );
501              
502             return $c;
503             }
504              
505             sub process_char {
506             my ( $self, %args ) = @_;
507              
508             my $c = $args{char};
509              
510             if ( $self->_get_arg_or_default( case_insensitive => %args ) ) {
511             return lc($c);
512             } else {
513             return $c;
514             }
515             }
516              
517             __PACKAGE__
518              
519             __END__
520              
521             =pod
522              
523             =head1 NAME
524              
525             Prompt::ReadKey - Darcs style single readkey option prompt.
526              
527             =head1 SYNOPSIS
528              
529             my $p = Prompt::ReadKey->new;
530              
531             my $name = $p->prompt(
532             prompt => "blah",
533             options => [
534             { name => "foo" },
535             {
536             name => "bar",
537             default => 1,
538             doc => "This is the bar command", # used in help message
539             keys => [qw(b x)], # defaults to substr($name, 0, 1)
540             },
541             ],
542             );
543              
544             =head1 DESCRIPTION
545              
546             This module aims to provide a very subclassible L<Term::ReadKey> based prompter
547             inspired by Darcs' (L<http://darcs.net>) fantastic command line user interface.
548              
549             Many options exist both as accessors for default values, and are passable as
550             named arguments to the methods of the api.
551              
552             The api is structured so that the underlying methods are usable as well, you
553             don't need to use the high level api to make use of this module if you don't
554             want to.
555              
556             =head1 METHODS
557              
558             =over 4
559              
560             =item prompt %args
561              
562             Display a prompt, with additinal formatting and processing of additional and/or
563             default options, an automated help option, etc.
564              
565             =item do_prompt %args
566              
567             Low level prompt, without processing of options and prompt reformatting.
568              
569             Affected by C<repeat_until_valid>.
570              
571             =item prompt_once %args
572              
573             Don't prompt repeatedly on invalid answers.
574              
575             =item print_prompt %args
576              
577             Just delegates to C<print> using the C<prompt> argument.
578              
579             =item prepare_options %args
580              
581             Returns a list of options, based on the arguments, defaults, various flags,
582             etc.
583              
584             =item process_options %args
585              
586             Delegates to C<process_option> for a list of options.
587              
588             =item process_option %args
589              
590             Low level option processor, checks for validity mostly.
591              
592             =item gather_options
593              
594             Merges the explicit default options, additional options, and optional help
595             option.
596              
597             =item get_help_keys %args
598              
599             Returns a list of keys that trigger the help command. Defaults to C<?> and
600             C<h>.
601              
602             If C<auto_help> is true then it returns C<help_keys>.
603              
604             =item create_help_option %args
605              
606             Creates an option from the C<get_help_keys> key list.
607              
608             =item display_help %args
609              
610             Prints out a help message.
611              
612             Affected by C<help_footer> and C<help_header>, delegates to
613             C<option_to_help_text> and C<tabulate_help_text> for the actual work, finally
614             sending the output to C<print>.
615              
616             =item tabulate_help_text %args
617              
618             Uses L<Text::Table> to pretty print the help.
619              
620             Affected by the C<help_headings> option.
621              
622             =item option_to_help_text %args
623              
624             Makes a hashref of text values from an option, to be formatted by
625             C<tabulate_help_text>.
626              
627             =item sort_options %args
628              
629             Sort the options. This is a stub for subclassing, the current implementation
630             leaves the options in the order they were gathered.
631              
632             =item filter_options %args
633              
634             Check the set of options for validity (duplicate names and keys, etc).
635              
636             Affected by the C<allow_duplicate_names> option.
637              
638             =item prompt_string %args
639              
640             Returns the prompt string (from default or args).
641              
642             =item format_options %args
643              
644             Format the option keys for the prompt. Appeneded to the actual prompt by C<format_prompt>.
645              
646             Concatenates the key skipping options for which C<is_help> is true in the spec.
647              
648             If the C<case_insensitive> option is true then the default command's key will
649             be uppercased, and the rest lowercased.
650              
651             =item format_prompt %args
652              
653             Append the output of C<format_options> in brackets to the actual prompt, and adds a space.
654              
655             =item read_option %args
656              
657             Wrapper for C<read_key> that returns the option selected.
658              
659             =item invalid_choice %args
660              
661             Called when an invalid key was entered. Uses C<print> internally.
662              
663             =item option_to_return_value %args
664              
665             Process the option into it's return value, triggerring callbacks or mapping to
666             the option name as requested.
667              
668             =item read_key %args
669              
670             calls C<ReadMode> and C<ReadKey> to get a single character from L<Term::ReadKey>.
671              
672             Affected by C<echo_key>, C<auto_newline>, C<readkey_mode>, C<readmode>.
673              
674             =item process_char %args
675              
676             Under C<case_insensitive> mode will lowercase the character specified.
677              
678             Called for every character read and every character in the option spec by
679             C<read_option>.
680              
681             =item print @text
682              
683             The default version will just call the builtin C<print>. It will locally set
684             C<$|> to 1, though that is probably superflous (I think C<ReadKey> will flush
685             anyway).
686              
687             This is the only function that does not take named arguments.
688              
689             =back
690              
691             =head1 OPTIONS AND ATTRIBUTES
692              
693             These attributes control default values for options.
694              
695             =over 4
696              
697             =item prompt
698              
699             The prompt to display.
700              
701             =item options
702              
703             The options to prompt for.
704              
705             =item additional_options
706              
707             Additional options to append to the default or explicitly specified options.
708              
709             Defaults to nothing.
710              
711             =item auto_help
712              
713             Whether or not to automatically create a help command.
714              
715             =item help_headings
716              
717             The headings of the help table.
718              
719             Takes an array of hash refs, which are expected to have the C<name> and
720             C<heading> keys filled in. The array is used for ordering and displaying the
721             help table.
722              
723             Defaults to B<Key>, B<Name>, B<Description>.
724              
725             =item help_header
726              
727             Text to prepend to the help message.
728              
729             Defaults to a simple description of the help screen.
730              
731             =item help_footer
732              
733             Text to append to the help message.
734              
735             No default value.
736              
737             =item help_keys
738              
739             The keys that C<create_help_option> will assign to the help option.
740              
741             Defaults to C<?> and C<h>.
742              
743             =item allow_duplicate_names
744              
745             Whether or not duplicate option names are allowed. Defaults to
746              
747             =item readkey_mode
748              
749             The argument to pass to C<ReadKey>. Default to C<0>. See L<Term::ReadKey>.
750              
751             =item readmode
752              
753             The value to give to C<ReadMode>. Defaults to C<3>. See L<Term::ReadKey>.
754              
755             =item echo_key
756              
757             Whether or not to echo back the key entered.
758              
759             =item auto_newline
760              
761             Whether or not to add a newline after reading a key (if the key is not newline
762             itself).
763              
764             =item return_option
765              
766             Overrides C<return_name> and the callback firing mechanism, so that the option
767             spec is always returned.
768              
769             =item return_name
770              
771             When returning a value from C<option_to_return_value>, and there is no
772             callback, will cause the name of the option to be returned instead of the
773             option spec.
774              
775             Defaults to true.
776              
777             =item case_insensitive
778              
779             Option keys are treated case insensitively.
780              
781             Defuaults to true.
782              
783             =item repeat_until_valid
784              
785             When invalid input is entered, reprompt until a valid choice is made.
786              
787             =back
788              
789             =head1 AUTHOR
790              
791             Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
792              
793             =head1 COPYRIGHT
794              
795             Copyright (c) 2008 Yuval Kogman. All rights reserved
796             This program is free software; you can redistribute
797             it and/or modify it under the same terms as Perl itself.
798              
799             =cut