File Coverage

blib/lib/Text/HyperScript.pm
Criterion Covered Total %
statement 87 91 95.6
branch 20 22 90.9
condition 4 6 66.6
subroutine 14 15 93.3
pod 5 5 100.0
total 130 139 93.5


line stmt bran cond sub pod time code
1 6     6   1395152 use 5.008001;
  6         57  
2 6     6   33 use strict;
  6         18  
  6         126  
3 6     6   28 use warnings;
  6         15  
  6         360  
4              
5             package Text::HyperScript;
6              
7             our $VERSION = "0.06";
8              
9 6     6   2846 use Exporter::Lite;
  6         4402  
  6         35  
10              
11             our @EXPORT = qw(raw true false text h);
12              
13             sub raw {
14 2     2 1 122 my $html = $_[0];
15 2         11 return bless \$html, 'Text::HyperScript::NodeString';
16             }
17              
18             sub true {
19 4     4 1 118 my $true = !!1;
20 4         28 return bless \$true, 'Text::HyperScript::Boolean';
21             }
22              
23             sub false {
24 1     1 1 4 my $false = !!0;
25 1         5 return bless \$false, 'Text::HyperScript::Boolean';
26             }
27              
28             # copied from HTML::Escape::PurePerl
29             my %escape = (
30             '&' => '&',
31             '>' => '>',
32             '<' => '<',
33             q{"} => '"',
34             q{'} => ''',
35             q{`} => '`',
36             '{' => '{',
37             '}' => '}',
38             );
39              
40             sub text {
41 524     524 1 1020 my ($src) = @_;
42 524         893 $src =~ s/([&><"'`{}])/$escape{$1}/ge;
  24         83  
43 524         1202 return $src;
44             }
45              
46             sub h {
47 137     137 1 424 my $tag = text(shift);
48 137         316 my $html = qq(<${tag});
49              
50 137         222 my %attrs;
51             my @contents;
52              
53 137         259 for my $data (@_) {
54 251 100       582 if ( ref $data eq 'Text::HyperScript::NodeString' ) {
55 3         4 push @contents, ${$data};
  3         6  
56 3         6 next;
57             }
58              
59 248 100       463 if ( ref $data eq 'HASH' ) {
60 127         232 %attrs = ( %attrs, %{$data} );
  127         456  
61 127         279 next;
62             }
63              
64 121 100       237 if ( ref $data eq 'ARRAY' ) {
65 2         4 push @contents, @{$data};
  2         8  
66 2         4 next;
67             }
68              
69 119         224 push @contents, text($data);
70             }
71              
72 137         367 for my $prefix ( sort keys %attrs ) {
73 128         240 my $data = $attrs{$prefix};
74 128 100       280 if ( !ref $data ) {
75 117         201 $html .= q{ } . text($prefix) . q{="} . text($data) . q{"};
76              
77 117         302 next;
78             }
79              
80 11 100 66     37 if ( ref $data eq 'Text::HyperScript::Boolean' && ${$data} ) {
  2         75  
81 2         14 $html .= " " . text($prefix);
82              
83 2         6 next;
84             }
85              
86 9 100       30 if ( ref $data eq 'HASH' ) {
87             PREFIX:
88 7         11 for my $suffix ( sort keys %{$data} ) {
  7         20  
89 8         16 my $key = text($prefix) . '-' . text($suffix);
90 8         13 my $value = $data->{$suffix};
91              
92 8 100       23 if ( !ref $value ) {
93 5         12 $html .= qq( ${key}=") . text($value) . qq(");
94              
95 5         11 next PREFIX;
96             }
97              
98 3 100 66     11 if ( ref $value eq 'Text::HyperScript::Boolean' && ${$value} ) {
  1         4  
99 1         3 $html .= qq( ${key});
100              
101 1         3 next PREFIX;
102             }
103              
104 2 50       6 if ( ref $value eq 'ARRAY' ) {
105 2         7 $html .= qq( ${key}=") . ( join q{ }, map { text($_) } sort @{$value} ) . qq(");
  4         9  
  2         6  
106              
107 2         5 next PREFIX;
108             }
109              
110 0         0 $html .= qq( ${key}=") . text($value) . qq(");
111             }
112              
113 7         14 next;
114             }
115              
116 2 50       6 if ( ref $data eq 'ARRAY' ) {
117 2         4 $html .= q( ) . text($prefix) . q(=") . ( join q{ }, map { text($_) } sort @{$data} ) . q(");
  4         6  
  2         8  
118              
119 2         5 next;
120             }
121              
122 0         0 $html .= q{ } . text($prefix) . q(=") . text($data) . q(");
123             }
124              
125 137 100       321 if ( @contents == 0 ) {
126 14         25 $html .= " />";
127 14         96 return bless \$html, 'Text::HyperScript::NodeString';
128             }
129              
130 123         357 $html .= q(>) . join( q{}, @contents ) . qq();
131 123         1195 return bless \$html, 'Text::HyperScript::NodeString';
132             }
133              
134             package Text::HyperScript::NodeString;
135              
136 6     6   4682 use overload q("") => \&to_string;
  6         24  
  6         67  
137              
138             sub new {
139 0     0   0 my ( $class, $html ) = @_;
140 0         0 return bless \$html, $class;
141             }
142              
143             sub to_string {
144 137     137   80511 return ${ $_[0] };
  137         686  
145             }
146              
147             package Text::HyperScript::Boolean;
148              
149 6     6   1004 use overload ( q(bool) => \&is_true, q(==) => \&is_true );
  6         14  
  6         32  
150              
151             sub is_true {
152 4     4   15 return !!${ $_[0] };
  4         51  
153             }
154              
155             sub is_false {
156 2     2   3 return !${ $_[0] };
  2         23  
157             }
158              
159             package Text::HyperScript;
160              
161             1;
162              
163             =encoding utf-8
164              
165             =head1 NAME
166              
167             Text::HyperScript - The HyperScript like library for Perl.
168              
169             =head1 SYNOPSIS
170              
171             use feature qw(say);
172             use Text::HyperScript qw(h true);
173              
174             # tag only
175             say h('hr'); # => '
'
176             say h(script => q{}); # => ''
177              
178             # tag with content
179             say h('p', 'hi,'); # => '

hi,

'
180             say h('p', ['hi,']); # => '

hi,

'
181              
182             say h('p', 'hi', h('b', ['anonymous'])); # => '

hi,anonymous

'
183             say h('p', 'foo', ['bar'], 'baz'); # => '

foobarbarz

'
184              
185             # tag with attributes
186             say h('hr', { id => 'foo' }); # => '
'
187             say h('hr', { id => 'foo', class => 'bar'}); # => '
'
188             say h('hr', { class => ['foo', 'bar', 'baz'] }); # => '
'
189              
190             # tag with prefixed attributes
191             say h('hr', { data => { foo => 'bar' } }); # => '
'
192             say h('hr', { data => { foo => [qw(foo bar baz)] } }); # => '
'
193              
194             # tag with value-less attribute
195             say h('script', { crossorigin => true }, ""); #
196              
197             =head1 DESCRIPTION
198              
199             This module is a html/xml string generator like as hyperscirpt.
200              
201             The name of this module contains B,
202             but this module features isn't same of another language or original implementation.
203              
204             This module has submodule for some tagset:
205              
206             HTML5: L
207              
208             =head1 FUNCTIONS
209              
210             =head2 h
211              
212             This function makes html/xml text by perl code.
213              
214             This function is complex. but it's powerful.
215              
216             B:
217              
218             h($tag, [ \%attrs, $content, ...])
219              
220             =over
221              
222             =item C<$tag>
223              
224             Tag name of element.
225              
226             This value should be C value.
227              
228             =item C<\%attrs>
229              
230             Attributes of element.
231              
232             Result of attributes sorted by alphabetical according.
233              
234             You could pass to theses types as attribute values:
235              
236             =over
237              
238             =item C
239              
240             If you passed to this type, attribute value became a C value.
241              
242             For example:
243              
244             h('hr', { id => 'id' }); # => '
'
245              
246             =item C
247              
248             If you passed to this type, attribute value became a value-less attribute.
249              
250             For example:
251              
252             # `true()` returns Text::HyperScript::Boolean value as !!1 (true)
253             h('script', { crossorigin => true }); # => ''
254              
255             =item C
256              
257             If you passed to this type, attribute value became a B (alphabetical according),
258             delimited by whitespace C value,
259              
260             For example:
261              
262             h('hr', { class => [qw( foo bar baz )] });
263             # => '
'
264              
265             =item C
266              
267             This type is a shorthand of prefixed attributes.
268              
269             For example:
270              
271             h('hr', { data => { id => 'foo', flags => [qw(bar baz)], enabled => true } });
272             # => '
'
273              
274             =back
275              
276             =item C<$contnet>
277              
278             Contents of element.
279              
280             You could pass to these types:
281              
282             =over
283              
284             =item C
285              
286             Plain text as content.
287              
288             This value always applied html/xml escape.
289              
290             =item C
291              
292             Raw html/xml string as content.
293              
294             B,
295             B.
296              
297             =item C
298              
299             The ArrayRef of C<$content>.
300              
301             This type value is flatten of other C<$content> value.
302              
303             =back
304              
305             =back
306              
307             =head2 text
308              
309             This function returns a html/xml escaped text.
310              
311             If you use untrusted stirng for display,
312             you should use this function for wrapping untrusted content.
313              
314             =head2 raw
315              
316             This function makes a instance of C.
317              
318             Instance of C has C method,
319             that return text with html/xml markup.
320              
321             The value of C always does not escaped,
322             you should not use this function for display untrusted content.
323             Please use C instead of this function.
324              
325             =head2 true / false
326              
327             This functions makes instance of C value.
328              
329             Instance of C has two method like as C and C,
330             these method returns that value pointed C or C values.
331              
332             Usage of these functions for make html5 value-less attribute.
333              
334             For example:
335              
336             h('script', { crossorigin => true }, ''); # => ''
337              
338             =head1 QUESTION AND ANSWERS
339              
340             =head2 How do I get element of empty content like as `script`?
341              
342             This case you chould gets element string by pass to empty string.
343              
344             For example:
345              
346             h('script', ''); #
347              
348             =head2 Why all attributes and attribute values sorted by alphabetical according?
349              
350             This reason that gets same result on randomized orderd hash keys.
351              
352             =head1 LICENSE
353              
354             Copyright (C) OKAMURA Naoki a.k.a nyarla.
355              
356             This library is free software; you can redistribute it and/or modify
357             it under the same terms as Perl itself.
358              
359             =head1 AUTHOR
360              
361             OKAMURA Naoki a.k.a nyarla: Enyarla@kalaclista.comE
362              
363             =head1 SEE ALSO
364              
365             L
366              
367             =cut