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