File Coverage

blib/lib/Text/Clevery/Function.pm
Criterion Covered Total %
statement 302 331 91.2
branch 136 176 77.2
condition 17 27 62.9
subroutine 27 30 90.0
pod 10 10 100.0
total 492 574 85.7


line stmt bran cond sub pod time code
1             package Text::Clevery::Function;
2 14     14   84 use strict;
  14         24  
  14         555  
3 14     14   76 use warnings;
  14         24  
  14         468  
4              
5 14     14   73 use parent qw(Text::Xslate::Bridge);
  14         23  
  14         108  
6              
7 14     14   29942 use Any::Moose '::Util::TypeConstraints';
  14         39  
  14         112  
8 14     14   4523 use File::Spec;
  14         30  
  14         432  
9              
10 14         1621 use Scalar::Util qw(
11             blessed
12             looks_like_number
13 14     14   77 );
  14         23  
14              
15 14         2308 use Text::Xslate::Util qw(
16             p any_in literal_to_value
17             mark_raw html_escape
18             $STRING
19 14     14   81 );
  14         25  
20              
21 14         116137 use Text::Clevery::Util qw(
22             safe_join safe_cat
23             make_tag
24             true false
25             ceil floor
26 14     14   9467 );
  14         32  
27              
28             my $Bool = subtype __PACKAGE__ . '.Bool', as 'Bool';
29             my $Str = subtype __PACKAGE__ . '.Str', as 'Str|Object';
30             my $Int = subtype __PACKAGE__ . '.Int', as 'Int';
31             my $Array = subtype __PACKAGE__ . '.Array', as 'ArrayRef';
32             my $ListLike = subtype __PACKAGE__ . '.List', as "$Array|$Str";
33             my $AssocArray = subtype __PACKAGE__ . '.AssocArray', as 'ArrayRef|HashRef';
34              
35             require Text::Clevery;
36             our $EngineClass = 'Text::Clevery';
37              
38             # Implemented as statements:
39             # {capture}, {foreach}, {literal}, {section}, {strip}
40             # {include}
41             my %function = map { $_ => __PACKAGE__->can($_) || _make_not_impl($_) } qw(
42             config_load
43             include_php
44             insert
45              
46             assign
47             counter
48             cycle
49             debug
50             eval
51             fetch
52             html_checkboxes
53             html_image
54             html_options
55             html_radios
56             html_select_date
57             html_select_time
58             html_table
59             mailto
60             math
61             popup
62             pupup_init
63             textformat
64             );
65             __PACKAGE__->bridge(function => \%function);
66              
67             sub _make_not_impl {
68 154     154   461 my($name) = @_;
69 154     0   1012 return sub { die "Function {$name} is not implemented.\n" };
  0         0  
70             }
71              
72             sub _required {
73 0     0   0 my($name, $level) = @_;
74 0 0       0 my $function = (caller($level ? $level + 1 : 1))[3];
75 0         0 Carp::croak("Required: '$name' attribute for $function");
76             }
77              
78             sub _bad_param {
79 0     0   0 my($type, $name, $value) = @_;
80 0         0 Carp::croak("InvalidValue for '$name': " . $type->get_message($value));
81             }
82              
83             sub _parse_args {
84 93     93   163 my $args = shift;
85 93 50       339 if(@_ % 5) {
86 0         0 Carp::croak("Oops: " . p(@_));
87             }
88 93         470 while(my($name, $var_ref, $type, $required, $default) = splice @_, 0, 5) {
89 757 100       7368 if(defined $args->{$name}) {
    50          
90 232         452 my $value = delete $args->{$name};
91 232 50       1535 $type->check($value)
92             or _bad_param($type, $name, $value);
93 232         320 ${$var_ref} = $value;
  232         892  
94             }
95             elsif($required){
96 0         0 _required($name, 1);
97             }
98             else {
99 525         532 ${$var_ref} = $default;
  525         1959  
100             }
101             }
102 93 100       124 return if keys(%{$args}) == 0;
  93         386  
103              
104 2 50       10 if(defined wantarray) {
105 2         5 return map { $_ => $args->{$_} } sort keys %{$args};
  2         11  
  2         10  
106             }
107             else {
108 0 0       0 if(%{$args}) {
  0         0  
109 0         0 my $name = (caller 0)[3];
110 0         0 warnings::warn(misc => "$name: Unknown option(s): "
111 0         0 . join ", ", sort keys %{$args});
112             }
113             }
114             }
115              
116             sub config_load {
117 5     5 1 10887 _parse_args(
118             {@_},
119             file => \my $file, $Str, true, undef,
120             section => \my $section, $Str, false, undef,
121             scope => \my $scope, $Str, false, 'local', # or 'parent', 'global'
122             );
123              
124 5         2079 require Config::Tiny;
125 5   33     2014 my $c = Config::Tiny->read($file)
126             || Carp::croak(Config::Tiny->errstr);
127              
128 5         1025 my %config;
129              
130 5 100 50     26 my $root = defined($section)
131             ? $config{$section} ||= {}
132             : \%config;
133              
134 5         11 while(my($section_name, $section_config) = each %{$c}) {
  10         41  
135 5 100 50     31 my $storage = $section_name eq '_'
136             ? $root
137             : ($config{$section_name} ||= {});
138              
139 5         10 while(my($key, $literal) = each %{$section_config}) {
  25         434  
140 20         62 $storage->{$key} = literal_to_value($literal);
141             }
142             }
143              
144 5         43 my $context = $EngineClass->get_current_context;
145 5   100     45 my $top = $context->_storage->{config} ||= {
146             '@global' => {}, # prototype of all the config storages
147             };
148              
149 5 100       23 if($scope eq 'local') {
150 4         20 my $this = $context->config;
151 4         8 %{$this} = (%{$this}, %config);
  4         18  
  4         15  
152             }
153             else { # TODO: distingwish between 'global' and 'parent'
154 1         1459 require Storable;
155 1         4271 foreach my $this(values %{$top}) {
  1         5  
156 1         2 %{$this} = (%{$this}, %{ Storable::dclone(\%config) });
  1         6  
  1         2  
  1         100  
157             }
158             }
159              
160 5         73 return '';
161             }
162              
163             #sub php; # never implemented!
164             #sub strip
165              
166             #sub assign
167              
168             sub counter {
169 13     13 1 11891 _parse_args(
170             {@_},
171             # name => var_ref, type, required, default
172             name => \my $name, $Str, false, 'default',
173             start => \my $start, $Int, false, undef,
174             skip => \my $skip, $Int, false, undef,
175             direction => \my $direction, $Str, false, 'up', # or 'down'
176             print => \my $print, $Bool, false, true,
177             assign => \my $assign, $Str, false, undef,
178             );
179              
180 13         66 my $storage = $EngineClass->get_current_context->_storage;
181 13 100 100     107 my $this = $storage->{counter}{$name} ||= {
    100          
182             count => defined($start) ? $start : 1,
183             skip => defined($skip) ? $skip : 1,
184             };
185              
186 13 50       29 if($assign) {
187 0         0 die "cycle: 'assign' is not supported";
188             }
189              
190 13 100       33 my $retval = $print ? $this->{count} : '';
191              
192 13 50       25 if($direction eq 'up') {
193 13         23 $this->{count} += $this->{skip};
194             }
195             else {
196 0         0 $this->{count} -= $this->{skip};
197             }
198              
199 13         107 return $retval;
200             }
201              
202             sub cycle {
203 21     21 1 7703 _parse_args(
204             {@_},
205             # name => var_ref, type, required, default
206             name => \my $name, $Str, false, 'default',
207             values => \my $values, $ListLike, false, undef,
208             print => \my $print, $Bool, false, true,
209             advance => \my $advance, $Bool, false, true,
210             delimiter => \my $delimiter, $Str, false, ',',
211             assign => \my $assign, $Str, false, undef,
212             reset => \my $reset, $Bool, false, false,
213             );
214              
215 21         88 my $storage = $EngineClass->get_current_context->_storage;
216 21   100     64 my $this = $storage->{cycle}{$name} ||= {
217             values => [],
218             index => 0,
219             };
220              
221 21 100       37 if(defined $values) {
222 10 100       23 if(ref($values) eq 'ARRAY') {
223 5         6 @{$this->{values}} = @{$values};
  5         19  
  5         8  
224             }
225             else {
226 5         39 @{$this->{values}} = (split /$delimiter/, $values);
  5         17  
227 5         11 $values = $this->{values};
228             }
229             }
230             else {
231 11         15 $values = $this->{values};
232             }
233              
234 21 50       24 if(!@{$values}) {
  21         42  
235 0         0 _required('values');
236             }
237              
238 21 100       43 if($reset) {
239 1         3 $this->{index} = 0;
240             }
241              
242 21 50       59 if($assign) {
243 0         0 die "cycle: 'assign' is not supported";
244             }
245              
246 21 100       45 my $retval = $print
247             ? $values->[$this->{index}]
248             : '';
249              
250 21 100       34 if($advance) {
251 15 100       16 if(++$this->{index} >= @{$values}) {
  15         37  
252 5         8 $this->{index} = 0;
253             }
254             }
255              
256 21         147 return $retval;
257             }
258              
259             #sub debug
260             #sub eval
261             #sub fetch
262              
263             sub _split_assoc_array {
264 7     7   12 my($assoc) = @_;
265 7         10 my @keys;
266             my @values;
267 7 100       24 if(ref $assoc eq 'HASH') {
268 3         4 foreach my $key(sort keys %{$assoc}) {
  3         17  
269 6         9 push @keys, $key;
270 6         13 push @values, $assoc->{$key};
271             }
272             }
273             else {
274 4         8 foreach my $pair(@{$assoc}) {
  4         12  
275 8         20 push @keys, $pair->[0];
276 8         23 push @values, $pair->[1];
277             }
278             }
279 7         25 return(\@keys, \@values);
280             }
281              
282             sub html_checkboxes {
283 8     8 1 16987 my @extra = _parse_args(
284             {@_},
285             # name => var_ref, type, required, default
286             name => \my $name, $Str, false, 'checkbox',
287             values => \my $values, $Array, undef, undef,
288             output => \my $output, $Array, undef, undef,
289             selected => \my $selected, $ListLike, false, [],
290             options => \my $options, $AssocArray, undef, undef,
291             separator => \my $separator, $Str, false, q{},
292             labels => \my $labels, $Bool, false, true,
293             );
294              
295 8 100       30 if(defined $options) {
296 1         5 ($values, $output) = _split_assoc_array($options);
297             }
298             else {
299 7 50       17 $values or _required('values');
300 7 50       15 $output or _required('output');
301             }
302              
303 8 100       28 if(ref $selected ne 'ARRAY') {
304 6         14 $selected = [$selected];
305             }
306              
307 8         13 my @result;
308 8         17 for(my $i = 0; $i < @{$values}; $i++) {
  31         92  
309 23         34 my $value = $values->[$i];
310              
311 23         67 my $input = safe_cat(make_tag(
312             input => undef,
313             type => 'checkbox',
314             name => $name,
315             value => $value,
316 23 100       33 any_in($value, @{$selected}) ? (checked => 'checked') : (),
317             @extra,
318             ), html_escape($output->[$i])),
319             ;
320              
321 23 100       106 $input = make_tag(label => $input) if $labels;
322              
323 23         69 push @result, safe_cat( $input, $separator);
324             }
325 8         30 return safe_join("\n", @result);
326             }
327              
328             sub html_image {
329 5     5 1 8910 my @extra = _parse_args(
330             {@_},
331             # name => var_ref, type, required, default
332             file => \my $file, $Str, true, undef,
333             height => \my $height, $Str, false, undef,
334             width => \my $width, $Str, false, undef,
335             basedir => \my $basedir, $Str, false, q{},
336             alt => \my $alt, $Str, false, q{},
337             href => \my $href, $Str, false, undef,
338             path_prefix
339             => \my $path_prefix, $Str, false, '',
340             );
341              
342              
343 5 100 66     31 if(!(defined $height and defined $width)) {
344 4         7 eval {
345 4         45 require Image::Size;
346 4 50       64 if($file =~ m{\A /}xms) {
347 0         0 my $env = $EngineClass->get_current_context->env;
348 0   0     0 $basedir = $env->{DOCUMENT_ROOT} || '.';
349             }
350 4         85 my $image_path = File::Spec->catfile($basedir, $file);
351             # it returns (undef, undef, $status_message) on fails
352 4         27 ($width, $height) = Image::Size::imgsize($image_path);
353             };
354             }
355              
356 5         11553 my $img = make_tag(
357             img => undef,
358             src => $path_prefix . $file,
359             alt => $alt,
360             width => $width,
361             height => $height,
362             @extra,
363             );
364 5 100       16 if(defined $href) {
365 1         4 $img = make_tag(a => $img, href => $href);
366             }
367 5         145 return $img;
368             }
369              
370             sub _build_options {
371 26     26   43 my($values, $labels, $selected) = @_;
372 26         46 my @result;
373 26         40 for(my $i = 0; $i < @{$values}; $i++) {
  374         910  
374 348         660 my $value = $values->[$i];
375 348         432 my $label = $labels->[$i];
376              
377 348 100 66     9346 if(!(ref($label) eq 'ARRAY' or ref($label) eq 'HASH')) {
378 346         1071 push @result, make_tag(
379             option => $label,
380             # label => $label,
381             value => $value,
382 346 100       425 (any_in($value, @{$selected}) ? (selected => 'selected') : ()),
383             );
384             }
385             else {
386 2         5 my($v, $l) = _split_assoc_array($label);
387 2         9 my @group = _build_options($v, $l, $selected);
388 2         7 push @result, make_tag(
389             optgroup => safe_join("\n", "", @group, ""),
390             label => $value,
391             );
392              
393             }
394             }
395 26         135 return @result;
396             }
397              
398             sub html_options {
399 24     24 1 9037 my @extra = _parse_args(
400             {@_},
401             values => \my $values, $Array, undef, undef,
402             output => \my $output, $Array, undef, undef,
403             selected => \my $selected, $ListLike, false, [],
404             options => \my $options, $AssocArray, undef, undef,
405             name => \my $name, $Str, false, undef,
406             );
407              
408 24 100       84 if(defined $options) {
409 2         9 ($values, $output) = _split_assoc_array($options);
410             }
411             else {
412 22 50       58 $values or _required('values');
413 22 50       48 $output or _required('output');
414             }
415              
416 24 50       68 if(ref $selected ne 'ARRAY') {
417 24         65 $selected = [$selected];
418             }
419              
420 24         75 my @result = _build_options($values, $output, $selected);
421              
422 24 100       74 if(defined $name) {
423 4         17 return make_tag(
424             select => safe_join("\n", '', @result, ''),
425             name => $name,
426             @extra,
427             );
428             }
429             else {
430 20         74 return safe_join("\n", @result);
431             }
432             }
433              
434             sub html_radios {
435 4     4 1 8718 my @extra = _parse_args(
436             {@_},
437             name => \my $name, $Str, false, "radio",
438             values => \my $values, $Array, undef, undef,
439             output => \my $output, $Array, undef, undef,
440             selected => \my $selected, $Str, false, q{},
441             options => \my $options, $AssocArray, undef, undef,
442             separator => \my $separator, $Str, false, q{},
443             assign => \my $assign, $Str, false, q{},
444             );
445              
446 4 100       19 if(defined $options) {
447 2         9 ($values, $output) = _split_assoc_array($options);
448             }
449             else {
450 2 50       7 $values or _required('values');
451 2 50       9 $output or _required('output');
452             }
453              
454 4 50       11 if($assign) {
455 0         0 die 'html_radios: "assign" is not supported';
456             }
457              
458 4         17 my @result;
459 4         10 for(my $i = 0; $i < @{$values}; $i++) {
  12         36  
460 8         18 my $value = $values->[$i];
461 8         15 my $label = $output->[$i];
462              
463 8         33 my $id = safe_join '_', $name, $value;
464              
465 8 100       218 my $radio = safe_cat make_tag(
466             input => undef,
467             type => 'radio',
468             name => $name,
469             value => $value,
470             id => $id,
471             ($selected eq $value ? (checked => 'checked') : ()),
472             @extra,
473             ), $label;
474 8         33 $radio = make_tag(label => $radio, for => $id);
475 8 100       26 if(length $separator) {
476 2         6 $radio = safe_cat $radio, $separator;
477             }
478              
479 8         25 push @result, $radio;
480             }
481              
482 4         15 return safe_join "\n", @result;
483             }
484              
485             sub _init_time_object {
486 10     10   22 my($time) = @_;
487 10 100       72 $time = time() if not defined $time;
488              
489 10 50 66     103 if(!(blessed($time) && $time->can('epoch'))) {
490 10 100       48 if(looks_like_number($time)) {
491 9         64 $time = Time::Piece->new($time);
492             }
493             else {
494             # YYY-MM-DD HH:MM:SS style timestamp
495 1         9 $time = Time::Piece->strptime($time, q{%Y-%m-%d %H:%M:%S});
496             }
497             }
498 10         505 return $time;
499             }
500              
501             sub _deparse_html_attr {
502 90     90   163 my($attr) = @_;
503 90 100       316 return if not $attr;
504              
505 15 50       314 my($name, $value) = $attr =~ m{
506             (\w+) = (\w+ | $STRING)
507             }xms or return;
508              
509 15 100       91 if($value =~ /\A " (.*) " \z/xms) {
    100          
510 2         7 $value = $1;
511             }
512             elsif($value =~ /\A ' (.*) ' \z/xms) {
513 9         27 $value = $1;
514             }
515 15         29 $value =~ s/"/"/g; # ensure " is gone
516 15         25 $value =~ s/'/'/g; # ensure ' is gone
517 15         93 return mark_raw($name) => mark_raw($value);
518             }
519              
520             sub _build_datetime_options {
521 19     19   331 my($field_array, $prefix, $moniker,
522             $empty, $values_ref, $names_ref, $selected,
523             @extra) = @_;
524              
525 19 50       99 my $name = defined($field_array)
526             ? safe_cat( $field_array, '[', $prefix, $moniker, ']')
527             : safe_cat( $prefix, $moniker);
528              
529 19 100       65 if(defined $empty) {
530 1         3 $names_ref = [$empty, @{$names_ref}];
  1         2  
531 1         2 $values_ref = [q{}, @{$values_ref}];
  1         3  
532             }
533              
534 19         58 my $options = html_options(
535             values => $values_ref,
536             output => $names_ref,
537             selected => $selected,
538             );
539 41         87 return make_tag(
540             select => safe_cat("\n", $options, "\n"),
541             name => $name,
542              
543 19         100 map { _deparse_html_attr($_) } @extra,
544             );
545             }
546              
547             sub html_select_date {
548 7     7 1 18945 _parse_args(
549             {@_},
550             prefix => \my $prefix, $Str, false, 'Date_',
551             time => \my $time, $Str, false, undef,
552             start_year => \my $start_year, $Str, false, undef,
553             end_year => \my $end_year, $Str, false, undef,
554              
555             display_days => \my $display_days, $Bool, false, true,
556             display_months => \my $display_months, $Bool, false, true,
557             display_years => \my $display_years, $Bool, false, true,
558              
559             month_format => \my $month_format, $Str, false, '%B', # for strftime
560             month_value_format => \my $month_value_format, $Str, false, '%m', # for strftime
561             day_format => \my $day_format, $Str, false, '%02d', # for sprintf
562             day_value_format => \my $day_value_format, $Str, false, '%d', # for sprintf
563              
564             year_as_text => \my $year_as_text, $Bool, false, false,
565             reverse_years => \my $reverse_years, $Bool, false, false,
566             field_array => \my $field_array, $Str, false, undef,
567              
568             day_size => \my $day_size, $Int, false, undef,
569             month_size => \my $month_size, $Int, false, undef,
570             year_size => \my $year_size, $Int, false, undef,
571              
572             all_extra => \my $all_extra, $Str, false, undef,
573             day_extra => \my $day_extra, $Str, false, undef,
574             month_extra => \my $month_extra, $Str, false, undef,
575             year_extra => \my $year_extra, $Str, false, undef,
576              
577             year_empty => \my $year_empty, $Str, false, undef,
578             month_empty => \my $month_empty, $Str, false, undef,
579             day_empty => \my $day_empty, $Str, false, undef,
580              
581             field_order => \my $field_order, $Str, false, 'MDY',
582             field_separator => \my $field_separator, $Str, false, "\n",
583             );
584              
585 7         1200 require Time::Piece;
586              
587             # complex default values
588 7         10347 $time = _init_time_object($time);
589              
590 7 100       30 if(not defined $start_year) {
    50          
591 5         19 $start_year = $time->year;
592             }
593             elsif($start_year =~ /\A [+-]/xms) {
594 2         9 $start_year = $time->year + $start_year; # relative
595             }
596              
597 7 100       131 if(not defined $end_year) {
    50          
598 5         10 $end_year = $start_year;
599             }
600             elsif($end_year =~ /\A [+-]/xms) {
601 2         6 $end_year = $time->year + $end_year; # relative
602             }
603             # build HTML
604 7         16 my %result;
605              
606 7 100       23 if($display_months) {
607 3         6 my @names;
608             my @values;
609 3         10 for my $m(1 .. 12) {
610 36         425 my $t = Time::Piece->strptime($m, '%m');
611 36         817 push @names, $t->strftime($month_format);
612 36         455 push @values, $t->strftime($month_value_format);
613             }
614 3 100       40 $result{M} = _build_datetime_options(
615             $field_array, $prefix, 'Month',
616             $month_empty,
617             \@values,
618             \@names,
619             $time->strftime($month_value_format),
620             (defined $month_size ? qq{size='$month_size'} : ()),
621             $all_extra,
622             $month_extra,
623             );
624             }
625              
626 7 100       32 if($display_days) {
627 3         5 my @days;
628             my @dayvals;
629 3         6 for my $d(1 .. 31) {
630 93         158 push @days, sprintf($day_format, $d);
631 93         149 push @dayvals, sprintf($day_value_format, $d);
632             }
633 3 100       19 $result{D} = _build_datetime_options(
634             $field_array, $prefix, 'Day',
635             $month_empty,
636             \@dayvals,
637             \@days,
638             sprintf($day_value_format, $time->mday), # day of month
639             (defined $day_size ? qq{size='$day_size'} : ()),
640             $all_extra,
641             $day_extra,
642             );
643             }
644              
645 7 100       47 if($display_years) {
646 5         16 my @years = ($start_year .. $end_year);
647 5 50       15 if($reverse_years) {
648 0         0 @years = reverse @years;
649             }
650 5 100       25 $result{Y} = _build_datetime_options(
651             $field_array, $prefix, 'Year',
652             $year_empty,
653             \@years,
654             \@years,
655             $time->year,
656             (defined $year_size ? qq{size='$year_size'} : ()),
657             $all_extra,
658             $year_extra,
659             );
660             }
661              
662 7         54 my @order = split //, uc $field_order;
663 7         28 return safe_join $field_separator, grep { defined } @result{@order};
  21         48  
664             }
665              
666             sub html_select_time {
667 3     3 1 5813 _parse_args(
668             {@_},
669             prefix => \my $prefix, $Str, false, 'Time_',
670             time => \my $time, $Str, false, undef,
671              
672             display_hours => \my $display_hours, $Bool, false, true,
673             display_minutes => \my $display_minutes, $Bool, false, true,
674             display_seconds => \my $display_seconds, $Bool, false, true,
675             display_meridian => \my $display_meridian, $Bool, false, true, # am/pm
676              
677             use_24_hours => \my $use_24_hours, $Bool, false, true,
678             minute_interval => \my $minute_interval, $Int, false, 1,
679             second_interval => \my $second_interval, $Int, false, 1,
680             field_array => \my $field_array, $Str, false, undef,
681              
682             all_extra => \my $all_extra, $Str, false, undef,
683             hour_extra => \my $hour_extra, $Str, false, undef,
684             minute_extra => \my $minute_extra, $Str, false, undef,
685             second_extra => \my $second_extra, $Str, false, undef,
686             meridian_exra => \my $meridian_extra, $Str, false, undef,
687              
688             hour_empty => \my $hour_empty, $Str, false, undef,
689             minute_empty => \my $minute_empty, $Str, false, undef,
690             second_empty => \my $second_empty, $Str, false, undef,
691             meridian_empty => \my $meridian_empty, $Str, false, undef,
692              
693             field_separator => \my $field_separator, $Str, false, "\n",
694             );
695              
696 3         1289 require Time::Piece;
697              
698             # complex default values
699 3         12968 $time = _init_time_object($time);
700              
701             # build HTML
702 3         5 my @result;
703 3 50       11 if($display_hours) {
704 3 100       12 my $hour_format = $use_24_hours ? '%H' : '%I';
705              
706 3         15 my @hours;
707 3 100       16 for my $i($use_24_hours ? (0 .. 23) : (1 .. 12)) {
708 60         133 push @hours, sprintf('%02d', $i);
709             }
710 3         21 push @result, _build_datetime_options(
711             $field_array, $prefix, 'Hour',
712             $hour_empty,
713             \@hours,
714             \@hours,
715             $time->strftime($hour_format),
716             $all_extra,
717             $hour_extra,
718             );
719             }
720              
721 3 100       22 if($display_minutes) {
722 2         4 my @minutes;
723 2         9 for(my $i = 0; $i < 60; $i += $minute_interval) {
724 66         368 push @minutes, sprintf('%02d', $i);
725             }
726 2         13 my $selected = sprintf '%02d',
727             int($time->day_of_month / $minute_interval) * $minute_interval;
728              
729 2         25 push @result, _build_datetime_options(
730             $field_array, $prefix, 'Minute',
731             $minute_empty,
732             \@minutes,
733             \@minutes,
734             $selected,
735             $all_extra,
736             $minute_extra,
737             );
738             }
739              
740 3 100       22 if($display_seconds) {
741 2         3 my @seconds;
742 2         153 for(my $i = 0; $i < 60; $i += $second_interval) {
743 66         351 push @seconds, sprintf('%02d', $i);
744             }
745              
746 2         12 my $selected = sprintf '%02d',
747             int($time->second / $second_interval) * $second_interval;
748 2         23 push @result, _build_datetime_options(
749             $field_array, $prefix, 'Second',
750             $second_empty,
751             \@seconds,
752             \@seconds,
753             $selected,
754             $all_extra,
755             $second_extra,
756             );
757             }
758              
759 3 100 66     28 if($display_meridian && !$use_24_hours) {
760 1         3 my $meridian_format = '%p';
761              
762 1         7 push @result, _build_datetime_options(
763             $field_array, $prefix, 'Meridian',
764             $meridian_empty,
765             [qw(am pm)],
766             [qw(AM PM)],
767             lc($time->strftime($meridian_format)),
768             $all_extra,
769             $meridian_extra,
770             );
771             }
772              
773 3         12 return safe_join $field_separator, @result;
774             }
775              
776             sub _html_table_attr {
777 46     46   58 my($attrs, $n) = @_;
778 3         8 return _deparse_html_attr(
779             ref($attrs) eq 'ARRAY'
780 46 100       113 ? $attrs->[ $n % @{$attrs} ] # cycle
781             : $attrs
782             );
783             }
784              
785             sub html_table {
786 3     3 1 5184 _parse_args(
787             {@_},
788             loop => \my $loop, $Array, true, undef,
789             cols => \my $cols, $ListLike, false, undef,
790             rows => \my $rows, $Int, false, undef,
791             inner => \my $inner, $Str, false, 'cols', # or 'rows'
792             caption => \my $caption, $Str, false, undef,
793             table_attr => \my $table_attr, $Str, false, q{border="1"},
794             th_attr => \my $th_attr, $ListLike, false, undef,
795             tr_attr => \my $tr_attr, $ListLike, false, undef,
796             td_attr => \my $td_attr, $ListLike, false, undef,
797             trailpad => \my $trailpad, $Str, false, mark_raw(' '),
798             hdir => \my $hdir, $Str, false, 'right', # or 'left'
799             vdir => \my $vdir, $Str, false, 'down', # or 'up'
800             );
801              
802 3         8 my $loop_count = @{$loop};
  3         7  
803              
804 3         5 my $cols_count;
805 3 100       24 if(looks_like_number($cols)) {
    50          
    100          
806 1         2 $cols_count = $cols;
807 1         2 undef $cols;
808             }
809             elsif(ref $cols eq 'ARRAY') {
810 0         0 $cols_count = @{$cols};
  0         0  
811             }
812             elsif(defined $cols){
813 1         9 $cols = [ split /,/, $cols ];
814 1         3 $cols_count = @{$cols};
  1         3  
815             }
816             else {
817 1         3 $cols_count = 3;
818             }
819              
820 3 50       10 if(not defined $rows) {
    0          
821 3         20 $rows = ceil($loop_count / $cols_count);
822             }
823             elsif(not defined $cols) {
824 0 0       0 if(defined $rows) {
825 0         0 $cols_count = ceil($loop_count / $rows);
826             }
827             }
828              
829             # build HTML
830 3         5 my @table;
831 3 50       10 if(defined $caption) {
832 0         0 push @table, make_tag caption => $caption;
833             }
834              
835 3 100       18 if(defined $cols) {
836 1 50       5 if($hdir ne 'right') {
837 0         0 $cols = [reverse @{$cols}];
  0         0  
838             }
839 1         2 my @h;
840 1         5 for(my $r = 0; $r < $cols_count; $r++) {
841 4         12 push @h, make_tag(th => $cols->[$r],
842             _html_table_attr($th_attr, $r));
843             }
844 1         7 my $tr = make_tag(tr => safe_cat("\n", @h, "\n"));
845 1         6 push @table, make_tag thead => safe_join("\n", '', $tr, '');
846             }
847              
848 3         5 my @tbody;
849 3         12 for(my $r = 0; $r < $rows; $r++) {
850 9 50       25 my $rx = ($vdir eq 'down')
851             ? $r * $cols_count
852             : ($rows - 1 - $r) * $cols_count;
853              
854 9         10 my @d;
855 9         21 for(my $c = 0; $c < $cols_count; $c++) {
856 33 50       70 my $x = ($hdir eq 'right')
857             ? $rx + $c
858             : $rx + $cols_count - 1 - $c;
859 33 50       131 if($inner ne 'cols') {
860 0         0 $x = floor($x / $cols_count) + ($x % $cols_count) * $rows;
861             }
862              
863 33 100       91 push @d, make_tag
864             td => ($x < $loop_count ? $loop->[$x] : $trailpad),
865             _html_table_attr($td_attr, $r);
866             }
867              
868 9         29 push @tbody, make_tag(tr => safe_cat(@d),
869             _html_table_attr($tr_attr, $r));
870             }
871              
872 3 50       10 if(@tbody) {
873 3         13 push @table, make_tag(tbody => safe_join "\n", '', @tbody, '');
874             }
875 3         17 return make_tag
876             table => safe_join("\n", '', @table, ''),
877             _deparse_html_attr($table_attr);
878             }
879              
880             #sub mailto
881             #sub math
882             #sub popup
883             #sub popup_init
884             #sub textformat
885              
886 14     14   173 no Any::Moose '::Util::TypeConstraints';
  14         35  
  14         158  
887             1;
888             __END__