File Coverage

blib/lib/MarpaX/Database/Terminfo/Interface.pm
Criterion Covered Total %
statement 463 637 72.6
branch 186 372 50.0
condition 64 149 42.9
subroutine 44 49 89.8
pod 18 18 100.0
total 775 1225 63.2


line stmt bran cond sub pod time code
1 16     16   3326582 use strict;
  16         29  
  16         521  
2 16     16   63 use warnings FATAL => 'all';
  16         22  
  16         897  
3              
4             package MarpaX::Database::Terminfo::Interface;
5 16     16   5550 use MarpaX::Database::Terminfo;
  16         38  
  16         449  
6 16     16   7353 use MarpaX::Database::Terminfo::String;
  16         31  
  16         428  
7 16     16   75 use MarpaX::Database::Terminfo::Constants qw/:all/;
  16         24  
  16         2062  
8 16     16   74 use File::ShareDir qw/:ALL/;
  16         20  
  16         1899  
9 16     16   68 use Carp qw/carp croak/;
  16         19  
  16         744  
10 16     16   65 use Sereal::Decoder 3.015 qw/decode_sereal/;
  16         410  
  16         891  
11 16     16   8957 use Time::HiRes qw/usleep/;
  16         18156  
  16         56  
12 16     16   2461 use Log::Any qw/$log/;
  16         19  
  16         85  
13 16     16   1705 use constant BAUDBYTE => 9; # From GNU Ncurses: 9 = 7 bits + 1 parity + 1 stop
  16         20  
  16         14053  
14 16     16   10027 our $HAVE_POSIX = eval "use POSIX; 1;" || 0;
  16         78576  
  16         84  
15              
16             # ABSTRACT: Terminfo interface
17              
18             our $VERSION = '0.012'; # VERSION
19              
20              
21             sub new {
22 16     16 1 2055080 my ($class, $optp) = @_;
23              
24 16   100     125 $optp //= {};
25              
26 16 50       89 if (ref($optp) ne 'HASH') {
27 0         0 croak 'Options must be a reference to a HASH';
28             }
29              
30 16   33     174 my $file = $optp->{file} // $ENV{MARPAX_DATABASE_TERMINFO_FILE} // '';
      50        
31 16   33     126 my $txt = $optp->{txt} // $ENV{MARPAX_DATABASE_TERMINFO_TXT} // '';
      50        
32 16   33     184 my $bin = $optp->{bin} // $ENV{MARPAX_DATABASE_TERMINFO_BIN} // dist_file('MarpaX-Database-Terminfo', 'share/ncurses-terminfo.sereal');
      33        
33             my $caps = $optp->{caps} // $ENV{MARPAX_DATABASE_TERMINFO_CAPS} // (
34 16 50 33     2815 $^O eq 'aix' ? dist_file('MarpaX-Database-Terminfo', 'share/ncurses-Caps.aix4') :
    50 33        
    50          
35             $^O eq 'hpux' ? dist_file('MarpaX-Database-Terminfo', 'share/ncurses-Caps.hpux11') :
36             $^O eq 'dec_osf' ? dist_file('MarpaX-Database-Terminfo', 'share/ncurses-Caps.osf1r5') :
37             dist_file('MarpaX-Database-Terminfo', 'share/ncurses-Caps'));
38              
39 16   33     1331 my $cache_stubs_as_txt = $optp->{cache_stubs_as_txt} // $ENV{MARPAX_DATABASE_TERMINFO_CACHE_STUBS_AS_TXT} // 1;
      50        
40 16   33     124 my $cache_stubs = $optp->{cache_stubs} // $ENV{MARPAX_DATABASE_TERMINFO_CACHE_STUBS} // 1;
      50        
41 16         29 my $stubs_txt;
42             my $stubs_bin;
43 16 50       48 if ($cache_stubs) {
44 16   33     190 $stubs_txt = $optp->{stubs_txt} // $ENV{MARPAX_DATABASE_TERMINFO_STUBS_TXT} // '';
      100        
45 16   33     259 $stubs_bin = $optp->{stubs_bin} // $ENV{MARPAX_DATABASE_TERMINFO_STUBS_BIN} // dist_file('MarpaX-Database-Terminfo', 'share/ncurses-terminfo-stubs.sereal');
      33        
46             } else {
47 0         0 $stubs_txt = '';
48 0         0 $stubs_bin = '';
49             }
50 16   33     1423 my $bsd_tputs = $optp->{bsd_tputs} // $ENV{MARPAX_DATABASE_TERMINFO_BSD_TPUTS} // 0;
      50        
51 16   66     103 my $use_env = $optp->{use_env } // $ENV{MARPAX_DATABASE_TERMINFO_USE_ENV} // 1;
      50        
