Replace #13335 with
[p5sagit/p5-mst-13.2.git] / lib / Term / Cap.pm
CommitLineData
a0d0e21e 1package Term::Cap;
2ef86165 2
cb1a09d0 3use Carp;
85e6fe83 4
2ef86165 5use vars qw($VERSION);
6
7$VERSION = '1.02';
b75c8c73 8
e14c93b3 9# Version undef: Thu Dec 14 20:02:42 CST 1995 by sanders@bsdi.com
10# Version 1.00: Thu Nov 30 23:34:29 EST 2000 by schwern@pobox.com
11# [PATCH] $VERSION crusade, strict, tests, etc... all over lib/
12# Version 1.01: Wed May 23 00:00:00 CST 2001 by d-lewart@uiuc.edu
13# Avoid warnings in Tgetent and Tputs
2ef86165 14# Version 1.02: Sat Nov 17 13:50:39 GMT 2001 by jns@gellyfish.com
15# Altered layout of the POD
16# Added Test::More to PREREQ_PM in Makefile.PL
17# Fixed no argument Tgetent()
a687059c 18
cb1a09d0 19# TODO:
20# support Berkeley DB termcaps
21# should probably be a .xs module
22# force $FH into callers package?
23# keep $FH in object at Tgetent time?
24
25=head1 NAME
26
27Term::Cap - Perl termcap interface
28
29=head1 SYNOPSIS
30
31 require Term::Cap;
32 $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
33 $terminal->Trequire(qw/ce ku kd/);
34 $terminal->Tgoto('cm', $col, $row, $FH);
35 $terminal->Tputs('dl', $count, $FH);
36 $terminal->Tpad($string, $count, $FH);
37
38=head1 DESCRIPTION
39
40These are low-level functions to extract and use capabilities from
41a terminal capability (termcap) database.
42
2ef86165 43More information on the terminal capabilities will be found in the
44termcap manpage on most Unix-like systems.
cb1a09d0 45
2ef86165 46=head2 METHODS
cb1a09d0 47
2ef86165 48=over 4
cb1a09d0 49
50The output strings for B<Tputs> are cached for counts of 1 for performance.
51B<Tgoto> and B<Tpad> do not cache. C<$self-E<gt>{_xx}> is the raw termcap
52data and C<$self-E<gt>{xx}> is the cached version.
53
54 print $terminal->Tpad($self->{_xx}, 1);
55
56B<Tgoto>, B<Tputs>, and B<Tpad> return the string and will also
57output the string to $FH if specified.
58
cb1a09d0 59
60=cut
61
62# Returns a list of termcap files to check.
63sub termcap_path { ## private
64 my @termcap_path;
65 # $TERMCAP, if it's a filespec
7a2e2cd6 66 push(@termcap_path, $ENV{TERMCAP})
67 if ((exists $ENV{TERMCAP}) &&
39e571d4 68 (($^O eq 'os2' || $^O eq 'MSWin32' || $^O eq 'dos')
fe6f1558 69 ? $ENV{TERMCAP} =~ /^[a-z]:[\\\/]/is
70 : $ENV{TERMCAP} =~ /^\//s));
c07a80fd 71 if ((exists $ENV{TERMPATH}) && ($ENV{TERMPATH})) {
cb1a09d0 72 # Add the users $TERMPATH
73 push(@termcap_path, split(/(:|\s+)/, $ENV{TERMPATH}))
74 }
75 else {
76 # Defaults
77 push(@termcap_path,
78 $ENV{'HOME'} . '/.termcap',
79 '/etc/termcap',
80 '/usr/share/misc/termcap',
81 );
a687059c 82 }
cb1a09d0 83 # return the list of those termcaps that exist
2ef86165 84 return grep(-f, @termcap_path);
748a9306 85}
86
2ef86165 87=item B<Tgetent>
88
89Returns a blessed object reference which the user can
90then use to send the control strings to the terminal using B<Tputs>
91and B<Tgoto>.
92
93The function extracts the entry of the specified terminal
94type I<TERM> (defaults to the environment variable I<TERM>) from the
95database.
96
97It will look in the environment for a I<TERMCAP> variable. If
98found, and the value does not begin with a slash, and the terminal
99type name is the same as the environment string I<TERM>, the
100I<TERMCAP> string is used instead of reading a termcap file. If
101it does begin with a slash, the string is used as a path name of
102the termcap file to search. If I<TERMCAP> does not begin with a
103slash and name is different from I<TERM>, B<Tgetent> searches the
104files F<$HOME/.termcap>, F</etc/termcap>, and F</usr/share/misc/termcap>,
105in that order, unless the environment variable I<TERMPATH> exists,
106in which case it specifies a list of file pathnames (separated by
107spaces or colons) to be searched B<instead>. Whenever multiple
108files are searched and a tc field occurs in the requested entry,
109the entry it names must be found in the same file or one of the
110succeeding files. If there is a C<:tc=...:> in the I<TERMCAP>
111environment variable string it will continue the search in the
112files as above.
113
114The extracted termcap entry is available in the object
115as C<$self-E<gt>{TERMCAP}>.
116
117It takes a hash reference as an argument with two optional keys:
118
119=over 2
120
121=item OSPEED
122
123The terminal output bit rate (often mistakenly called the baud rate)
124for this terminal - if not set a warning will be generated
125and it will be defaulted to 9600. I<OSPEED> can be be specified as
126either a POSIX termios/SYSV termio speeds (where 9600 equals 9600) or
127an old DSD-style speed ( where 13 equals 9600).
128
129
130=item TERM
131
132The terminal type whose termcap entry will be used - if not supplied it will
133default to $ENV{TERM}: if that is not set then B<Tgetent> will croak.
134
135=back
136
137It calls C<croak> on failure.
138
139=cut
140
cb1a09d0 141sub Tgetent { ## public -- static method
142 my $class = shift;
2ef86165 143 my ($self) = @_;
144
145 $self = {} unless defined $self;
146 bless $self, $class;
147
cb1a09d0 148 my($term,$cap,$search,$field,$max,$tmp_term,$TERMCAP);
149 local($termpat,$state,$first,$entry); # used inside eval
150 local $_;
151
152 # Compute PADDING factor from OSPEED (to be used by Tpad)
153 if (! $self->{OSPEED}) {
154 carp "OSPEED was not set, defaulting to 9600";
155 $self->{OSPEED} = 9600;
156 }
157 if ($self->{OSPEED} < 16) {
158 # delays for old style speeds
159 my @pad = (0,200,133.3,90.9,74.3,66.7,50,33.3,16.7,8.3,5.5,4.1,2,1,.5,.2);
160 $self->{PADDING} = $pad[$self->{OSPEED}];
161 }
162 else {
163 $self->{PADDING} = 10000 / $self->{OSPEED};
164 }
165
166 $self->{TERM} = ($self->{TERM} || $ENV{TERM} || croak "TERM not set");
167 $term = $self->{TERM}; # $term is the term type we are looking for
168
169 # $tmp_term is always the next term (possibly :tc=...:) we are looking for
170 $tmp_term = $self->{TERM};
171 # protect any pattern metacharacters in $tmp_term
172 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
173
c07a80fd 174 my $foo = (exists $ENV{TERMCAP} ? $ENV{TERMCAP} : '');
cb1a09d0 175
176 # $entry is the extracted termcap entry
fe6f1558 177 if (($foo !~ m:^/:s) && ($foo =~ m/(^|\|)${termpat}[:|]/s)) {
cb1a09d0 178 $entry = $foo;
179 }
180
2ef86165 181 my @termcap_path = termcap_path();
e66fb0c2 182
183 unless (@termcap_path || $entry)
184 {
185 # last resort--fake up a termcap from terminfo
186 local $ENV{TERM} = $term;
2f6e8d9f 187 if ($^O eq 'VMS') { # we use REAL dec terminals, not stinkin' emulators!
188 $entry = 'vt220|vt200|DEC VT220 in vt100 emulation mode:'
189 .'am:mi:xn:xo:'
190 .'co#80:li#24:'
191 .'RA=\E[?7l:SA=\E[?7h:'
192 .'ac=kkllmmjjnnwwqquuttvvxx:ae=\E(B:al=\E[L:as=\E(0:'
193 .'bl=^G:cd=\E[J:ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:'
194 .'cr=^M:cs=\E[%i%d;%dr:dc=\E[P:dl=\E[M:do=\E[B:'
195 .'ei=\E[4l:ho=\E[H:im=\E[4h:'
196 .'is=\E[1;24r\E[24;1H:'
197 .'nd=\E[C:'
198 .'kd=\E[B::kl=\E[D:kr=\E[C:ku=\E[A:le=^H:'
199 .'mb=\E[5m:md=\E[1m:me=\E[m:mr=\E[7m:'
200 .'kb=\0177:'
201 .'r2=\E>\E[24;1H\E[?3l\E[?4l\E[?5l\E[?7h\E[?8h\E=:rc=\E8:'
202 .'sc=\E7:se=\E[27m:sf=\ED:so=\E[7m:sr=\EM:ta=^I:'
203 .'ue=\E[24m:up=\E[A:us=\E[4m:ve=\E[?25h:vi=\E[?25l:';
204 } else {
2ef86165 205 eval
206 {
bf25f2b5 207 $entry = `infocmp -C 2>/dev/null`;
bf25f2b5 208 }
e66fb0c2 209 }
2f6e8d9f 210 }
e66fb0c2 211
cb1a09d0 212 croak "Can't find a valid termcap file" unless @termcap_path || $entry;
213
214 $state = 1; # 0 == finished
215 # 1 == next file
216 # 2 == search again
217
218 $first = 0; # first entry (keeps term name)
219
220 $max = 32; # max :tc=...:'s
221
222 if ($entry) {
223 # ok, we're starting with $TERMCAP
224 $first++; # we're the first entry
225 # do we need to continue?
226 if ($entry =~ s/:tc=([^:]+):/:/) {
227 $tmp_term = $1;
228 # protect any pattern metacharacters in $tmp_term
229 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
230 }
231 else {
232 $state = 0; # we're already finished
233 }
234 }
235
236 # This is eval'ed inside the while loop for each file
237 $search = q{
54310121 238 while (<TERMCAP>) {
cb1a09d0 239 next if /^\\t/ || /^#/;
240 if ($_ =~ m/(^|\\|)${termpat}[:|]/o) {
241 chomp;
242 s/^[^:]*:// if $first++;
243 $state = 0;
54310121 244 while ($_ =~ s/\\\\$//) {
245 defined(my $x = <TERMCAP>) or last;
246 $_ .= $x; chomp;
247 }
cb1a09d0 248 last;
748a9306 249 }
cb1a09d0 250 }
55497cff 251 defined $entry or $entry = '';
e14c93b3 252 $entry .= $_ if $_;
cb1a09d0 253 };
748a9306 254
cb1a09d0 255 while ($state != 0) {
256 if ($state == 1) {
257 # get the next TERMCAP
258 $TERMCAP = shift @termcap_path
259 || croak "failed termcap lookup on $tmp_term";
260 }
261 else {
262 # do the same file again
263 # prevent endless recursion
264 $max-- || croak "failed termcap loop at $tmp_term";
265 $state = 1; # ok, maybe do a new file next time
266 }
267
268 open(TERMCAP,"< $TERMCAP\0") || croak "open $TERMCAP: $!";
269 eval $search;
270 die $@ if $@;
271 close TERMCAP;
272
273 # If :tc=...: found then search this file again
274 $entry =~ s/:tc=([^:]+):/:/ && ($tmp_term = $1, $state = 2);
275 # protect any pattern metacharacters in $tmp_term
276 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
a687059c 277 }
cb1a09d0 278
279 croak "Can't find $term" if $entry eq '';
280 $entry =~ s/:+\s*:+/:/g; # cleanup $entry
281 $entry =~ s/:+/:/g; # cleanup $entry
282 $self->{TERMCAP} = $entry; # save it
283 # print STDERR "DEBUG: $entry = ", $entry, "\n";
a687059c 284
748a9306 285 # Precompile $entry into the object
cb1a09d0 286 $entry =~ s/^[^:]*://;
748a9306 287 foreach $field (split(/:[\s:\\]*/,$entry)) {
cb1a09d0 288 if ($field =~ /^(\w\w)$/) {
289 $self->{'_' . $field} = 1 unless defined $self->{'_' . $1};
290 # print STDERR "DEBUG: flag $1\n";
748a9306 291 }
292 elsif ($field =~ /^(\w\w)\@/) {
cb1a09d0 293 $self->{'_' . $1} = "";
294 # print STDERR "DEBUG: unset $1\n";
a687059c 295 }
296 elsif ($field =~ /^(\w\w)#(.*)/) {
cb1a09d0 297 $self->{'_' . $1} = $2 unless defined $self->{'_' . $1};
298 # print STDERR "DEBUG: numeric $1 = $2\n";
a687059c 299 }
300 elsif ($field =~ /^(\w\w)=(.*)/) {
cb1a09d0 301 # print STDERR "DEBUG: string $1 = $2\n";
302 next if defined $self->{'_' . ($cap = $1)};
a687059c 303 $_ = $2;
304 s/\\E/\033/g;
ecfc5424 305 s/\\(\d\d\d)/pack('c',oct($1) & 0177)/eg;
a687059c 306 s/\\n/\n/g;
307 s/\\r/\r/g;
308 s/\\t/\t/g;
309 s/\\b/\b/g;
310 s/\\f/\f/g;
311 s/\\\^/\377/g;
312 s/\^\?/\177/g;
63f2c1e1 313 s/\^(.)/pack('c',ord($1) & 31)/eg;
a687059c 314 s/\\(.)/$1/g;
315 s/\377/^/g;
cb1a09d0 316 $self->{'_' . $cap} = $_;
a687059c 317 }
cb1a09d0 318 # else { carp "junk in $term ignored: $field"; }
a687059c 319 }
cb1a09d0 320 $self->{'_pc'} = "\0" unless defined $self->{'_pc'};
321 $self->{'_bc'} = "\b" unless defined $self->{'_bc'};
322 $self;
a687059c 323}
324
cb1a09d0 325# $terminal->Tpad($string, $cnt, $FH);
2ef86165 326
327=item B<Tpad>
328
329Outputs a literal string with appropriate padding for the current terminal.
330
331It takes three arguments:
332
333=over 2
334
335=item B<$string>
336
337The literal string to be output. If it starts with a number and an optional
338'*' then the padding will be increased by an amount relative to this number,
339if the '*' is present then this amount will me multiplied by $cnt. This part
340of $string is removed before output/
341
342=item B<$cnt>
343
344Will be used to modify the padding applied to string as described above.
345
346=item B<$FH>
347
348An optional filehandle (or IO::Handle ) that output will be printed to.
349
350=back
351
352The padded $string is returned.
353
354=cut
355
cb1a09d0 356sub Tpad { ## public
357 my $self = shift;
358 my($string, $cnt, $FH) = @_;
359 my($decr, $ms);
a687059c 360
2ef86165 361 if ($string =~ /(^[\d.]+)(\*?)(.*)$/) {
a687059c 362 $ms = $1;
748a9306 363 $ms *= $cnt if $2;
a687059c 364 $string = $3;
cb1a09d0 365 $decr = $self->{PADDING};
a687059c 366 if ($decr > .1) {
367 $ms += $decr / 2;
cb1a09d0 368 $string .= $self->{'_pc'} x ($ms / $decr);
a687059c 369 }
370 }
371 print $FH $string if $FH;
372 $string;
373}
374
cb1a09d0 375# $terminal->Tputs($cap, $cnt, $FH);
2ef86165 376
377=item B<Tputs>
378
379Output the string for the given capability padded as appropriate without
380any parameter substitution.
381
382It takes three arguments:
383
384=over 2
385
386=item B<$cap>
387
388The capability whose string is to be output.
389
390=item B<$cnt>
391
392A count passed to Tpad to modify the padding applied to the output string.
393If $cnt is zero or one then the resulting string will be cached.
394
395=item B<$FH>
396
397An optional filehandle (or IO::Handle ) that output will be printed to.
398
399=back
400
401The appropriate string for the capability will be returned.
402
403=cut
404
cb1a09d0 405sub Tputs { ## public
406 my $self = shift;
407 my($cap, $cnt, $FH) = @_;
408 my $string;
748a9306 409
2ef86165 410 $cnt = 0 unless $cnt;
411
748a9306 412 if ($cnt > 1) {
cb1a09d0 413 $string = Tpad($self, $self->{'_' . $cap}, $cnt);
748a9306 414 } else {
cb1a09d0 415 # cache result because Tpad can be slow
e14c93b3 416 unless (exists $self->{$cap}) {
417 $self->{$cap} = exists $self->{"_$cap"} ?
418 Tpad($self, $self->{"_$cap"}, 1) : undef;
419 }
420 $string = $self->{$cap};
748a9306 421 }
422 print $FH $string if $FH;
423 $string;
424}
425
cb1a09d0 426# $terminal->Tgoto($cap, $col, $row, $FH);
2ef86165 427
428=item B<Tgoto>
429
430B<Tgoto> decodes a cursor addressing string with the given parameters.
431
432There are four arguments:
433
434=over 2
435
436=item B<$cap>
437
438The name of the capability to be output.
439
440=item B<$col>
441
442The first value to be substituted in the output string ( usually the column
443in a cursor addressing capability )
444
445=item B<$row>
446
447The second value to be substituted in the output string (usually the row
448in cursor addressing capabilities)
449
450=item B<$FH>
451
452An optional filehandle (or IO::Handle ) to which the output string will be
453printed.
454
455=back
456
457Substitutions are made with $col and $row in the output string with the
458following sprintf() line formats:
459
460 %% output `%'
461 %d output value as in printf %d
462 %2 output value as in printf %2d
463 %3 output value as in printf %3d
464 %. output value as in printf %c
465 %+x add x to value, then do %.
466
467 %>xy if value > x then add y, no output
468 %r reverse order of two parameters, no output
469 %i increment by one, no output
470 %B BCD (16*(value/10)) + (value%10), no output
471
472 %n exclusive-or all parameters with 0140 (Datamedia 2500)
473 %D Reverse coding (value - 2*(value%16)), no output (Delta Data)
474
475The output string will be returned.
476
477=cut
478
cb1a09d0 479sub Tgoto { ## public
480 my $self = shift;
481 my($cap, $code, $tmp, $FH) = @_;
482 my $string = $self->{'_' . $cap};
483 my $result = '';
484 my $after = '';
485 my $online = 0;
486 my @tmp = ($tmp,$code);
487 my $cnt = $code;
748a9306 488
a687059c 489 while ($string =~ /^([^%]*)%(.)(.*)/) {
490 $result .= $1;
491 $code = $2;
492 $string = $3;
493 if ($code eq 'd') {
9f68db38 494 $result .= sprintf("%d",shift(@tmp));
a687059c 495 }
496 elsif ($code eq '.') {
9f68db38 497 $tmp = shift(@tmp);
a687059c 498 if ($tmp == 0 || $tmp == 4 || $tmp == 10) {
499 if ($online) {
cb1a09d0 500 ++$tmp, $after .= $self->{'_up'} if $self->{'_up'};
a687059c 501 }
502 else {
cb1a09d0 503 ++$tmp, $after .= $self->{'_bc'};
a687059c 504 }
505 }
506 $result .= sprintf("%c",$tmp);
507 $online = !$online;
508 }
509 elsif ($code eq '+') {
9f68db38 510 $result .= sprintf("%c",shift(@tmp)+ord($string));
a687059c 511 $string = substr($string,1,99);
512 $online = !$online;
513 }
514 elsif ($code eq 'r') {
9f68db38 515 ($code,$tmp) = @tmp;
516 @tmp = ($tmp,$code);
a687059c 517 $online = !$online;
518 }
519 elsif ($code eq '>') {
520 ($code,$tmp,$string) = unpack("CCa99",$string);
9f68db38 521 if ($tmp[$[] > $code) {
522 $tmp[$[] += $tmp;
a687059c 523 }
524 }
525 elsif ($code eq '2') {
9f68db38 526 $result .= sprintf("%02d",shift(@tmp));
a687059c 527 $online = !$online;
528 }
529 elsif ($code eq '3') {
9f68db38 530 $result .= sprintf("%03d",shift(@tmp));
a687059c 531 $online = !$online;
532 }
533 elsif ($code eq 'i') {
9f68db38 534 ($code,$tmp) = @tmp;
535 @tmp = ($code+1,$tmp+1);
a687059c 536 }
537 else {
538 return "OOPS";
539 }
540 }
cb1a09d0 541 $string = Tpad($self, $result . $string . $after, $cnt);
748a9306 542 print $FH $string if $FH;
543 $string;
544}
545
cb1a09d0 546# $terminal->Trequire(qw/ce ku kd/);
2ef86165 547
548=item B<Trequire>
549
550Takes a list of capabilities as an argument and will croak if one is not
551found.
552
553=cut
554
cb1a09d0 555sub Trequire { ## public
556 my $self = shift;
557 my($cap,@undefined);
558 foreach $cap (@_) {
559 push(@undefined, $cap)
560 unless defined $self->{'_' . $cap} && $self->{'_' . $cap};
748a9306 561 }
cb1a09d0 562 croak "Terminal does not support: (@undefined)" if @undefined;
a687059c 563}
564
5651;
2ef86165 566__END__
567
568=back
569
570=head1 EXAMPLES
571
572 use Term::Cap;
573
574 # Get terminal output speed
575 require POSIX;
576 my $termios = new POSIX::Termios;
577 $termios->getattr;
578 my $ospeed = $termios->getospeed;
579
580 # Old-style ioctl code to get ospeed:
581 # require 'ioctl.pl';
582 # ioctl(TTY,$TIOCGETP,$sgtty);
583 # ($ispeed,$ospeed) = unpack('cc',$sgtty);
584
585 # allocate and initialize a terminal structure
586 $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
587
588 # require certain capabilities to be available
589 $terminal->Trequire(qw/ce ku kd/);
590
591 # Output Routines, if $FH is undefined these just return the string
cb1a09d0 592
2ef86165 593 # Tgoto does the % expansion stuff with the given args
594 $terminal->Tgoto('cm', $col, $row, $FH);
595
596 # Tputs doesn't do any % expansion.
597 $terminal->Tputs('dl', $count = 1, $FH);
598
599=head1 COPYRIGHT AND LICENSE
600
601Please see the README file in distribution.
602
603=head1 AUTHOR
604
605This module is part of the core Perl distribution and is also maintained
606for CPAN by Jonathan Stowe <jns@gellyfish.com>.
607
608=head1 SEE ALSO
609
610termcap(5)
611
612=cut