File Coverage

blib/lib/Sumi/CSS/Util.pm
Criterion Covered Total %
statement 35 44 79.5
branch 9 14 64.2
condition 1 2 50.0
subroutine 5 7 71.4
pod n/a
total 50 67 74.6


line stmt bran cond sub pod time code
1             package Sumi::CSS::Util;
2              
3             our @ISA = qw(Exporter);
4             our @EXPORT_OK = qw(_walk _parse_rules _remove_comments);
5              
6             my $stmt_at_rule_re = qr/^\s*(\@(?:charset|import|namespace))\b([^;]*);(.*)$/ms;
7             my $block_at_rule_re = qr/^\s*\@(media|supports|layer|scope)\b(.*)$/;
8              
9 3     3   2623 use Text::Balanced qw(extract_bracketed);
  3         43869  
  3         403  
10 3     3   1883 use Mojo::Util qw/trim/;
  3         575472  
  3         3407  
11              
12             sub _walk {
13 0     0   0 my $rules = shift;
14 0 0   0   0 my ($cb, $cb_s, $cb_f) = map { $_ ? $_ : sub {} } @_[0..2];
  0         0  
15              
16 0         0 for ($rules->@*) {
17 0 0       0 if (ref $_->{style} eq "ARRAY") {
18 0         0 $cb_s->($_);
19 0         0 _walk($_->{style}, $cb, $cb_s, $cb_f);
20 0         0 $cb_f->($_);
21             } else {
22 0         0 $cb->($_)
23             }
24             }
25             }
26              
27              
28             sub _remove_comments {
29 84     84   241 my $css = shift;
30 84         168 my $out = '';
31 84         303 while (length $css) {
32             # Match strings (single or double quoted)
33 34198 100       89033 if ($css =~ s/^("([^"\\]|\\.)*"|'([^'\\]|\\.)*')//s) {
    100          
34 144         529 $out .= $1; # keep strings intact
35             }
36             # Match comments
37             elsif ($css =~ s{^/\*[^*]*\*+([^/*][^*]*\*+)*/}{}s) {
38             # drop comment
39             }
40             else {
41             # copy one char if nothing matches
42 33918         72283 $out .= substr($css, 0, 1, '');
43             }
44             }
45 84         435 return $out;
46             }
47              
48              
49             sub _parse_rules {
50 84     84   298 my $rest = _remove_comments(shift);
51 84   50     594 my $opts = shift || {};
52              
53 84         313 my ($selector, $style, $type);
54 84         0 my $rules;
55 84         406 while ($rest =~ /\S/) {
56 400         5203 ($rest, $selector, $style, $type) = _parse_one_rule($rest);
57             push $rules->@*,
58 400 50       3737 ($opts->{unblessed} ? { selector => $selector, style => $style } :
59             Sumi::CSS::Rule->new({ selector => $selector, style => $style }));
60             }
61 84         1608 return $rules;
62             }
63              
64             sub _parse_one_rule {
65 400     400   729 my $rest = shift;
66 400         856 my ($selector, $rule);
67              
68 400 100       3348 if ($rest =~ $stmt_at_rule_re) {
69 40         238 return $3, [ trim($1 . $2) ], $rule;
70             }
71              
72 360         2396 ($selector, $rest) = $rest =~ /^\s*(.+?)(\{.+)/ms;
73 360         1310 ($rule, $rest) = extract_bracketed($rest, '{}');
74 360         136309 $rule =~ s/\s*\n\s*/ /gms;
75              
76 360 100       2273 if ($selector =~ $block_at_rule_re) {
77 56         255 $rule =~ /^\s*\{(.+)\}\s*$/ms;
78 56         176 $rule = _parse_rules($1);
79 56         106 $type = "block_at";
80             } else {
81 304         622 $type = "style";
82             }
83 360         1230 $selector = [ split /\s*\,\s*/, trim($selector) ];
84 360         6366 return $rest, $selector, $rule;
85             }
86              
87             1