52              
53             # -------------
54             # Load Database
55             # -------------
56 16         32 my $db = undef;
57 16         24 my $db_ok = 0;
58 16 50       51 if ($file) {
59 0         0 my $fh;
60 0 0       0 if ($log->is_debug) {
61 0         0 $log->debugf('Loading %s', $file);
62             }
63 0 0       0 if (! open($fh, '<', $file)) {
64 0         0 carp "Cannot open $file, $!";
65             } else {
66 0         0 my $content = do {local $/; <$fh>;};
  0         0  
  0         0  
67 0 0       0 close($fh) || carp "Cannot close $file, $!";
68 0 0       0 if ($log->is_debug) {
69 0         0 $log->debugf('Parsing %s', $file);
70             }
71 0         0 eval {$db = MarpaX::Database::Terminfo->new()->parse(\$content)->value()};
  0         0  
72 0 0       0 if ($@) {
73 0         0 carp $@;
74             } else {
75 0         0 $db_ok = 1;
76             }
77             }
78             }
79 16 50 33     91 if (! $db_ok && $txt) {
80 0 0       0 if ($log->is_debug) {
81 0         0 $log->debugf('Parsing txt');
82             }
83 0         0 eval {$db = MarpaX::Database::Terminfo->new()->parse(\$txt)->value()};
  0         0  
84 0 0       0 if ($@) {
85 0         0 carp $@;
86             } else {
87 0         0 $db_ok = 1;
88             }
89             }
90 16 50       100 $db_ok = _load_sereal($bin, $db) unless $db_ok;
91 16 50       69 if (! $db_ok) {
92 0         0 croak 'Cannot get a valid terminfo database';
93             }
94             # -----------------------
95             # Load terminfo<->termcap
96             # -----------------------
97 16         81 my %t2other = ();
98 16         36 my %c2other = ();
99 16         40 my %capalias = ();
100 16         37 my %infoalias = ();
101             {
102 16 50       36 if ($log->is_debug) {
  16         188  
103 0         0 $log->debugf('Loading %s', $caps);
104             }
105 16         317 my $fh;
106 16 50       1418 if (! open($fh, '<', $caps)) {
107 0         0 carp "Cannot open $caps, $!";
108             } else {
109             #
110             # Get translations
111             #
112 16         35 my $line = 0;
113 16         568 while (defined($_ = <$fh>)) {
114 20160         13956 ++$line;
115 20160 100       34369 if (/^\s*#/) {
116 11408         22502 next;
117             }
118 8752         59168 s/\s*$//;
119 8752 100       17093 if (/^\s*capalias\b/) {
    100          
120 704         3835 my ($capalias, $alias, $name, $set, $description) = split(/\s+/, $_, 5);
121 704         4178 $capalias{$alias} = {name => $name, set => $set, description => $description};
122             } elsif (/^\s*infoalias\b/) {
123 96         518 my ($infoalias, $alias, $name, $set, $description) = split(/\s+/, $_, 5);
124 96         576 $infoalias{$alias} = {name => $name, set => $set, description => $description};
125             } else {
126 7952         64261 my ($variable, $feature, $type, $termcap, $keyname, $keyvalue, $translation, $description) = split(/\s+/, $_, 8);
127 7952 100       27694 if ($type eq 'bool') {
    100          
    50          
128 704         707 $type = TERMINFO_BOOLEAN;
129             } elsif ($type eq 'num') {
130 624         639 $type = TERMINFO_NUMERIC;
131             } elsif ($type eq 'str') {
132 6624         6281 $type = TERMINFO_STRING;
133             } else {
134 0         0 $log->warnf('%s(%d): wrong type \'%s\'', $caps, $line, $type); exit;
  0         0  
135 0         0 next;
136             }
137 7952         20207 $t2other{$feature} = {type => $type, termcap => $termcap, variable => $variable};
138 7952         43175 $c2other{$termcap} = {type => $type, feature => $feature, variable => $variable};
139             }
140             }
141 16 50       673 close($fh) || carp "Cannot close $caps, $!";
142             }
143             }
144             # -----------------
145             # Load stubs as txt
146             # -----------------
147 16         42 my $cached_stubs_as_txt = {};
148 16         31 my $cached_stubs_as_txt_ok = 0;
149 16 50       67 if ($cache_stubs) {
150 16 100       62 if ($stubs_txt) {
151 1         1 my $fh;
152 1 50       5 if ($log->is_debug) {
153 0         0 $log->debugf('Loading %s', $stubs_txt);
154             }
155 1 50       38 if (! open($fh, '<', $stubs_txt)) {
156 0         0 carp "Cannot open $stubs_txt, $!";
157             } else {
158 1         2 my $content = do {local $/; <$fh>;};
  1         7  
  1         1327  
159 1 50       11 close($fh) || carp "Cannot close $stubs_txt, $!";
160 1 50       5 if ($log->is_debug) {
161 0         0 $log->debugf('Evaluating %s', $stubs_txt);
162             }
163             {
164             #
165             # Because Data::Dumper have $VARxxx
166             #
167 16     16   81 no strict 'vars';
  16         22  
  16         68886  
  1         7  
168             #
169             # Untaint data
170             #
171 1         1327 my ($untainted) = $content =~ m/(.*)/s;
172 1         79937 $cached_stubs_as_txt = eval $untainted; ## no critic
173 1 50       14 if ($@) {
174 0         0 carp "$stubs_txt: $@";
175             } else {
176 1         9 $cached_stubs_as_txt_ok = 1;
177             }
178             }
179             }
180             }
181 16 100 66     144 if (! $cached_stubs_as_txt_ok && $stubs_bin) {
182 15         78 $cached_stubs_as_txt_ok = _load_sereal($stubs_bin, $cached_stubs_as_txt);
183             }
184             }
185              
186             my $self = {
187             _terminfo_db => $db,
188             _terminfo_current => undef,
189             _t2other => \%t2other,
190             _c2other => \%c2other,
191             _capalias => \%capalias,
192             _infoalias => \%infoalias,
193             _stubs => {},
194             _cache_stubs => $cache_stubs,
195             _cached_stubs => {},
196             _cache_stubs_as_txt => $cache_stubs_as_txt,
197             _cached_stubs_as_txt => $cached_stubs_as_txt,
198 16     1   336 _flush => [ sub {} ],
199             _bsd_tputs => $bsd_tputs,
200             _term => undef, # Current terminal
201             _use_env => $use_env,
202             };
203              
204 16         120 bless($self, $class);
205              
206             #
207             # Initialize
208             #
209 16         80 $self->_terminfo_init();
210              
211 16         161 return $self;
212             }
213              
214             sub _load_sereal {
215 31     31   66 my $bin = shift; # Output is on the stack at $_[0]
216 31         52 my $rc = 0;
217              
218 31         46 my $fh;
219 31 50       253 if ($log->is_debug) {
220 0         0 $log->debugf('Loading %s', $bin);
221             }
222 31 50       2435 if (! open($fh, '<', $bin)) {
223 0         0 carp "Cannot open $bin, $!";
224             } else {
225 31 50       144 if (! binmode $fh) {
226 0         0 carp "Cannot binmode $bin, $!";
227             } else {
228 31         273 my @stat = stat($fh);
229 31 50       96 if (! @stat) {
230 0         0 carp "Cannot stat $bin, $!";
231             } else {
232 31         57 my $bytes = $stat[7];
233 31         40 my $blob;
234 31 50       36852 if (read($fh, $blob, $bytes) != $bytes) {
235 0         0 carp "Cannot read $bytes bytes from $bin, $!";
236             } else {
237 31         533 my $decoder = Sereal::Decoder->new();
238 31 50       94 eval {
239 31         678065 $decoder->decode($blob, $_[0]);
240 31         888 $rc = 1;
241             } || carp "Cannot deserialize $bin, $@";
242             }
243             }
244             }
245 31 50       1312 close($fh) || carp "Cannot close $bin, $!";
246             }
247              
248 31         305 return $rc;
249             }
250              
251              
252             sub _terminfo_db {
253 7817     7817   8014 my ($self) = (@_);
254 7817 50 33     17714 if ($log->is_warn && ! defined($self->{_terminfo_db})) {
255 0         0 $log->warnf('Undefined database');
256             }
257 7817         50003 return $self->{_terminfo_db};
258             }
259              
260              
261             sub _terminfo_current {
262 5549     5549   7418 my $self = shift;
263 5549 100       10416 if (@_) {
264 2675         5006 $self->{_terminfo_current} = shift;
265             }
266 5549 50 33     10152 if ($log->is_warn && ! defined($self->{_terminfo_current})) {
267 0         0 $log->warnf('Undefined current terminfo entry');
268             }
269 5549         49969 return $self->{_terminfo_current};
270             }
271              
272              
273             sub _t2other {
274 819324     819324   569167 my ($self) = (@_);
275 819324 50 33     1011964 if ($log->is_warn && ! defined($self->{_t2other})) {
276 0         0 $log->warnf('Undefined terminfo->termcap translation hash');
277             }
278 819324         3997944 return $self->{_t2other};
279             }
280              
281              
282             sub _c2other {
283 0     0   0 my ($self) = (@_);
284 0 0 0     0 if ($log->is_warn && ! defined($self->{_c2other})) {
285 0         0 $log->warnf('Undefined terminfo->termcap translation hash');
286             }
287 0         0 return $self->{_c2other};
288             }
289              
290              
291             sub _capalias {
292 0     0   0 my ($self) = (@_);
293 0 0 0     0 if ($log->is_warn && ! defined($self->{_capalias})) {
294 0         0 $log->warnf('Undefined terminfo->termcap translation hash');
295             }
296 0         0 return $self->{_capalias};
297             }
298              
299              
300             sub _infoalias {
301 0     0   0 my ($self) = (@_);
302 0 0 0     0 if ($log->is_warn && ! defined($self->{_infoalias})) {
303 0         0 $log->warnf('Undefined terminfo->termcap translation hash');
304             }
305 0         0 return $self->{_infoalias};
306             }
307              
308              
309             sub _terminfo_init {
310 61     61   135 my ($self) = (@_);
311 61 100       299 if (! defined($self->{_terminfo_current})) {
312 16   50     130 $self->tgetent($ENV{TERM} || 'unknown');
313             }
314 61         135 return defined($self->_terminfo_current);
315             }
316              
317              
318             sub flush {
319 1     1 1 2 my ($self, $cb, @args) = @_;
320 1 50       4 if (defined($cb)) {
321 0         0 $self->{_flush} = [ $cb, @args ];
322             }
323 1         3 return $self->{_flush};
324             }
325              
326              
327             sub _find {
328 5141     5141   10239 my ($self, $name, $from) = @_;
329              
330 5141         6617 my $rc = undef;
331 5141   100     16433 $from //= '';
332              
333 5141 50       13911 if ($log->is_debug) {
334 0 0       0 if ($from) {
335 0         0 $log->debugf('Loading %s -> %s', $from, $name);
336             } else {
337 0         0 $log->debugf('Loading %s', $name);
338             }
339             }
340              
341 5141         30024 my $terminfo_db = $self->_terminfo_db;
342 5141 50       11788 if (defined($terminfo_db)) {
343 5141         4485 foreach (@{$terminfo_db}) {
  5141         9564  
344 3542482         2264422 my $terminfo = $_;
345              
346 3542482 100       2073938 if (grep {$_ eq $name} @{$terminfo->{alias}}) {
  5459044         8882561  
  3542482         4374941  
347 5141 50       25390 if ($log->is_trace) {
348 0         0 $log->tracef('Found alias \'%s\' in terminfo with aliases %s longname \'%s\'', $name, $terminfo->{alias}, $terminfo->{longname});
349             }
350 5141         41618 $rc = $terminfo;
351 5141         10040 last;
352             }
353             }
354             }
355 5141         13725 return $rc;
356             }
357              
358             sub tgetent {
359 2675     2675 1 1816482 my ($self, $name, $fh) = (@_);
360              
361 2675 50       8795 if (! defined($self->_terminfo_db)) {
362 0         0 return -1;
363             }
364 2675         7103 my $found = $self->_find($name);
365 2675 50       7558 if (! defined($found)) {
366 0         0 return 0;
367             }
368             #
369             # Process cancellations and use=
370             #
371 2675         6858 my %cancelled = ();
372             {
373 2675         2748 my %featured = ();
  2675         5173  
374 2675         3149 my $i = 0;
375 2675         3799 while ($i <= $#{$found->{feature}}) {
  214469         336972  
376 211794         177951 my $feature = $found->{feature}->[$i];
377 211794 100 100     960969 if ($feature->{type} == TERMINFO_BOOLEAN && substr($feature->{name}, -1, 1) eq '@') {
    100 100        
378 1051         2223 my $cancelled = $feature->{name};
379 1051         1723 substr($cancelled, -1, 1, '');
380 1051         2685 $cancelled{$cancelled} = 1;
381 1051 50       2029 if ($log->is_trace) {
382 0         0 $log->tracef('[Loading %s] New cancellation %s', $name, $cancelled);
383             }
384 1051         5707 ++$i;
385             } elsif ($feature->{type} == TERMINFO_STRING && $feature->{name} eq 'use') {
386 2466 50       5094 if ($log->is_trace) {
387 0         0 $log->tracef('[Loading %s] use=\'%s\' with cancellations %s', $name, $feature->{value}, [ keys %cancelled ]);
388             }
389 2466         14366 my $insert = $self->_find($feature->{value}, $name);
390 2466 50       5849 if (! defined($insert)) {
391 0         0 return 0;
392             }
393 2466         4063 my @keep = ();
394 2466         2567 foreach (@{$insert->{feature}}) {
  2466         6818  
395 107761 100       171393 if (exists($cancelled{$_->{name}})) {
396 910 50       1762 if ($log->is_trace) {
397 0         0 $log->tracef('[Loading %s] Skipping cancelled feature \'%s\' from terminfo with aliases %s longname \'%s\'', $name, $_->{name}, $insert->{alias}, $insert->{longname});
398             }
399 910         3955 next;
400             }
401 106851 100       161548 if (exists($featured{$_->{name}})) {
402 6713 50       9840 if ($log->is_trace) {
403 0         0 $log->tracef('[Loading %s] Skipping overwriting feature \'%s\' from terminfo with aliases %s longname \'%s\'', $name, $_->{name}, $insert->{alias}, $insert->{longname});
404             }
405 6713         27245 next;
406             }
407 100138 50       132961 if ($log->is_trace) {
408 0         0 $log->tracef('[Loading %s] Pushing feature %s from terminfo with aliases %s longname \'%s\'', $name, $_, $insert->{alias}, $insert->{longname});
409             }
410 100138         385310 push(@keep, $_);
411             }
412 2466         3187 splice(@{$found->{feature}}, $i, 1, @keep);
  2466         23013  
413             } else {
414 208277 50       316564 if ($log->is_trace) {
415 0         0 $log->tracef('[Loading %s] New feature %s', $name, $feature);
416             }
417 208277         949547 $featured{$feature->{name}} = 1;
418 208277         202271 ++$i;
419             }
420             }
421             }
422             #
423             # Remember cancelled things
424             #
425 2675         9384 $found->{cancelled} = \%cancelled;
426             #
427             # Drop needless cancellations
428             #
429             {
430 2675         4247 my $i = $#{$found->{feature}};
  2675         5563  
431 2675         5665 foreach (reverse @{$found->{feature}}) {
  2675         6655  
432 209328 100 100     351661 if ($_->{type} == TERMINFO_BOOLEAN && substr($_->{name}, -1, 1) eq '@') {
433 1051 50       2130 if ($log->is_trace) {
434 0         0 $log->tracef('[Loading %s] Dropping cancellation \'%s\' from terminfo', $name, $found->{feature}->[$i]->{name});
435             }
436 1051         4888 splice(@{$found->{feature}}, $i, 1);
  1051         1690  
437             }
438 209328         180474 --$i;
439             }
440             }
441             #
442             # Drop commented features
443             #
444             {
445 2675         4729 my $i = $#{$found->{feature}};
  2675         3570  
  2675         4232  
  2675         7121  
446 2675         3565 foreach (reverse @{$found->{feature}}) {
  2675         6082  
447 208277 100       312898 if (substr($_->{name}, 0, 1) eq '.') {
448 2 50       8 if ($log->is_trace) {
449 0         0 $log->tracef('[Loading %s] Dropping commented \'%s\' from terminfo', $name, $found->{feature}->[$i]->{name});
450             }
451 2         12 splice(@{$found->{feature}}, $i, 1);
  2         7  
452             }
453 208277         169584 --$i;
454             }
455             }
456             #
457             # The raw terminfo is is the features referenced array.
458             # For faster lookup we fill the terminfo, termcap and variable hashes.
459             # These are used in the subroutine _tget().
460             #
461 2675         6837 $found->{terminfo} = {};
462 2675         16143 $found->{termcap} = {};
463 2675         12968 $found->{variable} = {};
464 2675         13702 my $pad_char = undef;
465 2675         3890 my $cursor_up = undef;
466 2675         4050 my $backspace_if_not_bs = undef;
467             {
468 2675         4101 foreach (@{$found->{feature}}) {
  2675         4388  
  2675         6019  
469 208275         184856 my $feature = $_;
470 208275         243494 my $key = $feature->{name};
471             #
472             # For terminfo lookup
473             #
474 208275 50       302481 if (! exists($found->{terminfo}->{$key})) {
475 208275         284487 $found->{terminfo}->{$key} = $feature;
476             } else {
477 0 0       0 if ($log->is_warn) {
478 0         0 $log->warnf('[Loading %s] Multiple occurence of feature \'%s\'', $name, $key);
479             }
480             }
481             #
482             # Translation exist ?
483             #
484 208275 100       226042 if (! exists($self->_t2other->{$key})) {
485 4592 50       6896 if ($log->is_trace) {
486 0         0 $log->tracef('[Loading %s] Untranslated feature \'%s\'', $name, $key);
487             }
488 4592         20136 next;
489             }
490             #
491             # Yes, check consistency
492             #
493 203683         237716 my $type = $self->_t2other->{$key}->{type};
494 203683 50       309855 if ($feature->{type} != $type) {
495 0 0       0 if ($log->is_warn) {
496 0         0 $log->warnf('[Loading %s] Wrong type when translating feature \'%s\': %d instead of %d', $name, $key, $type, $feature->{type});
497             }
498 0         0 next;
499             }
500             #
501             # Convert to termcap
502             #
503 203683         218046 my $termcap = $self->_t2other->{$key}->{termcap};
504 203683 50       306922 if (! defined($termcap)) {
505 0 0       0 if ($log->is_trace) {
506 0         0 $log->tracef('[Loading %s] Feature \'%s\' has no termcap equivalent', $name, $key);
507             }
508             } else {
509 203683 50       296911 if ($log->is_trace) {
510 0         0 $log->tracef('[Loading %s] Pushing termcap feature \'%s\'', $name, $termcap);
511             }
512 203683 50       915353 if (! exists($found->{termcap}->{$termcap})) {
513 203683         325612 $found->{termcap}->{$termcap} = $feature;
514             } else {
515 0 0       0 if ($log->is_warn) {
516 0         0 $log->warnf('[Loading %s] Multiple occurence of termcap \'%s\'', $name, $termcap);
517             }
518             }
519             }
520             #
521             # Convert to variable
522             #
523 203683         222456 my $variable = $self->_t2other->{$key}->{variable};
524 203683 50       287222 if (! defined($variable)) {
525 0 0       0 if ($log->is_trace) {
526 0         0 $log->tracef('[Loading %s] Feature \'%s\' has no variable equivalent', $name, $key);
527             }
528             } else {
529 203683 50       282280 if ($log->is_trace) {
530 0         0 $log->tracef('[Loading %s] Pushing variable feature \'%s\'', $name, $variable);
531             }
532 203683 50       906549 if (! exists($found->{variable}->{$variable})) {
533 203683         286509 $found->{variable}->{$variable} = $feature;
534             #
535             # Keep track of pad_char, cursor_up and backspace_if_not_bs
536 203683 100       303689 if ($type == TERMINFO_STRING) {
537 180968 100       650573 if ($variable eq 'pad_char') {
    100          
    100          
538 15         36 $pad_char = $feature;
539 15 50       33 if ($log->is_trace) {
540 0         0 $log->tracef('[Loading %s] pad_char is \'%s\'', $name, $pad_char->{value});
541             }
542             } elsif ($variable eq 'cursor_up') {
543 2452         3163 $cursor_up = $feature;
544 2452 50       4893 if ($log->is_trace) {
545 0         0 $log->tracef('[Loading %s] cursor_up is \'%s\'', $name, $cursor_up->{value});
546             }
547             } elsif ($variable eq 'backspace_if_not_bs') {
548 20         33 $backspace_if_not_bs = $feature;
549 20 50       52 if ($log->is_trace) {
550 0         0 $log->tracef('[Loading %s] backspace_if_not_bs is \'%s\'', $name, $backspace_if_not_bs->{value});
551             }
552             }
553             }
554             } else {
555 0 0       0 if ($log->is_warn) {
556 0         0 $log->warnf('[Loading %s] Multiple occurence of variable \'%s\'', $name, $key);
557             }
558             }
559             }
560             }
561              
562             # The variables PC, UP and BC are set by tgetent to the terminfo entry's data for pad_char, cursor_up and backspace_if_not_bs, respectively.
563             #
564             # PC is used in the delay function.
565             #
566 2675 100       6642 if (defined($pad_char)) {
567 15 50       52 if ($log->is_trace) {
568 0         0 $log->tracef('[Loading %s] Initialized PC to \'%s\'', $name, $pad_char->{value});
569             }
570 15         97 $found->{variable}->{PC} = $pad_char;
571             }
572             #
573             # UP is not used by ncurses.
574             #
575 2675 100       5936 if (defined($cursor_up)) {
576 2452 50       5337 if ($log->is_trace) {
577 0         0 $log->tracef('[Loading %s] Initialized UP to \'%s\'', $name, $cursor_up->{value});
578             }
579 2452         13758 $found->{variable}->{UP} = $cursor_up;
580             }
581             #
582             # BC is used in the tgoto emulation.
583             #
584 2675 100       5160 if (defined($backspace_if_not_bs)) {
585 20 50       75 if ($log->is_trace) {
586 0         0 $log->tracef('[Loading %s] Initialized BC to \'%s\'', $name, $backspace_if_not_bs->{value});
587             }
588 20         171 $found->{variable}->{BC} = $backspace_if_not_bs;
589             }
590             #
591             # The variable ospeed is set in a system-specific coding to reflect the terminal speed.
592             #
593 2675         8966 my ($baudrate, $ospeed) = $self->_get_ospeed_and_baudrate($fh);
594 2675         15453 my $OSPEED = {name => 'ospeed', type => TERMINFO_NUMERIC, value => $ospeed};
595 2675 50       5757 if ($log->is_trace) {
596 0         0 $log->tracef('[Loading %s] Initialized ospeed to %d', $name, $OSPEED->{value});
597             }
598 2675         14645 $found->{variable}->{ospeed} = $OSPEED;
599             #
600             # The variable baudrate is used eventually in delay
601             #
602 2675         8269 my $BAUDRATE = {name => 'baudrate', type => TERMINFO_NUMERIC, value => $baudrate};
603 2675 50       5473 if ($log->is_trace) {
604 0         0 $log->tracef('[Loading %s] Initialized baudrate to %d', $name, $BAUDRATE->{value});
605             }
606 2675         14140 $found->{variable}->{baudrate} = $BAUDRATE;
607             #
608             # ospeed and baudrate are add-ons, not in the terminfo database.
609             # If you look to the terminfo<->Caps translation files, you will see that none of ospeed
610             # nor baudrate variables exist. Nevertheless, we check if they these entries WOULD exist
611             # and warn about it, because we would overwrite them.
612             #
613 2675 50       8160 if (exists($found->{terminfo}->{ospeed})) {
614 0 0       0 if ($log->is_warn) {
615 0         0 $log->tracef('[Loading %s] Overwriting ospeed to \'%s\'', $name, $OSPEED->{value});
616             }
617             }
618 2675         6221 $found->{terminfo}->{ospeed} = $found->{variable}->{ospeed};
619 2675 50       6075 if (exists($found->{terminfo}->{baudrate})) {
620 0 0       0 if ($log->is_warn) {
621 0         0 $log->tracef('[Loading %s] Overwriting baudrate to \'%s\'', $name, $BAUDRATE->{value});
622             }
623             }
624 2675         7234 $found->{terminfo}->{baudrate} = $found->{variable}->{baudrate};
625             }
626              
627             #
628             # Remove any static/dynamic var
629             #
630 2675         8319 $found->{_static_vars} = [];
631 2675         5462 $found->{_dynamic_vars} = [];
632              
633 2675         8102 $self->_terminfo_current($found);
634              
635             #
636             # Create stubs for every string
637             #
638 2675         7388 $self->_stubs($name);
639              
640 2675         25311 return 1;
641             }
642              
643             sub _stub {
644 185265     185265   220038 my ($self, $featurevalue) = @_;
645              
646 185265 50       245967 if ($self->{_cache_stubs}) {
647 185265 100       350818 if (exists($self->{_cached_stubs}->{$featurevalue})) {
648 176711 50       251919 if ($log->is_trace) {
649 0         0 $log->tracef('Getting \'%s\' compiled stub from cache', $featurevalue);
650             }
651 176711         862946 $self->{_stubs}->{$featurevalue} = $self->{_cached_stubs}->{$featurevalue};
652             }
653             }
654 185265 100       303301 if (! exists($self->{_stubs}->{$featurevalue})) {
655 8554         9882 my $stub_as_txt = undef;
656 8554 50       11855 if ($self->{_cache_stubs_as_txt}) {
657 8554 100       20397 if (exists($self->{_cached_stubs_as_txt}->{$featurevalue})) {
658 8552 50       17635 if ($log->is_trace) {
659 0         0 $log->tracef('Getting \'%s\' stub as txt from cache', $featurevalue);
660             }
661 8552         52041 $stub_as_txt = $self->{_cached_stubs_as_txt}->{$featurevalue};
662             }
663             }
664 8554 100       14818 if (! defined($stub_as_txt)) {
665             #
666             # Very important: we restore the ',': it is parsed as either
667             # and EOF (normal case) or an ENDIF (some entries are MISSING
668             # the '%;' ENDIF tag at the very end). I am not going to change
669             # the grammar when documentation says that a string follows
670             # the ALGOL68, which has introduced the ENDIF tag to solve the
671             # IF-THEN-ELSE-THEN ambiguity.
672             # There is no side-effect doing so, but keeping the grammar clean.
673 2         4 my $string = "$featurevalue,";
674 2 50       7 if ($log->is_trace) {
675 0         0 $log->tracef('Parsing \'%s\'', $string);
676             }
677 2         31 my $parseTreeValue = MarpaX::Database::Terminfo::String->new()->parse(\$string)->value();
678             #
679             # Enclose the result for anonymous subroutine evaluation
680             # We reindent everything by two spaces
681             #
682 2         669 my $indent = join("\n", @{${$parseTreeValue}});
  2         7  
  2         15  
683 2         35 $indent =~ s/^/ /smg;
684 2         17 $stub_as_txt = "
685             #
686             # Stub version of: $featurevalue
687             #
688             sub {
689             my (\$self, \$dynamicp, \$staticp, \@param) = \@_;
690             # Initialized with \@param to be termcap compatible
691             my \@iparam = \@param;
692             my \$rc = '';
693              
694             $indent
695             return \$rc;
696             }
697             ";
698 2 50       9 if ($log->is_trace) {
699 0         0 $log->tracef('Parsing \'%s\' gives stub: %s', $string, $stub_as_txt);
700             }
701 2 50       35 if ($self->{_cache_stubs_as_txt}) {
702 2         11 $self->{_cached_stubs_as_txt}->{$featurevalue} = $stub_as_txt;
703             }
704             }
705 8554 50       13604 if ($log->is_trace) {
706 0         0 $log->tracef('Compiling \'%s\' stub', $featurevalue);
707             }
708             #
709             # Untaint data
710             #
711 8554         60673 my ($untainted) = $stub_as_txt =~ m/(.*)/s;
712 8554         1024505 $self->{_stubs}->{$featurevalue} = eval $untainted; ## no critic
713 8554 50       17723 if ($@) {
714 0         0 carp "Problem with $featurevalue\n$stub_as_txt\n$@\nReplaced by a stub returning empty string...";
715 0     0   0 $self->{_stubs}->{$featurevalue} = sub {return '';};
  0         0  
716             }
717 8554 50       15740 if ($self->{_cache_stubs}) {
718 8554         19729 $self->{_cached_stubs}->{$featurevalue} = $self->{_stubs}->{$featurevalue};
719             }
720             }
721              
722 185265         294254 return $self->{_stubs}->{$featurevalue};
723             }
724              
725             sub _stubs {
726 2675     2675   4639 my ($self, $name) = @_;
727              
728 2675         5572 $self->{_stubs} = {};
729              
730 2675         55701 foreach (values %{$self->_terminfo_current->{terminfo}}) {
  2675         6942  
731 213625         147014 my $feature = $_;
732 213625 100       304079 if ($feature->{type} == TERMINFO_STRING) {
733 185259         194816 $self->_stub($feature->{value});
734             }
735             }
736             }
737              
738             #
739             # _get_ospeed_and_baudrate calculates baudrate and ospeed
740             #
741             # POSIX module does not contain all the constants. Here they are.
742             #
743             our %OSPEED_TO_BAUDRATE = (
744             0 => 0,
745             1 => 50,
746             2 => 75,
747             3 => 110,
748             4 => 134,
749             5 => 150,
750             6 => 200,
751             7 => 300,
752             8 => 600,
753             9 => 1200,
754             10 => 1800,
755             11 => 2400,
756             12 => 4800,
757             13 => 9600,
758             14 => 19200,
759             15 => 38400,
760             4097 => 57600,
761             4098 => 115200,
762             4099 => 230400,
763             4100 => 460800,
764             4101 => 500000,
765             4102 => 576000,
766             4103 => 921600,
767             4104 => 1000000,
768             4105 => 1152000,
769             4107 => 2000000,
770             4108 => 2500000,
771             4109 => 3000000,
772             4110 => 3500000,
773             4111 => 4000000,
774             );
775              
776             sub _get_ospeed_and_baudrate {
777 2675     2675   3685 my ($self, $fh) = (@_);
778              
779 2675         5677 my $baudrate = 0;
780 2675         4118 my $ospeed = 0;
781              
782 2675 50       4875 if (defined($fh)) {
783 0         0 my $reffh = ref($fh);
784 0 0       0 if ($reffh ne 'GLOB') {
785 0 0       0 if ($log->is_warn) {
786 0   0     0 $log->warnf('filehandle should be a reference to GLOB instead of %s', $reffh || '');
787             }
788             }
789 0         0 $fh = undef;
790             }
791              
792 2675 100       8734 if (defined($ENV{MARPAX_DATABASE_TERMINFO_OSPEED})) {
793 2         5 $ospeed = $ENV{MARPAX_DATABASE_TERMINFO_OSPEED};
794             } else {
795 2673 50       5991 if ($HAVE_POSIX) {
796 2673         5675 my $termios = eval { POSIX::Termios->new() };
  2673         18072  
797 2673 50       4955 if (! defined($termios)) {
798 0 0       0 if ($log->is_trace) {
799 0         0 $log->tracef('POSIX::Termios->new() failure, %s', $@);
800             }
801             } else {
802 2673 50 50     23230 my $fileno = defined($fh) ? fileno($fh) : (fileno(\*STDIN) || 0);
803 2673 50       5769 if ($log->is_trace) {
804 0         0 $log->tracef('Trying to get attributes on fileno %d', $fileno);
805             }
806 2673         15849 eval {$termios->getattr($fileno)};
  2673         18148  
807 2673 50       5699 if ($@) {
808 0 0       0 if ($log->is_trace) {
809 0         0 $log->tracef('POSIX::Termios::getattr(%d) failure, %s', $fileno, $@);
810             }
811 0         0 $termios = undef;
812             }
813             }
814 2673 50       5885 if (defined($termios)) {
815 2673         2707 my $this = eval { $termios->getospeed() };
  2673         9189  
816 2673 50       5901 if (! defined($this)) {
817 0 0       0 if ($log->is_trace) {
818 0         0 $log->tracef('getospeed() failure, %s', $@);
819             }
820             } else {
821 2673         4627 $ospeed = $this;
822 2673 50       6379 if ($log->is_trace) {
823 0         0 $log->tracef('getospeed() returned %d', $ospeed);
824             }
825             }
826             }
827             }
828             }
829              
830              
831              
832 2675 50       22410 if (! exists($OSPEED_TO_BAUDRATE{$ospeed})) {
833 0 0       0 if ($log->is_warn) {
834 0         0 $log->warnf('ospeed %d is an unknown value', $ospeed);
835             }
836 0         0 $ospeed = 0;
837             }
838              
839 2675 100       6167 if (! $ospeed) {
840 2673         3035 $ospeed = 13;
841 2673 50       7724 if ($log->is_warn) {
842 0         0 $log->warnf('ospeed defaulting to %d', $ospeed);
843             }
844             }
845              
846 2675   50     22294 $baudrate = $ENV{MARPAX_DATABASE_TERMINFO_BAUDRATE} || $OSPEED_TO_BAUDRATE{$ospeed} || 0;
847              
848 2675 50       5256 if ($log->is_trace) {
849 0         0 $log->tracef('ospeed/baudrate: %d/%d', $ospeed, $baudrate);
850             }
851              
852 2675         16222 return ($baudrate, $ospeed);
853             }
854              
855             #
856             # space refers to termcap, feature (i.e. terminfo) or variable
857             #
858             sub _tget {
859 45     45   76 my ($self, $space, $default, $default_if_cancelled, $default_if_wrong_type, $default_if_found, $type, $id, $areap) = (@_);
860              
861 45         50 my $rc = $default;
862 45         60 my $found = undef;
863              
864 45 50       99 if ($self->_terminfo_init()) {
865             #
866             # First lookup in the hashes. If found, we will get the raw terminfo feature entry.
867             #
868 45 100       93 if (! exists($self->_terminfo_current->{$space}->{$id})) {
869             #
870             # No such entry
871             #
872 15 50       40 if ($log->is_trace) {
873 0         0 $log->tracef('No %s entry with id \'%s\'', $space, $id);
874             }
875             } else {
876             #
877             # Get the raw terminfo entry. The only entries for which it may not There is no check, it must exist by construction, c.f.
878             # routine tgetent(), even for variables ospeed and baudrate that are add-ons.
879             #
880 30         64 my $t = $self->_terminfo_current->{$space}->{$id};
881 30         69 my $feature = $self->_terminfo_current->{terminfo}->{$t->{name}};
882 30 50       76 if ($log->is_trace) {
883 0         0 $log->tracef('%s entry with id \'%s\' maps to terminfo feature %s', $space, $id, $feature);
884             }
885 30 50 66     241 if (defined($default_if_cancelled) && exists($self->_terminfo_current->{cancelled}->{$feature->{name}})) {
886 0 0       0 if ($log->is_trace) {
887 0         0 $log->tracef('Cancelled %s feature %s', $space, $feature->{name});
888             }
889 0         0 $rc = $default_if_cancelled;
890             } else {
891             #
892             # Check if this is the correct type
893             #
894 30 100       100 if ($feature->{type} == $type) {
    50          
895 27         32 $found = $feature;
896 27 100       81 if ($type == TERMINFO_STRING) {
897 20 100       82 $rc = defined($default_if_found) ? $default_if_found : \$feature->{value};
898             } else {
899 7 100       43 $rc = defined($default_if_found) ? $default_if_found : $feature->{value};
900             }
901             } elsif (defined($default_if_wrong_type)) {
902 3 50       9 if ($log->is_trace) {
903 0         0 $log->tracef('Found %s feature %s with type %d != %d', $space, $id, $feature->{type}, $type);
904             }
905 3         18 $rc = $default_if_wrong_type;
906             }
907             }
908             }
909             }
910              
911 45 100 100     326 if (defined($found) && defined($areap) && ref($areap)) {
      66        
912 12 100       29 if ($type == TERMINFO_STRING) {
913 10 100       11 if (! defined(${$areap})) {
  10         30  
914 3         5 ${$areap} = '';
  3         6  
915             }
916 10   100     11 my $pos = pos(${$areap}) || 0;
917 10         9 substr(${$areap}, $pos, 0, $found->{value});
  10         31  
918 10         22 pos(${$areap}) = $pos + length($found->{value});
  10         32  
919             } else {
920 2         3 ${$areap} = $found->{value};
  2         5  
921             }
922             }
923              
924 45         202 return $rc;
925             }
926              
927              
928             sub delay {
929 2     2 1 5 my ($self, $ms) = @_;
930              
931             #
932             # $self->{_outc} and $self->{_outcArgs} are created/destroyed by tputs() and al.
933             #
934 2         4 my $outc = $self->{_outc};
935 2 50       5 if (defined($outc)) {
936 2         3 my $PC;
937 2 100 66     8 if ($self->tvgetflag('no_pad_char') || ! $self->tvgetstr('PC', \$PC)) {
938             #
939             # usleep() unit is micro-second
940             #
941 1         1000221 usleep($ms * 1000);
942             } else {
943             #
944             # baudrate is always defined.
945             #
946 1         2 my $baudrate;
947 1         4 $self->tvgetnum('baudrate', \$baudrate);
948 1         3 my $nullcount = int(($ms * $baudrate) / (BAUDBYTE * 1000));
949             #
950             # We have no interface to 'tack' program, so no need to have a global for _nulls_sent
951             #
952 1         4 while ($nullcount-- > 0) {
953 1         3 &$outc($self->tparm($PC), @{$self->{_outcArgs}});
  1         3  
954             }
955             #
956             # Call for a flush
957             #
958 1         7 my ($flushcb, @flushargs) = @{$self->flush};
  1         4  
959 1         3 &$flushcb(@flushargs);
960             }
961             }
962             }
963              
964              
965             sub tgetflag {
966 1     1 1 6 my ($self, $id) = @_;
967 1         5 return $self->_tget('termcap', 0, undef, undef, undef, TERMINFO_BOOLEAN, $id, undef);
968             }
969              
970              
971             sub tigetflag {
972 4     4 1 12 my ($self, $id) = @_;
973 4         10 return $self->_tget('terminfo', 0, 0, -1, undef, TERMINFO_BOOLEAN, $id, undef);
974             }
975              
976              
977             sub tvgetflag {
978 4     4 1 14 my ($self, $id) = @_;
979 4         15 return $self->_tget('variable', 0, 0, 0, 1, TERMINFO_BOOLEAN, $id);
980             }
981              
982              
983             sub tgetnum {
984 1     1 1 6 my ($self, $id) = @_;
985 1         5 return $self->_tget('termcap', -1, undef, undef, undef, TERMINFO_NUMERIC, $id, undef);
986             }
987              
988              
989             sub tigetnum {
990 4     4 1 10 my ($self, $id) = @_;
991 4         12 return $self->_tget('terminfo', -1, -1, -2, undef, TERMINFO_NUMERIC, $id, undef);
992             }
993              
994              
995             sub tvgetnum {
996 3     3 1 15 my ($self, $id, $areap) = @_;
997 3         12 return $self->_tget('variable', 0, 0, 0, 1, TERMINFO_NUMERIC, $id, $areap);
998             }
999              
1000              
1001             sub tgetstr {
1002 7     7 1 22 my ($self, $id, $areap) = @_;
1003 7         21 return $self->_tget('termcap', 0, undef, undef, undef, TERMINFO_STRING, $id, $areap);
1004             }
1005              
1006              
1007             sub tigetstr {
1008 9     9 1 41 my ($self, $id) = @_;
1009 9         36 return $self->_tget('terminfo', 0, 0, -1, undef, TERMINFO_STRING, $id, undef);
1010             }
1011              
1012              
1013             sub tvgetstr {
1014 12     12 1 24 my ($self, $id, $areap
1015             ) = @_;
1016 12         34 return $self->_tget('variable', 0, 0, 0, 1, TERMINFO_STRING, $id, $areap);
1017             }
1018              
1019              
1020             sub tputs {
1021 3     3 1 9 my ($self, $str, $affcnt, $outc, @outcArgs) = @_;
1022              
1023 3         10 $self->{_outc} = $outc;
1024 3         9 $self->{_outcArgs} = \@outcArgs;
1025              
1026 3         12 $self->_tputs($str, $affcnt, $outc, @outcArgs);
1027              
1028 3         10 $self->{_outc} = undef;
1029 3         18078 $self->{_outcArgs} = undef;
1030             }
1031              
1032             sub _tputs {
1033 3     3   7 my ($self, $str, $affcnt, $outc, @outcArgs) = @_;
1034              
1035 3   50     8 $affcnt //= 1;
1036              
1037 3         5 my $bell = '';
1038 3         15 $self->tvgetstr('bell', \$bell);
1039 3         4 my $flash_screen = '';
1040 3         10 $self->tvgetstr('flash_screen', \$flash_screen);
1041              
1042 3         4 my $always_delay;
1043             my $normal_delay;
1044              
1045 3 50       11 if (! defined($self->{_term})) {
1046             #
1047             # No current terminal: setuppterm() has not been called
1048             #
1049 3         5 $always_delay = 0;
1050 3         6 $normal_delay = 1;
1051             } else {
1052 0         0 my $xon_xoff = $self->tvgetflag('xon_xoff');
1053 0         0 my $padding_baud_rate = 0;
1054 0         0 $self->tvgetnum('padding_baud_rate', \$padding_baud_rate);
1055 0         0 my $baudrate = 0;
1056 0         0 $self->tvgetnum('baudrate', \$baudrate);
1057              
1058 0 0 0     0 $always_delay = ($str eq $bell || $str eq $flash_screen) ? 1 : 0;
1059 0 0 0     0 $normal_delay = (! $xon_xoff && $padding_baud_rate && $baudrate >= $padding_baud_rate) ? 1 : 0;
1060             }
1061              
1062 3         5 my $trailpad = 0;
1063 3         9 pos($str) = undef;
1064 3 50 33     13 if ($self->{_bsd_tputs} && length($str) > 0) {
1065 0 0       0 if ($str =~ /^([[:digit:]]+)(?:\.([[:digit:]])?[[:digit:]]*)?(\*)?/) {
1066 0 0       0 my ($one, $two, $three) = (
    0          
1067             substr($str, $-[1], $+[1] - $-[1]),
1068             defined($-[2]) ? substr($str, $-[2], $+[2] - $-[2]) : 0,
1069             defined($-[3]) ? 1 : 0);
1070 0         0 $trailpad = $one * 10;
1071 0         0 $trailpad += $two;
1072 0 0       0 if ($three) {
1073 0         0 $trailpad *= $affcnt;
1074             }
1075 0         0 pos($str) = $+[0];
1076             }
1077             }
1078 3         6 my $indexmax = length($str);
1079 3   50     17 my $index = pos($str) || 0;
1080 3         11 while ($index <= $indexmax) {
1081 53         82 my $c = substr($str, $index, 1);
1082 53 100       72 if ($c ne '$') {
1083 51         86 &$outc($c, @outcArgs);
1084             } else {
1085 2         3 $index++;
1086 2 50       7 $c = ($index <= $indexmax) ? substr($str, $index, 1) : '';
1087 2 50       6 if ($c ne '<') {
1088 0         0 &$outc('$', @outcArgs);
1089 0 0       0 if ($c) {
1090 0         0 &$outc($c, @outcArgs);
1091             }
1092             } else {
1093 2 50       8 $c = (++$index <= $indexmax) ? substr($str, $index, 1) : '';
1094 2 50 33     26 if ((! ($c =~ /[[:digit:]]/) && $c ne '.') ||
      33        
1095             # Note: if $index is after the end $str, perl treat it as the end
1096             index($str, '>', $index) < $index) {
1097 0         0 &$outc('$', @outcArgs);
1098 0         0 &$outc('<', @outcArgs);
1099             #
1100             # The EOF will automatically go here
1101             #
1102 0         0 next;
1103             }
1104              
1105 2         4 my $number = 0;
1106 2 50       9 $c = ($index <= $indexmax) ? substr($str, $index, 1) : '';
1107 2         8 while ($c =~ /[[:digit:]]/) {
1108 5         8 $number = $number * 10 + $c;
1109 5 50       18 $c = (++$index <= $indexmax) ? substr($str, $index, 1) : '';
1110             }
1111 2         5 $number *= 10;
1112 2 50       6 $c = ($index <= $indexmax) ? substr($str, $index, 1) : '';
1113 2 50       8 if ($c eq '.') {
1114 0 0       0 $c = ($index <= $indexmax) ? substr($str, $index, 1) : '';
1115 0 0       0 if ($c =~ /[[:digit:]]/) {
1116 0         0 $number += $c;
1117 0         0 $index++;
1118             }
1119 0   0     0 while (($index <= $indexmax) && substr($str, $index, 1) =~ /[[:digit:]]/) {
1120 0         0 $index++;
1121             }
1122             }
1123 2         3 my $mandatory = 0;
1124 2 50       9 $c = ($index <= $indexmax) ? substr($str, $index, 1) : '';
1125 2   33     11 while ($c eq '*' || $c eq '/') {
1126 0 0       0 if ($c eq '*') {
1127 0         0 $number *= $affcnt;
1128 0         0 $index++;
1129             } else {
1130 0         0 $mandatory = 1;
1131 0         0 $index++;
1132             }
1133 0 0       0 $c = ($index <= $indexmax) ? substr($str, $index, 1) : '';
1134             }
1135              
1136 2 50 33     18 if ($number > 0 && ($always_delay || $normal_delay || $mandatory)) {
      33        
1137 2         11 $self->delay(int($number / 10));
1138             }
1139             }
1140             }
1141              
1142 53         153 $index++;
1143             }
1144              
1145 3 0 0     34 if ($trailpad > 0 && ($always_delay || $normal_delay)) {
      33        
1146 0         0 $self->delay(int($trailpad / 10));
1147             }
1148             }
1149              
1150              
1151             sub putp {
1152 1     1 1 7 my ($self, $str) = @_;
1153              
1154 1     38   5 return $self->tputs($str, 1, sub {print STDOUT shift});
  38         787  
1155             }
1156              
1157              
1158             sub _tparm {
1159 6     6   15 my ($self, $string, @param) = (@_);
1160              
1161 6         17 my $stub = $self->_stub($string);
1162              
1163 6         23 return $self->$stub($self->_terminfo_current->{_dynamic_vars}, $self->_terminfo_current->{_static_vars}, @param);
1164             }
1165              
1166             sub tparm {
1167 3     3 1 20 my ($self, $string, @param) = (@_);
1168              
1169 3         10 return $self->_tparm($string, @param);
1170             }
1171              
1172              
1173             sub tgoto {
1174 3     3 1 40 my ($self, $string, $col, $row) = (@_);
1175             #
1176             # We are in a pure terminfo workflow: capnames capability are translated to a terminfo feature, and the
1177             # string feature is derived from the found terminfo feature.
1178             # Reversal of arguments is intentional
1179             #
1180 3         12 return $self->_tparm($string, $row, $col);
1181             }
1182              
1183              
1184             sub use_env {
1185 0     0 1   my $self = shift;
1186              
1187 0 0         if (@_) {
1188 0           $self->{_use_env} = shift;
1189             #
1190             # If user gave undef as argument, convert it to 0.
1191             #
1192 0 0         if (! defined($self->{_use_env})) {
1193 0           $self->{_use_env} = 0;
1194             }
1195             #
1196             # Finally convert it to 1 if ! false
1197             #
1198 0 0         if (! $self->{_use_env}) {
1199 0           $self->{_use_env} = 1;
1200             }
1201             }
1202              
1203 0           return $self->{_use_env};
1204             }
1205              
1206              
1207             1;
1208              
1209             __END__