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 = '0.9901';
4              
5 9     9   167899 use v5.14;
  9         79  
6 9     9   49 use warnings;
  9         18  
  9         230  
7 9     9   752 use utf8;
  9         27  
  9         45  
8 9     9   194 use Carp;
  9         15  
  9         459  
9 9     9   681 use Data::Dumper;
  9         7046  
  9         542  
10             {
11 9     9   5653 no warnings 'redefine', 'once';
  9         23  
  9         8276  
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 861466 my $class = shift;
35 582         3577 my $obj = bless { %default }, $class;
36 582 50       2683 $obj->configure(@_) if @_;
37 582         1185 $obj;
38             }
39              
40             sub configure {
41 582     582 0 872 my $obj = shift;
42 582         1870 while (my($k, $v) = splice @_, 0, 2) {
43 2328 50       4536 if (not exists $default{$k}) {
44 0         0 croak "$k: invalid parameter";
45             }
46 2328 100       4042 if ($k eq 'test') {
47 582         812 $obj->{$k} = do {
48 582 50   0   1538 if (not $v) { sub { 1 } }
  0 50       0  
  0 0       0  
49 582     893   3347 elsif (ref $v eq 'Regexp') { sub { $_[0] =~ $v } }
  893         5639  
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     3547 $k eq 'length' and ( ref $v eq 'CODE' or die );
55 1746         5073 $obj->{$k} = $v;
56             }
57             }
58 582         865 $obj;
59             }
60              
61             sub encode {
62 582     582 1 2250 my $obj = shift;
63 582         1586 $obj->{replace} = [];
64 582 100       2255 my $conceal = $obj->concealer(grep defined, $obj->{except}, @_)
65             or return @_;
66 576 50       1564 my $match = $obj->{match} or die;
67 576         947 my $test = $obj->{test};
68 576         1782 for my $arg (grep defined, @_) {
69 893 50 100     2395 $test->($arg) or next if $test;
70 856         4363 $arg =~ s{$match}{
71 856 100       1753 if (my($replace, $regex, $len) = $conceal->(${^MATCH})) {
72 855         1845 push @{$obj->{replace}}, [ $regex, ${^MATCH}, $len ];
  855         3029  
73 855         3444 $replace;
74             } else {
75 1         6 ${^MATCH};
76             }
77             }pge;
78             }
79 576         4389 @_;
80             }
81              
82             sub decode {
83 582     582 1 6376 my $obj = shift;
84 582 100       792 my @replace = @{$obj->{replace}} or return @_;
  582         1912  
85             ARGS:
86 569         1254 for (@_) {
87 1169         2877 for my $i (0 .. $#replace) {
88 855         1122 my($regex, $orig, $len) = @{$replace[$i]};
  855         1821  
89 855 100       7843 if (s/$regex/_replace(${^MATCH}, $orig, $len)/pe) {
  600         1383  
90 600 50       1215 if ($obj->{ordered}) {
91 600         1149 splice @replace, 0, $i + 1;
92             } else {
93 0         0 splice @replace, $i, 1;
94             }
95 600         1521 redo ARGS;
96             }
97             }
98             }
99 569         1175 @_;
100             }
101              
102             sub _replace {
103 600     600   1512 my($matched, $orig, $len) = @_;
104 600         1145 my $width = length $matched;
105 600 100       1145 if ($width == $len) {
106 593         2448 $orig;
107             } else {
108 7         18 _trim($orig, $width);
109             }
110             }
111              
112             sub _trim {
113 7     7   17 my($str, $width) = @_;
114 9     9   5113 use Text::ANSI::Fold;
  9         530004  
  9         4819  
115 7         21 state $f = Text::ANSI::Fold->new(padding => 1);
116 7         84 my($folded, $rest, $w) = $f->fold($str, width => $width);
117 7 100       18690 if ($w <= $width) {
    50          
118 6         31 $folded;
119             } elsif ($width == 1) {
120 1         6 ' '; # 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 924 my $obj = shift;
128 582         1026 my $max = $obj->{max};
129 582         1870 local $_ = join '', @_;
130 582         845 my @a;
131 582         836 my @range = do {
132 2328         10441 map { $_->[0] .. $_->[1] }
133 582   33     718 @{ $obj->{range} //= $obj->char_range };
  582         2352  
134             };
135 582         1585 for my $i (@range) {
136 61935         91247 my $c = pack "C", $i;
137 61935 100       129855 push @a, $c if index($_, $c) < 0;
138 61935 100 66     168163 last if $max && @a > $max;
139             }
140 582 100       1374 return if @a < 2;
141 576         801 my $lead = do { local $" = ''; qr/[^\Q@a\E]*+/ };
  576         992  
  576         11317  
142 576         1496 my $b = shift @a;
143             return sub {
144 856     856   3672 my $len = $obj->{length}->($_[0] =~ s/\X\cH+//gr);
145 856 100       29558 return if $len < 1;
146 855         2636 my $a = $a[ (state $n)++ % @a ];
147 855         1475 my $bl = $len - 1;
148 855         22330 ( $a . ($b x $bl), qr/\A${lead}\K\Q$a$b\E{0,$bl}(?!\Q$b\E)/, $len );
149 576         6482 };
150             }
151              
152             sub char_range {
153 582     582 0 866 my $obj = shift;
154 582   50     1280 my $v = $obj->{visible} // 0;
155 582 50       1048 if ($v == 0) { $char_range{STRAIGHT} }
  582 0       2245  
    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__