Read data from users correctly, effectively, and without thinking about it. Even when you know the right way to handle interactive I/O [Hack #13], the resulting code can still be frustratingly messy: my $offset; print "Enter an offset: " if is_interactive; GET_OFFSET: while (<>) { chomp; if (m/\\A [+-] \\d+ \\z/x) { $offset = $_; last GET_OFFSET; } print "Enter an offset (please enter an integer): " if is_interactive; } You can achieve exactly the same effect (and much more) with the prompt( ) subroutine provided by the IO::Prompt CPAN module. Instead of all the above infrastructure code, just write: use IO::Prompt; my $offset = prompt( "Enter an offset: ", -integer ); prompt( ) prints the string you give it, reads a line from standard input, chomps it, and then tests the input value against any constraint you specify (for example, -integer). If the constraint is not satisfied, the prompt repeats, along with a clarification of what was wrong. When the user finally enters an acceptable value, prompt( ) returns it. Most importantly, prompt( ) is smart enough not to bother writing out any prompts if the application isn't running interactively, so you don't have to code explicitly for that case.
Train -reqprompt( ) has a general mechanism for telling it what kind of input you need and how to ask for that input. For example: my $hex_num = prompt( "Enter a hex number> ", -req => { "A *hex* number please!> " => qr/^[0-9A-F]+$/i } ); print "That's ", hex($hex_num), " in base 10\\n"; When this code executes, you will see something like: Enter a hex number> 2B|!2B A *hex* number please!> C3P0 A *hex* number please!> 124C1 That's 74945 in base 10 The -req argument takes a hash reference, in which each value is something to test the input against, and each key is a secondary prompt to print when the test fails. The tests can be regexes (which the input must match) or subroutines (which receive the input as $_ and should return true if that input satisfies the constraint). For example: my $factor = prompt( "Enter a prime: ", -req => { "Try again: " => sub { is_prime($_) } } ); Yea or NayOne particularly useful constraint that prompt( ) supports is a mode that accepts only the letters y or n as input: if (prompt -YESNO, "Quit? ") { save_changes($changes) if $changes && prompt -yes, "Save changes? "; print "Changes: $changes\\n"; exit; } The first call to prompt( ) requires the user to type a word beginning with Y or N. It will ignore anything else and return the prompt with an explanation. If the input is Y, the call will return true; if N, it will return false. On the other hand, the second call (with the -yes argument) actually accepts any input. If that string starts with a y or Y, prompt( ) returns true; for any other input, it returns false. For example: Quit? q Quit? (Please enter 'Y' or 'N') Y Save changes? n Changes: not saved These different combinations of -YES/-yes/-no/-NO allow for varying degrees of punctiliousness in obtaining the user's consent. In particular, using -YESNO forces users to hit Shift and one of only two possible keys, which often provides enough of a pause to prevent unthinking responses that they'll deeply regret about 0.1 seconds after hitting Enter. At the Touch of a ButtonOn the other hand, sometimes it's immensely annoying to have to press Enter at all. Sometimes you want to hit a single key and just let the application get on with things. Thus prompt( ) provides a single character mode: for my $file (@matching_files) { next unless prompt -one_char, -yes, "Copy $file? "; copy($file, "$backup_dir/$file"); } With -one_char in effect, the first typed character completes the entire input operation. In this case, prompt( ) returns true only if that character was y or Y. Of course, single character mode can accept more than just y and n. For example, the following call allows the user to select a drive instantly, simply by typing its single character name (in upper- or lowercase): my $drive = uc prompt "Select a drive: ", -one_char, -req => { "Please select A-F: " => qr/[A-F]/i }; Engage Cloaking DeviceYou can tell prompt( ) not to echo input (good for passwords): my $passwd = prompt( "First password: ", -echo=>"" ); or to echo something different in place of what you actually type (also good for passwords): my $passwd = prompt( "Second password: ", -echo=>"*" ); This allows you to produce interfaces like: First password: Second password: ******** What's On the Menu?Often you can't rely on users to type in the right responses; it's easier to list them and ask the user to choose. This is menu-driven interaction, and prompt( ) supports various forms of it. The simplest is just to give the subroutine a list of possible responses in an array: my $device = prompt 'Activate which device?', -menu => [ 'Sharks with "laser" beams', 'Disinhibiter gas grenades', 'Death ray', 'Mirror ball', ]; print "Activating $device in 10:00 and counting...\\n"; This produces the request: Activate which device? a. Sharks with "laser" beams b. Disinhibiter gas grenades c. Death ray d. Mirror ball > q (Please enter a-d) > d Activating Mirror ball in 10:00 and counting... The menu call to prompt only accepts characters in the range displayed, and returns the value corresponding to the character entered. You can also pass the -menu option a hash reference: my $device = prompt 'Initiate which master plan?', -menu => { Cousteau => 'Sharks with "laser" beams', Libido => 'Disinhibiter gas grenades', Friar => 'Death ray', Shiny => 'Mirror ball', }; print "Activating $device in 10:00 and counting...\\n"; in which case it will show the list of keys and return the value corresponding to the key selected: Initiate which master plan? a. Cousteau b. Friar c. Libido d. Shiny > d Activating Mirror ball in 10:00 and counting... You can even nest hashes and arrays: my $device = prompt 'Select your platform:', -menu => { Windows => [ 'WinCE', 'WinME', 'WinNT' ], MacOS => { 'MacOS 9' => 'Mac (Classic)', 'MacOS X' => 'Mac (New Age)', }, Linux => 'Linux', }; to create hierarchical menus: Select your platform: a. Linux b. MacOS c. Windows > b MacOS: a. MacOS 9 b. Mac OS X > b Compiling for Mac (New Age)... |