VMS piping fixes from Charles Lane (perl -P should
[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;
2ef86165 187 eval
188 {
bf25f2b5 189 $entry = `infocmp -C 2>/dev/null`;
bf25f2b5 190 }
e66fb0c2 191 }
192
cb1a09d0 193 croak "Can't find a valid termcap file" unless @termcap_path || $entry;
194
195 $state = 1; # 0 == finished
196 # 1 == next file
197 # 2 == search again
198
199 $first = 0; # first entry (keeps term name)
200
201 $max = 32; # max :tc=...:'s
202
203 if ($entry) {
204 # ok, we're starting with $TERMCAP
205 $first++; # we're the first entry
206 # do we need to continue?
207 if ($entry =~ s/:tc=([^:]+):/:/) {
208 $tmp_term = $1;
209 # protect any pattern metacharacters in $tmp_term
210 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
211 }
212 else {
213 $state = 0; # we're already finished
214 }
215 }
216
217 # This is eval'ed inside the while loop for each file
218 $search = q{
54310121 219 while (<TERMCAP>) {
cb1a09d0 220 next if /^\\t/ || /^#/;
221 if ($_ =~ m/(^|\\|)${termpat}[:|]/o) {
222 chomp;
223 s/^[^:]*:// if $first++;
224 $state = 0;
54310121 225 while ($_ =~ s/\\\\$//) {
226 defined(my $x = <TERMCAP>) or last;
227 $_ .= $x; chomp;
228 }
cb1a09d0 229 last;
748a9306 230 }
cb1a09d0 231 }
55497cff 232 defined $entry or $entry = '';
e14c93b3 233 $entry .= $_ if $_;
cb1a09d0 234 };
748a9306 235
cb1a09d0 236 while ($state != 0) {
237 if ($state == 1) {
238 # get the next TERMCAP
239 $TERMCAP = shift @termcap_path
240 || croak "failed termcap lookup on $tmp_term";
241 }
242 else {
243 # do the same file again
244 # prevent endless recursion
245 $max-- || croak "failed termcap loop at $tmp_term";
246 $state = 1; # ok, maybe do a new file next time
247 }
248
249 open(TERMCAP,"< $TERMCAP\0") || croak "open $TERMCAP: $!";
250 eval $search;
251 die $@ if $@;
252 close TERMCAP;
253
254 # If :tc=...: found then search this file again
255 $entry =~ s/:tc=([^:]+):/:/ && ($tmp_term = $1, $state = 2);
256 # protect any pattern metacharacters in $tmp_term
257 $termpat = $tmp_term; $termpat =~ s/(\W)/\\$1/g;
a687059c 258 }
cb1a09d0 259
260 croak "Can't find $term" if $entry eq '';
261 $entry =~ s/:+\s*:+/:/g; # cleanup $entry
262 $entry =~ s/:+/:/g; # cleanup $entry
263 $self->{TERMCAP} = $entry; # save it
264 # print STDERR "DEBUG: $entry = ", $entry, "\n";
a687059c 265
748a9306 266 # Precompile $entry into the object
cb1a09d0 267 $entry =~ s/^[^:]*://;
748a9306 268 foreach $field (split(/:[\s:\\]*/,$entry)) {
cb1a09d0 269 if ($field =~ /^(\w\w)$/) {
270 $self->{'_' . $field} = 1 unless defined $self->{'_' . $1};
271 # print STDERR "DEBUG: flag $1\n";
748a9306 272 }
273 elsif ($field =~ /^(\w\w)\@/) {
cb1a09d0 274 $self->{'_' . $1} = "";
275 # print STDERR "DEBUG: unset $1\n";
a687059c 276 }
277 elsif ($field =~ /^(\w\w)#(.*)/) {
cb1a09d0 278 $self->{'_' . $1} = $2 unless defined $self->{'_' . $1};
279 # print STDERR "DEBUG: numeric $1 = $2\n";
a687059c 280 }
281 elsif ($field =~ /^(\w\w)=(.*)/) {
cb1a09d0 282 # print STDERR "DEBUG: string $1 = $2\n";
283 next if defined $self->{'_' . ($cap = $1)};
a687059c 284 $_ = $2;
285 s/\\E/\033/g;
ecfc5424 286 s/\\(\d\d\d)/pack('c',oct($1) & 0177)/eg;
a687059c 287 s/\\n/\n/g;
288 s/\\r/\r/g;
289 s/\\t/\t/g;
290 s/\\b/\b/g;
291 s/\\f/\f/g;
292 s/\\\^/\377/g;
293 s/\^\?/\177/g;
63f2c1e1 294 s/\^(.)/pack('c',ord($1) & 31)/eg;
a687059c 295 s/\\(.)/$1/g;
296 s/\377/^/g;
cb1a09d0 297 $self->{'_' . $cap} = $_;
a687059c 298 }
cb1a09d0 299 # else { carp "junk in $term ignored: $field"; }
a687059c 300 }
cb1a09d0 301 $self->{'_pc'} = "\0" unless defined $self->{'_pc'};
302 $self->{'_bc'} = "\b" unless defined $self->{'_bc'};
303 $self;
a687059c 304}
305
cb1a09d0 306# $terminal->Tpad($string, $cnt, $FH);
2ef86165 307
308=item B<Tpad>
309
310Outputs a literal string with appropriate padding for the current terminal.
311
312It takes three arguments:
313
314=over 2
315
316=item B<$string>
317
318The literal string to be output. If it starts with a number and an optional
319'*' then the padding will be increased by an amount relative to this number,
320if the '*' is present then this amount will me multiplied by $cnt. This part
321of $string is removed before output/
322
323=item B<$cnt>
324
325Will be used to modify the padding applied to string as described above.
326
327=item B<$FH>
328
329An optional filehandle (or IO::Handle ) that output will be printed to.
330
331=back
332
333The padded $string is returned.
334
335=cut
336
cb1a09d0 337sub Tpad { ## public
338 my $self = shift;
339 my($string, $cnt, $FH) = @_;
340 my($decr, $ms);
a687059c 341
2ef86165 342 if ($string =~ /(^[\d.]+)(\*?)(.*)$/) {
a687059c 343 $ms = $1;
748a9306 344 $ms *= $cnt if $2;
a687059c 345 $string = $3;
cb1a09d0 346 $decr = $self->{PADDING};
a687059c 347 if ($decr > .1) {
348 $ms += $decr / 2;
cb1a09d0 349 $string .= $self->{'_pc'} x ($ms / $decr);
a687059c 350 }
351 }
352 print $FH $string if $FH;
353 $string;
354}
355
cb1a09d0 356# $terminal->Tputs($cap, $cnt, $FH);
2ef86165 357
358=item B<Tputs>
359
360Output the string for the given capability padded as appropriate without
361any parameter substitution.
362
363It takes three arguments:
364
365=over 2
366
367=item B<$cap>
368
369The capability whose string is to be output.
370
371=item B<$cnt>
372
373A count passed to Tpad to modify the padding applied to the output string.
374If $cnt is zero or one then the resulting string will be cached.
375
376=item B<$FH>
377
378An optional filehandle (or IO::Handle ) that output will be printed to.
379
380=back
381
382The appropriate string for the capability will be returned.
383
384=cut
385
cb1a09d0 386sub Tputs { ## public
387 my $self = shift;
388 my($cap, $cnt, $FH) = @_;
389 my $string;
748a9306 390
2ef86165 391 $cnt = 0 unless $cnt;
392
748a9306 393 if ($cnt > 1) {
cb1a09d0 394 $string = Tpad($self, $self->{'_' . $cap}, $cnt);
748a9306 395 } else {
cb1a09d0 396 # cache result because Tpad can be slow
e14c93b3 397 unless (exists $self->{$cap}) {
398 $self->{$cap} = exists $self->{"_$cap"} ?
399 Tpad($self, $self->{"_$cap"}, 1) : undef;
400 }
401 $string = $self->{$cap};
748a9306 402 }
403 print $FH $string if $FH;
404 $string;
405}
406
cb1a09d0 407# $terminal->Tgoto($cap, $col, $row, $FH);
2ef86165 408
409=item B<Tgoto>
410
411B<Tgoto> decodes a cursor addressing string with the given parameters.
412
413There are four arguments:
414
415=over 2
416
417=item B<$cap>
418
419The name of the capability to be output.
420
421=item B<$col>
422
423The first value to be substituted in the output string ( usually the column
424in a cursor addressing capability )
425
426=item B<$row>
427
428The second value to be substituted in the output string (usually the row
429in cursor addressing capabilities)
430
431=item B<$FH>
432
433An optional filehandle (or IO::Handle ) to which the output string will be
434printed.
435
436=back
437
438Substitutions are made with $col and $row in the output string with the
439following sprintf() line formats:
440
441 %% output `%'
442 %d output value as in printf %d
443 %2 output value as in printf %2d
444 %3 output value as in printf %3d
445 %. output value as in printf %c
446 %+x add x to value, then do %.
447
448 %>xy if value > x then add y, no output
449 %r reverse order of two parameters, no output
450 %i increment by one, no output
451 %B BCD (16*(value/10)) + (value%10), no output
452
453 %n exclusive-or all parameters with 0140 (Datamedia 2500)
454 %D Reverse coding (value - 2*(value%16)), no output (Delta Data)
455
456The output string will be returned.
457
458=cut
459
cb1a09d0 460sub Tgoto { ## public
461 my $self = shift;
462 my($cap, $code, $tmp, $FH) = @_;
463 my $string = $self->{'_' . $cap};
464 my $result = '';
465 my $after = '';
466 my $online = 0;
467 my @tmp = ($tmp,$code);
468 my $cnt = $code;
748a9306 469
a687059c 470 while ($string =~ /^([^%]*)%(.)(.*)/) {
471 $result .= $1;
472 $code = $2;
473 $string = $3;
474 if ($code eq 'd') {
9f68db38 475 $result .= sprintf("%d",shift(@tmp));
a687059c 476 }
477 elsif ($code eq '.') {
9f68db38 478 $tmp = shift(@tmp);
a687059c 479 if ($tmp == 0 || $tmp == 4 || $tmp == 10) {
480 if ($online) {
cb1a09d0 481 ++$tmp, $after .= $self->{'_up'} if $self->{'_up'};
a687059c 482 }
483 else {
cb1a09d0 484 ++$tmp, $after .= $self->{'_bc'};
a687059c 485 }
486 }
487 $result .= sprintf("%c",$tmp);
488 $online = !$online;
489 }
490 elsif ($code eq '+') {
9f68db38 491 $result .= sprintf("%c",shift(@tmp)+ord($string));
a687059c 492 $string = substr($string,1,99);
493 $online = !$online;
494 }
495 elsif ($code eq 'r') {
9f68db38 496 ($code,$tmp) = @tmp;
497 @tmp = ($tmp,$code);
a687059c 498 $online = !$online;
499 }
500 elsif ($code eq '>') {
501 ($code,$tmp,$string) = unpack("CCa99",$string);
9f68db38 502 if ($tmp[$[] > $code) {
503 $tmp[$[] += $tmp;
a687059c 504 }
505 }
506 elsif ($code eq '2') {
9f68db38 507 $result .= sprintf("%02d",shift(@tmp));
a687059c 508 $online = !$online;
509 }
510 elsif ($code eq '3') {
9f68db38 511 $result .= sprintf("%03d",shift(@tmp));
a687059c 512 $online = !$online;
513 }
514 elsif ($code eq 'i') {
9f68db38 515 ($code,$tmp) = @tmp;
516 @tmp = ($code+1,$tmp+1);
a687059c 517 }
518 else {
519 return "OOPS";
520 }
521 }
cb1a09d0 522 $string = Tpad($self, $result . $string . $after, $cnt);
748a9306 523 print $FH $string if $FH;
524 $string;
525}
526
cb1a09d0 527# $terminal->Trequire(qw/ce ku kd/);
2ef86165 528
529=item B<Trequire>
530
531Takes a list of capabilities as an argument and will croak if one is not
532found.
533
534=cut
535
cb1a09d0 536sub Trequire { ## public
537 my $self = shift;
538 my($cap,@undefined);
539 foreach $cap (@_) {
540 push(@undefined, $cap)
541 unless defined $self->{'_' . $cap} && $self->{'_' . $cap};
748a9306 542 }
cb1a09d0 543 croak "Terminal does not support: (@undefined)" if @undefined;
a687059c 544}
545
5461;
2ef86165 547__END__
548
549=back
550
551=head1 EXAMPLES
552
553 use Term::Cap;
554
555 # Get terminal output speed
556 require POSIX;
557 my $termios = new POSIX::Termios;
558 $termios->getattr;
559 my $ospeed = $termios->getospeed;
560
561 # Old-style ioctl code to get ospeed:
562 # require 'ioctl.pl';
563 # ioctl(TTY,$TIOCGETP,$sgtty);
564 # ($ispeed,$ospeed) = unpack('cc',$sgtty);
565
566 # allocate and initialize a terminal structure
567 $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
568
569 # require certain capabilities to be available
570 $terminal->Trequire(qw/ce ku kd/);
571
572 # Output Routines, if $FH is undefined these just return the string
cb1a09d0 573
2ef86165 574 # Tgoto does the % expansion stuff with the given args
575 $terminal->Tgoto('cm', $col, $row, $FH);
576
577 # Tputs doesn't do any % expansion.
578 $terminal->Tputs('dl', $count = 1, $FH);
579
580=head1 COPYRIGHT AND LICENSE
581
582Please see the README file in distribution.
583
584=head1 AUTHOR
585
586This module is part of the core Perl distribution and is also maintained
587for CPAN by Jonathan Stowe <jns@gellyfish.com>.
588
589=head1 SEE ALSO
590
591termcap(5)
592
593=cut