File Coverage

blib/lib/Dev/Util/Query.pm
Criterion Covered Total %
statement 30 68 44.1
branch 1 24 4.1
condition 2 6 33.3
subroutine 6 9 66.6
pod 4 4 100.0
total 43 111 38.7


line stmt bran cond sub pod time code
1             package Dev::Util::Query;
2              
3 4     4   4469 use Dev::Util::Syntax;
  4         9  
  4         31  
4 4     4   61 use Exporter qw(import);
  4         14  
  4         186  
5              
6 4     4   2492 use IO::Interactive qw(is_interactive);
  4         29915  
  4         47  
7 4     4   3126 use IO::Prompt qw(); # don't import prompt
  4         53623  
  4         139  
8 4     4   50 use Term::ReadKey;
  4         8  
  4         5703  
9              
10             our $VERSION = version->declare("v2.19.35");
11              
12             our %EXPORT_TAGS = (
13             misc => [ qw(
14             banner
15             display_menu
16             yes_no_prompt
17             prompt
18             )
19             ],
20             );
21              
22             # add all the other ":class" tags to the ":all" class, deleting duplicates
23             {
24             my %seen;
25             push @{ $EXPORT_TAGS{ all } }, grep { !$seen{ $_ }++ } @{ $EXPORT_TAGS{ $_ } }
26             foreach keys %EXPORT_TAGS;
27             }
28             Exporter::export_ok_tags('all');
29              
30             sub banner {
31 2     2 1 261497 my $banner = shift;
32 2   100     12 my $fh = shift || \*STDOUT;
33              
34 2         3 my $width;
35 2 50       13 if ( is_interactive() ) {
36 0         0 ($width) = Term::ReadKey::GetTerminalSize();
37             }
38             else {
39 2         37 $width = 80;
40             }
41              
42 2         7 my $spacer = ( $width - 2 ) - length($banner);
43 2         7 my $lspace = int( $spacer / 2 );
44 2         6 my $rspace = $lspace + $spacer % 2;
45              
46 2         33 print $fh "#" x $width . "\n";
47 2         16 print $fh "#" . " " x ( $width - 2 ) . "#" . "\n";
48 2         14 print $fh "#" . " " x $lspace . $banner . " " x $rspace . "#" . "\n";
49 2         10 print $fh "#" . " " x ( $width - 2 ) . "#" . "\n";
50 2         11 print $fh "#" x $width . "\n";
51 2         7 print $fh "\n";
52              
53 2         9 return;
54             }
55              
56             sub display_menu {
57 0     0 1   my $msg = shift;
58 0           my $choices_ref = shift;
59              
60 0           my %choice_hash = map { $choices_ref->[$_] => $_ } 0 .. $#{ $choices_ref };
  0            
  0            
61              
62 0           my $chosen = IO::Prompt::prompt(
63             -prompt => $msg,
64             -onechar,
65             -menu => $choices_ref,
66             -default => 'a'
67             );
68              
69 0           return $choice_hash{ $chosen };
70             }
71              
72             # Maintain API for existing code even thought changing to IO::Prompt
73             sub yes_no_prompt {
74 0     0 1   my ($settings) = @_;
75 0           my $ynd;
76              
77 0 0         if ( exists $settings->{ default } ) {
78 0 0         $ynd = ( $settings->{ default } ) ? ' ([Y]/N)' : ' (Y/[N])';
79             }
80             else {
81 0           $ynd = ' (Y/N)';
82             }
83              
84 0           my $msg = $settings->{ prepend };
85 0   0       $msg .= $settings->{ text } || q{};
86 0           $msg .= $ynd;
87 0           $msg .= $settings->{ append };
88              
89             my $response
90             = IO::Prompt::prompt(
91             -prompt => $msg,
92             -onechar,
93 0 0         -default => ( $settings->{ default } ) ? 'Y' : 'N',
94             -yes_no,
95             -require => { "Please choose${ynd}: " => qr/[YN]/i }
96             );
97 0 0         return ( $response->{ value } =~ m/[yY]/ ) ? 1 : 0;
98             }
99              
100             sub prompt {
101 0     0 1   my ($settings) = @_;
102              
103 0           my $msg = $settings->{ prepend };
104 0   0       $msg .= $settings->{ text } || q{};
105 0 0         $msg .= " [$settings->{default}]" if ( defined $settings->{ default } );
106 0           $msg .= $settings->{ append };
107              
108 0           my $prompt_args = { -prompt => $msg };
109 0 0         if ( $settings->{ noecho } ) { $prompt_args->{ -echo } = q{} }
  0            
110             ## if ( $settings->{ okempty } ) { ... } # TODO: figure out okempty sol'n
111 0 0         if ( defined $settings->{ default } ) {
112 0           $prompt_args->{ -default } = $settings->{ default };
113             }
114 0 0         if ( defined $settings->{ valid } ) {
115 0 0         if ( ref( $settings->{ valid } ) eq 'ARRAY' ) {
    0          
116 0           $prompt_args->{ -menu } = $settings->{ valid };
117 0           $prompt_args->{ -one_char } = $msg;
118             }
119             elsif ( ref( $settings->{ valid } ) eq 'CODE' ) {
120              
121             # $prompt_args->{ -require } = { '%s (dir must exist): ' => \&dir_writable };
122             $prompt_args->{ -require }
123 0           = { '%s (response not valid): ' => $settings->{ valid } };
124             }
125             else {
126 0           croak "Validitiy test malformed.\n";
127             }
128             }
129 0           my $response = IO::Prompt::prompt($prompt_args);
130 0 0         print "\n" if ( exists $settings->{ noecho } );
131              
132 0           return $response->{ value };
133             }
134              
135             # TODO: must reverse logic of calls to valid
136              
137             1; # End of Dev::Util::Query
138              
139             =pod
140              
141             =encoding utf-8
142              
143             =head1 NAME
144              
145             Dev::Util::Query - Functions to prompt user for input, y/n, or menus.
146              
147             =head1 VERSION
148              
149             Version v2.19.35
150              
151             =head1 SYNOPSIS
152              
153             Dev::Util::Query - provides functions to ask the user for input.
154              
155             use Dev::Util::Query;
156              
157             banner( "Hello World", $outputFH );
158              
159             my $msg = 'Pick a choice from the list:';
160             my @items = ( 'choice one', 'choice two', 'choice three', );
161             my $choice = display_menu( $msg, \@items );
162              
163              
164             my $action = yes_no_prompt(
165             { text => "Rename Files?", default => 1, });
166              
167             my $dir = prompt(
168             { text => "Enter Destination Dir",
169             valid => \&dir_writable,
170             }
171             );
172              
173             =head1 EXPORT_TAGS
174              
175             =over 4
176              
177             =item B<:misc>
178              
179             =over 8
180              
181             =item display_menu
182              
183             =item prompt
184              
185             =item yes_no_prompt
186              
187             =item banner
188              
189             =back
190              
191             =back
192              
193             =head1 SUBROUTINES
194              
195             =head2 B<banner(MESSAGE, FH)>
196              
197             Print a banner message on the supplied file handle (defaults to C<STDOUT>)
198              
199             banner( "Hello World" );
200             banner( "Hello World", $outputFH );
201              
202             C<MESSAGE> The message to display in the banner
203              
204             C<FH> is a file handle where the banner will be output, default: STDOUT
205              
206             =head2 B<display_menu(MSG,ITEMS)>
207              
208             Display a simple menu of options. The choices come from an array. Returns the index of the choice.
209              
210             C<MSG> a string or variable containing the prompt message to display.
211              
212             C<ITEMS> a reference to an array of the choices to list
213              
214             my $msg = 'Pick one of the suits: ';
215             my @items = qw( hearts clubs spades diamonds );
216             display_menu( $msg, \@items );
217              
218              
219             =head2 B<yes_no_prompt(ARGS_HASH)>
220              
221             Prompt user for a yes or no response. Takes a single character for input, must be C<[yYnN\n]>.
222             A carriage return will return the default. Returns 1 for yes, 0 for no.
223              
224             B<ARGS_HASH:>
225             { text => TEXT, default => DEFAULT_BOOL, prepend => PREPEND, append => APPEND }
226              
227             C<TEXT> The text of the prompt.
228              
229             C<DEFAULT_BOOL> Set the default response: 1 -> Yes ([Y]/N), 0 -> No (Y/[N]), undef -> none
230              
231             C<PREPEND> Text to prepend to TEXT
232              
233             C<APPEND> Text to append to TEXT
234              
235             my $action = yes_no_prompt(
236             { text => "Rename Files?",
237             default => 1,
238             prepend => '>' x 3,
239             append => ': '
240             }
241             );
242              
243             =head2 B<prompt(ARGS_HASH)>
244              
245             Prompt user for input.
246              
247             B<ARGS_HASH:>
248             { text => TEXT, default => DEFAULT, valid => VALID, prepend => PREPEND, append => APPEND, noecho => ECHO_BOOL }
249              
250             C<DEFAULT> Set the default response, optionally.
251              
252             C<VALID> Ensures the response is valid. Can be a list or array reference, in which case
253             the values will be presented as a menu. Alternately, it can be a code ref, where the
254             subroutine is run with C<$_> set to the response. An invalid response will re-prompt
255             the user for input.
256              
257             C<ECHO_BOOL> Normally (the default 0) text will be echoed as it is typed. If set to 1
258             text will not be echoed back to the screen.
259              
260             my $interval = prompt(
261             { text => "Move Files Daily or Monthly",
262             valid => [ 'daily', 'monthly' ],
263             default => 'daily',
264             prepend => '> ' x 3,
265             append => ': ',
266             noecho => 0
267             }
268             );
269             my $dir = prompt(
270             { text => "Enter Destination Dir",
271             valid => \&dir_writable,
272             prepend => '<' x 3,
273             append => ': '
274             }
275             );
276             my $color = prompt(
277             { text => "What is your favorite color",
278             prepend => '.' x 3,
279             append => ': '
280             }
281             );
282              
283             B<Note>: The API for this function is maintained to support the existing code base that uses it.
284             It would probably be better to use C<IO::Prompter> for new code.
285              
286             =head1 AUTHOR
287              
288             Matt Martini, C<< <matt at imaginarywave.com> >>
289              
290             =head1 BUGS
291              
292             Please report any bugs or feature requests to C<bug-dev-util at rt.cpan.org>, or through
293             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dev-Util>. I will
294             be notified, and then you'll automatically be notified of progress on your bug as I make changes.
295              
296             =head1 SUPPORT
297              
298             You can find documentation for this module with the perldoc command.
299              
300             perldoc Dev::Util::Query
301              
302             You can also look for information at:
303              
304             =over 4
305              
306             =item * RT: CPAN's request tracker (report bugs here)
307              
308             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Dev-Util>
309              
310             =item * Search CPAN
311              
312             L<https://metacpan.org/release/Dev-Util>
313              
314             =back
315              
316             =head1 ACKNOWLEDGMENTS
317              
318             =head1 LICENSE AND COPYRIGHT
319              
320             This software is Copyright © 2019-2025 by Matt Martini.
321              
322             This is free software, licensed under:
323              
324             The GNU General Public License, Version 3, June 2007
325              
326             =cut
327              
328             __END__