File Coverage

blib/lib/Sumi/CSS.pm
Criterion Covered Total %
statement 31 40 77.5
branch 2 2 100.0
condition 2 2 100.0
subroutine 7 9 77.7
pod 4 4 100.0
total 46 57 80.7


line stmt bran cond sub pod time code
1             package Sumi::CSS;
2              
3 3     3   519940 use Mojo::Base qw/-base -signatures/;
  3         38372  
  3         17  
4 3     3   14800 use Class::Method::Modifiers;
  3         5699  
  3         263  
5 3     3   2546 use Storable qw(dclone);
  3         9707  
  3         350  
6              
7 3     3   5052 use Sumi::CSS::Util qw(_walk _parse_rules _remove_comments);
  3         18  
  3         309  
8 3     3   5024 use Sumi::CSS::Rule;
  3         11  
  3         20  
9              
10             # ABSTRACT: Mojo-like access to cascading style sheets
11              
12             has rules => sub { [] }; # array of rule nodes
13              
14             my $stmt_at_rule_re = qr/^\s*(\@(?:charset|import|namespace))\b([^;]*);(.*)$/ms;
15             my $block_at_rule_re = qr/^\s*\@(media|supports|layer|scope)\b(.*)$/;
16              
17             around new => sub {
18             my $orig = shift;
19             my $self = shift;
20             my $ret;
21             if ($_[1] && !ref $_[1]) {
22             my $css = shift;
23             return $self->new(@_)->parse($css);
24             }
25             return $orig->($self, @_);
26             };
27              
28             sub parse {
29 28     28 1 2558 my $self = shift;
30 28         85 my $css = shift;
31 28         141 $self->rules(_parse_rules($css));
32 28         345 $self;
33             }
34              
35             sub walk {
36 0     0 1 0 my $self = shift;
37 0         0 my $rules = $self->rules;
38 0         0 _walk($rules, @_);
39 0         0 return $self;
40             }
41              
42             sub to_string {
43 14     14 1 179 my $self = shift;
44 14   100     61 my $opts = shift || {};
45 14         40 my $minimize = $opts->{minimize};
46 14         52 my $indent = $opts->{indent};
47              
48 14         54 my $css = join "\n", map { $_->to_string(0, $indent) } $self->rules->@*;
  158         540  
49              
50 14 100       67 if ($minimize) {
51 7         19 for ($css) {
52 7         103 s/^[ \t]+//mg;
53 7         300 s/[ \t]+$//mg;
54 7         148 s/\n/ /g;
55             }
56             }
57 14         85 return $css;
58             }
59              
60              
61             sub localized {
62 0     0 1   my $self = dclone(shift());
63 0           my $lstr = shift;
64 0           $self->rules([ map { $_->localized($lstr) } $self->rules->@* ]);
  0            
65 0           $self;
66             }
67              
68              
69             =head1 NAME
70              
71             Sumi::CSS - Mojo-like access to cascading style sheets
72              
73             =head1 SYNOPSIS
74              
75             use Sumi::CSS;
76              
77             # Parse CSS text
78             my $css = Sumi::CSS->new->parse(<<'CSS');
79             @media screen {
80             body { color: black; background: white; }
81             }
82             .foo { color: red; }
83             CSS
84              
85             # Walk the rules
86             $css->walk(sub ($rule) {
87             say $rule->to_string;
88             });
89              
90             # Get the full CSS back as string
91             say $css->to_string;
92              
93             # Minify output
94             say $css->to_string({ minimize => 1 });
95              
96             # Indented output (custom depth)
97             say $css->to_string({ indent => 2 });
98              
99             # Create a localized copy of the CSS
100             my $localized = $css->localized('.en');
101              
102             =head1 DESCRIPTION
103              
104             L provides a L-style object interface for parsing,
105             walking, and reconstructing CSS documents with support of nested at-rules.
106              
107             =head1 ATTRIBUTES
108              
109             =head2 rules
110              
111             my $rules = $css->rules;
112             $css->rules([ $rule1, $rule2 ]);
113              
114             An array reference of L objects representing parsed CSS rules.
115              
116             =head1 METHODS
117              
118             =head2 new
119              
120             my $css = Sumi::CSS->new;
121             my $css = Sumi::CSS->new->parse($css_text);
122             my $css = Sumi::CSS->new($other_css);
123              
124             Creates a new C object.
125             If called with a string as the first argument, it automatically parses
126             that string as CSS content.
127              
128             =head2 parse
129              
130             $css = $css->parse($css_text);
131              
132             Parses a CSS string and populates the internal rule list.
133              
134             =head2 walk
135              
136             $css->walk(sub ($rule) { ... });
137              
138             Walks over all CSS rules (recursively for nested at-rules) and applies
139             the provided callback. Returns the invocant for chaining.
140              
141             =head2 to_string
142              
143             my $string = $css->to_string;
144             my $string = $css->to_string({ minimize => 1 });
145             my $string = $css->to_string({ indent => 2 });
146              
147             Serializes the parsed CSS back into text.
148             Takes an optional hash reference with the following keys:
149              
150             =over 4
151              
152             =item * C
153              
154             If true, removes extra whitespace and newlines for compact output.
155              
156             =item * C
157              
158             Controls indentation depth (default: 4 spaces).
159              
160             =back
161              
162             =head2 localized
163              
164             my $localized = $css->localized('.en');
165              
166             Returns a deep-cloned version of the CSS tree with selectors localized
167             using the given prefix (e.g. prepending C<.en> to class selectors).
168             This can be used for namespacing or scoping CSS for localization or components.
169              
170             =head1 INTERNALS
171              
172             =head2 _parse_rules, _walk, _remove_comments
173              
174             These internal helper functions are provided by L and are
175             not intended for external use.
176              
177             =head1 SEE ALSO
178              
179             L, L, L, L
180              
181             =head1 AUTHOR
182              
183             Simone Cesano
184              
185             =head1 LICENSE
186              
187             This module is released under the same terms as Perl itself.
188              
189             =cut
190              
191             1;