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   54482 use strict;
  2         4  
  2         53  
4 2     2   8 use warnings;
  2         2  
  2         59  
5 2     2   961 use Ref::Util qw/is_scalarref is_arrayref is_hashref is_blessed_ref/;
  2         1351  
  2         164  
6 2     2   441 use UNIVERSAL::Object;
  2         2009  
  2         42  
7 2     2   903 use Data::GUID;
  2         32912  
  2         14  
8 2     2   1343 use Autoload::AUTOCAN;
  2         680  
  2         498  
9              
10             our $VERSION = '0.10';
11              
12 2     2   843 use feature qw/switch/;
  2         5  
  2         194  
13 2     2   1540 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  2         17  
  2         13  
14              
15             our @ISA;
16 2     2   919 BEGIN { @ISA = ('UNIVERSAL::Object') }
17             our %HAS;
18              
19             BEGIN {
20 2     2   45 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 2138         9890 $_ => sub { undef }
41 306         701 } @ATTRIBUTES,
42             qw/parent data/
43             ),
44             (
45             map {
46 42         428 $_ => sub { [] }
47 6         202 } qw/children after_element before_element/
48             ),
49 1         32 tag => sub { die "$_ is required" },
50 14         56 attribute_list => sub { \@ATTRIBUTES },
51 14         105 guid => sub { Data::GUID->new->as_string },
52 2         5 );
53              
54 2         27 for my $attr ( @ATTRIBUTES,
55             qw/data tag attribute_list children after_element before_element guid parent/
56             )
57             {
58 2     2   10 no strict 'refs';
  2         3  
  2         1039  
59             {
60 318         224 *{"has_$attr"} = sub {
  318         853  
61 5893     5893   6412 my $val = $_[0]->{$attr};
62 5893 100       14232 defined $val or return undef;
63 154 100       215 is_arrayref($val) and return scalar @{$val};
  117         233  
64 6         8 is_hashref($val) and return map { $_; }
65 37 100       65 sort keys %{$val};
  2         8  
66 35         72 return 1;
67             }
68 318         753 };
69             {
70 318     1   243 *{"clear_$attr"} = sub { undef $_[0]->{$attr} }
  318         870  
  1         418  
71 318         542 };
72             {
73 318         250 *{"$attr"} = sub {
  318         5410  
74 122     122   894 my $val = $_[0]->{$attr};
75 122 100       309 defined $_[1] or return $val;
76             is_arrayref($val) && not is_arrayref( $_[1] )
77 3 50 33     20 and return push @{$val}, $_[1];
  3         8  
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         754 };
84             }
85             }
86              
87             sub AUTOCAN {
88 18     18 0 1940 my ( $self, $meth ) = @_;
89 18 50       138 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 3304 my ( $self, $args ) = @_;
97              
98 15         34 for my $ele (qw/children before_element after_element/) {
99 45 50       174 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       68 if (is_arrayref($args->{data})) {
106 1         2 for ( 0 .. ( scalar @{ $args->{data} } - 1 ) ) {
  1         6  
107 3 50 33     26 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         43 return $args;
113             }
114              
115             sub build_element {
116 8     8 0 14 my ( $self, $build_args, $parent ) = @_;
117              
118 8   66     35 $build_args->{parent} = $parent // $self;
119 8 50       22 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         31 return $self->new($build_args);
125             }
126              
127             sub add_child {
128 8     8 1 725 my $action = 'children';
129 8 100 100     68 if ( defined $_[2] and my $parent = $_[0]->{parent} ) {
    100          
130 5         19 my $guid = $_[0]->guid;
131 5         8 my $index = 0;
132 5         14 ++$index until $parent->children->[$index]->guid eq $guid;
133 5 100       18 ++$index if $_[2] eq 'after';
134 5         18 my $element = $_[0]->build_element( $_[1], $parent );
135 5         23 splice @{ $parent->{children} }, $index, 0, $element;
  5         18  
136 5         15 return $element;
137             }
138             elsif ( defined $_[2] ) {
139 2         7 $action = sprintf "%s_element", $_[2];
140             }
141              
142 3         10 my $child = $_[0]->build_element( $_[1] );
143 3         19 $_[0]->$action($child);
144 3         15 return $child;
145             }
146              
147             sub add_before_element {
148 3     3 1 1808 return $_[0]->add_child( $_[1], 'before' );
149             }
150              
151             sub add_after_element {
152 4     4 1 2678 return $_[0]->add_child( $_[1], 'after' );
153             }
154              
155             sub render {
156 38     38 1 1351 my $html_attributes = '';
157 38         32 for my $attribute ( @{ $_[0]->attribute_list } ) {
  38         62  
158 5738         4416 my $html_attribute = $attribute;
159 5738         6030 $html_attribute =~ s/_/-/;
160 5738         7188 my $has_action = sprintf 'has_%s', $attribute;
161 5738 100       8561 if ( $_[0]->$has_action ) {
162 36         107 $html_attributes .= sprintf( '%s="%s" ',
163             $html_attribute,
164             $_[0]->_attribute_value( $attribute, $has_action ) );
165             }
166             }
167              
168 38         71 my $tag = $_[0]->tag;
169 38         76 my $render_element = $_[0]->_render_element;
170 38         102 my $html = sprintf '<%s %s>%s', $tag, $html_attributes,
171             $render_element, $tag;
172              
173 38 100       59 if ( $_[0]->has_before_element ) {
174 2         3 for ( @{ $_[0]->before_element } ) {
  2         5  
175 2         4 $html = sprintf "%s%s", $_->render, $html;
176             }
177             }
178              
179 38 100       63 if ( $_[0]->has_after_element ) {
180 1         3 for ( @{ $_[0]->after_element } ) {
  1         2  
181 1         3 $html = sprintf "%s%s", $html, $_->render;
182             }
183             }
184              
185 38         59 return $_[0]->_tidy_html($html);
186             }
187              
188             sub text {
189 39 100   39 0 67 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   58 my $element = $_[0]->text;
258 38 100       70 if ( $_[0]->has_children ) {
259 6         8 for ( @{ $_[0]->children } ) {
  6         16  
260 21         36 $element .= $_->render;
261             }
262             }
263 38         49 return $element;
264             }
265              
266             sub _attribute_value {
267 38     38   55 my ( $self, $attribute, $has_action ) = @_;
268              
269 38   66     81 $has_action //= sprintf( 'has_%s', $attribute );
270 38         50 given ( ref $_[0]->{$attribute} ) {
271 38         48 when (/HASH/) {
272 1         2 my $value = '';
273             map {
274 1 100       3 $value and $value .= ' ';
  3         6  
275 3         5 $value .= $_[0]->{$attribute}->{$_};
276             } $_[0]->$has_action;
277 1         4 return $value;
278             }
279 37         38 when (/ARRAY/) {
280 3         6 my $value = '';
281 3         4 for ( @{ $_[0]->{$attribute} } ) {
  3         8  
282 9 100       19 $value and $value .= ' ';
283 9 50 33     45 is_scalarref( \$_ ) and $value .= $_ and next;
284 0         0 $value .= $self->build_element($_)->render;
285 0         0 next;
286             }
287 3         10 return $value;
288             }
289 34         30 default {
290 34         122 return $_[0]->{$attribute};
291             }
292             }
293             }
294              
295             sub _tidy_html {
296 38     38   261 $_[1] =~ s/\s+>/>/g;
297 38         122 return $_[1];
298             }
299              
300             1;
301              
302             __END__