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