IO-Interactive
view release on metacpan or search on metacpan
lib/IO/Interactive.pm view on Meta::CPAN
use 5.008;
package IO::Interactive;
use strict;
use warnings;
$IO::Interactive::VERSION = '1.027';
sub is_interactive {
my ($out_handle) = (@_, select); # Default to default output handle
# Not interactive if output is not to terminal...
return 0 if not -t $out_handle;
# If *ARGV is opened, we're interactive if...
if ( tied(*ARGV) or defined(fileno(ARGV)) ) { # this is what 'Scalar::Util::openhandle *ARGV' boils down to
# ...it's currently opened to the magic '-' file
return -t *STDIN if defined $ARGV && $ARGV eq '-';
# ...it's at end-of-file and the next file is the magic '-' file
return @ARGV>0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV;
# ...it's directly attached to the terminal
return -t *ARGV;
}
# If *ARGV isn't opened, it will be interactive if *STDIN is attached
# to a terminal.
else {
return -t *STDIN;
}
}
local (*DEV_NULL, *DEV_NULL2);
my $dev_null;
BEGIN {
pipe *DEV_NULL, *DEV_NULL2
or die "Internal error: can't create null filehandle";
$dev_null = \*DEV_NULL;
}
sub interactive {
my ($out_handle) = (@_, \*STDOUT); # Default to STDOUT
return &is_interactive ? $out_handle : $dev_null;
}
sub _input_pending_on {
my ($fh) = @_;
my $read_bits = "";
my $bit = fileno($fh);
return if $bit < 0;
vec($read_bits, fileno($fh), 1) = 1;
select $read_bits, undef, undef, 0.1;
return $read_bits;
}
sub busy (&) {
my ($block_ref) = @_;
# Non-interactive busy-ness is easy...just do it
if (!is_interactive()) {
$block_ref->();
open my $fh, '<', \ "";
return $fh;
}
# Otherwise fork off an interceptor process...
my ($read, $write);
pipe $read, $write;
my $child = fork;
# Within that interceptor process...
if (!$child) {
# Prepare to send back any intercepted input...
use IO::Handle;
close $read;
$write->autoflush(1);
# Intercept that input...
while (1) {
if (_input_pending_on(\*ARGV)) {
# Read it...
my $res = <ARGV>;
# Send it back to the parent...
print {$write} $res;
# Admonish them for not waiting...
print {*STDERR} "That input was ignored. ",
"Please don't press any keys yet.\n";
}
}
exit;
}
# Meanwhile, back in the parent...
close $write;
# Temporarily close the input...
local *ARGV;
open *ARGV, '<', \ "";
# Do the job...
$block_ref->();
# Take down the interceptor...
kill 9, $child;
wait;
# Return whatever the interceptor caught...
return $read;
}
sub import {
my ($package) = shift;
my $caller = caller;
# Export each sub if it's requested...
for my $request ( @_ ) {
no strict 'refs';
my $impl = *{$package.'::'.$request}{CODE};
if(!$impl || $request =~ m/\A _/xms) {
require Carp;
Carp::croak("Unknown subroutine ($request()) requested");
}
*{$caller.'::'.$request} = $impl;
}
}
1; # Magic true value required at end of module
__END__
=encoding utf8
=head1 NAME
IO::Interactive - Utilities for interactive I/O
=head1 VERSION
This document describes IO::Interactive version 1.02
=head1 SYNOPSIS
use IO::Interactive qw(is_interactive interactive busy);
if ( is_interactive() ) {
print "Running interactively\n";
}
# or...
print {interactive} "Running interactively\n";
$fh = busy {
do_noninteractive_stuff();
}
=head1 DESCRIPTION
This module provides three utility subroutines that make it easier to
develop interactive applications.
The C<ARGV> filehandle, the one that C<< <> >> or an empty
C<readline()> uses, has various magic associated with it. It's not
actually opened until you try to read from it. Checking C<-t ARGV>
before you've tried to read from it might give you the wrong answer.
Not only that, you might not read from C<ARGV>. If the value in
C<@ARGV> is the magic filename C<-> (a convention to mean the standard
filehandle for input or output), C<ARGV> might actually be C<STDIN>.
You don't want to think about all of this. This module is discussed in
I<Perl Best Practices> on page 218. Also see the C<ARGV> entry in
L<perlvar> and the C<readline> entry in L<perlfunc>.
=over
=item C<is_interactive()>
This subroutine returns true if C<*ARGV> and the currently selected
filehandle (usually C<*STDOUT>) are connected to the terminal. The
test is considerably more sophisticated than:
-t *ARGV && -t *STDOUT
as it takes into account the magic behaviour of C<*ARGV>.
You can also pass C<is_interactive> a writable filehandle, in which
case it requires that filehandle be connected to a terminal (instead
of the currently selected). The usual suspect here is C<*STDERR>:
if ( is_interactive(*STDERR) ) {
carp $warning;
}
Note that C<is_interactive> may return true in a Windows Scheduled
Task. See Github #6 (https://github.com/briandfoy/io-interactive/issues/6).
=item C<interactive()>
This subroutine returns C<*STDOUT> if C<is_interactive> is true. If
C<is_interactive()> is false, C<interactive> returns a filehandle that
does not print.
This makes it easy to create applications that print out only when the
application is interactive:
print {interactive} "Please enter a value: ";
my $value = <>;
You can also pass C<interactive> a writable filehandle, in which case it
writes to that filehandle if it is connected to a terminal (instead of
writing to C<*STDOUT>). Once again, the usual suspect is C<*STDERR>:
print {interactive(*STDERR)} $warning;
=item C<busy {...}>
This subroutine takes a block as its single argument and executes that block.
Whilst the block is executed, C<*ARGV> is temporarily replaced by a closed
filehandle. That is, no input from C<*ARGV> is possible in a C<busy> block.
Furthermore, any attempts to send input into the C<busy> block through
C<*ARGV> is intercepted and a warning message is printed to C<*STDERR>.
The C<busy> call returns a filehandle that contains the intercepted input.
A C<busy> block is therefore useful to prevent attempts at input when the
program is busy at some non-interactive task.
=back
=head1 DIAGNOSTICS
=over
=item Unknown subroutine (%s) requested
This module only exports the three subroutines described above.
You asked for something else. Maybe you misspelled the subroutine you wanted.
=back
=head1 CONFIGURATION AND ENVIRONMENT
IO::Interactive requires no configuration files or environment variables.
=head1 DEPENDENCIES
This module requires the C<openhandle()> subroutine from the
Scalar::Util module.
=head1 INCOMPATIBILITIES
None reported.
=head1 BUGS AND LIMITATIONS
No bugs have been reported.
Please report any bugs or feature requests to Github
L<https://github.com/briandfoy/io-interactive/issues>.
=head1 SOURCE AVAILABILITY
This code is in GitHub:
https://github.com/briandfoy/io-interactive
=head1 AUTHOR
Damian Conway C<< <DCONWAY@cpan.org> >>
Currently maintained by brian d foy C<< <briandfoy@pobox.com> >>.
1.01 patch DMUEY C<< dmuey@cpan.org >>
=head1 LICENCE AND COPYRIGHT
Copyright © 2005-2021, Damian Conway C<< <DCONWAY@cpan.org> >>. All rights reserved.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=head1 DISCLAIMER OF WARRANTY
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
( run in 1.238 second using v1.01-cache-2.11-cpan-39bf76dae61 )