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   1026 use strict;
  2         2  
  2         49  
2 2     2   6 use warnings;
  2         2  
  2         75  
3             package Inky;
4             $Inky::VERSION = '0.161800';
5 2     2   927 use Moo;
  2         18992  
  2         14  
6 2     2   3348 use strictures 2;
  2         2450  
  2         63  
7 2     2   1153 use namespace::clean;
  2         17332  
  2         10  
8 2     2   1286 use Mojo::DOM;
  2         109042  
  2         63  
9 2     2   835 use Const::Fast;
  2         2020  
  2         13  
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   86 my ($element, @classes) = @_;
24 33 100       62 if ($element->attr('class')) {
25 9         114 push @classes, split /\s+/xms, $element->attr('class');
26             }
27 33         554 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         57 my %skipped = map { $_ => 1 } qw;
  288         427  
35 36         59 my $result = '';
36 36         108 my $attrs = $element->attr;
37              
38 36         417 for my $attr (sort keys %{$attrs}) {
  36         164  
39 40 100       80 next if exists $skipped{$attr};
40 5   50     15 my $value = $attrs->{$attr} // '';
41 5         18 $result .= qq! $attr="$value"!;
42             }
43 36         198 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   5 my ($self, $element, $inner) = @_;
131              
132 4         4 my $expander = q{};
133              
134             # Prepare optional target attribute for the element
135 4         3 my $target = '';
136 4 100       7 $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       57 $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     85 my @el_classes = split /\s+/xms, $element->attr('class') // '';
147 4 100       51 if (scalar grep { $_ eq 'expand' || $_ eq 'expanded' } @el_classes) {
  3 100       14  
148 1         3 $inner = sprintf '
%s
', $inner;
149 1         3 $expander = qq!\n
150             }
151              
152             # The . button class is always there, along with any others on the
153             # element
154 4         6 return sprintf '%s
%s
',
155             _classes($element, 'button'), $inner, $expander;
156             }
157              
158             sub _make_center {
159 5     5   11 my ($self, $element, $inner) = @_;
160              
161 5 100       17 if ($element->children->size > 0) {
162             $element->children->each(sub {
163 4     4   283 my ($e) = @_;
164 4         17 $e->attr('align', 'center');
165 4   50     92 my @classes = split /\s+/xms, $e->attr('class') // q{};
166 4         87 $e->attr('class', join q{ }, @classes, 'float-center');
167 4         268 });
168             $element->find('item, .menu-item')->each(sub {
169 1     1   291 my ($e) = @_;
170 1   50     5 my @classes = split /\s+/xms, $e->attr('class') // q{};
171 1         27 $e->attr('class', join q{ }, @classes, 'float-center');
172 4         105 });
173             }
174 5         752 $element->attr('data-parsed', q{});
175 5         108 return sprintf '%s', $element->to_string;
176             }
177              
178             sub _component_factory {
179 53     53   64 my ($self, $element) = @_;
180              
181 53         115 my $inner = $element->content;
182              
183 53         3101 my $tag = $element->tag;
184             return $COMPONENTS{$tag}->($self, $element, $inner)
185 53 50       732 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   14 my ($self, $col) = @_;
193              
194 16         16 my $output = q{};
195 16         30 my $inner = $col->content;
196 16         714 my @classes = ();
197 16         20 my $expander = q{};
198              
199 16         32 my $attributes = $col->attr;
200             my $attr_no_expander = exists $attributes->{'no-expander'}
201 16 100       179 ? $attributes->{'no-expander'}
202             : 0;
203             $attr_no_expander = 1
204             if exists $attributes->{'no-expander'}
205 16 100 100     57 && !defined $attributes->{'no-expander'};
206 16 100       34 $attr_no_expander = 0
207             if $attr_no_expander eq 'false';
208              
209             # Add 1 to include current column
210 16         32 my $col_count = $col->following->size
211             + $col->preceding->size
212             + 1;
213              
214             # Inherit classes from the tag
215 16 100       3051 if ($col->attr('class')) {
216 1         13 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     220 my $small_size = $col->attr('small') || $self->column_count;
222 16   66     213 my $large_size = $col->attr('large')
223             || $col->attr('small')
224             || int($self->column_count / $col_count);
225              
226 16         291 push @classes, sprintf 'small-%s', $small_size;
227 16         27 push @classes, sprintf 'large-%s', $large_size;
228              
229             # Add the basic "columns" class also
230 16         20 push @classes, 'columns';
231              
232             # Determine if it's the first or last column, or both
233 16 100       33 push @classes, 'first'
234             if !$col->preceding('columns, .columns')->size;
235 16 100       2311 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     2246 if ($large_size == $self->column_count && $col->find('.row, row')->size == 0 && !$attr_no_expander) {
      100        
242 4         747 $expander = qq!\n
243             }
244              
245             # Final HTML output
246 16         604 $output = <<'END';
247            
248             %s
249            
250             %s
251            
252            
253            
254             END
255              
256 16         36 my $class = join q{ }, @classes;
257 16         32 return sprintf $output,
258             $class, _add_standard_attributes($col), $inner, $expander
259             }
260              
261             sub _extract_raws {
262 42     42   44 my ($string) = @_;
263              
264 42         37 my @raws;
265 42         40 my $i = 0;
266 42         47 my $str = $string;
267 42         99 my $rx = qr{<\s*raw\s*>(.*?)}xism;
268              
269 42         269 while (my ($raw) = $str =~ $rx) {
270 4         6 push @raws, $raw;
271 4         20 $str =~ s/$rx/###RAW$i###/xsm;
272 4         16 $i++;
273             }
274 42         106 return (\@raws, $str);
275             }
276              
277             sub _reinject_raws {
278 42     42   54 my ($string, $raws) = @_;
279              
280 42         40 my $str = $string;
281 42         46 for my $i (0..$#{$raws}) {
  42         119  
282 4         41 $str =~ s{\#{3}RAW$i\#{3}}{$raws->[$i]}xms;
283             }
284 42         356 return $str;
285             }
286              
287             sub release_the_kraken {
288 42     42 1 205 my ($self, $html) = @_;
289              
290 42         90 my ($raws, $string) = _extract_raws($html);
291              
292 42         137 my $dom = Mojo::DOM->new( $string );
293             my $tags = join ', ',
294 504 100       762 map { $_ eq 'center' ? "$_:not([data-parsed])" : $_ }
295 42         6980 @{ $self->_component_tags };
  42         125  
296              
297 42         122 while ($dom->find($tags)->size) {
298 53         49261 my $elem = $dom->find($tags)->first;
299 53         40548 my $new_html = $self->_component_factory($elem);
300 53         571 $elem->replace($new_html);
301             }
302 42         60629 $string = $dom->to_string;
303 42         5705 return _reinject_raws($string, $raws);
304             }
305              
306             1;
307              
308             __END__