File Coverage

blib/lib/App/ansiecho.pm
Criterion Covered Total %
statement 140 143 97.9
branch 52 58 89.6
condition 24 31 77.4
subroutine 22 23 95.6
pod 0 4 0.0
total 238 259 91.8


line stmt bran cond sub pod time code
1             package App::ansiecho;
2              
3             our $VERSION = "1.10";
4              
5 2     2   353872 use v5.14;
  2         6  
6 2     2   9 use warnings;
  2         2  
  2         97  
7              
8 2     2   468 use utf8;
  2         226  
  2         10  
9 2     2   575 use Encode;
  2         15039  
  2         164  
10 2     2   448 use Data::Dumper;
  2         6477  
  2         108  
11             {
12 2     2   9 no warnings 'redefine';
  2         2  
  2         176  
13 0     0   0 *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  0         0  
14             $Data::Dumper::Useperl = 1;
15             }
16 2     2   674 use open IO => 'utf8', ':std';
  2         1039  
  2         15  
17 2     2   1111 use Pod::Usage;
  2         89035  
  2         317  
18              
19 2     2   1065 use App::ansiecho::Util qw(decode_argv unescape);
  2         30  
  2         264  
20 2     2   1432 use Getopt::EX v1.24.1;
  2         445  
  2         144  
21 2     2   1346 use Text::ANSI::Printf 2.03 qw(ansi_sprintf);
  2         74915  
  2         237  
22             $Text::ANSI::Printf::REORDER = 1;
23 2     2   22 use List::Util qw(max);
  2         4  
  2         162  
24              
25 2     2   4776 use Getopt::EX::Hashed; {
  2         24363  
  2         22  
26             Getopt::EX::Hashed->configure(DEFAULT => [ is => 'rw' ]);
27             has debug => " " ;
28             has n => " " , action => sub { $_->terminate = '' };
29             has join => " j " , action => sub { $_->separate = '' };
30             has escape => " e ! " , default => 1;
31             has rgb24 => " ! " ;
32             has separate => " =s " , default => " ";
33             has help => " h " ;
34             has version => " v " ;
35              
36             has '+separate' => sub {
37             my($name, $arg) = map "$_", @_;
38             $_->$name = unescape($arg);
39             };
40              
41             has '+rgb24' => sub {
42             $Term::ANSIColor::Concise::RGB24 = !!$_[1];
43             };
44              
45             has '+help' => sub {
46             pod2usage
47             -verbose => 99,
48             -sections => [ qw(SYNOPSIS VERSION) ];
49             };
50              
51             has '+version' => sub {
52             say "Version: $VERSION";
53             exit;
54             };
55              
56             has terminate => default => "\n";
57             has params => default => [];
58              
59 2     2   1412 } no Getopt::EX::Hashed;
  2         5  
  2         16  
60              
61             sub run {
62 103     103 0 798064 my $app = shift;
63 103         358 $app->options(@_);
64 103         397 print $app->retrieve, $app->terminate;
65             }
66              
67             sub options {
68 103     103 0 207 my $app = shift;
69 103         511 my @argv = decode_argv @_;
70 2     2   2187 use Getopt::EX::Long qw(GetOptionsFromArray Configure ExConfigure);
  2         270522  
  2         470  
71 103         3012 ExConfigure BASECLASS => [ __PACKAGE__, "Getopt::EX" ];
72 103         4126 Configure qw(bundling no_getopt_compat pass_through);
73 103 50       8106 $app->getopt(\@argv) || pod2usage();
74 103         277867 $app->params(\@argv);
75 103         1060 $app;
76             }
77              
78 2     2   23 use Term::ANSIColor::Concise qw(ansi_color ansi_code);
  2         5  
  2         5488  
