File Coverage

blib/lib/Template/Caribou/Tags.pm
Criterion Covered Total %
statement 101 104 97.1
branch 29 34 85.2
condition 18 28 64.2
subroutine 21 21 100.0
pod 1 3 33.3
total 170 190 89.4


line stmt bran cond sub pod time code
1             package Template::Caribou::Tags;
2             our $AUTHORITY = 'cpan:YANICK';
3             #ABSTRACT: generates tags functions for Caribou templates
4             $Template::Caribou::Tags::VERSION = '1.2.2';
5 18     18   394306 use strict;
  18         37  
  18         757  
6 18     18   91 use warnings;
  18         34  
  18         1058  
7              
8 18     18   94 use Carp;
  18         34  
  18         1265  
9              
10 18     18   4150 use Template::Caribou::Role;
  18         70  
  18         599  
11              
12 18     18   12518 use List::AllUtils qw/ pairmap pairgrep /;
  18         140547  
  18         2077  
13 18     18   10479 use Ref::Util qw/ is_plain_hashref /;
  18         48284  
  18         1997  
14              
15 18     18   157 use parent 'Exporter::Tiny';
  18         67  
  18         332  
16 18     18   1765 use experimental 'signatures', 'postderef';
  18         53  
  18         163  
17 18     18   15929 use XML::Writer;
  18         282851  
  18         1941  
