File Coverage

blib/lib/Moonshine/Element.pm
Criterion Covered Total %
statement 192 195 98.4
branch 71 74 95.9
condition 26 32 81.2
subroutine 33 34 97.0
pod 10 16 62.5
total 332 351 94.5


line stmt bran cond sub pod time code
1             package Moonshine::Element;
2              
3 10     10   650303 use strict;
  10         77  
  10         216  
4 10     10   37 use warnings;
  10         14  
  10         228  
5 10     10   3264 use Ref::Util qw/is_scalarref is_arrayref is_hashref is_blessed_ref/;
  10         11296  
  10         528  
6 10     10   2866 use UNIVERSAL::Object;
  10         27753  
  10         221  
7 10     10   3373 use Data::GUID;
  10         137661  
  10         48  
8 10     10   4485 use Autoload::AUTOCAN;
  10         2994  
  10         43  
9              
10             our $VERSION = '0.12';
11              
12 10     10   3152 use feature qw/switch/;
  10         17  
  10         645  
13 10     10   959 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  10         44  
  10         70  
14              
15             our @ISA;
16 10     10   3031 BEGIN { @ISA = ('UNIVERSAL::Object') }
17             our %HAS;
18              
19             BEGIN {
20 10     10   169 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 11649         35394 $_ => sub { undef }
41 1530         3382 } @ATTRIBUTES,
42             qw/parent data/
43             ),
44             (
45             map {
46 208         467 $_ => sub { [] }
47 30         788 } qw/children after_element before_element/
48             ),
49 1         21 tag => sub { die "$_ is required" },
50 77         194 attribute_list => sub { \@ATTRIBUTES },
51 77         333 guid => sub { Data::GUID->new->as_string },
52 10         26 );
53              
54 10         66 for my $attr ( @ATTRIBUTES,
55             qw/data tag attribute_list children after_element before_element guid parent/
56             )
57             {
58 10     10   52 no strict 'refs';
  10         17  
  10         3473  
59             {
60 1590         1567 *{"has_$attr"} = sub {
  1590         4169  
61 23491     23491   26670 my $val = $_[0]->{$attr};
62 23491 100       40964 defined $val or return undef;
63 704 100       993 is_arrayref($val) and return scalar @{$val};
  520         1011  
64 43         82 is_hashref($val) and return map { $_; }
65 184 100       293 sort keys %{$val};
  25         63  
66 159         273 return 1;
67             }
68 1590         3703 };
69             {
70 1590     2   1735 *{"clear_$attr"} = sub { undef $_[0]->{$attr} }
  1590         4200  
  2         927  
71 1590         3118 };
72             {
73 1590         1657 *{"$attr"} = sub {
  1590         21025  
74 410     410   2778 my $val = $_[0]->{$attr};
75 410 100       852 defined $_[1] or return $val;
76             is_arrayref($val) && not is_arrayref( $_[1] )
77 18 100 66     87 and return push @{$val}, $_[1];
  11         28  
78             is_hashref($val) && is_hashref( $_[1] )
79 7 100 100     26 and map { $_[0]->{$attr}->{$_} = $_[1]->{$_} } keys %{ $_[1] }
  2   66     10  
  1         4  
80             and return 1;
81 6 100       20 $_[0]->{$attr} = $_[1] and return 1;
82             }
83 1590         3551 };
84             }
85             }
86              
87             sub AUTOCAN {
88 131     131 0 111616 my ( $self, $meth ) = @_;
89 131 100       1830 return if $meth =~ /BUILD|DEMOLISH/;
90 13         31 my $element = $self->get_element($meth, ['name']);
91 13 50   13   230 return sub { $element } if $element;
  13         162  
92 0         0 die "AUTOCAN: ${meth} cannot be found";
93             }
94              
95             sub BUILDARGS {
96 77     77 1 8692 my ( $self, $args ) = @_;
97              
98 77         134 for my $ele (qw/children before_element after_element/) {
99 231 100       743 next unless is_arrayref($args->{$ele});
100 23         27 for ( 0 .. ( scalar @{ $args->{$ele} } - 1 ) ) {
  23         50  
101 23         48 $args->{$ele}[$_] = $self->build_element( $args->{$ele}[$_] );
102             }
103             }
104            
105 77 100       208 if (is_arrayref($args->{data})) {
106 27         35 for ( 0 .. ( scalar @{ $args->{data} } - 1 ) ) {
  27         77  
107 53 100 66     199 next unless is_hashref($args->{data}[$_]) or is_blessed_ref($args->{data}[$_]);
108 3         8 $args->{data}[$_] = $self->build_element( $args->{data}[$_] );
109             }
110             }
111              
112 77         161 return $args;
113             }
114              
115             sub build_element {
116 52     52 0 1720 my ( $self, $build_args, $parent ) = @_;
117              
118 52   66     171 $build_args->{parent} = $parent // $self;
119 52 100       108 if ( is_blessed_ref($build_args) ) {
120 9 100       50 return $build_args if $build_args->isa('Moonshine::Element');
121 1         11 die "I'm not a Moonshine::Element";
122             }
123              
124 43         113 return $self->new($build_args);
125             }
126              
127             sub add_child {
128 19     19 1 2743 my $action = 'children';
129 19 100 100     115 if ( defined $_[2] and my $parent = $_[0]->{parent} ) {
    100          
130 8         21 my $guid = $_[0]->guid;
131 8         12 my $index = 0;
132 8         19 ++$index until $parent->children->[$index]->guid eq $guid;
133 8 100       18 ++$index if $_[2] eq 'after';
134 8         19 my $element = $_[0]->build_element( $_[1], $parent );
135 8         36 splice @{ $parent->{children} }, $index, 0, $element;
  8         20  
136 8         26 return $element;
137             }
138             elsif ( defined $_[2] ) {
139 5         17 $action = sprintf "%s_element", $_[2];
140             }
141              
142 11         36 my $child = $_[0]->build_element( $_[1] );
143 11         134 $_[0]->$action($child);
144 11         66 return $child;
145             }
146              
147             sub insert_child {
148 2     2 0 1436 my $element = $_[0]->build_element( $_[2] );
149 2         10 splice @{ $_[0]->{children} }, $_[1], 0, $element;
  2         7  
150 2         6 return $element;
151             }
152              
153             sub add_before_element {
154 5     5 1 1667 return $_[0]->add_child( $_[1], 'before' );
155             }
156              
157             sub add_after_element {
158 8     8 1 4566 return $_[0]->add_child( $_[1], 'after' );
159             }
160              
161             sub render {
162 151     151 1 31226 my $html_attributes = '';
163 151         183 for my $attribute ( @{ $_[0]->attribute_list } ) {
  151         284  
164 22801         22436 my $html_attribute = $attribute;
165 22801         25540 $html_attribute =~ s/_/-/;
166 22801         27916 my $has_action = sprintf 'has_%s', $attribute;
167 22801 100       32497 if ( $_[0]->$has_action ) {
168 121         233 $html_attributes .= sprintf( '%s="%s" ',
169             $html_attribute,
170             $_[0]->_attribute_value( $attribute, $has_action ) );
171             }
172             }
173              
174 151         299 my $tag = $_[0]->tag;
175 151         313 my $render_element = $_[0]->_render_element;
176 151         394 my $html = sprintf '<%s %s>%s', $tag, $html_attributes,
177             $render_element, $tag;
178              
179 151 100       271 if ( $_[0]->has_before_element ) {
180 8         16 for ( @{ $_[0]->before_element } ) {
  8         22  
181 8         24 $html = sprintf "%s%s", $_->render, $html;
182             }
183             }
184              
185 151 100       245 if ( $_[0]->has_after_element ) {
186 7         12 for ( @{ $_[0]->after_element } ) {
  7         21  
187 7         20 $html = sprintf "%s%s", $html, $_->render;
188             }
189             }
190              
191 151         267 return $_[0]->_tidy_html($html);
192             }
193              
194             sub text {
195 156 100   156 0 4283 return $_[0]->has_data ? $_[0]->_attribute_value('data') : '';
196             }
197              
198             sub set {
199 3 100   3 0 1581 is_hashref( $_[1] ) or die "args passed to set must be a hashref";
200              
201 2         3 for my $attribute ( keys %{ $_[1] } ) {
  2         5  
202 4         10 $_[0]->$attribute( $_[1]->{$attribute} );
203             }
204              
205 2         5 return $_[0];
206             }
207              
208             sub get_element {
209 31     31 1 46 for my $ele (qw/before_element data children after_element/) {
210 97 100       151 next unless is_arrayref($_[0]->{$ele});
211 74         65 for my $e ( @{$_[0]->{$ele}} ) {
  74         110  
212 38 100       53 next unless is_blessed_ref($e);
213 30         27 for ( @{ $_[2] } ) {
  30         41  
214 30         68 my $has = sprintf 'has_%s', $_;
215 30 100 100     46 $e->$has and $e->_attribute_value($_, $has) =~ m/$_[1]/
216             and return $e;
217             }
218 13         29 my $found = $e->get_element( $_[1], $_[2] );
219 13 100       26 return $found if $found;
220             }
221             }
222 9         13 return undef;
223             }
224              
225             sub get_element_by_id {
226 6 100   6 1 3811 is_scalarref(\$_[1]) or die "first param passed to get_element_by_id not a scalar";
227 5         14 return $_[0]->get_element($_[1], ['id']);
228             }
229              
230             sub get_element_by_name {
231 0 0   0 1 0 is_scalarref(\$_[1]) or die "first param passed to get_element_by_name not a scalar";
232 0         0 return $_[0]->get_element($_[1], ['name']);
233             }
234              
235             sub get_elements {
236 46   100 46 1 3856 $_[3] //= [];
237 46         54 for my $ele (qw/before_element data children after_element/) {
238 184 100       242 next unless is_arrayref($_[0]->{$ele});
239 175         156 for my $e ( @{$_[0]->{$ele}} ) {
  175         225  
240 118 100       144 next unless is_blessed_ref($e);
241 37         37 for ( @{ $_[2] } ) {
  37         42  
242 37         66 my $has = sprintf 'has_%s', $_;
243             $e->$has and $e->_attribute_value($_, $has) =~ m/$_[1]/
244 37 100 100     54 and push @{ $_[3] }, $e;
  18         36  
245             }
246 37         63 $e->get_elements( $_[1], $_[2], $_[3] );
247             }
248             }
249 46         63 return $_[3];
250             }
251              
252             sub get_elements_by_class {
253 4 100   4 0 1656 is_scalarref(\$_[1]) or die "first param passed to get_elements_by_class not a scalar";
254 3         8 return $_[0]->get_elements($_[1], ['class']);
255             }
256              
257             sub get_elements_by_tag {
258 2 100   2 1 1107 is_scalarref(\$_[1]) or die "first param passed to get_elements_by_tag not a scalar";
259 1         4 return $_[0]->get_elements($_[1], ['tag']);
260             }
261              
262             sub _render_element {
263 151     151   270 my $element = $_[0]->text;
264 151 100       272 if ( $_[0]->has_children ) {
265 32         39 for ( @{ $_[0]->children } ) {
  32         73  
266 52         120 $element .= $_->render;
267             }
268             }
269 151         249 return $element;
270             }
271              
272             sub _attribute_value {
273 237     237   422 my ( $self, $attribute, $has_action ) = @_;
274              
275 237   66     568 $has_action //= sprintf( 'has_%s', $attribute );
276 237         352 given ( ref $_[0]->{$attribute} ) {
277 237         342 when (/HASH/) {
278 12         17 my $value = '';
279             map {
280 12 100       30 $value and $value .= ' ';
  20         41  
281 20         45 $value .= $_[0]->{$attribute}->{$_};
282             } $_[0]->$has_action;
283 12         39 return $value;
284             }
285 225         294 when (/ARRAY/) {
286 67         88 my $value = '';
287 67         86 for ( @{ $_[0]->{$attribute} } ) {
  67         163  
288 117 100       207 $value and $value .= ' ';
289 117 100 66     419 is_scalarref( \$_ ) and $value .= $_ and next;
290 3         8 $value .= $self->build_element($_)->render;
291 3         6 next;
292             }
293 67         155 return $value;
294             }
295 158         173 default {
296 158         645 return $_[0]->{$attribute};
297             }
298             }
299             }
300              
301             sub _tidy_html {
302 151     151   705 $_[1] =~ s/\s+>/>/g;
303 151         623 return $_[1];
304             }
305              
306             1;
307              
308             __END__