File Coverage

blib/lib/Ecma48/Util.pm
Criterion Covered Total %
statement 136 162 83.9
branch 49 84 58.3
condition 24 46 52.1
subroutine 29 33 87.8
pod 13 14 92.8
total 251 339 74.0


line stmt bran cond sub pod time code
1             package Ecma48::Util;
2              
3 2     2   108127 use strict;
  2         6  
  2         110  
4 2     2   12 use warnings;
  2         31  
  2         66  
5 2     2   54 use 5.014;
  2         11  
  2         89  
6             # ^- * short names for control chars @ 5.14 (but full only in 5.16...)??XXX
7             # * charnames::string_vianame @ 5.14
8             # * s///r @ 5.14
9             #use feature ':5.10';
10 2     2   10 use Exporter 'import';
  2         3  
  2         76  
11 2     2   10 use Carp;
  2         3  
  2         180  
12 2     2   20561 use charnames qw(:full :short);
  2         269329  
  2         17  
13             #use Taint::Util 'untaint'; use Data::Dump 'dump';
14             our @EXPORT_OK=qw(remove_seqs move_seqs_before_lastnl split_seqs
15             ensure_terminating_nl remove_terminating_nl
16             quotectrl quote_ctrl quote_nongraph
17             ctrl_chars ctrl_regex seq_regex
18             remove_bs_bolding replace_bs_bolding closing_seq
19             remove_fillchars *PREFER_UNICODE_SYMBOLS); # $PREFER_UNICODE_SYMBOLS
20             our %EXPORT_TAGS=(ALL => [ grep /^[^*$@%]/,@EXPORT_OK ], # except vars
21             NL => [qw(ensure_terminating_nl remove_terminating_nl
22             move_seqs_before_lastnl)],
23             DEL => [qw(remove_seqs remove_terminating_nl
24             remove_bs_bolding remove_fillchars)],
25             BS => [qw(remove_bs_bolding replace_bs_bolding)],
26             QUOT=> [qw(quotectrl quote_ctrl quote_nongraph)],
27             INFO=> [qw(ctrl_chars closing_seq)],
28             RE => [qw(ctrl_chars ctrl_regex seq_regex)],
29             VAR => [qw(*PREFER_UNICODE_SYMBOLS)]
30             );
31             %EXPORT_TAGS=(%EXPORT_TAGS, (map { lc $_ => $EXPORT_TAGS{$_} } keys %EXPORT_TAGS));
32              
33             our $VERSION='0.01';
34              
35             #~~ protos
36             sub closing_seq ($);
37             sub quotectrl ($); sub quote_ctrl ($);
38              
39             #~~ Control variables
40             our $PREFER_UNICODE_SYMBOLS=0;
41              
42             # ---------------------------------------------------------------------------
43              
44             #~~ helper subs
45             #*** _name2code *** js<10.10.2012
46             our %metactrl=(DMI => '`', INT => 'a', EMI => 'b', RIS => 'c', CMD => 'd',
47             LS2 => 'n', LS3 => 'o', LS3R => '|', LS2R => '}', LS1R => '~');
48             our %xtractrl=(EM => "\cY", IS4 => "\c\\", IS3 => "\c]", IS2 => "\c^", IS1 => "\c_",
49             FS => "\c\\", GS => "\c]", RS => "\c^", US => "\c_");
50             sub _name2code ($)
51 1     1   3 { my $n=shift;
52             #use charnames qw(:full :short);
53 1 50       6 return $xtractrl{$n} if exists $xtractrl{$n};
54 1 50       8 return "\e".$metactrl{$n} if exists $metactrl{$n};
55 1         7 return charnames::string_vianame($n);
56             }
57              
58             #*** _code2name *** js<10.10.2012
59             sub _code2name ($)
60 2     2   2685 { use re 'taint';
  2         6  
  2         1230  
61 0     0   0 my $c=shift; my $name;
  0         0  
62 0         0 state $n={ # EM as EOM, IS4..IS1 as FS GS RS US for "\N{...}" compliance
63             # would prefer TAB over HT, but TAB not available before perl v5.16
64             # also added PAD,HOP&IND&SGC, not part of ECMA48
65             # SGC=SINGLE GRAPHIC CHARACTER INTRODUCER
66             (#map { charnames::vianame($_)//undef => $_ }
67 0         0 map { _name2code $_ => $_ }
68             qw(NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
69             DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EOM SUB ESC FS GS RS US
70             DEL PAD HOP BPH NBH IND NEL SSA ESA HTS HTJ VTS PLD PLU RI SS2 SS3
71             DCS PU1 PU2 STS CCH MW SPA EPA SOS SGC SCI CSI ST OSC PM APC))
72             };
73 0 0       0 $c=chr $c if $c=~/^\d+$/;
74 0 0       0 $name=$n->{$c} if exists $n->{$c};
75 0 0 0     0 $name//=$metactrl{$1} if $c=~/^\e(.)$/ && exists $metactrl{$1};
      0        
76 0   0     0 $name//=charnames::viacode ord $c;
77             #$name=~s/CHARACTER$/CHAR/;
78 0         0 return $name;
79             }
80              
81             #*** _re_clear *** js14.10.2012
82             #sub _re_clear (@) {
83             # local $"='|'; my $re=@_==1 ? $_[0] : qr(@_);
84             # my $ch=qr([^\\] | \\x[\dA-F]{2} | \\0[0-7]{0,3})xai;
85             # $re=~s/(?:\(\?\^\w*?:|\|)\K ((?:$ch\|)+$ch) (?= \||\)$ )/'['.$1=~s(\|)()gr.']'/gex;
86             # $re=~s/ (?
87             # #return bless \$re, 'Regexp';
88             # return $re; # qr($re)
89             #}
90             # s{(?:^[^:]*:|\|)\K ((?:[^\\]\|)+[^\\]) (?= \||\)$ )}{'['.$1=~s(\|)()gr.']'}gerx
91              
92             #*** _ctrlcharvisu *** ausgelagert js15.10.2012
93             #* \e => \\e and so on, see quotectrl for more info
94             sub _ctrlcharsymb ($) # prefer unicode symbols
95 4     4   6 { my $c=shift;
96 4 50       36 return chr(0x2400+ord $c) if $c=~/[\00-\x20]/;
97 0 0       0 return "\x{2421}" if $c eq "\x7F";
98             return # No symbol available
99 0         0 }
100             sub _ctrlcharvisu ($)
101 9     9   17 { state $h={ "\e" => '\\e', "\a" => '\\a', "\r" => '\\r',
102             "\cH" => '\\cH', "\00" => '\\00' };
103 9 100       21 my $c=shift; my $v; $v=$c if substr($c,-1)=~/^[\n\f\t]$/;
  9         11  
  9         41  
104 9 100 66     38 $v//=_ctrlcharsymb($c) if $PREFER_UNICODE_SYMBOLS;
105 9 100 66     42 $v//=$h->{$c} if exists $h->{$c};
106 9 50       21 my $name=$v ? '' : _code2name $c;
107 9 0 33     20 $v//=$name ? "\\N{$name}" : sprintf '\\x%02x', ord $c;
108 9         72 return $v
109             }
110             # ---------------------------------------------------------------------------
111              
112             #*** ctrl_chars, ctrl_regex *** js<10.10.2012
113             #* return a regex with matches the Ctrlchars and its 7bit-Equivalents.
114             #* param: @_...as Names like CAN, as Number or as String
115             #* A new param for each Ctrlchar is needed.
116             #* invariant: GIGO
117             sub ctrl_chars (@)
118 2     2   14 { use charnames qw(:full :short);
  2         4  
  2         11  
119 1 50       352 my @re=map { $_, $_=~/^[\x80-\x9f]$/ ? "\e$_"^"\00\xC0" : () } # add 7bit
  1 0       9  
    50          
120 1     1 1 4 map { $_=~/^\w\w|^U\+/a ? _name2code($_) :
121             $_=~/^\d+$/a ? chr($_) : $_ } @_;
122 1 50       5 local $"='|'; return wantarray ? @re : qr(@re)
  1         19  
123             }
124 0     0 0 0 sub ctrl_regex (@) { return scalar ctrl_chars @_ }
125              
126             #{ use charnames qw(:full :short);
127             # my $re=join '|',
128             # map { $_, $_=~/^[\x80-\x9f]$/ ? "\e$_"^"\00\xC0" : () } # add 7bit
129             # map { $_=~/^\w\w|U\+/ ? _name2code($_) :
130             # $_=~/^\d+$/ ? chr($_) : $_ } @_;
131             # return qr($re)
132             #}
133              
134             #*** quotectrl *** js<10.10.2012
135             # comment: to it late to minify ... of diff. of what it does and what you thing it does.
136             sub quote_ctrl ($) # \r for \n, \n, \f, (NEL 0x85??), (DEL 0x7f??)
137             { # [[:cntrl:]]?? instead [\00-\x1F\x7F-\x9F]? -v
138 3     3 1 17 my $re=qr/((?:\r*\n)|[\00-\x1F\x7F-\x9F])/;
139 3 50       40 return defined wantarray ? $_[0]=~s{$re}{ _ctrlcharvisu $1 }ger
  9         25  
140 0         0 : $_[0]=~s{$re}{ _ctrlcharvisu $1 }ge;
141             #my $r=...; untaint $r; return $r;
142             }
143             *quotectrl=\"e_ctrl;
144              
145             sub quote_nongraph ($) # \r for \n, \n, \f, (NEL 0x85??), (DEL 0x7f??)
146             { # [[:cntrl:]]?? instead [\00-\x1F\x7F-\x9F]? -v
147 0     0 1 0 my $re=qr/((?:\r*\n)|[^[:graph:]])/;
148 0 0       0 return defined wantarray ? $_[0]=~s{$re}{ _ctrlcharvisu $1 }ger
  0         0  
149 0         0 : $_[0]=~s{$re}{ _ctrlcharvisu $1 }ge;
150             #my $r=...; untaint $r; return $r;
151             }
152              
153             # ---------------------------------------------------------------------------
154              
155             #*** seq_regex *** js<10.10.2012
156             my $CSI=qr(\x9b|\e\[); # ctrl_regex 'CSI'
157             my ($OSC,$APC,$DCS,$PM)=(qr"\x9d|\e]",qr"\x9f|\e_",qr"\x90|\eP",qr"\x9e|\e^");
158             my $XTD=qr($OSC|$APC|$DCS|$PM); # ctrl_regex qw(OSC APC DCS PM)
159             my $SOS=qr(\x98|\eX); # Start of String
160             my $CAN=qr(\cX|\ea); # CAN:\cX=\N{CAN}, INT:\ea, CMD:\ed
161             my $SFT= "\x0f\x0e"; # join '',ctrl_regex qw(SI SO); # Kap9
162             my $XTDbase="\t-\r\x20-0x7e";
163             #my $G01_94=qr([\x21-\x7E\xA1-\xFE]);
164             #my $G01_96=qr([\x20-\x7F\xA0-\xFF]);
165             my $FIN=qr([@-~]|$CAN); # for CSI: privat p-~ mostly [a-z\@[\]^|{}_`]
166             my $ST =qr(\cG|\x9c|\e\\|$CAN); # ctrl_regex qw(ST ALERT CAN)
167             our $SEQ=qr{ $CSI [:<=>?]? [\d;]* [\x20-/]? $FIN
168             | $XTD (?:[$SFT$XTDbase]* | [\xA0-\xFE$XTDbase]*) $ST
169             | $SOS [^\x98\x9c]*? $ST
170             | \e [\x20-/]* (?:[0-~]|$CAN) }ixa; # was: [\x20-/;]*, why?
171              
172             # \e[`-~] | \e[\x20-/]*[0-_] vs. \e[\x20-/]*[0-~] because of DEC 2nd
173             # \e![0-~] ... no param in ECMA-48, but many stuff with ... outside exist
174              
175             #*** seq_regex *** js<10.10.2012
176 0     0 1 0 sub seq_regex () { $SEQ }
177              
178             #*** _flip *** js17.10.2012
179             #* replaces < with > and so on
180             sub _flip ($)
181 12     12   21 { state $OPP={ 'REVERSED '=> '', map { my @r=split '/'; @r,reverse @r }
  3         10  
  3         19  
182             qw(LESS/GREATER LEFT/RIGHT LEFTWARDS/RIGHTWARDS) };
183 12         23 state $OPPm=join '|',keys $OPP;
184 12         27 my ($s)=@_;
185 24         34 return join '', map
186 12         54 { my $r=$_;
187 24 100       79 unless ($r=~tr!´`<>\[\](){}\\\/!`´><\]\[)(}{//!) # tr: \.../ but /.../
188 14         19 { my $dir; my $cname=charnames::viacode ord;
  14         59  
189 14 100       51984 if ($cname=~/\b($OPPm)\b/oi)
190 2 50 33     25 { if (($dir=$1) && exists $OPP->{$dir=$1})
191 2         29 { $cname=~s/\Q$dir\E/$OPP->{$dir}/e;
  2         11  
192 2   33     2948 $r=charnames::string_vianame($cname)//$_;
193             }
194             }
195 14 100 33     847 $r=charnames::string_vianame("REVERSED $cname")//$_
      100        
196             if ord>0x100 && $r eq $_; # try if unicode and we have no success so far
197             # XXX
198             }
199             $r
200 24         499 } split '', $s;
201             }
202              
203             #*** closing_seq *** js17.10.2012
204             #* find counterpart for opening sequence.
205             sub closing_seq ($)
206 21     21 1 134 { my ($open)=@_;
207 7         16 state $CLS={ (map { $_=>$_+20 } 2..5,7..9), 1 => 22, 6 => 25, 20 => 23,
  9         17  
208 16 100       233 (map { $_ => 10 } 11..19), 51 => 54, 52 => 54, 53 => 55,
209 21         40 (map { $_ => $_<40?39:49 } 30..37,40..47),
210             };
211 21         60 given ($open)
212             { when (/^[^\x01-\x1F\x80-\x9F]*$/ && !/^[\d;]*?\d[\d;]*$/) # no control char inside
213 21 100 100     231 { return '' if $_ eq '';
  13         772  
214 12         129 my $opp=_flip($_); # reverse all: .oO _*/
215 12 50 66     258 return reverse $opp if $opp ne $_ || m{[-°^*+~_/'"[:punct:]\s]};
216 0         0 carp "Don't know a fitting closing pedant, use '$_' as-is.";
217 0         0 return $_
218             }
219 8         210 when (/^($CSI)([\d;]+)m\z/) { return $1.closing_seq($2).'m' }
  3         16  
220 5         41 when (/^($CSI[\d;]+)h\z/) { return "${1}l" }
  0         0  
221             when (/^\d+$/)
222 5         22 { #say "debug: _=$_".dump $CLS;
223 4 50       57 return $CLS->{0+$_} if exists $CLS->{0+$_};
224 0         0 carp "Don't know a fitting closing sequence, use reset.";
225 0         0 return 0;
226             }
227             when (/^[\d;]*;[\d;]*\z/)
228 1 50       6 { return 39 if /^0*38;/; # XXX
  1         7  
229 1 50       6 return 49 if /^0*48;/;
230 1         4 return join ';',map { closing_seq(0+$_) } grep { $_ ne '' } split ';', $open;
  1         99  
  2         10  
231             }
232             default
233 0         0 { carp "Don't know a fitting closing sequence.";
  0         0  
234             return
235 0         0 }
236             }
237             }
238              
239             # ---------------------------------------------------------------------------
240              
241             #*** remove_seqs *** js<10.10.2012
242             sub remove_seqs ($)
243 2     2   15760 { use re 'taint';
  2         5  
  2         442  
244 4 50   4 1 105 return defined wantarray ? $_[0]=~s/$SEQ//gr : $_[0]=~s/$SEQ//g;
245             }
246              
247             #*** split_seqs *** js<10.10.2012
248             #* split string and return a list where escape seq are marked by being scalar references.
249 1 100   1 1 474 sub split_seqs ($) { map { /$SEQ/ ? \$_ : $_ } split /($SEQ)/,$_[0] }
  5         53  
250              
251             sub move_seqs_before_lastnl ($) # e.g. color before nl
252 2     2 1 23 { use re 'taint'; my $re=qr/([\s\r\n])+($SEQ)+\s*\z/m;
  2     1   4  
  2         745  
  1         256  
253 1 50       27 return defined wantarray ? $_[0]=~s/$re/$2$1/mr : $_[0]=~s/$re/$2$1/m;
254             }
255              
256             sub ensure_terminating_nl ($) # if not only space
257 3     3 1 14 { my $test=remove_seqs $_[0];
258 3 100 66     31 my $nl= $test=~m/\r?\n\h*?\z/ || $test!~/\S/ ? '' : "\n";
259 3 50       10 return $_[0].=$nl unless defined wantarray;
260 3         20 return "$_[0]$nl"
261             }
262             sub remove_terminating_nl ($)
263 2     2 1 14 { use re 'taint'; my $re=qr/\r?\n((?:\h|$SEQ)*?)\z/;
  2     3   4  
  2         339  
  3         316  
264             #return $_[0]=~s/\r?\n((?:\h|$SEQ)*?)\z/$1/r;
265 3 50       57 return defined wantarray ? $_[0]=~s/$re/$1/r : $_[0]=~s/$re/$1/;
266             }
267              
268             #*** remove_fillchar *** js15.10.2012
269             #* return input with removed DEL, NUL and CRs directly before other CRs
270             #* removed: ... and SPACE-BS pairs if the are not inside a word.
271             sub remove_fillchars ($)
272 2     2 1 12 { use re 'taint'; my $re=qr/[\00\x7F]|\r(?=\r)/; # |(?
  2     2   4  
  2         281  
  2         12  
273 2 50       40 return defined wantarray ? $_[0]=~s/$re//gr : $_[0]=~s/$re//g;
274             }
275              
276             #*** remove_bs_bolding *** js15.10.2012
277             sub remove_bs_bolding ($) # ecma-6 not part of ecma-48
278 2     2 1 13 { use re 'taint'; my $re=qr/([[:graph:]])\cH(?=\g1)/;
  2     2   4  
  2         270  
  2         13  
279 2 50       39 return defined wantarray ? $_[0]=~s/$re//gr : $_[0]=~s/$re//g;
280             }
281              
282             #*** replace_bs_bolding *** js17.10.2012
283             sub replace_bs_bolding ($;$$$) # ecma-6 not part of ecma-48
284 2     2   24 { use re 'taint';
  2         4  
  2         2384  
285 4 50   4 1 18 my $s=defined wantarray ? \do{ my $dummy=$_[0] } : \$_[0];
  4         14  
286 4   50     18 my $b=$_[1]//1; my $e=$_[2]//closing_seq($b); my $i=$_[3]//'';
  4   100     18  
  4   100     20  
287 4 50       12 for ($b,$e) { $_="\e[${_}m" if /^[\d;]+\z/ }
  8         31  
288             #for ($$s) { s/([[:graph:]])(?:\cH\g1)+/$b$1$e/g; s/\Q$e$b//g; }
289 4         10 my $emiss=0;
290 4         31 $$s=~s{(?| ([[:graph:]])(?:(\cH)\g1)+ | (.)() )}
291 50         209 { my $r;
292 50 100       128 if (!$2) { $r=($emiss ? $e : '').$1; $emiss=0; }
  31 100       71  
  31         75  
293 19 100       50 else { $r=($emiss ? $i : $b).$1; $emiss=1; }
  19         24  
294 50         487 $r
295             }gsex;
296 4 100       17 $$s.=$e if $emiss;
297 4         34 return $$s;
298             }
299              
300             # ---------------------------------------------------------------------------
301             'very reduced';
302              
303             __END__