File Coverage

blib/lib/Text/Conceal.pm
Criterion Covered Total %
statement 100 113 88.5
branch 33 48 68.7
condition 7 12 58.3
subroutine 17 20 85.0
pod 3 6 50.0
total 160 199 80.4


line stmt bran cond sub pod time code
1             package Text::Conceal;
2              
3             our $VERSION = '1.01';
4              
5 9     9   143860 use v5.14;
  9         94  
6 9     9   42 use warnings;
  9         16  
  9         328  
7 9     9   520 use utf8;
  9         4795  
  9         56  
8 9     9   209 use Carp;
  9         13  
  9         462  
9 9     9   513 use Data::Dumper;
  9         5591  
  9         357  
10             {
11 9     9   106 no warnings 'redefine', 'once';
  9         18  
  9         6925  
12 0     0   0 *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  0         0  
13             $Data::Dumper::Useperl = 1;
14             $Data::Dumper::Sortkey = 1;
15             }
16              
17             my %char_range = (
18             STRAIGHT => [ [0x01=>0x07], [0x10=>0x1f], [0x21=>0x7e], [0x81=>0xfe] ],
19             MODERATE => [ [0x21=>0x7e], [0x01=>0x07], [0x10=>0x1f], [0x81=>0xfe] ],
20             VISIBLE => [ [0x21=>0x7e] ],
21             );
22              
23             my %default = (
24             test => undef,
25             length => sub { length $_[0] },
26             match => qr/.+/s,
27             except => '',
28             max => 0,
29             visible => 0,
30             ordered => 1,
31             );
32              
33             sub new {
34 582     582 1 726353 my $class = shift;
35 582         3208 my $obj = bless { %default }, $class;
36 582 50       2446 $obj->configure(@_) if @_;
37 582         988 $obj;
38             }
39              
40             sub configure {
41 582     582 0 744 my $obj = shift;
42 582         1672 while (my($k, $v) = splice @_, 0, 2) {
43 2328 50       3973 if (not exists $default{$k}) {
44 0         0 croak "$k: invalid parameter";
45             }
46 2328 100       3318 if ($k eq 'test') {
47 582         667 $obj->{$k} = do {
48 582 50   0   1429 if (not $v) { sub { 1 } }
  0 50       0  
  0 0       0  
49 582     893   3167 elsif (ref $v eq 'Regexp') { sub { $_[0] =~ $v } }
  893         4779  
50 0         0 elsif (ref $v eq 'CODE') { $v }
51 0     0   0 else { sub { 1 } }
  0         0  
52             };
53             } else {
54 1746 100 50     3240 $k eq 'length' and ( ref $v eq 'CODE' or die );
55 1746         4271 $obj->{$k} = $v;
56             }
57             }
58 582         748 $obj;
59             }
60              
61             sub encode {
62 582     582 1 2021 my $obj = shift;
63 582         1522 $obj->{replace} = [];
64 582 100       2050 my $conceal = $obj->concealer(grep defined, $obj->{except}, @_)
65             or return @_;
66 576 50       1374 my $match = $obj->{match} or die;
67 576         771 my $test = $obj->{test};
68 576         1595 for my $arg (grep defined, @_) {
69 893 50 100     2147 $test->($arg) or next if $test;
70 856         3809 $arg =~ s{$match}{
71 856 100       1648 if (my($replace, $regex, $len) = $conceal->(${^MATCH})) {
72 855         1377 push @{$obj->{replace}}, [ $regex, ${^MATCH}, $len ];
  855         2546  
73 855         3033 $replace;
74             } else {
75 1         5 ${^MATCH};
76             }
77             }pge;
78             }
79 576         3659 @_;
80             }
81              
82             sub decode {
83 582     582 1 6019 my $obj = shift;
84 582 100       745 my @replace = @{$obj->{replace}} or return @_;
  582         1642  
85             ARGS:
86 569         1120 for (@_) {
87 1169         2466 for my $i (0 .. $#replace) {
88 855         937 my($regex, $orig, $len) = @{$replace[$i]};
  855         1541  
89 855 100       6920 if (s/$regex/_replace(${^MATCH}, $orig, $len)/pe) {
  600         1210  
90 600 50       1317 if ($obj->{ordered}) {
91 600         1049 splice @replace, 0, $i + 1;
92             } else {
93 0         0 splice @replace, $i, 1;
94             }
95 600         1310 redo ARGS;
96             }
97             }
98             }
99 569         997 @_;
100             }
101              
102             sub _replace {
103 600     600   1328 my($matched, $orig, $len) = @_;
104 600         991 my $width = length $matched;
105 600 100       976 if ($width == $len) {
106 593         1979 $orig;
107             } else {
108 7         17 _trim($orig, $width);
109             }
110             }
111              
112             sub _trim {
113 7     7   14 my($str, $width) = @_;
114 9     9   4442 use Text::ANSI::Fold;
  9         440631  
  9         4128  
115 7         18 state $f = Text::ANSI::Fold->new(padding => 1);
116 7         75 my($folded, $rest, $w) = $f->fold($str, width => $width);
117 7 100       16145 if ($w <= $width) {
    50          
118 6         33 $folded;
119             } elsif ($width == 1) {
120 1         5 ' '; # wide char not fit to single column
121             } else {
122 0         0 die "Panic"; # should never reach here...
123             }
124             }
125              
126             sub concealer {
127 582     582 0 823 my $obj = shift;
128 582         885 my $max = $obj->{max};
129 582         1816 local $_ = join '', @_;
130 582         819 my @a;
131 582         641 my @range = do {
132 2328         9245 map { $_->[0] .. $_->[1] }
133 582   33     676 @{ $obj->{range} //= $obj->char_range };
  582         2049  
134             };
135 582         1248 for my $i (@range) {
136 61935         75815 my $c = pack "C", $i;
137 61935 100       104395 push @a, $c if index($_, $c) < 0;
138 61935 100 66     144367 last if $max && @a > $max;
139             }
140 582 100       1296 return if @a < 2;
141 576         687 my $lead = do { local $" = ''; qr/[^\Q@a\E]*+/ };
  576         846  
  576         10758  
142 576         1168 my $b = shift @a;
143             return sub {
144 856     856   3380 my $len = $obj->{length}->($_[0] =~ s/\X\cH+//gr);
145 856 100       25329 return if $len < 1;
146 855         2390 my $a = $a[ (state $n)++ % @a ];
147 855         1201 my $bl = $len - 1;
148 855         19450 ( $a . ($b x $bl), qr/\A${lead}\K\Q$a$b\E{0,$bl}(?!\Q$b\E)/, $len );
149 576         5570 };
150             }
151              
152             sub char_range {
153 582     582 0 766 my $obj = shift;
154 582   50     1240 my $v = $obj->{visible} // 0;
155 582 50       1079 if ($v == 0) { $char_range{STRAIGHT} }
  582 0       2246  
    0          
156 0           elsif ($v == 1) { $char_range{MODERATE} }
157 0           elsif ($v == 2) { $char_range{VISIBLE} }
158 0           else { die }
159             }
160              
161             1;
162              
163             __END__