File Coverage

blib/lib/Text/VisualPrintf/Transform.pm
Criterion Covered Total %
statement 102 115 88.7
branch 34 48 70.8
condition 8 15 53.3
subroutine 17 20 85.0
pod 3 6 50.0
total 164 204 80.3


line stmt bran cond sub pod time code
1             package Text::VisualPrintf::Transform;
2              
3 12     12   158 use v5.14;
  12         44  
4 12     12   69 use warnings;
  12         22  
  12         309  
5 12     12   648 use utf8;
  12         38  
  12         72  
6 12     12   276 use Carp;
  12         25  
  12         611  
7 12     12   63 use Data::Dumper;
  12         27  
  12         544  
8             {
9 12     12   67 no warnings 'redefine', 'once';
  12         39  
  12         11116  
10 0     0   0 *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  0         0  
11             $Data::Dumper::Useperl = 1;
12             $Data::Dumper::Sortkey = 1;
13             }
14              
15             my %char_range = (
16             STRAIGHT => [ [0x01=>0x07], [0x10=>0x1f], [0x21=>0x7e], [0x81=>0xfe] ],
17             MODERATE => [ [0x21=>0x7e], [0x01=>0x07], [0x10=>0x1f], [0x81=>0xfe] ],
18             VISIBLE => [ [0x21=>0x7e] ],
19             );
20              
21             my %default = (
22             test => undef,
23             length => sub { length $_[0] },
24             match => qr/.+/s,
25             except => '',
26             max => 0,
27             visible => 0,
28             ordered => 1,
29             );
30              
31             sub new {
32 576     576 1 1146 my $class = shift;
33 576         3447 my $obj = bless { %default }, $class;
34 576 50       2845 $obj->configure(@_) if @_;
35 576         1235 $obj;
36             }
37              
38             sub configure {
39 576     576 0 1082 my $obj = shift;
40 576         2167 while (my($k, $v) = splice @_, 0, 2) {
41 2304 50       4884 if (not exists $default{$k}) {
42 0         0 croak "$k: invalid parameter";
43             }
44 2304 100       4247 if ($k eq 'test') {
45 576         790 my $sub = do {
46 576 50   0   1920 if (not $v) { sub { 1 } }
  0 50       0  
  0 0       0  
47 576     887   2679 elsif (ref $v eq 'Regexp') { sub { $_[0] =~ $v } }
  887         6444  
48 0         0 elsif (ref $v eq 'CODE') { $v }
49 0     0   0 else { sub { 1 } }
  0         0  
50             };
51 576         1916 $obj->{$k} = $sub;
52             } else {
53 1728 100 50     4383 $k eq 'length' and ( ref $v eq 'CODE' or die );
54 1728         5112 $obj->{$k} = $v;
55             }
56             }
57 576         903 $obj;
58             }
59              
60             sub encode {
61 576     576 1 821 my $obj = shift;
62 576         1861 $obj->{replace} = [];
63 576 100 50     2429 my $guard = $obj->guard_maker($obj->{except} // '', @_)
64             or return @_;
65 570 50       1753 my $match = $obj->{match} or die;
66 570         1035 my $test = $obj->{test};
67 570         1205 for my $arg (grep { defined } @_) {
  887         2516  
68 887 100 66     2872 not $test or $test->($arg) or next;
69 850         4520 $arg =~ s{$match}{
70 850 100       2031 if (my($replace, $regex, $len) = $guard->(${^MATCH})) {
71 849         1969 push @{$obj->{replace}}, [ $regex, ${^MATCH}, $len ];
  849         3240  
72 849         3616 $replace;
73             } else {
74 1         7 ${^MATCH};
75             }
76             }pge;
77             }
78 570         4685 @_;
79             }
80              
81             sub decode {
82 576     576 1 1035 my $obj = shift;
83 576 100       830 my @replace = @{$obj->{replace}} or return @_;
  576         1850  
84             ARGS:
85 563         1307 for (@_) {
86 1157         3169 for my $i (0 .. $#replace) {
87 849         1254 my($regex, $orig, $len) = @{$replace[$i]};
  849         1988  
88 849 100       8151 if (s/$regex/_replace(${^MATCH}, $orig, $len)/pe) {
  594         1605  
89 594 50       1320 if ($obj->{ordered}) {
90 594         1330 splice @replace, 0, $i + 1;
91             } else {
92 0         0 splice @replace, $i, 1;
93             }
94 594         1715 redo ARGS;
95             }
96             }
97             }
98 563         1358 @_;
99             }
100              
101             sub _replace {
102 594     594   1682 my($matched, $orig, $len) = @_;
103 594         1198 my $width = length $matched;
104 594 100       1312 if ($width == $len) {
105 587         2529 $orig;
106             } else {
107 7         17 _trim($orig, $width);
108             }
109             }
110              
111             sub _trim {
112 7     7   16 my($str, $width) = @_;
113 12     12   6506 use Text::ANSI::Fold;
  12         365293  
  12         5967  
114 7         23 state $f = Text::ANSI::Fold->new(padding => 1);
115 7         74 my($folded, $rest, $w) = $f->fold($str, width => $width);
116 7 100       2775 if ($w <= $width) {
    50          
117 6         30 $folded;
118             } elsif ($width == 1) {
119 1         5 ' '; # wide char not fit to single column
120             } else {
121 0         0 die "Panic"; # should never reach here...
122             }
123             }
124              
125             sub guard_maker {
126 576     576 0 922 my $obj = shift;
127 576         959 my $max = $obj->{max};
128 576         2070 local $_ = join '', @_;
129 576         945 my @a;
130 576         854 my @range = do {
131 2304         10923 map { $_->[0] .. $_->[1] }
132 576   33     823 @{ $obj->{range} //= $obj->char_range };
  576         2215  
133             };
134 576         1646 for my $i (@range) {
135 61923         120904 my $c = pack "C", $i;
136 61923 100       429555 push @a, $c unless /\Q$c/;
137 61923 100 66     219110 last if $max && @a > $max;
138             }
139 576 100       1546 return if @a < 2;
140 570         839 my $lead = do { local $" = ''; qr/[^\Q@a\E]*+/ };
  570         1127  
  570         9305  
141 570         1526 my $b = shift @a;
142             return sub {
143 850     850   2764 my $len = $obj->{length}->(+shift);
144 850 100       99283 return if $len < 1;
145 849         2396 my $a = $a[ (state $n)++ % @a ];
146 849         1546 my $bl = $len - 1;
147 849         22402 ( $a . ($b x $bl), qr/\G${lead}\K\Q$a$b\E{0,$bl}(?!\Q$b\E)/, $len );
148 570         6560 };
149             }
150              
151             sub char_range {
152 576     576 0 935 my $obj = shift;
153 576   50     1235 my $v = $obj->{visible} // 0;
154 576 50       1335 if ($v == 0) { $char_range{STRAIGHT} }
  576 0       2292  
    0          
155 0           elsif ($v == 1) { $char_range{MODERATE} }
156 0           elsif ($v == 2) { $char_range{VISIBLE} }
157 0           else { die }
158             }
159              
160             1;
161              
162             __END__