On Fri, Oct 20, 2023 at 11:52:25AM +0200, Marc Espie wrote:
> I guess i will probably leave it alone after this.
> This does quite a few things compared to my former patches.
>
> - totally get rid of eval, it doen't make sense anymore
> - declare variables before they get used, which tends to
> simplify things.
> - change quaint formatting to something more BSD like
> - update documentation to new style of doing OO
> - use defined logic on entry and such
> - always try to run infocmp as a last resort, even if
> we have a path.
> - run infocmp with the best options we have to get a good termcap
> - use \Q\E, which gets rid of termpat entirely
> - dedup the path along the way: for us, /etc/termcap
> and /usr/share/misc/termcap are the same.
> - redo recursion logic by just recording which term values we
> already saw, the max=32 value overflow was absurd, proper parsing
> yields roughly 10 or so tc redirections for xterm, not >32.
I eventually got an occation to test it. Except for the extra debug
print that sneaked. it works well for my use case. (pkg_* tools on a
terminal application from ports that ships its own terminfo entry).
ok matthieu@ with the print removed (see inline).
>
> Index: Cap.pm
> ===================================================================
> RCS file: /cvs/src/gnu/usr.bin/perl/cpan/Term-Cap/Cap.pm,v
> retrieving revision 1.3
> diff -u -p -r1.3 Cap.pm
> --- Cap.pm 18 Oct 2023 01:49:26 -0000 1.3
> +++ Cap.pm 20 Oct 2023 09:47:05 -0000
> @@ -16,8 +16,8 @@ sub croak
>
> use strict;
>
> +use v5.16;
> use vars qw($VERSION $VMS_TERMCAP);
> -use vars qw($termpat $state $first $entry);
>
> $VERSION = '1.17';
>
> @@ -33,7 +33,7 @@ Term::Cap - Perl termcap interface
> =head1 SYNOPSIS
>
> require Term::Cap;
> - $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
> + $terminal = Term::Cap->Tgetent({ TERM => undef, OSPEED => $ospeed });
> $terminal->Trequire(qw/ce ku kd/);
> $terminal->Tgoto('cm', $col, $row, $FH);
> $terminal->Tputs('dl', $count, $FH);
> @@ -75,10 +75,10 @@ if ( $^O eq 'VMS' )
>
> sub termcap_path
> { ## private
> - my @termcap_path;
> + my @l;
>
> # $TERMCAP, if it's a filespec
> - push( @termcap_path, $ENV{TERMCAP} )
> + push(@l, $ENV{TERMCAP})
> if (
> ( exists $ENV{TERMCAP} )
> && (
> @@ -87,23 +87,27 @@ sub termcap_path
> : $ENV{TERMCAP} =~ /^\//s
> )
> );
> - if ( ( exists $ENV{TERMPATH} ) && ( $ENV{TERMPATH} ) )
> - {
> -
> + if (exists $ENV{TERMPATH} && $ENV{TERMPATH}) {
> # Add the users $TERMPATH
> - push( @termcap_path, split( /(:|\s+)/, $ENV{TERMPATH} ) );
> - }
> - else
> - {
> -
> + push(@l, split( /(:|\s+)/, $ENV{TERMPATH}));
> + } else {
> # Defaults
> - push( @termcap_path,
> - exists $ENV{'HOME'} ? $ENV{'HOME'} . '/.termcap' : undef,
> - '/etc/termcap', '/usr/share/misc/termcap', );
> + if (exists $ENV{HOME}) {
> + push(@l, $ENV{HOME}.'/.termcap');
> + }
> + push(@l, '/etc/termcap', '/usr/share/misc/termcap', );
> + }
> + my @termcap_path;
> + my $seen = {};
> + for my $i (@l) {
> + next unless -f $i;
> + my $k = join(',', (stat _)[0,1]);
> + next if $seen->{$k};
> + push(@termcap_path, $i);
> + $seen->{$k} = 1;
> }
>
> - # return the list of those termcaps that exist
> - return grep { defined $_ && -f $_ } @termcap_path;
> + return @termcap_path;
> }
>
> =over 4
> @@ -164,195 +168,158 @@ It calls C<croak> on failure.
>
> sub Tgetent
> { ## public -- static method
> - my $class = shift;
> - my ($self) = @_;
> + my ($class, $self) = @_;
>
> $self = {} unless defined $self;
> bless $self, $class;
>
> - my ( $term, $cap, $search, $field, $max, $tmp_term, $TERMCAP );
> - local ( $termpat, $state, $first, $entry ); # used inside eval
> + my ($cap, $field);
> +
> local $_;
>
> # Compute PADDING factor from OSPEED (to be used by Tpad)
> - if ( !$self->{OSPEED} )
> - {
> - if ($^W)
> - {
> + if (!$self->{OSPEED}) {
> + if ($^W) {
> carp "OSPEED was not set, defaulting to 9600";
> }
> $self->{OSPEED} = 9600;
> }
> - if ( $self->{OSPEED} < 16 )
> - {
> -
> + if ($self->{OSPEED} < 16) {
> # delays for old style speeds
> 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
> );
> $self->{PADDING} = $pad[ $self->{OSPEED} ];
> - }
> - else
> - {
> + } else {
> $self->{PADDING} = 10000 / $self->{OSPEED};
> }
>
> - unless ( $self->{TERM} )
> - {
> - if ( $ENV{TERM} )
> - {
> - $self->{TERM} = $ENV{TERM} ;
> - }
> - else
> - {
> - if ( $^O eq 'MSWin32' )
> - {
> + unless ($self->{TERM}) {
> + if ($ENV{TERM}) {
> + $self->{TERM} = $ENV{TERM} ;
> + } else {
> + if ( $^O eq 'MSWin32' ) {
> $self->{TERM} = 'dumb';
> - }
> - else
> - {
> + } else {
> croak "TERM not set";
> }
> }
> }
>
> - $term = $self->{TERM}; # $term is the term type we are looking for
> + my $term = $self->{TERM}; # $term is the term type we are looking for
>
> # $tmp_term is always the next term (possibly :tc=...:) we are looking
> for
> - $tmp_term = $self->{TERM};
> + my $tmp_term = $term;
>
> - # protect any pattern metacharacters in $tmp_term
> - $termpat = $tmp_term;
> - $termpat =~ s/(\W)/\\$1/g;
> -
> - my $foo = ( exists $ENV{TERMCAP} ? $ENV{TERMCAP} : '' );
> -
> - # $entry is the extracted termcap entry
> - if ( ( $foo !~ m:^/:s ) && ( $foo =~ m/(^|\|)${termpat}[:|]/s ) )
> - {
> - $entry = $foo;
> + my $foo = (exists $ENV{TERMCAP} ? $ENV{TERMCAP} : '' );
> +
> + my $seen = {};
> + my $entry;
> + if (exists $ENV{TERMCAP}) {
> + $_ = $ENV{TERMCAP};
> + if ( !m:^/:s && m/(^|\|)\Q$tmp_term\E[:|]/s) {
> + # $entry is the extracted termcap entry
> + $entry = $_;
> + $seen->{$tmp_term} = 1;
> + }
> }
>
> my @termcap_path = termcap_path();
> + print "TEMCAP_PATH", join(' ', @termcap_path), "\n";
Remove this ^^
>
> - if ( !@termcap_path && !$entry )
> - {
> -
> - # last resort--fake up a termcap from terminfo
> - local $ENV{TERM} = $term;
> -
> - if ( $^O eq 'VMS' )
> - {
> + if (!@termcap_path && !$entry) {
> + if ( $^O eq 'VMS' ) {
> $entry = $VMS_TERMCAP;
> - }
> - else
> - {
> - if ( grep { -x "$_/infocmp" } split /:/, $ENV{PATH} )
> - {
> - eval {
> - my $tmp = `infocmp -C 2>/dev/null`;
> - $tmp =~ s/^#.*\n//gm; # remove comments
> - if ( ( $tmp !~ m%^/%s )
> - && ( $tmp =~ /(^|\|)${termpat}[:|]/s ) )
> - {
> - $entry = $tmp;
> - }
> - };
> - warn "Can't run infocmp to get a termcap entry: $@" if $@;
> - }
> - else
> - {
> - # this is getting desperate now
> - if ( $self->{TERM} eq 'dumb' )
> - {
> - $entry = 'dumb|80-column dumb
> tty::am::co#80::bl=^G:cr=^M:do=^J:sf=^J:';
> - }
> - }
> - }
> + }
> }
>
> - croak "Can't find a valid termcap file" unless @termcap_path || $entry;
> -
> - $state = 1; # 0 == finished
> + my $state = 1; # 0 == finished
> # 1 == next file
> # 2 == search again
> + # 3 == infocmp
>
> - $first = 0; # first entry (keeps term name)
> -
> - $max = 64; # max :tc=...:'s
> -
> - if ($entry)
> - {
> + my $first = 0; # first entry (keeps term name)
>
> + if (defined $entry) {
> # ok, we're starting with $TERMCAP
> $first++; # we're the first entry
> # do we need to continue?
> - if ( $entry =~ s/:tc=([^:]+):/:/ )
> - {
> + if ($entry =~ s/:tc=([^:]+):/:/ ) {
> $tmp_term = $1;
> -
> - # protect any pattern metacharacters in $tmp_term
> - $termpat = $tmp_term;
> - $termpat =~ s/(\W)/\\$1/g;
> - }
> - else
> - {
> + } else {
> $state = 0; # we're already finished
> }
> }
>
> - # This is eval'ed inside the while loop for each file
> - $search = q{
> - while (<TERMCAP>) {
> - next if /^\\t/ || /^#/;
> - if ($_ =~ m/(^|\\|)${termpat}[:|]/o) {
> + my $TERMCAP;
> + while ($state != 0) {
> + if ($state == 1) {
> + # get the next TERMCAP or get ready for infocmp
> + $TERMCAP = shift @termcap_path or $state = 3;
> + } elsif ($state == 3) {
> + croak "failed termcap lookup on $tmp_term";
> + } else {
> + # do the same file again
> + # prevent endless recursion
> + $state = 1; # ok, maybe do a new file next time
> + }
> +
> + my ($fh, $child);
> + if ($state == 3) {
> + my $child = open($fh, "-|");
> + # TODO this breaks on !UNIX
> + # not do anything, or let it break here
> + croak "cannot fork: $!" if !defined $child;
> + if (!$child) {
> + open(STDERR, ">", "/dev/null");
> + system('infocmp', '-CTr', $tmp_term);
> + exit(1);
> + }
> + } else {
> + open($fh, "<", $TERMCAP ) || croak "open $TERMCAP: $!";
> + }
> + undef $_;
> + while (<$fh>) {
> + next if /^\t/ || /^#/;
> + if (m/(^|\|)\Q$tmp_term\E[:|]/) {
> chomp;
> s/^[^:]*:// if $first++;
> $state = 0;
> - while ($_ =~ s/\\\\$//) {
> - defined(my $x = <TERMCAP>) or last;
> + $seen->{$tmp_term} = 1;
> + while (s/\\$//) {
> + defined(my $x = <$fh>) or last;
> $_ .= $x; chomp;
> }
> + if (defined $entry) {
> + $entry .= $_;
> + } else {
> + $entry = $_;
> + }
> last;
> }
> }
> - defined $entry or $entry = '';
> - $entry .= $_ if $_;
> - };
> -
> - while ( $state != 0 )
> - {
> - if ( $state == 1 )
> - {
> -
> - # get the next TERMCAP
> - $TERMCAP = shift @termcap_path
> - || croak "failed termcap lookup on $tmp_term";
> - }
> - else
> - {
> -
> - # do the same file again
> - # prevent endless recursion
> - $max-- || croak "failed termcap loop at $tmp_term";
> - $state = 1; # ok, maybe do a new file next time
> - }
> -
> - open( TERMCAP, "< $TERMCAP\0" ) || croak "open $TERMCAP: $!";
> - eval $search;
> - die $@ if $@;
> - close TERMCAP;
> + close($fh);
> + waitpid($child, 0) if defined $child;
> + next if !defined $entry;
>
> # If :tc=...: found then search this file again
> - $entry =~ s/:tc=([^:]+):/:/ && ( $tmp_term = $1, $state = 2 );
> -
> - # protect any pattern metacharacters in $tmp_term
> - $termpat = $tmp_term;
> - $termpat =~ s/(\W)/\\$1/g;
> + while ($entry =~ s/:tc=([^:]+):/:/) {
> + $tmp_term = $1;
> + if ($seen->{$tmp_term}) {
> + next;
> + }
> + $state = 2;
> + last;
> + }
> }
>
> - croak "Can't find $term" if $entry eq '';
> + if (!defined $entry) {
> + if ($self->{TERM} eq 'dumb') {
> + $entry = 'dumb|80-column dumb
> tty::am::co#80::bl=^G:cr=^M:do=^J:sf=^J:';
> + }
> + }
> + croak "Can't find $term" if !defined $entry;
> $entry =~ s/:+\s*:+/:/g; # cleanup $entry
> $entry =~ s/:+/:/g; # cleanup $entry
> $self->{TERMCAP} = $entry; # save it
>
--
Matthieu Herrb