File Coverage

blib/lib/Template/Caribou/Tags.pm
Criterion Covered Total %
statement 100 103 97.0
branch 31 38 81.5
condition 17 28 60.7
subroutine 21 21 100.0
pod 1 3 33.3
total 170 193 88.0


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