The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

—
—
—
—
—
—
# -----------------------------------------------------------------------------
=encoding utf8
=head1 NAME
Quiq::Terminal - Ein- und Ausgabe aufs Terminal
=head1 BASE CLASS
L<Quiq::Object>
=cut
# -----------------------------------------------------------------------------
use v5.10;
use strict;
our $VERSION = '1.225';
use Time::HiRes ();
# -----------------------------------------------------------------------------
=head1 METHODS
=head2 Klassenmethoden
=head3 askUser() - Erfrage vom Benutzer einen Wert
=head4 Synopsis
$val = $class->askUser($text,@opt);
=head4 Options
=over 4
=item -automatic => $bool (Default: 0)
Stelle keine Frage an den Benutzer, sondern liefere den Defaultwert.
Ist kein Defaultwert angegeben, wirf eine Exception. Diese Option ist
fĂĽr Programme nĂĽtzlich, die auch ohne Benutzerinteraktion ablaufen
können.
=item -default => $default (Default: keiner)
Liefere $default, wenn der Benutzer keinen Wert eingibt. An den
Prompt wird die Zeichenkette " ($default) " angehängt.
=item -inHandle => $fh (Default: *STDIN)
Filehandle, von der die Benutzereingabe gelesen wird.
=item -outHandle => $fh (Default: *STDOUT)
Filehandle, auf die der Prompt geschrieben wird.
=item -sloppy => $bool (Default: 0)
Beschränke die möglichen Antworten nicht auf die Liste $valSpec.
=item -timer => \$t (Default: undef)
Addiere Antwortzeit des Benutzer zu Zeitvariable $t hinzu. Dieses
Feature kann genutzt werden, um aus einer Zeitmessung des rufenden
Code die (langsame) Antwortzeit des Benutzers herauszunehmen.
my $t0 = Time::HiRes::gettimeofday;
...
Quiq::Terminal->askUser($prompt,
-timer=>\$t0,
...
);
...
printf "Elapsed: %.2f\n",Time::HiRes::gettimeofday-$t0;
Achtung: Der Wert der Zeitvariable wird in die Zukunft verschoben
und sollte daher nur zur Zeitdauermessung verwendet werden.
=item -timeout => $n
Liefere den Defaultwert nach $n Sekunden. Ist kein Defaultwert
angegeben, wirf eine Exception. Diese Option ist fĂĽr Programme
nĂĽtzlich, die einen automatischen Default-Ablauf haben, in den
der Benutzer aber eingreifen kann, wenn er das Programm bedient.
=item -ttyIn => $bool (Default: 0)
Lies Eingabe vom Terminal. Der Terminal-Eingabekanal (/dev/tty)
wird mit jedem Aufruf geöffnet und geschlossen.
=item -ttyOut => $bool (Default: 0)
Schreibe Ausgabe auf Terminal. Der Terminal-Ausgabekanal (/dev/tty)
wird mit jedem Aufruf geöffnet und geschlossen.
=item -values => $valSpec (Default: keiner)
Liste der zulässigen Antworten. Ist die Antwort nicht in der Liste
enthalten, wird die Frage erneut gestellt.
=back
=head4 Description
Fordere den Benutzer mit Prompt $text zur Eingabe eines
Werts auf. Der vom Benutzer eingegebene Wert wird zurĂĽckgeliefert.
Whitespace am Anfang und am Ende des Werts werden entfernt.
=head4 Example
Eingabe vom Terminal statt von STDIN per Filehandle:
my $tty = Quiq::FileHandle->new('<','/dev/tty');
my $val = Quiq::Terminal->askUser($prompt,-inHandle=>$tty);
$tty->close;
Dasselbe per Option:
my $val = Quiq::Terminal->askUser($prompt,-ttyIn=>1);
=cut
# -----------------------------------------------------------------------------
sub askUser {
my $class = shift;
my $prompt = shift;
# @_: @opt
# Optionen
my $automatic = 0;
my $color = '';
my $default = undef;
my $in = *STDIN;
my $out = *STDOUT;
my $sloppy = 0;
my $timer = undef;
my $timeout = undef;
my $ttyIn = 0;
my $ttyOut = 0;
my $values = undef;
if (@_) {
Quiq::Option->extract(\@_,
-automatic => \$automatic,
-color => \$color,
-default => \$default,
-inHandle => \$in,
-outHandle => \$out,
-sloppy => \$sloppy,
-timer => \$timer,
-timeout => \$timeout,
-ttyIn => \$ttyIn,
-ttyOut => \$ttyOut,
-values => \$values,
);
}
if ($automatic) {
if (defined $default) {
return $default;
}
else {
$class->throw(
'ASK-00001: Option -automatic without option -default',
);
}
}
if ($timeout && !defined $default) {
$class->throw(
'ASK-00002: Option -timeout without option -default',
);
}
my $reset = '';
if ($color) {
$color = $class->ansiEsc($color);
$reset = $class->ansiEsc('reset');
}
# Prompt generieren: "$prompt [$val1,$val2,...] ($def)"
$prompt = sprintf '%s%s%s',$color,$prompt,$reset;
my (@values,$valuesText);
if ($values) {
for my $val (split m|[/,]|,$values) {
my $text = $val;
if ($val =~ /\((.+)\)/) {
# (y)es,(a)bort
$val = $1;
$text = sprintf '%s(%s%s%s)%s',$`,$color,$val,$reset,$';
}
elsif ($val =~ /^(.*?)=(.*)/) {
# y=yes,a=abort
$val = $1;
$text = sprintf '%s%s%s=%s',$color,$val,$reset,$2;
}
push @values,$val;
$valuesText .= ',' if $valuesText;
$valuesText .= $text;
}
}
$prompt .= " [$valuesText]" if defined $valuesText;
$prompt .= " ($default)" if defined $default;
$prompt .= " " if $valuesText || defined $default;
# Eingabe lesen und prĂĽfen (falls -values)
my $t0 = Time::HiRes::gettimeofday;
my $answ;
while (1) {
if ($ttyOut) {
$out = Quiq::FileHandle->new('>','/dev/tty');
}
print $out $prompt;
if ($ttyIn) {
$in = Quiq::FileHandle->new('<','/dev/tty');
}
if ($timeout) {
eval {
local $SIG{ALRM} = sub {die "alarm\n"}; # \n erforderlich
alarm $timeout;
$answ = <$in>;
alarm 0;
};
if ($@) {
# Timeout abgelaufen
if ($@ ne "alarm\n") {
# Unerwarteten Fehler weiterleiten
die;
}
say $answ = $default;
}
}
else {
$answ = <$in>;
}
if ($ttyIn) {
$in->close;
}
if (defined $answ) {
# Wert bereinigen
$answ =~ s/^\s+//;
$answ =~ s/\s+$//;
$answ = $default if $answ eq '' && defined $default;
}
else { # eof
print $out "\n";
}
if ($ttyOut) {
$out->close;
}
if (@values) {
next if !defined $answ; # kein Ausstieg mit ^D
next if !$sloppy && !grep { $_ eq $answ } @values;
}
last; # Ausstieg
}
if ($timer) {
$$timer += Time::HiRes::gettimeofday-$t0;
}
return $answ;
}
# -----------------------------------------------------------------------------
=head3 ansiEsc() - Liefere ANSI Terminal Escape-Sequenz
=head4 Synopsis
$esc = $class->ansiEsc($str);
=head4 Description
Liefere die Terminal Escape-Sequenz $esc fĂĽr die in $str angegebenen
Terminal-Eigenschaften. Es kann eine Kombination aus Eigenschaften
angegeben werden. Die Eigenschaften werden durch Leerzeichen getrennt.
Beginnt $str mit ESC, d.h. ist $str bereits eine Escape-Sequenz,
wird $str unverändert zurückgeliefert.
B<Terminal-Eigenschaften>
Allgemein Vordergrund Hintergrund
----------- ----------- -----------
dark black on_black
bold red on_red
underline green on_green
blink yellow on_yellow
reverse blue on_blue
concealed magenta on_magenta
reset cyan on_cyan
white on_white
=head4 Example
Rote Schrift:
$esc = Quiq::Terminal->ansiEsc('red');
Fette weiĂźe Schrift auf rotem Grund:
$esc = Quiq::Terminal->ansiEsc('bold white on_red');
Terminal in den Anfangszustand zurĂĽckversetzen:
$esc = Quiq::Terminal->ansiEsc('reset');
=cut
# -----------------------------------------------------------------------------
sub ansiEsc {
my ($class,$str) = @_;
return substr($str,0,1) eq "\e"? $str: Term::ANSIColor::color($str);
}
# -----------------------------------------------------------------------------
=head3 width() - Liefere die Breite des Terminals
=head4 Synopsis
$width = $this->width;
=head4 Returns
Integer
=head4 Description
Ermittele die Anzahl der Spalten des Terminals und liefere diese zurĂĽck.
=cut
# -----------------------------------------------------------------------------
sub width {
my $this = shift;
my $cmd = 'tput cols';
my $width = `$cmd`;
Quiq::Exit->check($?,$cmd);
chomp $width;
return $width;
}
# -----------------------------------------------------------------------------
=head3 height() - Liefere die Höhe des Terminals
=head4 Synopsis
$height = $this->height;
=head4 Returns
Integer
=head4 Description
Ermittele die Anzahl der Zeilen des Terminals und liefere diese zurĂĽck.
=cut
# -----------------------------------------------------------------------------
sub height {
my $this = shift;
my $cmd = 'tput lines';
my $height = `$cmd`;
Quiq::Exit->check($?,$cmd);
chomp $height;
return $height;
}
# -----------------------------------------------------------------------------
=head1 VERSION
1.225
=head1 AUTHOR
Frank Seitz, L<http://fseitz.de/>
=head1 COPYRIGHT
Copyright (C) 2025 Frank Seitz
=head1 LICENSE
This code is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
# -----------------------------------------------------------------------------
1;
# eof