File Coverage

blib/lib/YATT/Lite/Util.pm
Criterion Covered Total %
statement 431 571 75.4
branch 148 270 54.8
condition 73 134 54.4
subroutine 101 120 84.1
pod 0 65 0.0
total 753 1160 64.9


line stmt bran cond sub pod time code
1             package YATT::Lite::Util;
2 39     39   169757 use strict;
  39         91  
  39         3262  
3 39     39   198 use warnings qw(FATAL all NONFATAL misc);
  39         72  
  39         2711  
4 39     39   730 use constant DEBUG_LOOKUP_PATH => $ENV{DEBUG_YATT_UTIL_LOOKUP_PATH};
  39         72  
  39         3470  
5              
6 39     39   1583 use URI::Escape ();
  39         67573  
  39         1924  
7 39     39   1501 use Tie::IxHash;
  39         157684  
  38         1654  
8              
9             require Scalar::Util;
10              
11             {
12             package YATT::Lite::Util;
13 38     38   713 use Exporter qw(import);
  38         80  
  38         2392  
14             BEGIN {
15 36     36   199 $INC{'YATT/Lite/Util.pm'} = 1;
16 36         614 our @EXPORT = qw/numLines coalesce default globref symtab lexpand escape
17             untaint_any ckeval ckrequire untaint_unless_tainted
18             dict_sort terse_dump catch
19             nonempty
20             subname
21             pkg2pm
22             globref_default
23             /;
24             }
25 36     35   402 use Carp;
  35         89  
  35         8483  
26             sub numLines {
27 3596 50   3596 0 9148 croak "undefined value for numLines!" unless defined $_[0];
28 3596         9978 $_[0] =~ tr|\n||;
29             }
30             sub coalesce {
31 1252     1252 0 2251 foreach my $item (@_) {
32 2486 100       8393 return $item if defined $item;
33             }
34 0         0 undef;
35             }
36             *default = \*coalesce;
37              
38             sub nonempty {
39 106 100   106 0 904 defined $_[0] && $_[0] ne '';
40             }
41              
42             sub define_const {
43 1155     1155 0 4080 my ($name_or_glob, $value) = @_;
44 1155 100       2557 my $glob = ref $name_or_glob ? $name_or_glob : globref($name_or_glob);
45 1155     0   8358 *$glob = my $const_sub = sub () { $value };
  0         0  
46 1155         3463 $const_sub;
47             }
48              
49             sub globref {
50 17916     17916 0 32819 my ($thing, @name) = @_;
51 17916   66     50161 my $class = ref $thing || $thing;
52 35     34   255 no strict 'refs';
  34         365  
  34         27371  
53 17916         20071 \*{join("::", $class, grep {defined} @name)};
  17916         27005  
  17852         146663  
54             }
55             sub globref_default {
56 6 50   6 0 18 unless (@_ == 2) {
57 0         0 croak "Too few arguments";
58             }
59 6         12 my ($globref, $default) = @_;
60 6         12 my $kind = ref $default;
61 6 50       8 *{$globref}{$kind} || do {
  6         98  
62 0         0 *{$globref} = $default;
  0         0  
63 0         0 $default;
64             };
65             }
66             sub symtab {
67 7787         14305 *{globref(shift, '')}{HASH}
68 7787     7787 0 8876 }
69             # XXX: Nice to have look_for_symtab, too.
70             sub look_for_globref {
71 7044     7044 0 10976 my ($class, $name) = @_;
72 7044         12047 my $symtab = symtab($class);
73 7044 100       20083 return undef unless defined $symtab->{$name};
74 6981         12670 globref($class, $name);
75             }
76             sub fields_hash {
77 6416 100   6416 0 11995 my $sym = look_for_globref(shift, 'FIELDS')
78             or return undef;
79 6395         8210 *{$sym}{HASH};
  6395         16730  
80             }
81             sub lexpand {
82             # lexpand can be used to counting.
83 1513 100   1513 0 48351 unless (defined $_[0]) {
    100          
    100          
    50          
84 512 50       2191 wantarray ? () : 0;
85             } elsif (not ref $_[0]) {
86 353         1029 $_[0]
87             } elsif (ref $_[0] eq 'ARRAY') {
88 646         857 @{$_[0]}
  646         2926  
89             } elsif (ref $_[0] eq 'HASH') {
90 2         4 %{$_[0]}
  2         25  
91             } else {
92 0 0       0 wantarray ? () : 0;
93             }
94             }
95             sub lsearch (&@) {
96 0     0 0 0 my $sub = shift;
97 0         0 my $i = 0;
98 0         0 foreach (@_) {
99 0 0       0 return $i if $sub->($_);
100 0         0 } continue {$i++}
101 0         0 return;
102             }
103             # $fn:e
104 0 0   0 0 0 sub extname { my $fn = shift; return $1 if $fn =~ s/\.(\w+)$// }
  0         0  
105             # $fn:r
106 44     44 0 967 sub rootname { my $fn = shift; $fn =~ s/\.\w+$//; join "", $fn, @_ }
  44         238  
  44         386  
107             # $fn:r:t
108             sub appname {
109 35     35 0 27031 my $fn = shift;
110 35         164 $fn =~ s/\.\w+$//;
111 35 50       556 return $1 if $fn =~ m{(\w+)$};
112             }
113 144     144 0 398 sub untaint_any { $_[0] =~ m{.*}s; $& }
  144         692  
114             our $DEBUG_INJECT_TAINTED = 0;
115             # untaint_unless_tainted($fn, read_file($fn))
116             sub untaint_unless_tainted {
117 271 50   271 0 1602 return $_[1] unless ${^TAINT};
118 0 0 0     0 if (defined $_[0] and not Scalar::Util::tainted($_[0])) {
119 0 0       0 $DEBUG_INJECT_TAINTED ? $_[1] : untaint_any($_[1]);
120             } else {
121 0         0 $_[1];
122             }
123             }
124             sub ckeval {
125             my $__SCRIPT__ = join "", grep {
126 281 50   281 0 600 defined $_ and Scalar::Util::tainted($_) ? croak "tainted! '$_'" : 1;
  1939 50       7650  
127             } @_;
128 281         368 my @__RESULT__;
129 281 50       658 if ($] < 5.014) {
130 0 0       0 if (wantarray) {
131 0         0 @__RESULT__ = eval $__SCRIPT__;
132             } else {
133 0         0 $__RESULT__[0] = eval $__SCRIPT__;
134             }
135 0 0       0 die $@ if $@;
136             } else {
137 281         402 local $@;
138 281 50       572 if (wantarray) {
139 0         0 @__RESULT__ = eval $__SCRIPT__;
140             } else {
141 16     15 0 180 $__RESULT__[0] = eval $__SCRIPT__;
  16     15 0 37  
  16     15 0 127  
  15     14   76  
  15     14   27  
  15     14   221  
  15     13   6509  
  15     13   35  
  15     13   85  
  14     13   79  
  14     13   27  
  14     13   144  
  14     12   73  
  14     11   29  
  14     11   71  
  14     11   1130  
  14     11   32  
  14     11   86  
  13     11   75  
  13     11   26  
  13     11   751  
  13     11   64  
  13     11   39  
  13     11   74  
  13     11   277  
  13     11   25  
  13     11   61  
  13     11   892  
  13     11   22  
  13     11   128  
  13     11   71  
  13     11   22  
  13     11   54  
  13     11   236  
  13     2   27  
  13     1   55  
  12     1   865  
  12         27  
  12         114  
  11         62  
  11         24  
  11         83  
  11         64  
  11         22  
  11         68  
  11         1034  
  11         20  
  11         67  
  11         65  
  11         23  
  11         52  
  11         59  
  11         23  
  11         53  
  11         902  
  11         25  
  11         49  
  11         65  
  11         19  
  11         51  
  11         59  
  11         20  
  11         48  
  11         902  
  11         23  
  11         51  
  11         64  
  11         168  
  11         45  
  11         63  
  11         20  
  11         50  
  11         851  
  11         24  
  11         52  
  11         59  
  11         21  
  11         50  
  11         55  
  11         20  
  11         48  
  11         878  
  11         24  
  11         51  
  11         61  
  11         20  
  11         47  
  11         62  
  11         18  
  11         46  
  11         934  
  11         33  
  11         49  
  11         63  
  11         17  
  11         54  
  11         60  
  11         20  
  11         213  
  11         941  
  11         21  
  11         47  
  281         46116  
  2         15  
  3         7  
  3         16  
  1         2  
  1         2  
  1         4  
  1         5  
  1         6  
  1         3  
  1         2  
  1         2  
  1         4  
142             }
143 281 100       43370 die $@ if $@;
144             }
145 280 50       1327 wantarray ? @__RESULT__ : $__RESULT__[0];
146             }
147             sub ckrequire {
148 67     67 0 633 ckeval("require $_[0]");
149             }
150 34     33   241 use Scalar::Util qw(refaddr);
  33         250  
  33         73350  
151             sub cached_in {
152 314     311 0 667 my ($dir, $dict, $name, $sys, $mark, $loader, $refresher) = @_;
153 314 100       880 if (not exists $dict->{$name}) {
154 61 100       272 my $item = $dict->{$name} = $loader ? $loader->($dir, $sys, $name)
155             : $dir->load($sys, $name);
156 58 100 100     393 $mark->{refaddr($item)} = 1 if $item and $mark;
157 58         249 $item;
158             } else {
159 253         538 my $item = $dict->{$name};
160 253 100 100     2312 unless ($item and ref $item
    100          
    100          
161             and (not $mark or not $mark->{refaddr($item)}++)) {
162             # nop
163             } elsif ($refresher) {
164 16         58 $refresher->($item, $sys, $name)
165             } elsif (my $sub = UNIVERSAL::can($item, 'refresh')) {
166 30         104 $sub->($item, $sys);
167             }
168 253         1106 $item;
169             }
170             }
171              
172             sub split_path {
173 27     27 0 9035 my ($path, $startDir, $cut_depth, $default_ext) = @_;
174             # $startDir is $app_root.
175             # $doc_root should resides under $app_root.
176 27   50     76 $cut_depth //= 1;
177 27   100     106 $default_ext //= "yatt";
178 27         133 $startDir =~ s,/+$,,;
179 27 50       246 unless ($path =~ m{^\Q$startDir\E}gxs) {
180 0         0 die "Can't split_path: prefix mismatch: $startDir vs $path";
181             }
182              
183 27         155 my ($dir, $pos, $file) = ($startDir, pos($path));
184             # *DO NOT* initialize $file. This loop relies on undefined-ness of $file.
185 27   100     798 while ($path =~ m{\G/+([^/]*)}gcxs and -e "$dir/$1" and not defined $file) {
      66        
186 50 100       111 if (-d _) {
187 31         88 $dir .= "/$1";
188             } else {
189 19         89 $file = $1;
190             # *DO NOT* last. To match one more time.
191             }
192             } continue {
193 50         1005 $pos = pos($path);
194             }
195              
196 27 50       85 $dir .= "/" if $dir !~ m{/$};
197 27         60 my $subpath = substr($path, $pos);
198 27 100       73 if (not defined $file) {
199 8 100 66     196 if ($subpath =~ m{^/(\w+)(?:/|$)} and -e "$dir/$1.$default_ext") {
    100          
    50          
200 6         18 $subpath = substr($subpath, 1+length $1);
201 6         15 $file = "$1.$default_ext";
202             } elsif (-e "$dir/index.$default_ext") {
203             # index.yatt should subsume all subpath.
204             } elsif ($subpath =~ s{^/([^/]+)$}{}) {
205             # Note: Actually, $file is not accesible in this case.
206             # This is just for better error diag.
207 1         4 $file = $1;
208             }
209             }
210              
211 27         56 my $loc = substr($dir, length($startDir));
212 27         76 while ($cut_depth-- > 0) {
213 25 50       119 $loc =~ s,^/[^/]+,,
214             or croak "Can't cut path location: $loc";
215 25         114 $startDir .= $&;
216             }
217              
218 27   100     272 ($startDir
219             , $loc
220             , $file // ''
221             , $subpath
222             , (not defined $file)
223             );
224             }
225              
226             sub lookup_dir {
227 58     58 0 123 my ($loc, $dirlist) = @_;
228 58         233 $loc =~ s{^/*}{/};
229 58         161 foreach my $dir (@$dirlist) {
230 58         134 my $real = "$dir$loc";
231 58 50       1222 next unless -d $real;
232 58 50       402 return wantarray ? ($real, $dir) : $real;
233             }
234             }
235              
236             sub lookup_path {
237 85     85 0 21773 my ($path_info, $dirlist, $index_name, $want_ext, $use_subpath) = @_;
238 85   100     286 $index_name //= 'index';
239 85   100     219 $want_ext //= '.yatt';
240 85         166 my $ixfn = $index_name . $want_ext;
241 85 50       163 my @dirlist = grep {defined $_ and -d $_} @$dirlist;
  121         2900  
242 85         100 print STDERR "dirlist" => terse_dump(@dirlist), "\n" if DEBUG_LOOKUP_PATH;
243 85         145 my $pi = $path_info;
244 85         170 my ($loc, $cur, $ext) = ("", "");
245             DIG:
246 85         532 while ($pi =~ s{^/+([^/]+)}{}) {
247 112         245 $cur = $1;
248 112 100       435 $ext = ($cur =~ s/(\.[^\.]+)$// ? $1 : undef);
249 112         141 print STDERR terse_dump(cur => $cur, ext => $ext), "\n" if DEBUG_LOOKUP_PATH;
250 112         212 foreach my $dir (@dirlist) {
251 140         304 my $base = "$dir$loc/$cur";
252 140 100 100     4588 if (defined $ext and -r "$base$ext") {
    100 100        
    100 100        
    100          
253             # If extension is specified and it is readable, use it.
254 31         295 return ($dir, "$loc/", "$cur$ext", $pi);
255             } elsif ($pi =~ m{^/} and -d $base) {
256             # path_info has '/' and directory exists.
257 33         81 next; # candidate
258             } elsif (-r (my $fn = "$base$want_ext")) {
259 20         177 return ($dir, "$loc/", "$cur$want_ext", $pi);
260             } elsif ($use_subpath
261             and -r (my $alt = "$dir$loc/$ixfn")) {
262 23   50     94 $ext //= "";
263 23         211 return ($dir, "$loc/", $ixfn, "/$cur$ext$pi", 1);
264             } else {
265             # Neither dir nor $cur$want_ext exists, it should be ignored.
266 33         80 undef $dir;
267             }
268             }
269             } continue {
270 38         65 $loc .= "/$cur";
271 38         65 print STDERR terse_dump(continuing => $loc), "\n" if DEBUG_LOOKUP_PATH;
272 38         107 @dirlist = grep {defined} @dirlist;
  60         318  
273             }
274 11         16 print STDERR terse_dump('end_of_loop'), "\n" if DEBUG_LOOKUP_PATH;
275              
276 11 100       90 return unless $pi =~ m{^/+$};
277              
278 8         17 foreach my $dir (@dirlist) {
279 9 100       187 next unless -r "$dir$loc/$ixfn";
280 8         66 return ($dir, "$loc/", "$ixfn", "", 1);
281             }
282              
283 0         0 print STDERR terse_dump('at_last'), "\n" if DEBUG_LOOKUP_PATH;
284 0         0 return;
285             }
286              
287             sub dict_order {
288 3     3 0 7 my ($a, $b, $start) = @_;
289 3 50       9 $start = 1 unless defined $start;
290 3         5 my ($result, $i) = (0);
291 3   33     20 for ($i = $start; $i <= $#$a and $i <= $#$b; $i++) {
292 30 100 66     134 if ($a->[$i] =~ /^\d/ and $b->[$i] =~ /^\d/) {
293 15         33 $result = $a->[$i] <=> $b->[$i];
294             } else {
295 15         31 $result = $a->[$i] cmp $b->[$i];
296             }
297 30 100       166 return $result unless $result == 0;
298             }
299 0         0 return $#$a <=> $#$b;
300             }
301              
302             # a => ['a', 'a']
303             # q1a => ['q1a', 'q', 1, 'a']
304             # q11b => ['q11b', 'q', 11, 'b']
305             sub dict_sort (@) {
306 2     2 0 611 map {$_->[0]} sort {dict_order($a,$b)} map {[$_, split /(\d+)/]} @_;
  4         15  
  3         8  
  4         63  
307             }
308              
309             sub captured (&) {
310 4     4 0 579 my ($code) = @_;
311 4 50       78 open my $fh, '>', \ (my $buffer = "") or die "Can't create capture buf:$!";
312 4         1345 $code->($fh);
313 3         32 $buffer;
314             }
315              
316             sub terse_dump {
317 160     160 0 28202 require Data::Dumper;
318             join ", ", map {
319 160         23295 Data::Dumper->new([$_])->Terse(1)->Indent(0)->Dump;
  222         4433  
320             } @_;
321             }
322              
323             sub is_debugging {
324 61 50   61 0 556 my $symtab = $main::{'DB::'} or return 0;
325 61         99 defined ${*{$symtab}{HASH}}{cmd_b}
  61         725  
326 61         104 }
327              
328             sub catch (&) {
329 52     52 0 2213 my ($sub) = @_;
330 52         104 local $@ = '';
331 52         100 eval { $sub->() };
  52         159  
332 52         15565 $@;
333             }
334             }
335              
336             sub dofile_in {
337 8     8 0 22 my ($pkg, $file) = @_;
338 8 50       255 unless (-e $file) {
    50          
339 0         0 croak "No such file: $file\n";
340             } elsif (not -r _) {
341 0         0 croak "Can't read file: $file\n";
342             }
343 8         107 ckeval("package $pkg; my \$result = do '$file'; die \$\@ if \$\@; \$result");
344             }
345              
346             sub compile_file_in {
347 3     3 0 7 my ($pkg, $file) = @_;
348 3 50       53 if (-d $file) {
349 0         0 croak "file '$file' is a directory!";
350             }
351 3         10 my $sub = dofile_in($pkg, $file);
352 3 50 33     23 unless (defined $sub and ref $sub eq 'CODE') {
353 0         0 die "file '$file' should return CODE (but not)!\n";
354             }
355 3         13 $sub;
356             }
357              
358              
359 4         6 BEGIN {
360 33     33   443 my %escape = (qw(< <
361             > >
362             --> -->
363             " "
364             & &)
365             , "\'", "'");
366              
367 33         2620 our $ESCAPE_UNDEF = '';
368              
369             sub escape {
370 278 100 100 278 0 8637 return if wantarray && !@_;
371 277         396 my @result;
372 277         505 foreach my $str (@_) {
373 277         527 push @result, do {
374 277 100       826 unless (defined $str) {
    100          
    100          
    50          
    0          
375 8         24 $ESCAPE_UNDEF;
376             } elsif (not ref $str) {
377 257         428 my $copy = $str;
378 257         558 $copy =~ s{([<>&\"\'])}{$escape{$1}}g;
379 257         736 $copy;
380             } elsif (ref $str eq 'SCALAR') {
381             # PASS Thru. (Already escaped)
382 5   33     27 $$str // $ESCAPE_UNDEF; # fail safe
383             } elsif (_is_escapable($str)) {
384 7         17 $str->as_escaped;
385             } elsif (my $sub = UNIVERSAL::can($str, 'cf_pairs')) {
386             ref($str).'->new('.(join(", ", map {
387 0         0 my ($k, $v) = @$_;
  0         0  
388 0         0 "$k => " . do {
389 0         0 my $esc = escape($v);
390 0 0       0 if (not defined $esc) {
    0          
391 0         0 'undef'
392             } elsif ($esc eq '') {
393 0         0 "''"
394             } else {
395 0         0 $esc;
396             }
397             };
398             } $sub->($str))).')';
399             } else {
400             # XXX: Is this secure???
401             # XXX: Should be JSON?
402 0         0 my $copy = terse_dump($str);
403 0         0 $copy =~ s{([<\"]|-->)}{$escape{$1}}g; # XXX: Minimum. May be insecure.
404 0         0 $copy;
405             }
406             };
407             }
408 277 100       1737 wantarray ? @result : $result[0];
409             }
410             }
411              
412             # XXX: Since method name "as_escaped" conflicts with CGen::Perl->as_escaped,
413             # We need a informational class for everything safely escapable
414             # via "as_escape()"
415             {
416             sub _is_escapable {
417 7     7   34 UNIVERSAL::isa($_[0], 'YATT::Lite::Util::escapable');
418             }
419             package
420             YATT::Lite::Util::escapable;
421             }
422              
423             {
424             package
425             YATT::Lite::Util::named_attr;
426 33     30   1293 BEGIN {our @ISA = ('YATT::Lite::Util::escapable')};
427 30     30   619 use overload qw("" as_string);
  30         24166  
  30         255  
428             sub as_string {
429 0     0   0 shift->[-1];
430             }
431             sub as_escaped {
432 7     7   77 sprintf q{ %s="%s"}, $_[0][0], $_[0][1];
433             }
434             }
435              
436             sub named_attr {
437 8     8 0 314 my $attname = shift;
438 8 100       16 my @result = grep {defined $_ && $_ ne ''} @_;
  12         64  
439 8 100       23 return '' unless @result;
440 7         12 bless [$attname, join ' ', map {escape($_)} @result]
  10         23  
441             , 'YATT::Lite::Util::named_attr';
442             }
443              
444             {
445             # XXX: These functions are deprecated. Use att_value_in() instead.
446              
447 0     0 0 0 sub value_checked { _value_checked($_[0], $_[1], checked => '') }
448 0     0 0 0 sub value_selected { _value_checked($_[0], $_[1], selected => '') }
449              
450             sub _value_checked {
451 0     0   0 my ($value, $hash, $then, $else) = @_;
452 0         0 sprintf q|value="%s"%s|, escape($value)
453             , _if_checked($hash, $value, $then, $else);
454             }
455              
456             sub _if_checked {
457 0     0   0 my ($in, $value, $then, $else) = @_;
458 0   0     0 $else //= '';
459 0 0       0 return $else unless defined $in;
460 0 0 0     0 if (ref $in ? $in->{$value // ''} : ($in eq $value)) {
    0          
461 0         0 " $then"
462             } else {
463 0         0 $else;
464             }
465             }
466             }
467              
468             {
469             our %input_spec = (select => [0, 0]
470             , radio => [1, 0]
471             , checkbox => [2, 1]);
472             sub att_value_in {
473 0     0 0 0 my ($in, $type, $name, $formal_value, $as_value) = @_;
474 0 0       0 defined (my $spec = $input_spec{$type})
475             or croak "Unknown type: $type";
476              
477 0         0 my ($typeid, $has_sfx) = @$spec;
478              
479 0 0 0     0 unless (defined $name and $name ne '') {
480 0         0 croak "name is empty";
481             }
482              
483 0 0 0     0 unless (defined $formal_value and $formal_value ne '') {
484 0         0 croak "value is empty";
485             }
486              
487 0         0 my @res;
488              
489 0 0 0     0 if ($type and $typeid) {
490 0         0 push @res, qq|type="$type"|;
491             }
492              
493 0 0       0 if ($typeid) {
494 0 0       0 my $sfx = $has_sfx ? '['.escape($formal_value).']' : '';
495 0         0 push @res, qq|name="@{[escape($name)]}$sfx"|;
  0         0  
496             }
497              
498 0 0       0 if (not $has_sfx) {
    0          
499             # select
500 0         0 push @res, qq|value="@{[escape($formal_value)]}"|;
  0         0  
501             } elsif ($as_value) {
502             # checkbox/radio, with explicit value
503 0         0 push @res, qq|value="@{[escape($as_value)]}"|;
  0         0  
504             }
505              
506 0 0       0 if (find_value_in($in, $name, $formal_value)) {
507 0 0       0 push @res, $typeid ? "checked" : "selected";
508             }
509              
510 0         0 join(" ", @res);
511             }
512              
513             sub find_value_in {
514 0     0 0 0 my ($in, $name, $formal_value) = @_;
515              
516 0         0 my $actual_value = do {
517 0 0       0 if (my $sub = $in->can("param")) {
    0          
518 0         0 $sub->($in, $name);
519             } elsif (ref $in eq 'HASH') {
520 0         0 $in->{$name};
521             } else {
522 0         0 croak "Can't extract parameter from $in";
523             }
524             };
525              
526 0 0       0 if (not defined $actual_value) {
    0          
    0          
    0          
527 0         0 0
528             } elsif (not ref $actual_value) {
529 0         0 $actual_value eq $formal_value
530             } elsif (ref $actual_value eq 'HASH') {
531 0         0 $actual_value->{$formal_value};
532             } elsif (ref $actual_value eq 'ARRAY') {
533 0     0   0 defined lsearch {$_ eq $formal_value} @$actual_value
  0         0  
534             } else {
535             undef
536 0         0 }
537             }
538             }
539              
540             # Verbatimly stolen from CGI::Simple
541             sub url_decode {
542 0     0 0 0 my ( $self, $decode ) = @_;
543 0 0       0 return () unless defined $decode;
544 0         0 $decode =~ tr/+/ /;
545 0         0 $decode =~ s/%([a-fA-F0-9]{2})/ pack "C", hex $1 /eg;
  0         0  
546 0         0 return $decode;
547             }
548              
549             sub url_encode {
550 15     15 0 83 my ( $self, $encode ) = @_;
551 15 50       40 return () unless defined $encode;
552             # XXX: Forward slash (and ':') is allowed, for cleaner url. This may break...
553 15         37 $encode
554 8         38 =~ s{([^A-Za-z0-9\-_.!~*'() /:])}{ uc sprintf "%%%02x",ord $1 }eg;
555 15         32 $encode =~ tr/ /+/;
556 15         69 return $encode;
557             }
558              
559             sub url_encode_kv {
560 0     0 0 0 my ($self, $k, $v) = @_;
561 0         0 url_encode($self, $k) . '=' . url_encode($self, $v);
562             }
563              
564             sub encode_query {
565 0     0 0 0 my ($self, $param, $sep) = @_;
566             # require URI;
567             # my $url = URI->new('http:');
568             # $url->query_form($item->{cf_PARAM});
569             # $url->query;
570 0 0       0 return $param unless ref $param;
571 0   0     0 join $sep // ';', do {
572 0 0       0 if (ref $param eq 'HASH') {
573             map {
574 0         0 url_encode_kv($self, $_, $param->{$_});
  0         0  
575             } keys %$param
576             } else {
577 0         0 my @param = @$param;
578 0         0 my @res;
579 0         0 while (my ($k, $v) = splice @param, 0, 2) {
580 0         0 my $ek = url_encode($self, $k);
581             push @res, $ek . '='. url_encode($self, $_)
582 0 0       0 for ref $v ? @$v : $v;
583             }
584 0         0 @res;
585             }
586             };
587             }
588              
589             sub callerinfo {
590 19   50 19 0 176 my ($pkg, $file, $line) = caller(shift // 1);
591 19         143 (file => $file, line => $line);
592             }
593              
594             sub ostream {
595 8 100 50 8 0 57 my $fn = ref $_[0] ? $_[0] : \ ($_[0] //= "");
596 8 50 50     260 open my $fh, '>' . ($_[1] // ''), $fn
597             or die "Can't create output memory stream: $!";
598 8         1182 $fh;
599             }
600              
601             sub read_file {
602 7     7 0 1414903 my ($fn, $layer) = @_;
603 7 50 50     936 open my $fh, '<' . ($layer // ''), $fn or die "Can't open '$fn': $!";
604 7         84 local $/;
605 7         335 scalar <$fh>;
606             }
607              
608             sub dispatch_all {
609 0     0 0 0 my ($this, $con, $prefix, $argSpec) = splice @_, 0, 4;
610 0 0       0 my ($nargs, @preargs) = ref $argSpec ? @$argSpec : $argSpec;
611 0         0 my @queue;
612 0         0 foreach my $item (@_) {
613 0 0       0 if (ref $item) {
614 0 0       0 print {$con} escape(splice @queue) if @queue;
  0         0  
615 0         0 my ($wname, @args) = @$item;
616 0 0       0 my $sub = $this->can('render_' . $prefix . $wname)
617             or croak "Can't find widget '$wname' in dispatch";
618 0   0     0 $sub->($this, $con, @preargs, splice(@args, 0, $nargs // 0), \@args);
619             } else {
620 0         0 push @queue, $item;
621             }
622             }
623 0 0       0 print {$con} escape(@queue) if @queue;
  0         0  
624             }
625              
626             sub dispatch_one {
627 0     0 0 0 my ($this, $con, $prefix, $nargs, $item) = @_;
628 0 0       0 if (ref $item) {
629 0         0 my ($wname, @args) = @$item;
630 0 0       0 my $sub = $this->can('render_' . $prefix . $wname)
631             or croak "Can't find widget '$wname' in dispatch";
632 0   0     0 $sub->($this, $con, splice(@args, 0, $nargs // 0), \@args);
633             } else {
634 0         0 print {$con} escape($item);
  0         0  
635             }
636             }
637              
638             sub con_error {
639 2     2 0 5 my ($con, $err, @args) = @_;
640 2 100 66     29 if ($con->can("raise") and my $sub = $con->can("error")) {
641 1         5 $sub->($con, $err, @args)
642             } else {
643 1         15 sprintf $err, @args;
644             }
645             }
646              
647             sub safe_render {
648 9     9 0 790 my ($this, $con, $wspec, @args) = @_;
649 9         51 my @nsegs = lexpand($wspec);
650 9 100       20 my $wname = join _ => map {defined $_ ? $_ : ''} @nsegs;
  13         45  
651 9 100       109 my $sub = $this->can("render_$wname")
652             or die con_error($con, "Can't find widget '%s'", $wname);
653 7         24 $sub->($this, $con, @args);
654             }
655              
656             sub mk_http_status {
657 29     29 0 84 my ($code) = @_;
658 29         182 require HTTP::Status;
659              
660 29         5165 my $message = HTTP::Status::status_message($code);
661 29         227 "Status: $code $message\015\012";
662             }
663              
664             sub list_isa {
665 609     609 0 2331 my ($pack, $all) = @_;
666 609         1147 my $symtab = symtab($pack);
667 609 100       2595 my $sym = $symtab->{ISA} or return;
668 507 50       631 my $isa = *{$sym}{ARRAY} or return;
  507         2004  
669 507 100       3127 return @$isa unless $all;
670             map {
671 31         71 [$_, list_isa($_, $all)];
  54         115  
672             } @$isa;
673             }
674              
675             sub set_inc {
676 168     168 0 328 my ($pkg, $val) = @_;
677 168         714 $pkg =~ s|::|/|g;
678 168   50     1799 $INC{$pkg.'.pm'} = $val || 1;
679             # $INC{$pkg.'.pmc'} = $val || 1;
680 168         399 $_[1];
681             }
682              
683             sub try_invoke {
684 229     229 0 397 my $obj = shift;
685 229         562 my ($method, @args) = lexpand(shift);
686 229         414 my $default = shift;
687 229 100       1378 if (my $sub = UNIVERSAL::can($obj, $method)) {
688 186         830 $sub->($obj, @args);
689             } else {
690 43 100       365 wantarray ? () : $default;
691             }
692             }
693              
694             sub NIMPL {
695 0   0 0 0 0 my ($pack, $file, $line, $sub, $hasargs) = caller($_[0] // 1);
696 0         0 croak "Not implemented call of '$sub'";
697             }
698              
699             sub shallow_copy {
700 0 0   0 0 0 if (ref $_[0] eq 'HASH') {
    0          
    0          
    0          
701 0         0 +{%{$_[0]}};
  0         0  
702             } elsif (ref $_[0] eq 'ARRAY') {
703 0         0 +[@{$_[0]}];
  0         0  
704             } elsif (not ref $_[0]) {
705 0         0 my $copy = $_[0];
706             } elsif ($_[1]) {
707             # Pass thru unknown refs if 2nd arg is true.
708 0         0 $_[0];
709             } else {
710 0         0 croak "Unsupported data type for shallow_copy: " . ref $_[0];
711             }
712             }
713              
714             if (not is_debugging() or catch {require Sub::Name}) {
715 278     278   505 *subname = sub { my ($name, $sub) = @_; $sub }
  278         581  
716             } else {
717             *subname = *Sub::Name::subname;
718             }
719              
720             sub incr_opt {
721 36     36 0 99 my ($key, $list) = @_;
722 36         105 my $hash = do {
723 36 100 33     303 if (@$list and defined $list->[0] and ref $list->[0] eq 'HASH') {
      66        
724 32         70 shift @$list;
725             } else {
726             +{}
727 4         9 }
728             };
729 36         78 $hash->{$key}++;
730 36         195 $hash;
731             }
732              
733             sub num_is_ge {
734 0 0 0 0 0 0 defined $_[0] and not ref $_[0] and $_[0] ne ''
      0        
      0        
735             and $_[0] =~ /^\d+$/ and $& >= $_[1];
736             }
737              
738             # Order preserving unique.
739             sub unique (@) {
740 3     3 0 17 my %dup;
741 3 100       7 map {$dup{$_}++ ? () : $_} @_;
  9         42  
742             }
743              
744             sub secure_text_plain {
745 4     4 0 6 shift;
746 4         17 ("Content-type" => "text/plain; charset=utf-8"
747             , "X-Content-Type-Options" => "nosniff" # To protect IE8~ from XSS.
748             );
749             }
750              
751             sub psgi_error {
752 4     4 0 10 my ($self, $status, $msg, @rest) = @_;
753 4         21 return [$status, [$self->secure_text_plain, @rest], [escape($msg)]];
754             }
755              
756             sub ixhash {
757 134     134 0 689 tie my %hash, 'Tie::IxHash', @_;
758 134         1731 \%hash;
759             }
760              
761             # Ported from: Rack::Utils.parse_nested_query
762             sub parse_nested_query {
763 113 100 100 113 0 6884 return {} unless defined $_[0] and $_[0] ne '';
764 77         125 my ($enc) = $_[1];
765 77   33     260 my $params = $_[2] // ixhash();
766 77 100       187 if (ref $_[0]) {
767 37 100       193 my @pairs = map {$enc ? map(Encode::decode($enc, $_), @$_) : @$_}
768 37 50       109 ref $_[0] eq 'ARRAY' ? $_[0] : [%{$_[0]}];
  0         0  
769 37         725 while (my ($k, $v) = splice @pairs, 0, 2) {
770 59         111 normalize_params($params, $k, $v);
771             }
772             } else {
773 40         172 foreach my $p (split /[;&]/, $_[0]) {
774             my ($k, $v) = map {
775 67         163 s/\+/ /g;
  127         513  
776 127         291 my $raw = URI::Escape::uri_unescape($_);
777 127 100       1125 $enc ? Encode::decode($enc, $raw) : $raw;
778             } split /=/, $p, 2;
779 67 100       539 normalize_params($params, $k, $v) if defined $k;
780             }
781             }
782 74         302 $params;
783             }
784              
785             sub normalize_params {
786 211     211 0 385 my ($params, $name, $v) = @_;
787 211 100       939 my ($k) = $name =~ m(\A[\[\]]*([^\[\]]+)\]*)
788             or return;
789              
790 209         538 my $after = substr($name, length $&);
791              
792 209 100 100     782 if ($after eq '') {
    100          
    100          
793 87         393 $params->{$k} = $v;
794             } elsif ($after eq "[]") {
795 33   100     134 my $item = $params->{$k} //= [];
796 33 100 50     724 croak "expected ARRAY (got ".(ref $item || 'String').") for param `$k'"
797             unless ref $item eq 'ARRAY';
798 32         68 push @$item, $v;
799             } elsif ($after =~ m(^\[\]\[([^\[\]]+)\]$) or $after =~ m(^\[\](.+)$)) {
800 27         47 my $child_key = $1;
801 27   100     101 my $item = $params->{$k} //= [];
802 27 100 50     596 croak "expected ARRAY (got ".(ref $item || 'String').") for param `$k'"
803             unless ref $item eq 'ARRAY';
804 26 100 66     137 if (@$item and ref $item->[-1] eq 'HASH'
      100        
805             and not exists $item->[-1]->{$child_key}) {
806 8         51 normalize_params($item->[-1], $child_key, $v);
807             } else {
808 18         48 push @$item, normalize_params(ixhash(), $child_key, $v);
809             }
810             } else {
811 62   66     260 my $item = $params->{$k} //= ixhash();
812 62 100 50     1188 croak "expected HASH (got ".(ref $item || 'String').") for param `$k'"
813             unless ref $item eq 'HASH';
814 61         131 $params->{$k} = normalize_params($item, $after, $v);
815             }
816              
817 204         2211 $params;
818             }
819              
820             sub pkg2pm {
821 151     151 0 251 my ($pack) = @_;
822 151         1195 $pack =~ s{::|'}{/}g;
823 151         704 "$pack.pm";
824             }
825              
826             #
827             # to put all functions into @EXPORT_OK.
828             #
829             {
830             our @EXPORT_OK = qw(define_const);
831             my $symtab = symtab(__PACKAGE__);
832             foreach my $name (grep {/^[a-z]/} keys %$symtab) {
833             my $glob = $symtab->{$name};
834             next unless *{$glob}{CODE};
835             push @EXPORT_OK, $name;
836             }
837             }
838              
839             1;