This is taken directly from the source code of ExtUtils::MakeMaker's prompt
function. It's possible, I suppose, that someone could go to lengths to trick it. But at some point the breakage must be owned by the breaker.
For most purposes this ought to be adequate:
my $isa_tty = -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT)) ;
First it checks if STDIN is opened to a TTY. If so, check if STDOUT is. If STDOUT is not, it must also neither be opened to a file nor a character special file.
Update:
IO::Prompt::Tiny uses the following:
# Copied (without comments) from IO::Interactive::Tiny by Daniel Muey,
# based on IO::Interactive by Damian Conway and brian d foy
sub _is_interactive {
my ($out_handle) = ( @_, select );
return 0 if not -t $out_handle;
if ( tied(*ARGV) or defined( fileno(ARGV) ) ) {
return -t *STDIN if defined $ARGV && $ARGV eq '-';
return @ARGV > 0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV;
return -t *ARGV;
}
else {
return -t *STDIN;
}
}
And IO::Interactive::Tiny adds comments to explain what's going on:
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)) ) { # IO::Interactive::Tiny: this is the only relavent part of Scalar::Util::openhandle() for 'openhandle *ARGV'
# ...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;
}
}
And I've verified that the logic in IO::Interactive mirrors that of IO::Interactive::Tiny. So, if your goal is to prompt where appropriate, consider using IO::Prompt::Tiny. And if your needs are more nuanced than IO::Prompt::Tiny supports, you can use IO::Interactive::Tiny to provide this specific functionality.
While you're probably mostly safe using your own solution, an advantage to using one of these CPAN modules is that they are presumably actively maintained and would receive but reports and eventual updates if they turn out to be inadequate to their advertised purpose.