File Coverage

blib/lib/Moonshine/Element.pm
Criterion Covered Total %
statement 137 191 71.7
branch 36 74 48.6
condition 10 32 31.2
subroutine 25 33 75.7
pod 10 15 66.6
total 218 345 63.1


line stmt bran cond sub pod time code
1             package Moonshine::Element;
2              
3 2     2   40120 use strict;
  2         3  
  2         56  
4 2     2   9 use warnings;
  2         3  
  2         63  
5 2     2   1107 use Ref::Util qw/is_scalarref is_arrayref is_hashref is_blessed_ref/;
  2         1192  
  2         183  
6 2     2   460 use UNIVERSAL::Object;
  2         1997  
  2         59  
7 2     2   1111 use Data::GUID;
  2         31866  
  2         12  
8 2     2   1299 use Autoload::AUTOCAN;
  2         779  
  2         419  
9              
10             our $VERSION = '0.11';
11              
12 2     2   688 use feature qw/switch/;
  2         2  
  2         191  
13 2     2   1221 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  2         16  
  2         9  
14              
15             our @ISA;
16 2     2   715 BEGIN { @ISA = ('UNIVERSAL::Object') }
17             our %HAS;
18              
19             BEGIN {
20 2     2   58 my @ATTRIBUTES =
21             qw/accept accept_charset accesskey action align alt async autocomplete
22             autofocus autoplay autosave bgcolor border buffered challenge charset checked cite class
23             code codebase color cols colspan content contenteditable contextmenu controls coords datetime
24             default defer dir dirname disabled download draggable dropzone enctype for form formaction
25             headers height hidden high href hreflang http_equiv icon id integrity ismap itemprop keytype
26             kind label lang language list loop low manifest max maxlength media method min multiple muted
27             name novalidate open optimum pattern ping placeholder poster preload radiogroup readonly rel
28             required reversed rows rowspan sandbox scope scoped seamless selected shape size sizes span
29             spellcheck src srcdoc srclang srcset start step style summary tabindex target title type usemap
30             value width wrap aria_autocomplete aria_atomic aria_busy aria_checked aria_controls
31             aria_disabled aria_dropeffect aria_flowto aria_grabbed aria_expanded aria_haspopup aria_hidden
32             aria_invalid aria_label aria_labelledby aria_live aria_level aria_multiline aria_multiselectable
33             aria_orientation aria_pressed aria_readonly aria_required aria_selected aria_sort aria_valuemax
34             aria_valuemin aria_valuenow aria_valuetext aria_owns aria_relevant role data_toggle data_target
35             aria_describedby onkeyup onkeydown onclick onchange/;
36              
37             %HAS = (
38             (
39             map {
40 2179         7418 $_ => sub { undef }
41 306         593 } @ATTRIBUTES,
42             qw/parent data/
43             ),
44             (
45             map {
46 43         120 $_ => sub { [] }
47 6         176 } qw/children after_element before_element/
48             ),
49 1         29 tag => sub { die "$_ is required" },
50 14         42 attribute_list => sub { \@ATTRIBUTES },
51 15         82 guid => sub { Data::GUID->new->as_string },
52 2         6 );
53              
54 2         16 for my $attr ( @ATTRIBUTES,
55             qw/data tag attribute_list children after_element before_element guid parent/
56             )
57             {
58 2     2   11 no strict 'refs';
  2         3  
  2         889  
59             {
60 318         218 *{"has_$attr"} = sub {
  318         741  
61 5893     5893   5308 my $val = $_[0]->{$attr};
62 5893 100       11278 defined $val or return undef;
63 154 100       207 is_arrayref($val) and return scalar @{$val};
  117         194  
64 6         9 is_hashref($val) and return map { $_; }
65 37 100       58 sort keys %{$val};
  2         6  
66 35         62 return 1;
67             }
68 318         677 };
69             {
70 318     1   235 *{"clear_$attr"} = sub { undef $_[0]->{$attr} }
  318         839  
  1         409  
71 318         481 };
72             {
73 318         227 *{"$attr"} = sub {
  318         4597  
74 122     122   744 my $val = $_[0]->{$attr};
75 122 100       249 defined $_[1] or return $val;
76             is_arrayref($val) && not is_arrayref( $_[1] )
77 3 50 33     20 and return push @{$val}, $_[1];
  3         7  
78             is_hashref($val) && is_hashref( $_[1] )
79 0 0 0     0 and map { $_[0]->{$attr}->{$_} = $_[1]->{$_} } keys %{ $_[1] }
  0   0     0  
  0         0  
80             and return 1;
81 0 0       0 $_[0]->{$attr} = $_[1] and return 1;
82             }
83 318         678 };
84             }
85             }
86              
87             sub AUTOCAN {
88 18     18 0 1528 my ( $self, $meth ) = @_;
89 18 50       112 return if $meth =~ /BUILD|DEMOLISH/;
90 0         0 my $element = $self->get_element($meth, ['name']);
91 0 0   0   0 return sub { $element } if $element;
  0         0  
92 0         0 die "AUTOCAN: ${meth} cannot be found";
93             }
94              
95             sub BUILDARGS {
96 15     15 1 2489 my ( $self, $args ) = @_;
97              
98 15         26 for my $ele (qw/children before_element after_element/) {
99 45 50       151 next unless is_arrayref($args->{$ele});
100 0         0 for ( 0 .. ( scalar @{ $args->{$ele} } - 1 ) ) {
  0         0  
101 0         0 $args->{$ele}[$_] = $self->build_element( $args->{$ele}[$_] );
102             }
103             }
104            
105 15 100       40 if (is_arrayref($args->{data})) {
106 1         2 for ( 0 .. ( scalar @{ $args->{data} } - 1 ) ) {
  1         6  
107 3 50 33     24 next unless is_hashref($args->{data}[$_]) or is_blessed_ref($args->{data}[$_]);
108 0         0 $args->{data}[$_] = $self->build_element( $args->{data}[$_] );
109             }
110             }
111              
112 15         32 return $args;
113             }
114              
115             sub build_element {
116 8     8 0 12 my ( $self, $build_args, $parent ) = @_;
117              
118 8   66     24 $build_args->{parent} = $parent // $self;
119 8 50       14 if ( is_blessed_ref($build_args) ) {
120 0 0       0 return $build_args if $build_args->isa('Moonshine::Element');
121 0         0 die "I'm not a Moonshine::Element";
122             }
123              
124 8         22 return $self->new($build_args);
125             }
126              
127             sub add_child {
128 8     8 1 431 my $action = 'children';
129 8 100 100     51 if ( defined $_[2] and my $parent = $_[0]->{parent} ) {
    100          
130 5         11 my $guid = $_[0]->guid;
131 5         7 my $index = 0;
132 5         11 ++$index until $parent->children->[$index]->guid eq $guid;
133 5 100       10 ++$index if $_[2] eq 'after';
134 5         9 my $element = $_[0]->build_element( $_[1], $parent );
135 5         18 splice @{ $parent->{children} }, $index, 0, $element;
  5         13  
136 5         13 return $element;
137             }
138             elsif ( defined $_[2] ) {
139 2         8 $action = sprintf "%s_element", $_[2];
140             }
141              
142 3         8 my $child = $_[0]->build_element( $_[1] );
143 3         16 $_[0]->$action($child);
144 3         11 return $child;
145             }
146              
147             sub add_before_element {
148 3     3 1 969 return $_[0]->add_child( $_[1], 'before' );
149             }
150              
151             sub add_after_element {
152 4     4 1 1897 return $_[0]->add_child( $_[1], 'after' );
153             }
154              
155             sub render {
156 38     38 1 858 my $html_attributes = '';
157 38         29 for my $attribute ( @{ $_[0]->attribute_list } ) {
  38         47  
158 5738         3886 my $html_attribute = $attribute;
159 5738         5256 $html_attribute =~ s/_/-/;
160 5738         5848 my $has_action = sprintf 'has_%s', $attribute;
161 5738 100       6991 if ( $_[0]->$has_action ) {
162 36         67 $html_attributes .= sprintf( '%s="%s" ',
163             $html_attribute,
164             $_[0]->_attribute_value( $attribute, $has_action ) );
165             }
166             }
167              
168 38         51 my $tag = $_[0]->tag;
169 38         70 my $render_element = $_[0]->_render_element;
170 38         163 my $html = sprintf '<%s %s>%s', $tag, $html_attributes,
171             $render_element, $tag;
172              
173 38 100       52 if ( $_[0]->has_before_element ) {
174 2         4 for ( @{ $_[0]->before_element } ) {
  2         4  
175 2         6 $html = sprintf "%s%s", $_->render, $html;
176             }
177             }
178              
179 38 100       55 if ( $_[0]->has_after_element ) {
180 1         3 for ( @{ $_[0]->after_element } ) {
  1         4  
181 1         4 $html = sprintf "%s%s", $html, $_->render;
182             }
183             }
184              
185 38         63 return $_[0]->_tidy_html($html);
186             }
187              
188             sub text {
189 39 100   39 0 50 return $_[0]->has_data ? $_[0]->_attribute_value('data') : '';
190             }
191              
192             sub set {
193 0 0   0 0 0 is_hashref( $_[1] ) or die "args passed to set must be a hashref";
194              
195 0         0 for my $attribute ( keys %{ $_[1] } ) {
  0         0  
196 0         0 $_[0]->$attribute( $_[1]->{$attribute} );
197             }
198              
199 0         0 return $_[0];
200             }
201              
202             sub get_element {
203 0     0 1 0 for my $ele (qw/before_element data children after_element/) {
204 0 0       0 next unless is_arrayref($_[0]->{$ele});
205 0         0 for my $e ( @{$_[0]->{$ele}} ) {
  0         0  
206 0 0       0 next unless is_blessed_ref($e);
207 0         0 for ( @{ $_[2] } ) {
  0         0  
208 0         0 my $has = sprintf 'has_%s', $_;
209 0 0 0     0 $e->$has and $e->_attribute_value($_, $has) =~ m/$_[1]/
210             and return $e;
211             }
212 0         0 my $found = $e->get_element( $_[1], $_[2] );
213 0 0       0 return $found if $found;
214             }
215             }
216 0         0 return undef;
217             }
218              
219             sub get_element_by_id {
220 0 0   0 1 0 is_scalarref(\$_[1]) or die "first param passed to get_element_by_id not a scalar";
221 0         0 return $_[0]->get_element($_[1], ['id']);
222             }
223              
224             sub get_element_by_name {
225 0 0   0 1 0 is_scalarref(\$_[1]) or die "first param passed to get_element_by_name not a scalar";
226 0         0 return $_[0]->get_element($_[1], ['name']);
227             }
228              
229             sub get_elements {
230 0   0 0 1 0 $_[3] //= [];
231 0         0 for my $ele (qw/before_element data children after_element/) {
232 0 0       0 next unless is_arrayref($_[0]->{$ele});
233 0         0 for my $e ( @{$_[0]->{$ele}} ) {
  0         0  
234 0 0       0 next unless is_blessed_ref($e);
235 0         0 for ( @{ $_[2] } ) {
  0         0  
236 0         0 my $has = sprintf 'has_%s', $_;
237             $e->$has and $e->_attribute_value($_, $has) =~ m/$_[1]/
238 0 0 0     0 and push @{ $_[3] }, $e;
  0         0  
239             }
240 0         0 $e->get_elements( $_[1], $_[2], $_[3] );
241             }
242             }
243 0         0 return $_[3];
244             }
245              
246             sub get_elements_by_class {
247 0 0   0 0 0 is_scalarref(\$_[1]) or die "first param passed to get_elements_by_class not a scalar";
248 0         0 return $_[0]->get_elements($_[1], ['class']);
249             }
250              
251             sub get_elements_by_tag {
252 0 0   0 1 0 is_scalarref(\$_[1]) or die "first param passed to get_elements_by_tag not a scalar";
253 0         0 return $_[0]->get_elements($_[1], ['tag']);
254             }
255              
256             sub _render_element {
257 38     38   61 my $element = $_[0]->text;
258 38 100       53 if ( $_[0]->has_children ) {
259 6         5 for ( @{ $_[0]->children } ) {
  6         10  
260 21         33 $element .= $_->render;
261             }
262             }
263 38         46 return $element;
264             }
265              
266             sub _attribute_value {
267 38     38   48 my ( $self, $attribute, $has_action ) = @_;
268              
269 38   66     70 $has_action //= sprintf( 'has_%s', $attribute );
270 38         48 given ( ref $_[0]->{$attribute} ) {
271 38         43 when (/HASH/) {
272 1         2 my $value = '';
273             map {
274 1 100       4 $value and $value .= ' ';
  3         5  
275 3         6 $value .= $_[0]->{$attribute}->{$_};
276             } $_[0]->$has_action;
277 1         5 return $value;
278             }
279 37         36 when (/ARRAY/) {
280 3         5 my $value = '';
281 3         4 for ( @{ $_[0]->{$attribute} } ) {
  3         10  
282 9 100       15 $value and $value .= ' ';
283 9 50 33     41 is_scalarref( \$_ ) and $value .= $_ and next;
284 0         0 $value .= $self->build_element($_)->render;
285 0         0 next;
286             }
287 3         9 return $value;
288             }
289 34         28 default {
290 34         116 return $_[0]->{$attribute};
291             }
292             }
293             }
294              
295             sub _tidy_html {
296 38     38   209 $_[1] =~ s/\s+>/>/g;
297 38         106 return $_[1];
298             }
299              
300             1;
301              
302             __END__