File Coverage

blib/lib/Moonshine/Element.pm
Criterion Covered Total %
statement 186 189 98.4
branch 71 74 95.9
condition 26 32 81.2
subroutine 35 36 97.2
pod 10 16 62.5
total 328 347 94.5


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