18              
19             our @EXPORT_OK = qw/ render_tag mytag attr /;
20              
21              
22 21     21 1 118 sub attr(@args){
  21         61  
  21         48  
23 21 100       55 return $_{$args[0]} if @args == 1;
24              
25 20 50       85 croak "number of attributes must be even" if @args % 2;
26              
27 18     18   164 no warnings 'uninitialized';
  18         35  
  18         7911  
28 20         95 while( my ( $k, $v ) = splice @args, 0, 2 ) {
29 28 100       110 if ( $k =~ s/^\+// ) {
    100          
30 3         15 $_{$k} = { map { $_ => 1 } split ' ', $_{$k} }
31 3 100       18 unless ref $_{$k};
32              
33 3         16 $_{$k}{$v} = 1;
34             }
35             elsif ( $k =~ s/^-// ) {
36 0         0 $_{$k} = { map { $_ => 1 } split ' ', $_{$k} }
37 1 50       5 unless ref $_{$k};
38              
39 1         5 delete $_{$k}{$v};
40             }
41             else {
42 24         117 $_{$k} = $v;
43             }
44             }
45              
46 20         52 return;
47             }
48              
49              
50             sub _generate_mytag {
51 177     177   18975 my ( undef, undef, $arg ) = @_;
52              
53             $arg->{'-as'} ||= $arg->{tag}
54 177 50 66     567 or die "mytag needs to be given '-as' or 'name'\n";
55              
56 177   100     441 my $tagname = $arg->{tag} || 'div';
57              
58             my $groom = sub {
59            
60 18     18   141 no warnings 'uninitialized';
  18         32  
  18         15348  
61              
62 83 100 66 83   604 if( my $defaults = $arg->{classes} || $arg->{class} ) {
63 0         0 $_{class} = { map { $_ => 1 } split ' ', $_{class} }
64 1 50       9 unless ref $_{class};
65 1 50       6 if( ref $defaults ) {
66 0   0     0 $_{class}{$_} //= 1 for @$defaults;
67             }
68             else {
69 1   50     10 $_{class}{$_} //= 1 for split ' ', $defaults;
70             }
71             }
72              
73 83   33     155 $_{$_} ||= $arg->{attr}{$_} for eval { keys %{ $arg->{attr} } };
  83         119  
  83         372  
74              
75 83 100       241 $arg->{groom}->() if $arg->{groom};
76 177         1008 };
77              
78             return sub :prototype(&) {
79 83     83   256214 my $inner = shift;
80 83   100     653 render_tag( $tagname, $inner, $groom, $arg->{indent}//1 );
81             }
82 177         1349 }
83              
84              
85             sub render_tag {
86 111     111 0 638393 my ( $tag, $inner_sub, $groom, $indent ) = @_;
87              
88 111   100     420 $indent //= 1;
89              
90 111 100       315 local $Template::Caribou::TAG_INDENT_LEVEL = $indent ? $Template::Caribou::TAG_INDENT_LEVEL : 0;
91              
92 111 100   6   352 my $sub = ref $inner_sub eq 'CODE' ? $inner_sub : sub { $inner_sub };
  6         16  
93              
94             # need to use the object for calls to 'show'
95 111   66     1238 my $bou = $Template::Caribou::TEMPLATE || Moose::Meta::Class->create_anon_class(
96             roles => [ 'Template::Caribou::Role' ]
97             )->new_object;
98              
99 111         190555 local %_;
100              
101 111         375 my $inner = do {
102 111         291 local $Template::Caribou::TAG_INDENT_LEVEL = $Template::Caribou::TAG_INDENT_LEVEL;
103              
104 111 100 66     2309 $Template::Caribou::TAG_INDENT_LEVEL++
105             if $Template::Caribou::TAG_INDENT_LEVEL // $bou->indent;
106              
107 111         405 $bou->get_render($sub);
108             };
109              
110 111 100       293 if ( $groom ) {
111 91         204 local $_ = "$inner"; # stringification required in case it's an object
112              
113 91         307 $groom->();
114              
115 91         200 $inner = $_;
116             }
117              
118             # Setting UNSAFE here so that the inner can be written with raw
119             # as we don't want inner to be escaped as it is already escaped
120 111         744 my $writer = XML::Writer->new(OUTPUT => 'self', UNSAFE => 1);
121 37     37   146 my @attributes = pairmap { ( $a => $b ) x (length $b > 0) }
122             map {
123             $_ => is_plain_hashref($_{$_})
124 6     12   43 ? join ' ', sort { $a cmp $b } pairmap { $a } pairgrep { $b } $_{$_}->%*
  11         41  
  12         47  
125 37 100       347 : $_{$_}
126             }
127 111         27354 grep { defined $_{$_} }
  37         117  
128             sort keys %_;
129              
130 18     18   166 no warnings qw/ uninitialized /;
  18         37  
  18         5965  
131              
132 111   66     732 my $prefix = !!$Template::Caribou::TAG_INDENT_LEVEL
133             && "\n" . ( ' ' x $Template::Caribou::TAG_INDENT_LEVEL );
134              
135 111 100       274 if (length($inner)) {
136 44         178 $writer->startTag($tag, @attributes);
137 44         1836 $writer->raw("$inner$prefix");
138 44         527 $writer->endTag($tag);
139             }
140             else {
141 67         276 $writer->emptyTag($tag, @attributes);
142             }
143              
144 111         4309 my $output = Template::Caribou::String->new( $prefix . $writer->to_string() );
145              
146 111         321 return print_raw( $output );
147             }
148              
149 112     112 0 185 sub print_raw($text) {
  112         188  
  112         177  
150 112         587 print ::RAW $text;
151 112         8090 return $text;
152             }
153              
154             1;
155              
156             __END__
157              
158             =pod
159              
160             =encoding UTF-8
161              
162             =head1 NAME
163              
164             Template::Caribou::Tags - generates tags functions for Caribou templates
165              
166             =head1 VERSION
167              
168             version 1.2.2
169              
170             =head1 SYNOPSIS
171              
172             package MyTemplate;
173              
174             use Template::Caribou;
175              
176             use Template::Caribou::Tags
177             mytag => {
178             -as => 'foo',
179             tag => 'p',
180             class => 'baz'
181             };
182              
183             template bar => sub {
184             foo { 'hello' };
185             };
186              
187             # <p class="baz">hello</p>
188             print __PACKAGE__->new->bar;
189              
190             =head1 DESCRIPTION
191              
192             This module provides the tools to create tag libraries, or ad-hoc tags.
193              
194             For pre-defined sets of tags, you may want to look at L<Template::Caribou::Tags::HTML>,
195             L<Template::Caribou::Tags::HTML::Extended>, and friends.
196              
197             =head2 Core functionality
198              
199             Tag functions are created using the C<render_tag> function. For example:
200              
201             package MyTemplate;
202              
203             use Template::Caribou;
204              
205             use Template::Caribou::Tags qw/ render_tag /;
206              
207             sub foo(&) { render_tag( 'foo', shift ) }
208              
209             # renders as '<foo>hi!</foo>'
210             template main => sub {
211             foo { "hi!" };
212             };
213              
214             =head2 Creating ad-hoc tags
215              
216             Defining a function and using C<render_tag> is a little bulky and, typically, will only be used when creating
217             tag libraries. In most cases,
218             the C<my_tag> export keyword can be used to create custom tags. For example, the
219             previous C<foo> definition could have been done this way:
220              
221             package MyTemplate;
222              
223             use Template::Caribou;
224              
225             use Template::Caribou::Tags
226             mytag => { tag => 'foo' };
227              
228             # renders as '<foo>hi!</foo>'
229             template main => sub {
230             foo {
231             "hi!";
232             };
233             };
234              
235             =head1 EXPORTS
236              
237             By default, nothing is exported.
238             The functions C<render_tag> and C<attr> can be exported by this module.
239              
240             Custom tag functions can also be defined via the export keyword C<mytag>.
241              
242             C<mytag> accepts the following arguments:
243              
244             =over
245              
246             =item tag => $name
247              
248             Tagname that will be used. If not specified, defaults to C<div>.
249              
250             =item -as => $name
251              
252             Name under which the tag function will be exported. If not specified, defaults to the
253             value of the C<tag> argument. At least one of C<-as> or C<tag> must be given explicitly.
254              
255             =item groom => sub { }
256              
257             Grooming function for the tag block. See C<render_tag> for more details.
258              
259             =item classes => \@classes
260              
261             Default value for the 'class' attribute of the tag.
262              
263             use Template::Caribou::Tags
264             # <div class="main">...</div>
265             mytag => { -as => 'main_div', classes => [ 'main' ] };
266              
267             If you want to remove a default class from the tag,
268             set its value to C<0> in C<%_>. E.g.,
269              
270             main_div { $_{class}{main} = 0; ... };
271              
272             =item attr => \%attributes
273              
274             Default set of attributes for the tag.
275              
276             use Template::Caribou::Tags
277             # <input disabled="disabled">...</input>
278             mytag => { -as => 'disabled_input', tag => 'input', attr => { disabled => 'disabled' } };
279              
280             =back
281              
282             =function attr( $name => $value )
283              
284             I recommend you use C<%_> directly instead.
285              
286             Accesses the attributes of a tag within its block.
287              
288             If provided an even number of parameters, sets the attributes to those values.
289              
290             div {
291             attr class => 'foo',
292             style => 'text-align: center';
293              
294             "hi there";
295             };
296              
297             # <div class="foo" style="text-align: center">hi there</div>
298              
299             Many calls to C<attr> can be done within the same block.
300              
301             div {
302             attr class => 'foo';
303             attr style => 'text-align: center';
304              
305             "hi there";
306             };
307              
308             # <div class="foo" style="text-align: center">hi there</div>
309              
310             To add/remove to an attribute instead of replacing its value, prefix the attribute name
311             with a plus or minus sign. Doing either will automatically
312             turn the value in C<%_> to a hashref.
313              
314             div {
315             attr class => 'foo baz';
316              
317             attr '+class' => 'bar';
318             attr '-class' => 'baz';
319              
320             "hi there";
321             };
322              
323             # <div class="foo bar">hi there</div>
324              
325             The value of an attribute can also be queried by passing a single argument to C<attr>.
326              
327             div {
328             ...; # some complex stuff here
329              
330             my $class = attr 'class';
331              
332             attr '+style' => 'text-align: center' if $class =~ /_centered/;
333              
334             ...;
335             }
336              
337             =function render_tag( $tag_name, $inner_block, \&groom, $indent )
338              
339             Prints out a tag in a template. The C<$inner_block> is a string or coderef
340             holding the content of the tag.
341              
342             If the C<$inner_block> is empty, the tag will be of the form
343             C<< <foo /> >>.
344              
345             render_tag( 'div', 'hello' ); # <div>hello</div>
346              
347             render_tag( 'div', sub { 'hello' } ) # <div>hello</div>
348              
349             render_tag( 'div', '' ); # <div />
350              
351             An optional grooming function can be passed. If it is, an hash holding the
352             attributes of the tag, and its inner content will be passed to it as C<%_> and C<$_>, respectively.
353              
354             # '<div>the current time is Wed Nov 25 13:18:33 2015</div>'
355             render_tag( 'div', 'the current time is DATETIME', sub {
356             s/DATETIME/scalar localtime/eg;
357             });
358              
359             # '<div class="mine">foo</div>'
360             render_tag( 'div', 'foo', sub { $_{class} = 'mine' } )
361              
362             An optional C<$indent> argument can also be given. If explicitly set to
363             C<false>, the tag won't be indented even when the template
364             is in pretty-print mode. Used for tags where whitespaces
365             are significant or would alter
366             the presentation (e.g., C<pre> or C<emphasis>). Defaults to C<true>.
367              
368             =head1 AUTHOR
369              
370             Yanick Champoux <yanick@cpan.org>
371              
372             =head1 COPYRIGHT AND LICENSE
373              
374             This software is copyright (c) 2023 by Yanick Champoux.
375              
376             This is free software; you can redistribute it and/or modify it under
377             the same terms as the Perl 5 programming language system itself.
378              
379             =cut