——————# -----------------------------------------------------------------------------
=encoding utf8
=head1 NAME
Quiq::Terminal - Ein- und Ausgabe aufs Terminal
=head1 BASE CLASS
L<Quiq::Object>
=cut
# -----------------------------------------------------------------------------
package
Quiq::Terminal;
use
v5.10;
use
strict;
use
warnings;
our
$VERSION
=
'1.225'
;
use
Quiq::Option;
use
Quiq::FileHandle;
use
Time::HiRes ();
use
Term::ANSIColor ();
use
Quiq::Exit;
# -----------------------------------------------------------------------------
=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'
);
}
$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
$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