File Coverage

blib/lib/Inky.pm
Criterion Covered Total %
statement 116 117 99.1
branch 30 32 93.7
condition 18 23 78.2
subroutine 18 18 100.0
pod 1 1 100.0
total 183 191 95.8


'; ', !; ', $inner; !;
line stmt bran cond sub pod time code
1 2     2   990 use strict;
  2         2  
  2         49  
2 2     2   6 use warnings;
  2         2  
  2         80  
3             package Inky;
4             $Inky::VERSION = '0.161810';
5 2     2   939 use Moo;
  2         18708  
  2         16  
6 2     2   2748 use strictures 2;
  2         2138  
  2         67  
7 2     2   1108 use namespace::clean;
  2         15465  
  2         7  
8 2     2   1176 use Mojo::DOM;
  2         100295  
  2         57  
9 2     2   756 use Const::Fast;
  2         1496  
  2         9  
10              
11             # ABSTRACT: Inky templates, in Perl
12              
13             const my $DEFAULT_SPACER_SIZE_PX => 16;
14             const my $DEFAULT_COLS => 12;
15              
16             has 'column_count' => ( is => 'ro', default => sub { $DEFAULT_COLS } );
17             has '_component_tags' => ( is => 'ro', default => sub { return [qw<
18             button row columns container callout inky block-grid menu item center
19             spacer wrapper
20             >]});
21              
22             sub _classes {
23 33     33   55 my ($element, @classes) = @_;
24 33 100       50 if ($element->attr('class')) {
25 9         96 push @classes, split /\s+/xms, $element->attr('class');
26             }
27 33         444 return join q{ }, @classes;
28             }
29              
30             sub _add_standard_attributes {
31 36     36   34 my ($element) = @_;
32              
33             # Keep all attributes but these
34 36         38 my %skipped = map { $_ => 1 } qw;
  288         352  
35 36         46 my $result = '';
36 36         61 my $attrs = $element->attr;
37              
38 36         318 for my $attr (sort keys %{$attrs}) {
  36         116  
39 40 100       72 next if exists $skipped{$attr};
40 5   50     8 my $value = $attrs->{$attr} // '';
41 5         10 $result .= qq! $attr="$value"!;
42             }
43 36         156 return $result;
44             }
45              
46             my %COMPONENTS = (
47             columns => sub {
48             my ($self, $element) = @_;
49             return $self->_make_column($element, 'columns');
50             },
51             row => sub {
52             my ($self, $element, $inner) = @_;
53             return sprintf '%s
',
54             _add_standard_attributes($element),
55             _classes($element, 'row'), $inner;
56             },
57             button => \&_make_button,
58             container => sub {
59             my ($self, $element, $inner) = @_;
60             return sprintf '
%s
',
61             _add_standard_attributes($element),
62             _classes($element, 'container'), $inner;
63             },
64             inky => sub {
65             return '
66             },
67             'block-grid' => sub {
68             my ($self, $element, $inner) = @_;
69             return sprintf '%s
',
70             _classes($element, 'block-grid', join q{}, 'up-', $element->attr('up')),
71             $inner;
72             },
73             menu => sub {
74             my ($self, $element, $inner) = @_;
75             my $center_attr = $element->attr('align') ? 'align="center"' : q{};
76             return sprintf '
%s
',
77             _add_standard_attributes($element),
78             _classes($element, 'menu'), $center_attr, $inner;
79             },
80             item => sub {
81             my ($self, $element, $inner) = @_;
82             my $target = '';
83             $target = sprintf ' target="%s"', $element->attr('target')
84             if $element->attr('target');
85             return sprintf '%s
86             _add_standard_attributes($element),
87             _classes($element, 'menu-item'), $element->attr('href'), $target, $inner;
88             },
89             center => \&_make_center,
90             callout => sub {
91             my ($self, $element, $inner) = @_;
92             return sprintf '
%s
',
93             _add_standard_attributes($element),
94             _classes($element, 'callout-inner'), $inner;
95             },
96             spacer => sub {
97             my ($self, $element, $inner) = @_;
98             my $size;
99             my $html = '';
100             if ($element->attr('size-sm') || $element->attr('size-lg')) {
101             if ($element->attr('size-sm')) {
102             $size = $element->attr('size-sm');
103             $html .= qq!
 
!;
104             }
105             if ($element->attr('size-lg')) {
106             $size = $element->attr('size-lg');
107             $html .= qq!
 
!;
108             }
109             } else {
110             $size = $element->attr('size') // $DEFAULT_SPACER_SIZE_PX;
111             $html = qq!
 
!;
112             }
113             if ($element->attr('size-sm') && $element->attr('size-lg')) {
114             return sprintf $html,
115             _classes($element,'spacer'),
116             _classes($element,'spacer'),
117             }
118             return sprintf $html,
119             _classes($element,'spacer');
120             },
121             wrapper => sub {
122             my ($self, $element, $inner) = @_;
123             return sprintf '
%s
',
124             _add_standard_attributes($element),
125             _classes($element, 'wrapper'), $inner;
126             },
127             );
128              
129             sub _make_button {
130 4     4   4 my ($self, $element, $inner) = @_;
131              
132 4         5 my $expander = q{};
133              
134             # Prepare optional target attribute for the element
135 4         3 my $target = '';
136 4 100       9 $target = ' target=' . $element->attr('target')
137             if $element->attr('target');
138              
139             # If we have the href attribute we can create an anchor for the inner
140             # of the button
141 4 50       54 $inner = sprintf '%s',
142             $element->attr('href'), $target, $inner
143             if $element->attr('href');
144              
145             # If the button is expanded, it needs a
tag around the content
146 4   100     82 my @el_classes = split /\s+/xms, $element->attr('class') // '';
147 4 100       50 if (scalar grep { $_ eq 'expand' || $_ eq 'expanded' } @el_classes) {
  3 100       13  
148 1         3 $inner = sprintf '
%s
', $inner;
149 1         2 $expander = qq!\n
150             }
151              
152             # The . button class is always there, along with any others on the
153             # element
154 4         7 return sprintf '%s
%s
',
155             _classes($element, 'button'), $inner, $expander;
156             }
157              
158             sub _make_center {
159 5     5   6 my ($self, $element, $inner) = @_;
160              
161 5 100       10 if ($element->children->size > 0) {
162             $element->children->each(sub {
163 4     4   174 my ($e) = @_;
164 4         18 $e->attr('align', 'center');
165 4   50     61 my @classes = split /\s+/xms, $e->attr('class') // q{};
166 4         59 $e->attr('class', join q{ }, @classes, 'float-center');
167 4         172 });
168             $element->find('item, .menu-item')->each(sub {
169 1     1   184 my ($e) = @_;
170 1   50     3 my @classes = split /\s+/xms, $e->attr('class') // q{};
171 1         16 $e->attr('class', join q{ }, @classes, 'float-center');
172 4         71 });
173             }
174 5         500 $element->attr('data-parsed', q{});
175 5         69 return sprintf '%s', $element->to_string;
176             }
177              
178             sub _component_factory {
179 53     53   53 my ($self, $element) = @_;
180              
181 53         98 my $inner = $element->content;
182              
183 53         2458 my $tag = $element->tag;
184             return $COMPONENTS{$tag}->($self, $element, $inner)
185 53 50       559 if exists $COMPONENTS{$tag};
186              
187             # If it's not a custom component, return it as-is
188 0         0 return sprintf '
%s
189             }
190              
191             sub _make_column {
192 16     16   10 my ($self, $col) = @_;
193              
194 16         13 my $output = q{};
195 16         25 my $inner = $col->content;
196 16         569 my @classes = ();
197 16         15 my $expander = q{};
198              
199 16         27 my $attributes = $col->attr;
200             my $attr_no_expander = exists $attributes->{'no-expander'}
201 16 100       143 ? $attributes->{'no-expander'}
202             : 0;
203             $attr_no_expander = 1
204             if exists $attributes->{'no-expander'}
205 16 100 100     43 && !defined $attributes->{'no-expander'};
206 16 100       26 $attr_no_expander = 0
207             if $attr_no_expander eq 'false';
208              
209             # Add 1 to include current column
210 16         27 my $col_count = $col->following->size
211             + $col->preceding->size
212             + 1;
213              
214             # Inherit classes from the tag
215 16 100       2505 if ($col->attr('class')) {
216 1         12 push @classes, split /\s+/xms, $col->attr('class');
217             }
218              
219             # Check for sizes. If no attribute is provided, default to small-12.
220             # Divide evenly for large columns
221 16   66     173 my $small_size = $col->attr('small') || $self->column_count;
222 16   66     171 my $large_size = $col->attr('large')
223             || $col->attr('small')
224             || int($self->column_count / $col_count);
225              
226 16         235 push @classes, sprintf 'small-%s', $small_size;
227 16         22 push @classes, sprintf 'large-%s', $large_size;
228              
229             # Add the basic "columns" class also
230 16         12 push @classes, 'columns';
231              
232             # Determine if it's the first or last column, or both
233 16 100       24 push @classes, 'first'
234             if !$col->preceding('columns, .columns')->size;
235 16 100       2045 push @classes, 'last'
236             if !$col->following('columns, .columns')->size;
237              
238             # If the column contains a nested row, the .expander class should not be
239             # used. The == on the first check is because we're comparing a string
240             # pulled from $.attr() to a number
241 16 100 100     1988 if ($large_size == $self->column_count && $col->find('.row, row')->size == 0 && !$attr_no_expander) {
      100        
242 4         473 $expander = qq!\n
243             }
244              
245             # Final HTML output
246 16         436 $output = <<'END';
247            
248             %s
249            
250             %s
251            
252            
253            
254             END
255              
256 16         29 my $class = join q{ }, @classes;
257 16         23 return sprintf $output,
258             $class, _add_standard_attributes($col), $inner, $expander
259             }
260              
261             sub _extract_raws {
262 42     42   33 my ($string) = @_;
263              
264 42         36 my @raws;
265 42         32 my $i = 0;
266 42         28 my $str = $string;
267 42         72 my $rx = qr{<\s*raw\s*>(.*?)}xism;
268              
269 42         211 while (my ($raw) = $str =~ $rx) {
270 4         5 push @raws, $raw;
271 4         20 $str =~ s/$rx/###RAW$i###/xsm;
272 4         15 $i++;
273             }
274 42         90 return (\@raws, $str);
275             }
276              
277             sub _reinject_raws {
278 42     42   41 my ($string, $raws) = @_;
279              
280 42         28 my $str = $string;
281 42         36 for my $i (0..$#{$raws}) {
  42         87  
282 4         42 $str =~ s{\#{3}RAW$i\#{3}}{$raws->[$i]}xms;
283             }
284 42         273 return $str;
285             }
286              
287             sub release_the_kraken {
288 42     42 1 152 my ($self, $html) = @_;
289              
290 42         54 my ($raws, $string) = _extract_raws($html);
291              
292 42         103 my $dom = Mojo::DOM->new( $string );
293             my $tags = join ', ',
294 504 100       624 map { $_ eq 'center' ? "$_:not([data-parsed])" : $_ }
295 42         5376 @{ $self->_component_tags };
  42         91  
296              
297 42         89 while ($dom->find($tags)->size) {
298 53         37644 my $elem = $dom->find($tags)->first;
299 53         31578 my $new_html = $self->_component_factory($elem);
300 53         406 $elem->replace($new_html);
301             }
302 42         47875 $string = $dom->to_string;
303 42         4735 return _reinject_raws($string, $raws);
304             }
305              
306             1;
307              
308             __END__