File Coverage

blib/lib/Text/Conceal.pm
Criterion Covered Total %
statement 103 117 88.0
branch 35 52 67.3
condition 9 14 64.2
subroutine 17 20 85.0
pod 3 6 50.0
total 167 209 79.9


line stmt bran cond sub pod time code
1             package Text::Conceal;
2              
3             our $VERSION = '1.05';
4              
5 11     11   697942 use v5.14;
  11         44  
6 11     11   63 use warnings;
  11         22  
  11         704  
7 11     11   893 use utf8;
  11         405  
  11         91  
8 11     11   349 use Carp;
  11         21  
  11         785  
9 11     11   857 use Data::Dumper;
  11         11556  
  11         772  
10             {
11 11     11   84 no warnings 'redefine', 'once';
  11         19  
  11         11645  
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             duplicate => 0,
32             zerowidth => undef,
33             );
34              
35             sub new {
36 592     592 1 4774176 my $class = shift;
37 592         5247 my $obj = bless { %default }, $class;
38 592 50       4458 $obj->configure(@_) if @_;
39 592         1776 $obj;
40             }
41              
42             sub configure {
43 592     592 0 1167 my $obj = shift;
44 592         3096 while (my($k, $v) = splice @_, 0, 2) {
45 3556 50       8511 if (not exists $default{$k}) {
46 0         0 croak "$k: invalid parameter";
47             }
48 3556 100       7821 if ($k eq 'test') {
49 592         978 $obj->{$k} = do {
50 592 50   0   2321 if (not $v) { sub { 1 } }
  0 50       0  
  0 0       0  
51 592     906   4862 elsif (ref $v eq 'Regexp') { sub { $_[0] =~ $v } }
  906         8060  
52 0         0 elsif (ref $v eq 'CODE') { $v }
53 0     0   0 else { sub { 1 } }
  0         0  
54             };
55             } else {
56 2964 100 50     7180 $k eq 'length' and ( ref $v eq 'CODE' or die );
57 2964         11017 $obj->{$k} = $v;
58             }
59             }
60 592         1092 $obj;
61             }
62              
63             sub encode {
64 592     592 1 3486 my $obj = shift;
65 592         1572 $obj->{replace} = [];
66 592 100       3357 my $conceal = $obj->concealer(grep defined, $obj->{except}, @_)
67             or return @_;
68 586 50       2188 my $match = $obj->{match} or die;
69 586         1233 my $test = $obj->{test};
70 586         2465 for my $arg (grep defined, @_) {
71 906 50 100     2968 $test->($arg) or next if $test;
72 869         5737 $arg =~ s{$match}{
73 869 100       2041 if (my($replace, $regex, $len) = $conceal->(${^MATCH})) {
74 866         1812 push @{$obj->{replace}}, [ $regex, ${^MATCH}, $len ];
  866         4586  
75 866         4549 $replace;
76             } else {
77 3         24 ${^MATCH};
78             }
79             }pge;
80             }
81 586         7153 @_;
82             }
83              
84             sub decode {
85 592     592 1 6853 my $obj = shift;
86 592 100       923 my @replace = @{$obj->{replace}} or return @_;
  592         2536  
87             ARGS:
88 577         1481 for (@_) {
89             # do not use "each @replace" here
90 1185         2862 for my $i (keys @replace) {
91 2089         3007 my($regex, $orig, $len) = @{$replace[$i]};
  2089         4769  
92 2089   100     4251 $regex // next;
93 2085 100       16434 if (s/$regex/_replace(${^MATCH}, $orig, $len)/pe) {
  608         1575  
94 608 50       1900 if ($obj->{duplicate}) {
    0          
95             ;
96             } elsif ($obj->{ordered}) {
97 0         0 splice @replace, 0, $i + 1;
98             } else {
99 0         0 splice @replace, $i, 1;
100             }
101 608         1890 redo ARGS;
102             }
103             }
104             }
105 577         2030 @_;
106             }
107              
108             sub _replace {
109 608     608   1988 my($matched, $orig, $len) = @_;
110 608         1345 my $width = length $matched;
111 608 100       1530 if ($width == $len) {
112 601         4273 $orig;
113             } else {
114 7         22 _trim($orig, $width);
115             }
116             }
117              
118             sub _trim {
119 7     7   16 my($str, $width) = @_;
120 11     11   6609 use Text::ANSI::Fold;
  11         597572  
  11         8061  
121 7         30 state $f = Text::ANSI::Fold->new(padding => 1);
122 7         119 my($folded, $rest, $w) = $f->fold($str, width => $width);
123 7 100       4333 if ($w <= $width) {
    50          
124 6         43 $folded;
125             } elsif ($width == 1) {
126 1         8 ' '; # wide char not fit to single column
127             } else {
128 0         0 die "Panic"; # should never reach here...
129             }
130             }
131              
132             sub concealer {
133 592     592 0 1166 my $obj = shift;
134 592         1270 my $max = $obj->{max};
135 592         2528 local $_ = join '', @_;
136 592         1043 my @a;
137 592         1005 my @range = do {
138 2368         16252 map { $_->[0] .. $_->[1] }
139 592   33     880 @{ $obj->{range} //= $obj->char_range };
  592         4150  
140             };
141 592         2073 for my $i (@range) {
142 61958         113693 my $c = pack "C", $i;
143 61958 100       147842 push @a, $c if index($_, $c) < 0;
144 61958 100 66     202303 last if $max && @a > $max;
145             }
146 592 100       1705 return if @a < 2;
147 586         880 my $lead = do { local $" = ''; qr/[^\Q@a\E]*+/ };
  586         2498  
  586         23812  
148 586         1903 my $b = shift @a;
149             return sub {
150 869     869   7887 my $len = $obj->{length}->($_[0] =~ s/\X\cH+//gr);
151 869 100       234382 if ($len == 0) {
152 7 100       55 if (defined(my $zerowidth = $obj->{zerowidth})) {
153 4         21 return($zerowidth, undef, $len);
154             } else {
155 3         16 return;
156             }
157             }
158 862         2932 my $a = $a[ (state $n)++ % @a ];
159 862         1684 my $bl = $len - 1;
160 862         39036 ( $a . ($b x $bl), qr/\A${lead}\K\Q$a$b\E{0,$bl}(?!\Q$b\E)/, $len );
161 586         11647 };
162             }
163              
164             sub char_range {
165 592     592 0 1581 my $obj = shift;
166 592   50     1991 my $v = $obj->{visible} // 0;
167 592 50       1369 if ($v == 0) { $char_range{STRAIGHT} }
  592 0       3374  
    0          
168 0           elsif ($v == 1) { $char_range{MODERATE} }
169 0           elsif ($v == 2) { $char_range{VISIBLE} }
170 0           else { die }
171             }
172              
173             1;
174              
175             __END__