79              
80             sub uniname {
81 11 50   11 0 33 local $_ = @_ ? shift : $_;
82 11 50       87 my @names = map { /^[\da-f]{1,6}$/i ? "\\N{U+$_}" : "\\N{$_}" } /[-+_\w ]+/g;
  11         82  
83 11         49 unescape join '', @names;
84             }
85              
86             sub retrieve {
87 245     245 0 466 my $app = shift;
88 245   100     989 my $count = shift // 0;
89 245         2185 my $in = $app->params;
90 245         1971 my(@style, @effect);
91              
92 245         0 my @out;
93 245         0 my @pending;
94 245     12   1231 my $charge = sub { push @pending, @_ };
  12         27  
95             my $discharge = sub {
96 594 100 100 594   2584 return if @pending == 0 and @_ == 0;
97 353 100 100     1309 if ($count == 0 and @out > 0 and $app->separate ne '') {
      100        
98 57         455 push @out, $app->separate;
99             }
100 353         1412 push @out, join '', splice(@pending), @_;
101 245         995 };
102              
103 245         817 while (@$in) {
104 427         933 my $arg = shift @$in;
105              
106             # -x : set separator
107 427 100       1244 if ($arg =~ /^-x(?u)?$/) {
108 3         23 my $uni = %+;
109 3   50     14 my $sep = $app->retrieve(1) // die "Not enough argument.\n";
110 3 100       43 $app->separate = $uni ? uniname($sep) : $sep;
111 3         27 next;
112             }
113             # -S
114 424 100       1080 if ($arg =~ /^-S$/) {
115 4         17 unshift @style, [ \&ansi_code ];
116 4         12 next;
117             }
118             # -c, -C
119 420 100       1191 if ($arg =~ /^-([cC])(.+)?$/) {
120 30 100       125 my $target = $1 eq 'c' ? \@effect : \@style;
121 30 100       144 my $color = defined $2 ? unescape($2) : $app->retrieve(1);
122 29         109 unshift @$target, [ \&ansi_color, $color ];
123 29         88 next;
124             }
125             # -F
126 390 100       958 if ($arg =~ /^-(F)(.+)?$/) {
127 9 100       43 my $format = defined $2 ? unescape($2) : $app->retrieve(1);
128 9         36 unshift @style, [ \&ansi_sprintf, $format ];
129 9         27 next;
130             }
131             # -U
132 381 100       857 if ($arg =~ /^-(U)$/) {
133 2         10 unshift @style, [ \&uniname ];
134 2         6 next;
135             }
136             # -E
137 379 100       937 if ($arg =~ /^-E$/) {
138 5         18 @style = ();
139 5         35 next;
140             }
141              
142             #
143             # -s, -i, -a : ANSI sequence
144             #
145 374 100       1474 if ($arg =~ /^-([sia])(.+)?$/) {
    100          
146 18         57 my $opt = $1;
147 18   33     98 my $text = $2 // shift(@$in) // die "Not enough argument.\n";
      50        
148 18         131 my $code = ansi_code($text);
149 18 50       3295 if ($opt eq 's') {
150 0         0 $arg = $code;
151             } else {
152 18 100 100     89 if (@out == 0 or $opt eq 'i') {
153 12         30 $charge->($code);
154             } else {
155 6         21 $out[-1] .= $code;
156             }
157 18         68 next;
158             }
159             }
160             #
161             # -f : format
162             #
163             elsif ($arg =~ /^-f(.+)?$/) {
164 61 50       358 my($format) = defined $1 ? unescape($1) : $app->retrieve(1);
165 60         124 state $param_re = do {
166 1         5 my $P = qr/\d+\$/;
167 1         48 my $W = qr/\d+|\*$P?/;
168 1         155 qr{ %% |
169             (? % $P?) [-+#0\ ]*+
170             (?: (? \* $P?+ )? v )?+
171             (?: (?$W) (?:\.(?$W))?+ | \.(?$W) )?+
172             (?:[hjlqLltz]|[csduoxefgXEGbBpnaAiDUOF])
173             }x;
174             };
175 60         148 my($pos, $n) = (0, 0);
176 60         963 while ($format =~ /$param_re/g) {
177 83   100     753 $+{I} // next;
178 77 100       636 for ($+{I}, grep { defined and /\*/ } @+{qw(I V A B C)}) {
  385         1729  
179 113 100       523 if (/(\d+)\$/) {
180 32         418 $pos = max($pos, $1);
181             } else {
182 81         507 $n++;
183             }
184             }
185             }
186 60         229 $n = max($pos, $n);
187 60         233 $arg = ansi_sprintf($format, $app->retrieve($n));
188             }
189             #
190             # -u : Unicode Name
191             #
192 353 100       49013 if ($arg =~ /^-u(.+)?$/) {
193 2         7 my $opt = $1;
194 2   33     16 my $name = $2 // shift(@$in) // die "Not enough argument.\n";
      50        
195 2         9 $arg = uniname($name);
196             }
197             #
198             # normal string argument
199             #
200             else {
201 351 100       1181 if ($app->escape) {
202 350         3064 $arg = unescape($arg);
203             }
204             }
205              
206             #
207             # apply @effect and @style
208             #
209 353         1153 for (splice(@effect), @style) {
210 86         4851 my($func, @opts) = @$_;
211 86         306 $arg = $func->(@opts, $arg);
212             }
213              
214 353         11840 $discharge->($arg);
215              
216 353 100       962 if ($count) {
217 188         418 my $out = @out + !!@pending;
218 188 50       583 die "Unexpected behavior.\n" if $out > $count;
219 188 100       540 last if $out == $count;
220             }
221             }
222 241         572 $discharge->();
223 241 100 100     946 die "Not enough argument.\n" if $count and @out < $count;
224 237 100       2550 wantarray ? @out : $out[0];
225             }
226              
227             1;
228              
229             __END__