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