File Coverage

blib/lib/Text/VisualPrintf/Transform.pm
Criterion Covered Total %
statement 101 114 88.6
branch 34 48 70.8
condition 8 15 53.3
subroutine 17 20 85.0
pod 3 6 50.0
total 163 203 80.3


line stmt bran cond sub pod time code
1             package Text::VisualPrintf::Transform;
2              
3 13     13   172 use v5.14;
  13         45  
4 13     13   71 use warnings;
  13         27  
  13         351  
5 13     13   667 use utf8;
  13         36  
  13         80  
6 13     13   307 use Carp;
  13         24  
  13         679  
7 13     13   79 use Data::Dumper;
  13         17  
  13         606  
8             {
9 13     13   116 no warnings 'redefine', 'once';
  13         40  
  13         12439  
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 593     593 1 1190 my $class = shift;
33 593         4009 my $obj = bless { %default }, $class;
34 593 50       2946 $obj->configure(@_) if @_;
35 593         1403 $obj;
36             }
37              
38             sub configure {
39 593     593 0 1020 my $obj = shift;
40 593         2100 while (my($k, $v) = splice @_, 0, 2) {
41 2372 50       5067 if (not exists $default{$k}) {
42 0         0 croak "$k: invalid parameter";
43             }
44 2372 100       4380 if ($k eq 'test') {
45 593         860 $obj->{$k} = do {
46 593 50   0   1778 if (not $v) { sub { 1 } }
  0 50       0  
  0 0       0  
47 593     904   3691 elsif (ref $v eq 'Regexp') { sub { $_[0] =~ $v } }
  904         6501  
48 0         0 elsif (ref $v eq 'CODE') { $v }
49 0     0   0 else { sub { 1 } }
  0         0  
50             };
51             } else {
52 1779 100 50     4053 $k eq 'length' and ( ref $v eq 'CODE' or die );
53 1779         5457 $obj->{$k} = $v;
54             }
55             }
56 593         1265 $obj;
57             }
58              
59             sub encode {
60 593     593 1 899 my $obj = shift;
61 593         1931 $obj->{replace} = [];
62 593 100 50     2412 my $guard = $obj->guard_maker($obj->{except} // '', @_)
63             or return @_;
64 587 50       1719 my $match = $obj->{match} or die;
65 587         991 my $test = $obj->{test};
66 587         1230 for my $arg (grep { defined } @_) {
  904         2563  
67 904 100 66     3050 not $test or $test->($arg) or next;
68 867         4978 $arg =~ s{$match}{
69 867 100       1881 if (my($replace, $regex, $len) = $guard->(${^MATCH})) {
70 866         1924 push @{$obj->{replace}}, [ $regex, ${^MATCH}, $len ];
  866         3468  
71 866         3712 $replace;
72             } else {
73 1         6 ${^MATCH};
74             }
75             }pge;
76             }
77 587         4609 @_;
78             }
79              
80             sub decode {
81 593     593 1 1079 my $obj = shift;
82 593 100       874 my @replace = @{$obj->{replace}} or return @_;
  593         2078  
83             ARGS:
84 580         1327 for (@_) {
85 1191         3222 for my $i (0 .. $#replace) {
86 866         1246 my($regex, $orig, $len) = @{$replace[$i]};
  866         1976  
87 866 100       8433 if (s/$regex/_replace(${^MATCH}, $orig, $len)/pe) {
  611         1524  
88 611 50       1398 if ($obj->{ordered}) {
89 611         1375 splice @replace, 0, $i + 1;
90             } else {
91 0         0 splice @replace, $i, 1;
92             }
93 611         1725 redo ARGS;
94             }
95             }
96             }
97 580         1338 @_;
98             }
99              
100             sub _replace {
101 611     611   1651 my($matched, $orig, $len) = @_;
102 611         1232 my $width = length $matched;
103 611 100       1328 if ($width == $len) {
104 604         2639 $orig;
105             } else {
106 7         17 _trim($orig, $width);
107             }
108             }
109              
110             sub _trim {
111 7     7   17 my($str, $width) = @_;
112 13     13   6973 use Text::ANSI::Fold;
  13         402353  
  13         6394  
113 7         21 state $f = Text::ANSI::Fold->new(padding => 1);
114 7         77 my($folded, $rest, $w) = $f->fold($str, width => $width);
115 7 100       2817 if ($w <= $width) {
    50          
116 6         65 $folded;
117             } elsif ($width == 1) {
118 1         7 ' '; # wide char not fit to single column
119             } else {
120 0         0 die "Panic"; # should never reach here...
121             }
122             }
123              
124             sub guard_maker {
125 593     593 0 969 my $obj = shift;
126 593         1055 my $max = $obj->{max};
127 593         2148 local $_ = join '', @_;
128 593         977 my @a;
129 593         840 my @range = do {
130 2372         11241 map { $_->[0] .. $_->[1] }
131 593   33     874 @{ $obj->{range} //= $obj->char_range };
  593         2434  
132             };
133 593         1746 for my $i (@range) {
134 61957         119983 my $c = pack "C", $i;
135 61957 100       455143 push @a, $c unless /\Q$c/;
136 61957 100 66     224317 last if $max && @a > $max;
137             }
138 593 100       1495 return if @a < 2;
139 587         924 my $lead = do { local $" = ''; qr/[^\Q@a\E]*+/ };
  587         1223  
  587         9821  
140 587         1572 my $b = shift @a;
141             return sub {
142 867     867   4203 my $len = $obj->{length}->(+shift =~ s/\X\cH+//gr);
143 867 100       117671 return if $len < 1;
144 866         2604 my $a = $a[ (state $n)++ % @a ];
145 866         1676 my $bl = $len - 1;
146 866         23036 ( $a . ($b x $bl), qr/\G${lead}\K\Q$a$b\E{0,$bl}(?!\Q$b\E)/, $len );
147 587         6784 };
148             }
149              
150             sub char_range {
151 593     593 0 983 my $obj = shift;
152 593   50     1315 my $v = $obj->{visible} // 0;
153 593 50       1233 if ($v == 0) { $char_range{STRAIGHT} }
  593 0       2297  
    0          
154 0           elsif ($v == 1) { $char_range{MODERATE} }
155 0           elsif ($v == 2) { $char_range{VISIBLE} }
156 0           else { die }
157             }
158              
159             1;
160              
161             __END__