File Coverage

blib/lib/Acme/EyeDrops.pm
Criterion Covered Total %
statement 430 450 95.5
branch 255 276 92.3
condition 142 170 83.5
subroutine 51 53 96.2
pod 29 29 100.0
total 907 978 92.7


line stmt bran cond sub pod time code
1             package Acme::EyeDrops;
2             require 5.006;
3 20     20   36956 use strict;
  20         43  
  20         713  
4 20     20   103 use warnings;
  20         41  
  20         663  
5 20     20   107 use vars qw($VERSION @ISA @EXPORT_OK);
  20         51  
  20         202764  
6             require Exporter; @ISA = qw(Exporter);
7             @EXPORT_OK = qw(ascii_to_sightly sightly_to_ascii
8             regex_print_sightly regex_eval_sightly clean_print_sightly
9             clean_eval_sightly regex_binmode_print_sightly
10             clean_binmode_print_sightly
11             get_eye_dir get_eye_shapes get_eye_string get_builtin_shapes
12             get_eye_properties get_eye_keywords find_eye_shapes
13             make_triangle make_siertri make_banner
14             border_shape invert_shape reflect_shape rotate_shape
15             reduce_shape expand_shape hjoin_shapes
16             pour_text pour_sightly sightly);
17             $VERSION = '1.61';
18             { # This table was generated by demo/gentable.pl.
19             my @C = (
20             q Z('!'^'!')Z,q Z('('^')')Z,q Z('<'^'>')Z,q Z('>'^'=')Z,
21             q Z('>'^':')Z,q Z('>'^';')Z,q Z('+'^'-')Z,q Z('*'^'-')Z,
22             q Z('+'^'#')Z,q Z('*'^'#')Z,q Z('!'^'+')Z,q Z('!'^'*')Z,
23             q Z('!'^'-')Z,q Z('!'^',')Z,q Z('!'^'/')Z,q Z('!'^'.')Z,
24             q Z('?'^'/')Z,q Z('<'^'-')Z,q Z('-'^'?')Z,q Z('.'^'=')Z,
25             q Z('+'^'?')Z,q Z('*'^'?')Z,q Z('?'^')')Z,q Z('<'^'+')Z,
26             q Z('%'^'=')Z,q Z('&'^'?')Z,q Z('?'^'%')Z,q Z('>'^'%')Z,
27             q Z('&'^':')Z,q Z('<'^'!')Z,q Z('?'^'!')Z,q Z('%'^':')Z,
28             q Z('{'^'[')Z,q Z'!'Z,q Z'\\\\'.'"'Z,q Z'#'Z,
29             q Z'\\\\'.'$'Z,q Z'%'Z,q Z'&'Z,q Z"'"Z,q Z'('Z,q Z')'Z,
30             q Z'*'Z,q Z'+'Z,q Z','Z,q Z'-'Z,q Z'.'Z,q Z'/'Z,
31             q Z('^'^('`'|'.'))Z,q Z('^'^('`'|'/'))Z,q Z('^'^('`'|','))Z,
32             q Z('^'^('`'|'-'))Z,q Z('^'^('`'|'*'))Z,q Z('^'^('`'|'+'))Z,
33             q Z('^'^('`'|'('))Z,q Z('^'^('`'|')'))Z,q Z(':'&'=')Z,
34             q Z(';'&'=')Z,q Z':'Z,q Z';'Z,q Z'<'Z,q Z'='Z,q Z'>'Z,q Z'?'Z,
35             q Z'\\\\'.'@'Z,q Z('`'^'!')Z,q Z('`'^'"')Z,q Z('`'^'#')Z,
36             q Z('`'^'$')Z,q Z('`'^'%')Z,q Z('`'^'&')Z,q Z('`'^"'")Z,
37             q Z('`'^'(')Z,q Z('`'^')')Z,q Z('`'^'*')Z,q Z('`'^'+')Z,
38             q Z('`'^',')Z,q Z('`'^'-')Z,q Z('`'^'.')Z,q Z('`'^'/')Z,
39             q Z('{'^'+')Z,q Z('{'^'*')Z,q Z('{'^')')Z,q Z('{'^'(')Z,
40             q Z('{'^'/')Z,q Z('{'^'.')Z,q Z('{'^'-')Z,q Z('{'^',')Z,
41             q Z('{'^'#')Z,q Z('{'^'"')Z,q Z('{'^'!')Z,q Z'['Z,
42             q Z'\\\\'.'\\\\'Z,q Z']'Z,q Z'^'Z,q Z'_'Z,
43             q Z'`'Z,q Z('`'|'!')Z,q Z('`'|'"')Z,q Z('`'|'#')Z,
44             q Z('`'|'$')Z,q Z('`'|'%')Z,q Z('`'|'&')Z,q Z('`'|"'")Z,
45             q Z('`'|'(')Z,q Z('`'|')')Z,q Z('`'|'*')Z,q Z('`'|'+')Z,
46             q Z('`'|',')Z,q Z('`'|'-')Z,q Z('`'|'.')Z,q Z('`'|'/')Z,
47             q Z('['^'+')Z,q Z('['^'*')Z,q Z('['^')')Z,q Z('['^'(')Z,
48             q Z('['^'/')Z,q Z('['^'.')Z,q Z('['^'-')Z,q Z('['^',')Z,
49             q Z('['^'#')Z,q Z('['^'"')Z,q Z('['^'!')Z,q Z'\\\\'.'{'Z,
50             q Z'|'Z,q Z'\\\\'.'}'Z,q Z'~'Z,q Z('!'^'^')Z
51             );
52             push @C, map(join('.', q#'\\\\'#, $C[120],
53             map($C[$_], unpack('C*', sprintf('%x', $_)))), 128..255);
54 162     162 1 11860 sub ascii_to_sightly { join '.', map($C[$_], unpack('C*', $_[0])) }
55             }
56 8     8 1 10639 sub sightly_to_ascii { eval eval q#'"'.# . $_[0] . q#.'"'# }
57              
58             sub regex_print_sightly {
59 4     4 1 66 q#''=~('('.'?'.'{'.# . ascii_to_sightly('print') . q#.'"'.# .
60             &ascii_to_sightly . q#.'"'.'}'.')')#;
61             }
62              
63             sub regex_binmode_print_sightly {
64 1     1 1 8 q#''=~('('.'?'.'{'.# . ascii_to_sightly('binmode(STDOUT);print')
65             . q#.'"'.# . &ascii_to_sightly . q#.'"'.'}'.')')#;
66             }
67              
68             sub regex_eval_sightly {
69 68     68 1 966 q#''=~('('.'?'.'{'.# . ascii_to_sightly('eval') . q#.'"'.# .
70             &ascii_to_sightly . q#.'"'.'}'.')')#;
71             }
72              
73             sub clean_print_sightly {
74 1     1 1 43 qq#print eval '"'.\n\n\n# . &ascii_to_sightly . q#.'"'#;
75             }
76              
77             sub clean_binmode_print_sightly {
78 2     2 1 14 qq#binmode(STDOUT);print eval '"'.\n\n\n# .
79             &ascii_to_sightly . q#.'"'#;
80             }
81              
82             sub clean_eval_sightly {
83 5     5 1 26 qq#eval eval '"'.\n\n\n# . &ascii_to_sightly . q#.'"'#;
84             }
85              
86             # -----------------------------------------------------------------
87              
88             sub _slurp_tfile {
89 301     301   31757 my $f = shift;
90 301         418 my $b = shift;
91 301 100       21122 open my $fh, '<', $f or die "open '$f': $!";
92 298 100       669 $b and binmode($fh);
93 298         2646 local $/; my $s = <$fh>; close($fh); $s;
  298         9842  
  298         3524  
  298         2735  
94             }
95              
96             # Poor man's properties (see also YAML, java.util.Properties).
97             # Return ref to property hash.
98             sub _get_properties {
99 601     601   3632 my $f = shift;
100 601 100       23871 open my $fh, '<', $f or die "open '$f': $!";
101 600         656 my $l; my %h;
102 600         6902 while (defined($l = <$fh>)) {
103 1323         1637 chomp($l);
104 1323 100       4802 if ($l =~ s/\\$//) {
105 92         177 my $n = <$fh>; $n =~ s/^\s+//; $l .= $n;
  92         280  
  92         177  
106 92 100       289 redo unless eof($fh);
107             }
108 1244         3140 $l =~ s/^\s+//; $l =~ s/\s+$//;
  1244         5150  
109 1244 100       2238 next unless length($l);
110 1236 100       2426 next if $l =~ /^#/;
111 1223         5163 my ($k, $v) = split(/\s*:\s*/, $l, 2);
112 1223         9028 $h{$k} = $v;
113             }
114 600         5543 close($fh);
115 600         2816 return \%h;
116             }
117              
118 1     1   54 sub _def_ihandler { print STDERR $_[0] }
119              
120             # Return largest no. of tokens with total length less than $slen ($slen > 0).
121             sub _guess_ntok {
122 12321     12321   18475 my ($rtok, $sidx, $slen, $rexact) = @_; my $tlen = 0;
  12321         15098  
123 12321         24196 for my $i ($sidx .. $sidx + $slen) {
124 12321         37374 ($tlen += length($rtok->[$i])) < $slen or
125 100545 100       224201 return $i - $sidx + (${$rexact} = $tlen == $slen);
126             }
127             # should never get here
128             }
129              
130             sub _guess_compact_ntok {
131 2182     2182   3777 my ($rtok, $sidx, $slen, $rexact, $fcompact) = @_; my $tlen = 0;
  2182         2084  
132 2182         3904 for my $i ($sidx .. $sidx + $slen + $slen) {
133 573         1552 ($tlen += length($rtok->[$i]) - ($i > $sidx+1 && $rtok->[$i-1] eq '.'
134             && substr($rtok->[$i], 0, 1) eq "'" && substr($rtok->[$i-2], 0, 1)
135 1351         9517 eq "'" ? (${$fcompact} = 3) : 0)) < $slen or
136 56         145 return $i - $sidx + ($tlen > $slen ? 0 : (${$rexact} = 1) +
137             ($i > $sidx && $rtok->[$i] eq '.' && substr($rtok->[$i-1], 0, 1)
138 18840 100 100     108446 eq "'" && $rtok->[$i+1] =~ /^'..$/ ? (${$fcompact} = 1) : 0));
    100 100        
    100          
    100          
139             }
140             # should never get here
141             }
142              
143             sub _compact_join {
144 167     167   223 my ($rtok, $sidx, $n) = @_; my $s = "";
  167         272  
145 167         286 for my $i ($sidx .. $sidx + $n - 1) {
146 3311 100 100     17411 if ($i > $sidx+1 && $rtok->[$i-1] eq '.' && substr($rtok->[$i], 0, 1)
      100        
      100        
147             eq "'" && substr($rtok->[$i-2], 0, 1) eq "'") {
148 433         900 substr($s, -2) = substr($rtok->[$i], 1); # 'a'.'b' to 'ab'
149             } else {
150 2878         4506 $s .= $rtok->[$i];
151             }
152             }
153 167         498 $s;
154             }
155              
156             # Pour $n tokens from @{$rtok} (starting at index $sidx) into string
157             # of length $slen. Return string or undef if unsuccessful.
158             sub _pour_chunk {
159 6364     6364   9615 my ($rtok, $sidx, $n, $slen) = @_;
160 6364         10212 my $eidx = $sidx + $n - 1; my $tlen = 0;
  6364         6417  
161 6364         8954 my $idot = my $iquote = my $i3quote = my $iparen = my $idollar = -1;
162 6364         10071 for my $i ($sidx .. $eidx) {
163 42363         54541 $tlen += length($rtok->[$i]);
164 42363 100       194372 if ($rtok->[$i] eq '.') { $idot = $i }
  5450 100       8288  
    100          
    100          
165 3739         8123 elsif ($rtok->[$i] eq '(') { $iparen = $i }
166 2950         4274 elsif (substr($rtok->[$i], 0, 1) eq '$') { $idollar = $i }
167             elsif ($rtok->[$i] =~ /^['"]/) {
168 12666 100       14536 $iquote = $i; $i3quote = $i if length($rtok->[$i]) == 3;
  12666         32459  
169             }
170             }
171 6364 50       15057 die "oops" if $tlen >= $slen;
172 6364         9354 my $i2 = (my $d = $slen - $tlen) >> 1;
173 234         763 $idot >= 0 && !($d%3) and return join("", @{$rtok}[$sidx .. $idot-1],
  234         1399  
174 6364 100 100     24130 ".''" x int($d/3), @{$rtok}[$idot .. $eidx]);
175 6130 100 100     27448 if (!($d&1) and $iquote >= 0 || $idollar >= 0) {
      66        
176 1709 100       3471 $iquote = $idollar if $iquote < 0;
177 1709         5834 return join("", @{$rtok}[$sidx .. $iquote-1], '(' x $i2 .
  1709         12357  
178 1709         3270 $rtok->[$iquote] . ')' x $i2, @{$rtok}[$iquote+1 .. $eidx]);
179             }
180 2330         10318 $i3quote >= 0 and return join("", @{$rtok}[$sidx .. $i3quote-1],
  2330         19822  
181             $d == 1 ? '"\\' . substr($rtok->[$i3quote], 1, 1) . '"' :
182             '(' x $i2 . '"\\' . substr($rtok->[$i3quote], 1, 1) . '"' .
183 4421 50       11413 ')' x $i2, @{$rtok}[$i3quote+1 .. $eidx]);
    100          
184 2091 100       11728 return unless $d == 1;
185 513         1156 $iparen >= 0 and return join("", @{$rtok}[$sidx .. $iparen-1],
  513         3560  
186 1204 100       2481 '+' . $rtok->[$iparen], @{$rtok}[$iparen+1 .. $eidx]);
187             # ouch, can't test for eq '(' in case next chunk also adds '+'
188 322         2042 $rtok->[$eidx] ne '=' && $rtok->[$sidx+$n] =~ /^['"]/ ?
189 691 100 100     6343 join("", @{$rtok}[$sidx .. $eidx], '+') : undef;
190             }
191              
192             sub _pour_compact_chunk {
193 72     72   105 my ($rtok, $sidx, $n, $slen) = @_; my @mytok;
  72         79  
194 72         181 for my $i ($sidx .. $sidx + $n - 1) {
195 1962 100 100     10591 if ($i > $sidx+1 && $rtok->[$i-1] eq '.' && substr($rtok->[$i], 0, 1)
      100        
      100        
196             eq "'" && substr($rtok->[$i-2], 0, 1) eq "'") {
197 196         213 pop(@mytok); my $qtok = pop(@mytok); # 'a'.'b' to 'ab'
  196         278  
198 196         554 push(@mytok, substr($qtok, 0, -1) . substr($rtok->[$i], 1));
199             } else {
200 1766         3398 push(@mytok, $rtok->[$i]);
201             }
202             }
203 72         156 push(@mytok, $rtok->[$sidx+$n]); # _pour_chunk checks next token
204 72         300 _pour_chunk(\@mytok, 0, $#mytok, $slen);
205             }
206              
207             # Pour unsightly text $txt into shape defined by string $tlines.
208             sub pour_text {
209 35     35 1 4913 my ($tlines, $txt, $gap, $tfill) = @_;
210 35         139 $txt =~ s/\s+//g;
211 35         86 my $ttlen = 0; my $txtend = length($txt);
  35         48  
212 35 100       1918 my @tnlines = map(length() ? [map length, split/([^ ]+)/] : undef,
213             split(/\n/, $tlines));
214 35         139 for my $r (grep($_, @tnlines)) {
215 232 100       243 for my $i (0 .. $#{$r}) { $i & 1 and $ttlen += $r->[$i] }
  232         609  
  1570         3626  
216             }
217 35         90 my $nshape = int($txtend/$ttlen); my $rem = $txtend % $ttlen;
  35         61  
218 35 100 100     158 if ($rem || !$nshape) {
219 29         42 ++$nshape;
220 29 100       126 $txt .= $tfill x (int(($ttlen-$rem)/length($tfill))+1)
221             if length($tfill);
222             }
223 35         51 my $s = ""; my $p = 0;
  35         47  
224 35         54 for (my $n = 1; 1; ++$n, $s .= "\n" x $gap) {
225 43         122 for my $r (@tnlines) {
226 234 100       450 if ($r) {
227 231         237 for my $i (0 .. $#{$r}) {
  231         471  
228 1514 100       2221 if ($i & 1) {
229 757         1006 $s .= substr($txt, $p, $r->[$i]); $p += $r->[$i];
  757         811  
230 757 100 100     6992 return "$s\n" if !length($tfill) && $p >= $txtend;
231             } else {
232 757         1291 $s .= ' ' x $r->[$i];
233             }
234             }
235             }
236 223         575 $s .= "\n";
237             }
238 32 100       109 last if $n >= $nshape;
239             }
240 24         799 $s;
241             }
242              
243             # Make filler code to stuff on end of program to fill last shape.
244             sub _make_filler {
245 91     91   1515 my $fv = shift; # list reference of filler variables
246 91         136 my $nfv = @{$fv};
  91         205  
247             # Beware with these filler values.
248             # Avoid $; $" ';' (to avoid clash with " and ; in later parsing).
249             # END block is trouble because it is executed after this filler.
250             # Setting $^ or $~ (but not $:) to weird values resets $@.
251             # For example: $~='?'&'!'; (this looks like a Perl bug to me).
252             # For now, just stick with letters and numbers.
253 91         2499 my @filleqto = (
254             [ q#'.'#, '^', q^'~'^ ], [ q#'@'#, '|', q^'('^ ],
255             [ q#')'#, '^', q^'['^ ], [ q#'`'#, '|', q^'.'^ ],
256             [ q#'('#, '^', q^'}'^ ], [ q#'`'#, '|', q^'!'^ ],
257             [ q#')'#, '^', q^'}'^ ], [ q#'*'#, '|', q^'`'^ ],
258             [ q#'+'#, '^', q^'_'^ ], [ q#'&'#, '|', q^'@'^ ],
259             [ q#'['#, '&', q^'~'^ ], [ q#','#, '^', q^'|'^ ]
260             );
261 91 100       557 $nfv > @filleqto and die "too many fv";
262 90         555 my $rem = @filleqto % $nfv;
263 90 100       664 $rem and splice(@filleqto, -$rem);
264 90         665 my $v = -1;
265 90         257 map(($fv->[++$v % $nfv], '=', @{$_}, ';'), @filleqto);
  1078         4395  
266             }
267              
268             # Pour sightly program $prog into shape defined by string $tlines.
269             sub pour_sightly {
270 88     88 1 660 my ($tlines, $prog, $gap, $fillv, $compact, $ihandler) = @_;
271 88   100     367 $ihandler ||= \&_def_ihandler;
272 88         194 my $ttlen = 0;
273 88 100       51851 my @tnlines = map(length() ? [map length, split/([^ ]+)/] : undef,
274             split(/\n/, $tlines));
275 88         1359 for my $r (grep($_, @tnlines)) {
276 4403 100       5190 for my $i (0 .. $#{$r}) { $i & 1 and $ttlen += $r->[$i] }
  4403         9733  
  23365         56860  
277             }
278 88         359 my $outstr = ""; my @ptok;
  88         220  
279 88 100       253 if ($prog) {
280 79 100       1434 if ($prog =~ /^''=~/g) {
    100          
281 69 50       1661 push(@ptok, ($tlines =~ /(\S+)/ ? length($1) : 0) == 3 ?
    100          
282             "'?'" : "''", '=~');
283             } elsif ($prog =~ /(.*eval.*\n\n\n)/g) {
284 7         61 $outstr .= $1;
285             }
286 79         56620 push(@ptok, $prog =~ /[().&|^]|'\\\\'|.../g); # ... is "'"|'.'
287             }
288 88         2112 my $iendprog = @ptok;
289 88 100       869 my @filler = _make_filler(ref($fillv) ? $fillv : [ '$:', '$~', '$^' ]);
290             # Note: 11 is the length of a filler item, for example, $:='.'^'~';
291             # And there are 6 tokens in each filler item: $: = '.' ^ '~' ;
292 88         13967 push(@ptok, 'Z', (@filler) x (int($ttlen/(11 * int(@filler / 6))) + 1));
293 88         440 my $sidx = 0;
294 88         728 for (my $nshape = 1; 1; ++$nshape, $outstr .= "\n" x $gap) {
295 171         350 for my $rline (@tnlines) {
296 7775 100       17039 unless ($rline) { $outstr .= "\n"; next }
  204         230  
  204         250  
297 7571         9095 for my $it (0 .. $#{$rline}) {
  7571         16890  
298 40945 100       111459 unless ($it & 1) {$outstr .= ' ' x $rline->[$it]; next }
  20540         74320  
  20540         28630  
299 20405 100       62533 (my $tlen = $rline->[$it]) == (my $plen = length($ptok[$sidx]))
300             and $outstr .= $ptok[$sidx++], next;
301 17606 100       36463 if ($plen > $tlen) {
302 3103         4415 $outstr .= '(' x $tlen;
303 3103         18380 splice(@ptok, $sidx+1, 0, (')') x $tlen);
304 3103 100       16777 $iendprog += $tlen if $sidx < $iendprog;
305 3103         6368 next;
306             }
307 14503         17927 my $fcompact = my $fexact = 0;
308 14503 100       41496 my $n = $compact ?
309             _guess_compact_ntok(\@ptok, $sidx, $tlen, \$fexact, \$fcompact)
310             : _guess_ntok(\@ptok, $sidx, $tlen, \$fexact);
311 14503 100       32370 if ($fexact) {
312 8576 100       36520 $outstr .= $fcompact ? _compact_join(\@ptok, $sidx, $n) :
313             join("", @ptok[$sidx .. $sidx+$n-1]);
314 8576         11465 $sidx += $n; next;
  8576         18412  
315             }
316 5927         6690 my $str;
317 5927 100 100     24125 --$n while $n > 0 && !defined($str = $fcompact ?
318             _pour_compact_chunk(\@ptok, $sidx, $n, $tlen) :
319             _pour_chunk(\@ptok, $sidx, $n, $tlen));
320 5927 100       15770 if ($n) { $outstr .= $str; $sidx += $n; next }
  5108         9749  
  5108         5799  
  5108         10285  
321 819   66     7443 ++$n while $n < $tlen && length($ptok[$sidx+$n]) < 2;
322 819 50       1689 die "oops ($n >= $tlen)" if $n >= $tlen;
323 819         1980 $outstr .= join("", @ptok[$sidx .. $sidx+$n-1]);
324 819         940 $sidx += $n;
325 819         1777 $outstr .= '(' x (my $nleft = $tlen - $n);
326 819         6385 splice(@ptok, $sidx+1, 0, (')') x $nleft);
327 819 100       2781 $iendprog += $nleft if $sidx < $iendprog;
328             }
329 7571         17136 $outstr .= "\n";
330             }
331 171         2489 $ihandler->("$nshape shapes completed.\n");
332 170 100       1100 last if $sidx >= $iendprog;
333             }
334 87         524 my $eidx = rindex($outstr, 'Z');
335 87 100       321 substr($outstr, $eidx, 1) = ';' if $eidx >= 0;
336 87 100 100     1212 return $outstr if $sidx == $iendprog || $sidx == $iendprog+1;
337 83 50       258 die "oops" if $eidx < 0;
338 83 100       5844 ref($fillv) or return substr($outstr, 0, $eidx) . (length($fillv) ?
    100          
339             pour_text(substr($outstr, $eidx), "", 0, $fillv) : "\n");
340 73 50       276 (my $idx = rindex($outstr, ';')) >= 0 or return $outstr;
341 73 100       2415 my @t = substr($outstr, $idx+1) =~
342             /[()&|^=;]|\$.|'[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"/g
343             or return $outstr;
344 70         186 my $nl = my $nr = my $ne = 0;
345 70         163 for my $c (@t) {
346 405 100       1343 if ($c eq '(') {++$nl} elsif ($c eq ')') {++$nr}
  110 100       143  
  60 100       85  
  62         116  
347             elsif ($c eq '=') {++$ne}
348             }
349 70 100 100     1840 if ($ne == 0 || $nl != $nr || $t[-1] eq '=') {
    100 100        
      100        
      100        
350 29         93 my $f = ';'; # Trouble: wipe out last bit with filler
351 29         189 for my $i ($idx+1 .. length($outstr)-2) {
352 2092 100       11461 substr($outstr, $i, 1) =~ tr/ \n// or
    100          
353             substr($outstr, $i, 1) = $f = $f eq '#' ? ';' : '#';
354             }
355             } elsif ($t[-1] eq '|' or $t[-1] eq '^' or $t[-1] eq '&') {
356 23         21618 $outstr =~ s/\S(\s*)$/;$1/;
357             }
358 70         13895 $outstr;
359             }
360              
361             # -----------------------------------------------------------------
362              
363             sub _border {
364 9     9   31 my ($a, $w, $c, $l, $r, $t, $b) = @_;
365 9         30 my $z = $c x ($w+$l+$r); my $f = $c x $l; my $g = $c x $r;
  9         21  
  9         21  
366 9         14 for (@{$a}) { $_ = $f . $_ . $g }
  9         27  
  531         1051  
367 9         22 unshift(@{$a}, ($z) x $t); push(@{$a}, ($z) x $b);
  9         58  
  9         16  
  9         31  
368             }
369              
370             sub border_shape {
371 6     6 1 37 my ($tlines, $gl, $gr, $gt, $gb, $wl, $wr, $wt, $wb) = @_;
372 6         217 my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0;
  6         52  
  6         16  
373 6 100       28 for my $l (@a) { $m = length($l) if length($l) > $m }
  350         813  
374 6         16 for my $l (@a) { $l .= ' ' x ($m - length($l)) }
  350         655  
375 6 50 66     83 $gl || $gr || $gt || $gb and _border(\@a, $m, ' ', $gl, $gr, $gt, $gb);
      66        
      33        
376 6 50 66     64 $wl || $wr || $wt || $wb and _border(\@a, $m+$gl+$gr,'#',$wl,$wr,$wt,$wb);
      66        
      33        
377 6         130 join("\n", @a, "");
378             }
379              
380             sub invert_shape {
381 2     2 1 10 my $tlines = shift;
382 2         57 my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0;
  2         29  
  2         7  
383 2 100       8 for my $l (@a) { $m = length($l) if length($l) > $m }
  97         325  
384 2         5 for my $l (@a) { $l .= ' ' x ($m - length($l)) }
  97         171  
385 2         25 my $s = join("\n", @a, ""); $s =~ tr/ #/# /;
  2         17  
386 2         182 $s =~ s/ +$//mg; $s;
  2         16  
387             }
388              
389             sub reflect_shape {
390 5     5 1 31 my $tlines = shift;
391 5         318 my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0;
  5         42  
  5         14  
392 5 100       15 for my $l (@a) { $m = length($l) if length($l) > $m }
  279         569  
393 5         426 my $s = join("\n", map(scalar reverse($_ . ' ' x ($m - length)), @a), "");
394 5         880 $s =~ s/ +$//mg; $s;
  5         54  
395             }
396              
397             sub hjoin_shapes {
398 2     2 1 185 my ($g, @shapes) = @_;
399 2         5 my $ml = 0; my @lines;
  2         5  
400 2 100       7 for my $s (@shapes) { my $n = $s =~ tr/\n//; $ml = $n if $n > $ml }
  4         17  
  4         17  
401 2         5 for my $tlines (@shapes) {
402 4         51 my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0;
  4         19  
  4         7  
403 4 100       9 for my $l (@a) { $m = length($l) if length($l) > $m }
  99         329  
404 4         10 for my $l (@a) { $l .= ' ' x ($m - length($l) + $g) }
  99         182  
405 4         18 push(@a, (' ' x ($m + $g)) x ($ml - @a));
406 4         12 for my $i (0..$#a) { $lines[$i] .= $a[$i] }
  100         207  
407             }
408 2         21 my $s = join("\n", @lines, "");
409 2         964 $s =~ s/ +$//mg; $s;
  2         20  
410             }
411              
412             sub reduce_shape {
413 3     3 1 16 my ($tlines, $f) = @_; my $i = $f++; my $s = "";
  3         11  
  3         18  
414 3         210 for my $l (grep(!(++$i%$f), split(/\n/, $tlines))) {
415 105         247 for ($i = 0; $i < length($l); $i += $f) { $s .= substr($l, $i, 1) }
  4927         13932  
416 105         327 $s .= "\n";
417             }
418 3         249 $s =~ s/ +$//mg; $s;
  3         21  
419             }
420              
421             sub expand_shape {
422 3     3 1 13 my ($s, $f) = @_; my $i = ' ' x ++$f; my $j = '#' x $f;
  3         13  
  3         9  
423 3         755 $s =~ s/ /$i/g; $s =~ s/#/$j/g; my $t = "";
  3         803  
  3         11  
424 3         82 for my $l (split(/^/, $s, -1)) { $t .= $l x $f } $t;
  141         410  
  3         71  
425             }
426              
427             # Rotate shape clockwise: 90, 180 or 270 degrees
428             # (other angles are left as an exercise for the reader:-)
429             sub rotate_shape {
430 14     14 1 63 my ($tlines, $degrees, $rtype, $flip) = @_;
431 14 100       421 $degrees == 180 and
432             return join("\n", reverse(split(/\n/, $tlines)), "");
433 11 100       48 my $t = $rtype==0 ? 2 : 1; my $inc = $rtype==1 ? 2 : 1;
  11 100       47  
434 11         565 my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0; my $s = "";
  11         505  
  11         20  
  11         74  
435 11 100       37 for my $l (@a) { $m = length($l) if length($l) > $m }
  536         1604  
436 11         24 for my $l (@a) { $l .= ' ' x ($m - length($l)) }
  536         863  
437 11 100       56 if ($degrees == 90) {
    50          
438 7 100       25 @a = reverse(@a) unless $flip;
439 7         28 for (my $i = 0; $i < $m; $i += $inc) {
440 423         675 for (@a) {$s .= substr($_, $i, 1) x $t} $s .= "\n"
  19881         52451  
  423         2509  
441             }
442             } elsif ($degrees == 270) {
443 4 100       29 @a = reverse(@a) if $flip;
444 4         18 for (my $i = $m-1; $i >= 0; $i -= $inc) {
445 289         746 for (@a) {$s .= substr($_, $i, 1) x $t} $s .= "\n"
  15369         30501  
  289         2964  
446             }
447             }
448 11         10166 $s =~ s/ +$//mg; $s;
  11         340  
449             }
450              
451             sub make_triangle {
452 5 100   5 1 435 my $w = shift; $w & 1 or ++$w; $w < 9 and $w = 9;
  5 100       33  
  5         23  
453 5         18 my $n = $w >> 1; my $s;
  5         46  
454 5         26 for (my $i=1;$i<=$w;$i+=2) { $s .= ' ' x $n-- . '#' x $i . "\n" }
  73         372  
455 5         44 $s;
456             }
457              
458             sub make_siertri {
459 8 100   8 1 725 my $w = shift; $w < 3 and $w = 5; my $n = 2 ** $w; my $s;
  8         35  
  8         19  
  8         14  
460 8         37 for my $i (0 .. $n-1) {
461 256 100       269 --$n; $s .= ' ' x $n .
  256         5124  
462             join('', map($n & $_ ? ' ' : '##', 0 .. $i)) . "\n";
463 8         95 } $s;
464             }
465              
466             sub make_banner {
467 1     1 1 2117 my ($w, $src) = @_;
468             # Linux /usr/games/banner can be used.
469             # CPAN Text::Banner will hopefully be enhanced so it can be used too.
470 1         5 my $b_exe = '/usr/games/banner';
471 1 50       34 -x $b_exe or die "'$b_exe' not available on this platform.";
472 0 0       0 my $f = $w ? "-w $w" : ""; $src =~ s/\s+/ /g; $src =~ s/ $//;
  0         0  
  0         0  
473             # Following characters not in /usr/games/banner character set:
474             # \ [ ] { } < > ^ _ | ~
475             # Also must escape ' from the shell.
476 0         0 $src =~ tr#_\\[]{}<>^|~'`#-/()()()H!T""#;
477 0         0 my $s = ""; my $len = length($src);
  0         0  
478 0         0 for (my $i = 0; $i < $len; $i += 512) {
479 0         0 my $cmd = "$b_exe $f '" . substr($src, $i, 512) . "'";
480 0 0       0 $s .= `$cmd`; my $rc = $? >> 8; $rc and die "<$cmd>: rc=$rc";
  0         0  
  0         0  
481             }
482 0         0 $s =~ s/\s+$/\n/; $s =~ s/ +$//mg;
  0         0  
483             # Remove as many leading spaces as possible.
484 0         0 my $m = 32000; # regex /^ {$m}/ blows up if $m > 32766
485 0 0       0 while ($s =~ /^( *)\S/mg) { $m = length($1) if length($1) < $m }
  0         0  
486 0 0       0 $s =~ s/^ {$m}//mg if $m; $s;
  0         0  
487             }
488              
489             # -------------------------------------------------------------------------
490              
491             sub _bi_all {
492 2     2   42 join "\n" x $_[0]->{Width},
493             map(_get_eye_string($_[0]->{EyeDir}, $_), _get_eye_shapes($_[0]->{EyeDir}))
494             }
495 1     1   21 sub _bi_triangle { make_triangle($_[0]->{Width}) }
496 3     3   39 sub _bi_siertri { make_siertri($_[0]->{Width}) }
497 0     0   0 sub _bi_banner { make_banner($_[0]->{Width}, $_[0]->{BannerString}) }
498 0     0   0 sub _bi_srcbanner { make_banner($_[0]->{Width}, $_[0]->{SourceString}) }
499              
500             {
501             my %builtin_shapes = (
502             'all' => \&_bi_all,
503             'triangle' => \&_bi_triangle,
504             'siertri' => \&_bi_siertri,
505             'banner' => \&_bi_banner,
506             'srcbanner' => \&_bi_srcbanner
507             );
508 1     1 1 199 sub get_builtin_shapes { sort keys %builtin_shapes }
509             # Return built-in shape string or undef if invalid shape.
510             sub _get_builtin_string {
511 74     74   286 my $shape = shift;
512 74 100       1352 return unless exists($builtin_shapes{$shape});
513 6         107 $builtin_shapes{$shape}->(shift);
514             }
515             }
516              
517             sub sightly {
518 98     98 1 1901949 my $ruarg = shift; my %arg = (
  98         1600  
519             Shape => "", ShapeString => "",
520             SourceFile => "", SourceString => "",
521             SourceHandle => undef, InformHandler => undef,
522             Width => 0, BannerString => "",
523             Text => 0, TextFiller => "",
524             Regex => 0, Compact => 0,
525             Print => 0, Binary => 0,
526             Gap => 0, Rotate => 0,
527             RotateType => 0, RotateFlip => 0,
528             Reflect => 0, Reduce => 0,
529             Expand => 0, Invert => 0,
530             TrailingSpaces => 0, RemoveNewlines => 0,
531             Indent => 0, BorderGap => 0,
532             BorderGapLeft => 0, BorderGapRight => 0,
533             BorderGapTop => 0, BorderGapBottom => 0,
534             BorderWidth => 0, BorderWidthLeft => 0,
535             BorderWidthRight => 0, BorderWidthTop => 0,
536             BorderWidthBottom => 0, TrapEvalDie => 0,
537             TrapWarn => 0, FillerVar => [],
538             EyeDir => get_eye_dir()
539             );
540 98         382 for my $k (keys %{$ruarg}) {
  98         1014  
541 439 100       1331 exists($arg{$k}) or die "invalid parameter '$k'";
542 438         1485 $arg{$k} = $ruarg->{$k};
543             }
544 97 100 100     890 length($arg{SourceFile}) && $arg{SourceHandle} and
545             die "cannot specify both SourceFile and SourceHandle";
546 96 100 100     773 length($arg{SourceFile}) && length($arg{SourceString}) and
547             die "cannot specify both SourceFile and SourceString";
548 95 100 100     1127 length($arg{SourceString}) && $arg{SourceHandle} and
549             die "cannot specify both SourceString and SourceHandle";
550 94 100 100     946 $arg{Shape} && $arg{ShapeString} and
551             die "cannot specify both Shape and ShapeString";
552 93 100       642 if (length($arg{SourceFile})) {
    100          
553 12         346 $arg{SourceString} = _slurp_tfile($arg{SourceFile}, $arg{Binary});
554             } elsif ($arg{SourceHandle}) {
555 5         64 local $/; $arg{SourceString} = readline($arg{SourceHandle});
  5         337  
556             }
557 92         247 my $fill = $arg{FillerVar};
558 92 100 100     1160 if (ref($fill) && !$arg{Text}) {
559             # Non-rigourous check for module (package) or END block.
560 79 100 100     120 @{$fill} or $fill = ($arg{SourceString} =~ /^\s*END\b/m or
  79 50       1622  
561             $arg{SourceString} =~ /^\s*package\b/m) ?
562             [ '$:', '$~', '$^' ] :
563             [ '$:', '$~', '$^', '$/', '$,', '$\\' ];
564             }
565 92 100       395 $arg{RemoveNewlines} and $arg{SourceString} =~ tr/\n//d;
566 92         264 my $shape = my $sightly = "";
567 92 50 100     2319 length($arg{SourceString}) && !$arg{Text} and $sightly = $arg{Print} ?
    100          
    100          
    100          
    100          
    100          
568             ( $arg{Regex} ? ( $arg{Binary} ?
569             regex_binmode_print_sightly($arg{SourceString}) :
570             regex_print_sightly($arg{SourceString}) ) :
571             ( $arg{Binary} ?
572             clean_binmode_print_sightly($arg{SourceString}) :
573             clean_print_sightly($arg{SourceString}) ) ) :
574             ( $arg{Regex} ? regex_eval_sightly($arg{SourceString}) :
575             clean_eval_sightly($arg{SourceString}) );
576 92 100       1775 if ($arg{ShapeString}) {
    100          
    100          
577 19         77 $shape = $arg{ShapeString};
578             } elsif ($arg{Shape}) {
579 61   66     737 $shape = join("\n" x $arg{Gap},
580             map(_get_builtin_string($_, \%arg) ||
581             (m#[./]# ? _slurp_tfile($_) : _get_eye_string($arg{EyeDir}, $_)),
582             split(/,/, $arg{Shape})));
583             } elsif ($arg{Width}) {
584 11 100 100     162 die "invalid width $arg{Width} (must be > 3)"
585             if !$arg{Text} && $arg{Width} < 4;
586 10         65 $shape = '#' x $arg{Width};
587             }
588 90 100 33     381 $shape or return "use re 'eval';\n" x ($arg{Regex} == 3 || ($arg{Regex} == 1 && $] >= 5.017)) . $sightly;
589 89 100       475 $arg{Rotate} and $shape = rotate_shape($shape, $arg{Rotate},
590             $arg{RotateType}, $arg{RotateFlip});
591 89 100       335 $arg{Reflect} and $shape = reflect_shape($shape);
592 89 100       996 $arg{Reduce} and $shape = reduce_shape($shape, $arg{Reduce});
593 89 100       409 $arg{Expand} and $shape = expand_shape($shape, $arg{Expand});
594 89 100       369 $arg{Invert} and $shape = invert_shape($shape);
595 89 50 100     4993 $arg{TrailingSpaces} ||
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
      66        
      66        
      33        
      33        
      33        
      33        
      33        
596             $arg{BorderGap} || $arg{BorderWidth} ||
597             $arg{BorderGapLeft} || $arg{BorderWidthLeft} ||
598             $arg{BorderGapRight} || $arg{BorderWidthRight} ||
599             $arg{BorderGapTop} || $arg{BorderWidthTop} ||
600             $arg{BorderGapBottom} || $arg{BorderWidthBottom} and
601             $shape = border_shape($shape,
602             $arg{BorderGapLeft} || $arg{BorderGap},
603             $arg{BorderGapRight} || $arg{BorderGap},
604             $arg{BorderGapTop} || $arg{BorderGap},
605             $arg{BorderGapBottom} || $arg{BorderGap},
606             $arg{BorderWidthLeft} || $arg{BorderWidth},
607             $arg{BorderWidthRight} || $arg{BorderWidth},
608             $arg{BorderWidthTop} || $arg{BorderWidth},
609             $arg{BorderWidthBottom} || $arg{BorderWidth});
610 89 100       328 if ($arg{Indent}) { my $s = ' ' x $arg{Indent}; $shape =~ s/^/$s/mg }
  1         13  
  1         88  
611             $arg{Text} and return
612 89 100       294 pour_text($shape, $arg{SourceString}, $arg{Gap}, $arg{TextFiller});
613 85   33     2230 "use re 'eval';\n" x ($arg{Regex} == 3 || ($arg{Regex} == 1 && $] >= 5.017)) .
614             'local $SIG{__WARN__}=sub{};' x $arg{TrapWarn} .
615             pour_sightly($shape, $sightly, $arg{Gap}, $fill, $arg{Compact},
616             $arg{InformHandler}) . "\n\n\n;die \$\@ if \$\@\n" x $arg{TrapEvalDie};
617             }
618              
619             # -------------------------------------------------------------------------
620              
621             sub _get_eye_shapes {
622 15     15   86 my $d = shift;
623 15 100       743 opendir my $dh, $d or die "opendir '$d': $!";
624 14         2957 my @e = sort map(/(.+)\.eye$/, readdir($dh));
625 14         407 closedir($dh); @e;
  14         232  
626             }
627              
628 187     187   1349 sub _get_eye_string { _slurp_tfile($_[0] . '/' . $_[1] . '.eye') }
629              
630             sub _get_eye_properties {
631 600     600   1346 my $f = $_[0] . '/' . $_[1] . '.eyp';
632 600 100       14629 -f $f or return;
633 595         1010 _get_properties($f);
634             }
635              
636             sub _get_eye_keywords {
637 2     2   62 my $d = shift;
638 2         4 my %h;
639 2         7 SHAPE: for my $s (_get_eye_shapes($d)) {
640 102 100       189 my $p = _get_eye_properties($d, $s) or next SHAPE; # no properties
641 101 100       1299 exists($p->{keywords}) or next SHAPE; # no keywords property
642 89 100       352 my @k = split(" ", $p->{keywords}) or next SHAPE; # no keywords
643 88         131 for my $k (@k) { push(@{$h{$k}}, $s) }
  136         155  
  136         665  
644             }
645 2         31 return \%h;
646             }
647              
648             sub _find_eye_shapes {
649 8     8   1320 my $d = shift;
650 8 100       36 @_ or die "oops, no keywords given";
651 7         57 my @skey = map([split/\s+OR\s+/], @_);
652 7         11 my @ret;
653 7         26 SHAPE: for my $s (_get_eye_shapes($d)) {
654 401 100       734 my $p = _get_eye_properties($d, $s) or next SHAPE; # no properties
655 398 100       977 exists($p->{keywords}) or next SHAPE; # no keywords property
656 351 100       1339 my @k = split(" ", $p->{keywords}) or next SHAPE; # no keywords
657 348         430 my %h; @h{@k} = ();
  348         2560  
658 348         506 for my $k (@skey) {
659             # XXX: short-circuiting List::Util::first() better than grep here.
660 388 100       370 grep(exists($h{$_}), @{$k}) or next SHAPE; # AND, all must be true
  388         2205  
661             }
662 22         170 push(@ret, $s);
663             }
664 7         100 return @ret;
665             }
666              
667 3     3 1 85 sub get_eye_shapes { _get_eye_shapes(get_eye_dir()) }
668 117     117 1 73169 sub get_eye_string { _get_eye_string(get_eye_dir(), shift) }
669 96     96 1 1555 sub get_eye_properties { _get_eye_properties(get_eye_dir(), shift) }
670 1     1 1 687 sub get_eye_keywords { _get_eye_keywords(get_eye_dir()) }
671 5     5 1 294 sub find_eye_shapes { _find_eye_shapes(get_eye_dir(), @_) }
672              
673             # $eye_dir is the directory containing the .eye file shapes.
674             # Note: $eye_dir is only eval-hostile line in EyeDrops.pm; do not change it
675             # for t/19_surrounds.t and "EyeDropping EyeDrops.pm" section of doco relies
676             # on it. Remove ".pm" from "...Acme/EyeDrops.pm" giving directory name.
677             my $eye_dir = __FILE__; chop($eye_dir);chop($eye_dir);chop($eye_dir);
678              
679 1     1 1 185 sub slurp_yerself { _slurp_tfile($eye_dir . '.pm') }
680              
681 321     321 1 9279 sub get_eye_dir { $eye_dir }
682              
683             1;
684              
685             __END__