File Coverage

blib/lib/Text/Sass.pm
Criterion Covered Total %
statement 434 443 97.9
branch 92 122 75.4
condition 6 8 75.0
subroutine 21 21 100.0
pod 4 4 100.0
total 557 598 93.1


line stmt bran cond sub pod time code
1             # -*- mode: cperl; tab-width: 8; indent-tabs-mode: nil; basic-offset: 2 -*-
2             # vim:ts=8:sw=2:et:sta:sts=2
3             #########
4             # Author: rmp
5             # Last Modified: $Date: 2012-11-10 16:33:37 +0000 (Sat, 10 Nov 2012) $
6             # Id: $Id: Sass.pm 75 2012-11-10 16:33:37Z zerojinx $
7             # $HeadURL: https://text-sass.svn.sourceforge.net/svnroot/text-sass/trunk/lib/Text/Sass.pm $
8             #
9             # Note to reader:
10             # Recursive regex processing can be very bad for your health.
11             # Sass & SCSS are both pretty cool. This module is not.
12             #
13             package Text::Sass;
14 24     24   122357 use strict;
  24         46  
  24         586  
15 24     24   110 use warnings;
  24         39  
  24         554  
16 24     24   109 use Carp;
  24         38  
  24         1869  
17 24     24   18487 use English qw(-no_match_vars);
  24         103419  
  24         216  
18 24     24   23494 use Text::Sass::Expr;
  24         62  
  24         765  
19 24     24   13935 use Text::Sass::Functions;
  24         77  
  24         775  
20 24     24   24018 use Data::Dumper;
  24         245643  
  24         1465  
21 24     24   162 use Readonly;
  24         52  
  24         136960  
22              
23             our $VERSION = q[1.0.0];
24             our $DEBUG = 0;
25             Readonly::Scalar our $DEBUG_SEPARATOR => 30;
26              
27             sub new {
28 41     41 1 10114 my ($class, $ref) = @_;
29              
30 41 50       151 if(!$ref) {
31 41         83 $ref = {};
32             }
33              
34 41         83 bless $ref, $class;
35 41         114 return $ref;
36             }
37              
38             sub css2sass {
39 3     3 1 17 my ($self, $str) = @_;
40              
41 3 50       12 if(!ref $self) {
42 0         0 $self = $self->new;
43             }
44              
45 3         6 my $symbols = {};
46 3         7 my $stash = [];
47 3         12 $self->_parse_css($str, $stash, $symbols);
48 3         11 return $self->_stash2sass($stash, $symbols);
49             }
50              
51             sub sass2css {
52 26     26 1 202 my ($self, $str) = @_;
53              
54 26 50       93 if(!ref $self) {
55 0         0 $self = $self->new;
56             }
57              
58 26         50 my $symbols = {};
59 26         54 my $stash = [];
60 26         44 my $chain = [];
61 26         152 $self->{_sass_indent} = 0;
62 26         128 $self->_parse_sass($str, $stash, $symbols, $chain);
63 25         110 return $self->_stash2css($stash, $symbols);
64             }
65              
66             sub scss2css {
67 14     14 1 67 my ($self, $str) = @_;
68              
69 14 50       46 if(!ref $self) {
70 0         0 $self = $self->new;
71             }
72              
73 14         23 my $symbols = {};
74 14         23 my $stash = [];
75 14         50 $self->_parse_css($str, $stash, $symbols);
76 14         41 return $self->_stash2css($stash, $symbols);
77             }
78              
79             sub _parse_sass {
80 163     163   313 my ($self, $str, $substash, $symbols, $chain) = @_;
81 163 50       388 $DEBUG and print {*STDERR} q[=]x$DEBUG_SEPARATOR, q[begin _parse_sass], q[=]x$DEBUG_SEPARATOR, "\n";
  0         0  
82              
83             #########
84             # insert blank links after code2:
85             # code1
86             # code2
87             # code3
88             # code4
89             #
90 163         694 $str =~ s/\n(\S)/\n\n$1/smxg;
91              
92             #########
93             # strip blank lines from:
94             #
95             # code
96             #
97 163         569 $str =~ s/^\s*\n(\s+)/$1/smxg;
98 163         758 my $groups = [split /\n\s*?\n/smx, $str];
99 163         345 for my $g (@{$groups}) {
  163         332  
100 229         1365 $self->_parse_sass_group($substash, $symbols, $chain, $g);
101             }
102              
103 160 50       368 $DEBUG and print {*STDERR} q[=]x$DEBUG_SEPARATOR, q[ end _parse_sass ], q[=]x$DEBUG_SEPARATOR, "\n";
  0         0  
104              
105 160         330 return 1;
106             }
107              
108             sub _parse_sass_group {
109 229     229   407 my ($self, $substash, $symbols, $chain, $group) = @_;
110              
111 229         652 my @lines = split /\n/smx, $group;
112              
113 229         663 while(my $line = shift @lines) {
114             #########
115             # /* comment */
116             # /* comment
117             #
118 230         344 $line =~ s{/[*].*?[*]/\s*}{}smx;
119 230         335 $line =~ s{/[*].*$}{}smx;
120              
121             #########
122             # !x = y variable declarations
123             #
124 230         328 $line =~ s{^\!(\S+)\s*=\s*(.*?)$}{
125 2         8 $symbols->{variables}->{$1} = $2;
126 2 50       6 $DEBUG and carp qq[VARIABLE $1 = $2];
127 2         5 q[];
128             }smxegi;
129              
130             #########
131             # $x : y variable declarations
132             #
133 230         349 $line =~ s{^\$(\S+)\s*:\s*(.*?)$}{
134 2         8 $symbols->{variables}->{$1} = $2;
135 2 50       6 $DEBUG and carp qq[VARIABLE $1 = $2];
136 2         5 q[];
137             }smxegi;
138              
139             #########
140             # =x | =x(!var)
141             # bla | bla
142             #
143             # mixin declaration
144             #
145 230         321 $line =~ s{^=(.*?)$}{
146 2         5 my $mixin_stash = {};
147 2         6 my $remaining = join "\n", @lines;
148 2         4 @lines = ();
149 2         4 my $proto = $1;
150 2         8 my ($func) = $1 =~ /^([^(]+)/smx;
151              
152             #########
153             # mixins are interpolated later, so we just store the string here
154             #
155 2         11 $symbols->{mixins}->{$func} = "$proto\n$remaining\n";
156 2 50       5 $DEBUG and carp qq[MIXIN $func];
157 2         6 q[];
158             }smxegi;
159              
160             #########
161             # @include
162             #
163             # mixin usage
164             #
165 230         407 $line =~ s{^\@include\s*(.*?)(?:[(](.*?)[)])?$}{
166 2         9 my ($func, $argstr) = ($1, $2);
167 2         5 my $mixin_str = $symbols->{mixins}->{$func};
168              
169 2         5 my $subsymbols = $symbols; # todo: correct scoping - is better as {%{$symbols}}
170 2 100       11 my $values = $argstr ? [split /\s*,\s*/smx, $argstr] : [];
171 2         9 my ($varstr) = $mixin_str =~ /^.*?[(](.*?)[)]/smx;
172 2 100       9 my $vars = $varstr ? [split /\s*,\s*/smx, $varstr] : [];
173              
174 2         3 for my $var (@{$vars}) {
  2         7  
175 1         4 $var =~ s/^[\!\$]//smx;
176 1         2 $subsymbols->{variables}->{$var} = shift @{$values};
  1         6  
177             }
178              
179 2         9 $mixin_str =~ s/^.*?\n//smx;
180 2         5 my $result = [];
181              
182 2         4 $self->_parse_sass($mixin_str, $result, $subsymbols, [@{$chain}]);
  2         9  
183 2         4 push @{$substash}, {"+$func" => $result};
  2         10  
184              
185 2 50       7 $DEBUG and carp qq[DYNAMIC MIXIN $func];
186 2         8 q[];
187             }smxegi;
188              
189             #########
190             # @mixin name
191             # bla
192             #
193             # mixin declaration
194             #
195 230         345 $line =~ s{^\@mixin\s+(.*?)$}{
196 2         5 my $mixin_stash = {};
197 2         8 my $remaining = join "\n", @lines;
198 2         5 @lines = ();
199 2         6 my $proto = $1;
200 2         9 my ($func) = $1 =~ /^([^(]+)/smx;
201              
202             #########
203             # mixins are interpolated later, so we just store the string here
204             #
205 2         11 $symbols->{mixins}->{$func} = "$proto\n$remaining\n";
206 2 50       8 $DEBUG and carp qq[MIXIN $func];
207 2         7 q[];
208             }smxegi;
209              
210             #########
211             # static +mixin
212             #
213 230         321 $line =~ s{^[+]([^(]+)$}{
214 1         3 my $func = $1;
215 1         3 my $mixin_str = $symbols->{mixins}->{$func};
216 1         10 $mixin_str =~ s/^.*?\n//smx;
217 1         2 my $result = [];
218              
219 1         2 $self->_parse_sass($mixin_str, $result, $symbols, [@{$chain}]);
  1         6  
220              
221 1         2 my $mixin_tag = (keys %{$result->[0]})[0];
  1         5  
222 1         2 push @{$substash}, {$mixin_tag => (values %{$result->[0]})[0]};
  1         3  
  1         3  
223 1 50       4 $DEBUG and carp qq[STATIC MIXIN $func / $mixin_tag];
224 1         4 q[];
225             }smxegi;
226              
227             #########
228             # interpolated +mixin(value)
229             #
230 230         327 $line =~ s{^[+](.*?)[(](.*?)[)]$}{
231 1         5 my ($func, $argstr) = ($1, $2);
232 1         3 my $mixin_str = $symbols->{mixins}->{$func};
233              
234 1         1 my $subsymbols = $symbols; # todo: correct scoping - is better as {%{$symbols}}
235 1         4 my $values = [split /\s*,\s*/smx, $argstr];
236 1         5 my ($varstr) = $mixin_str =~ /^.*?[(](.*?)[)]/smx;
237 1         3 my $vars = [split /\s*,\s*/smx, $varstr];
238              
239 1         2 for my $var (@{$vars}) {
  1         2  
240 1         10 $var =~ s/^[\!\$]//smx;
241 1         2 $subsymbols->{variables}->{$var} = shift @{$values};
  1         5  
242             }
243              
244 1         4 $mixin_str =~ s/^.*?\n//smx;
245 1         2 my $result = [];
246              
247 1         2 $self->_parse_sass($mixin_str, $result, $subsymbols, [@{$chain}]);
  1         5  
248 1         2 push @{$substash}, {"+$func" => $result};
  1         4  
249              
250 1 50       4 $DEBUG and carp qq[DYNAMIC MIXIN $func];
251 1         3 q[];
252             }smxegi;
253              
254             #########
255             # parent ref
256             #
257             # tag
258             # attribute: value
259             # &:pseudoclass
260             # attribute: value2
261             #
262 230         330 $line =~ s{^(&\s*.*?)$}{$self->_parse_sass_parentref($substash, $symbols, $chain, \@lines, $1)}smxegi;
  3         21  
263              
264             #########
265             # static and dynamic attr: value
266             # color: #aaa
267             #
268 230         791 $line =~ s{^(\S+)\s*[:=]\s*(.*?)$}{
269 83         176 my $key = $1;
270 83         158 my $val = $2;
271              
272 83 50       188 $DEBUG and carp qq[ATTR $key = $val];
273              
274 83 100       255 if($val =~ /^\s*$/smx) {
275 2         6 my $remaining = join "\n", @lines;
276 2         7 @lines = ();
277 2         9 my $ssubstash = [];
278 2         5 $self->_parse_sass($remaining, $ssubstash, $symbols, [@{$chain}]);
  2         7  
279 2         4 push @{$substash}, { "$key:" => $ssubstash };
  2         9  
280             } else {
281 81         153 push @{$substash}, { $key => $val };
  81         319  
282             }
283 83         231 q[];
284             }smxegi;
285              
286             #########
287             #
288             #
289 230 100       668 if ($line =~ /^([ ]+)(\S.*)$/smx) {
290 69         158 my $indent = $1;
291             # Indented
292 69 100       203 if (!$self->{_sass_indent}) {
293 26         73 $self->{_sass_indent} = length $1;
294             }
295              
296 69 100       745 if ($line =~ /^[ ]{$self->{_sass_indent}}(\S.*)$/smx) {
297 68         124 my $process = [];
298 68         218 while (my $l = shift @lines) {
299 91 50       548 if($l =~ /^[ ]{$self->{_sass_indent}}(.*)$/smx) {
    0          
300 91         118 push @{$process}, $1;
  91         475  
301             } elsif ($l !~ /^\s*$/xms) {
302             #########
303             # put it back where it came from
304             #
305 0         0 unshift @lines, $l;
306 0         0 last;
307             }
308             }
309              
310 68         112 my $remaining = join "\n", $1, @{$process};
  68         232  
311              
312 68 50       174 $DEBUG and carp qq[INDENTED $line CALLING DOWN REMAINING=$remaining ].Dumper($substash);
313 68         105 $self->_parse_sass($remaining, $substash, $symbols, [@{$chain}]);
  68         299  
314 67         189 $line = q[];
315              
316             } else {
317 1         10 croak qq[Illegal indent @{[length $indent]} we're using @{[$self->{_sass_indent}]} ($line)];
  1         4  
  1         198  
318             }
319             }
320              
321             #########
322             # .class
323             # #id
324             # element
325             # element2, element2
326             #
327             #
328 228         483 $line =~ s{^(\S+.*?)$}{
329 60         135 my $one = $1;
330 60         131 $one =~ s/\s+/ /smxg;
331              
332 60         153 my $remaining = join "\n", @lines;
333 60         131 @lines = ();
334 60         96 my $subsubstash = [];
335              
336 60 50       161 $DEBUG and carp qq[ELEMENT $one descending with REMAINING=$remaining];
337 60 50       152 $DEBUG and carp Dumper($substash);
338 60         105 $self->_parse_sass($remaining, $subsubstash, $symbols, [@{$chain}, $one]);
  60         366  
339 59         109 push @{$substash}, { $one => $subsubstash };
  59         183  
340 59 50       153 $DEBUG and carp qq[ELEMENT $one returned];
341 59 50       147 $DEBUG and carp Dumper($substash);
342 59         166 q[];
343             }smxegi;
344              
345 227 50 33     1075 $DEBUG and $line and carp qq[REMAINING $line];
346             }
347              
348 226         549 return 1;
349             }
350              
351             sub _parse_sass_parentref { ## no critic (ProhibitManyArgs) # todo: tidy this up!
352 3     3   12 my ($self, $substash, $symbols, $chain, $lines, $pseudo) = @_;
353              
354 3         4 my $remaining = join "\n", @{$lines};
  3         9  
355 3         7 @{$lines} = ();
  3         7  
356 3         5 my $newkey = join q[ ], @{$chain};
  3         9  
357 3         14 $pseudo =~ s/&/&$newkey/smx;
358              
359 3         6 my $subsubstash = [];
360 3         48 $self->_parse_sass($remaining, $subsubstash, $symbols, ['TBD']);
361 3         5 push @{$substash}, {$pseudo => $subsubstash};
  3         9  
362              
363 3         11 return q[];
364             }
365              
366             sub _css_nestedgroups {
367 32     32   55 my ($self, $str) = @_;
368              
369 32         53 my $groups = [];
370 32         49 my $groupstr = q[];
371 32         44 my $indent = 0;
372              
373 32         82 for my $i (0..length $str ) {
374 2399         3002 my $char = substr $str, $i, 1;
375 2399         2807 $groupstr .= $char;
376              
377 2399 100       4395 if ($char eq '{') {
378 54         72 $indent++;
379             }
380              
381 2399 100       4905 if ($char eq '}') {
382 54         64 $indent--;
383 54 100       117 if ($indent == 0) {
384 40         53 push @{$groups}, $groupstr;
  40         93  
385 40         79 $groupstr = q[];
386             }
387             }
388             }
389              
390 32         73 return $groups;
391             }
392              
393             sub _css_kvs {
394 37     37   73 my ($self, $str) = @_;
395              
396 37         57 my $groups = [];
397 37         61 my $groupstr = q[];
398 37         76 my $indent = 0;
399              
400 37         78 for my $i (0..length $str) {
401 1788         2379 my $char = substr $str, $i, 1;
402              
403 1788 100 100     4286 if ($char eq q[;] and $indent == 0) {
404 47         59 push @{$groups}, $groupstr;
  47         131  
405 47         77 $groupstr = q[];
406              
407             } else {
408 1741         2080 $groupstr .= $char;
409             }
410              
411 1788 100       3126 if ($char eq '{') {
412 12         19 $indent++;
413             }
414              
415 1788 100       3810 if ($char eq '}') {
416 12         15 $indent--;
417 12 100       32 if ($indent == 0) {
418 11         13 push @{$groups}, $groupstr;
  11         24  
419 11         21 $groupstr = q[];
420             }
421             }
422             }
423              
424 37         86 return $groups;
425             }
426              
427             sub _parse_css {
428 32     32   64 my ($self, $str, $substash, $symbols) = @_;
429 32         64 $str =~ s{/[*].*?[*]/}{}smxg;
430              
431             # Normalize line breaks
432 32         135 $str =~ s/\n//sg; ## no critic (RegularExpressions)
433 32         111 $str =~ s/;/;\n/sg; ## no critic (RegularExpressions)
434 32         105 $str =~ s/{/{\n/sg; ## no critic (RegularExpressions)
435 32         94 $str =~ s/}/}\n/sg; ## no critic (RegularExpressions)
436              
437             #########
438             # scss definitions
439             #
440 32         108 $str =~ s{^\s*\$(\S+)\s*:\s*(.*?)\s*\;}{
441 2         9 $symbols->{variables}->{$1} = $2;
442 2 50       8 $DEBUG and carp qq[VARIABLE $1 = $2];
443 2         14 q[];
444             }smxegi;
445              
446 32         84 my $groups = $self->_css_nestedgroups($str);
447              
448 32         46 for my $g (@{$groups}) {
  32         62  
449 40         220 my ($tokens, $block) = $g =~ m/([^{]*)[{](.*)[}]/smxg;
450 40         102 $tokens =~ s/^\s+//smx;
451 40         137 $tokens =~ s/\s+$//smx;
452 40         70 $tokens =~ s/\n\s+/\n/smx;
453 40         59 $tokens =~ s/\s+\n/\n/smx;
454              
455 40 100       111 if ($tokens =~ /^\s*\@mixin\s+(.*)$/smx) {
456 3         10 my $proto = $1;
457 3         12 my ($func) = $1 =~ /^([^(]+)/smx;
458 3         24 $symbols->{mixins}->{$func} = "$proto {\n$block\n}\n";
459 3 50       12 $DEBUG and carp qq[MIXIN $func];
460 3         6 next;
461             }
462              
463 37         96 my $kvs = $self->_css_kvs($block);
464 37         55 my $ssubstash = [];
465              
466 37         49 for my $kv (@{$kvs}) {
  37         74  
467 58         188 $kv =~ s/^\s+//smx;
468 58         177 $kv =~ s/\s+$//smx;
469              
470 58 50       125 if(!$kv) {
471 0         0 next;
472             }
473              
474 58 100       161 if ($kv =~ /[{].*[}]/smx) {
475 11         99 $self->_parse_css( $kv, $ssubstash, $symbols );
476 11         20 next;
477             }
478              
479 47 100       138 if ($kv =~ /^\s*\@include\s+(.*?)(?:[(](.*?)[)])?$/xms) {
480 4         14 my ($func, $argstr) = ($1, $2);
481 4         10 my $mixin_str = $symbols->{mixins}->{$func};
482              
483 4         7 my $subsymbols = $symbols; # todo: correct scoping - is better as {%{$symbols}}
484 4 100       11 my $values = $argstr ? [split /\s*,\s*/smx, $argstr] : [];
485 4         11 my ($varstr) = $mixin_str =~ /^.*?[(](.*?)[)]/smx;
486 4         19 my ($proto) = $mixin_str =~ /^\s*([^{]*\S)\s*[{]/smx;
487 4 100       19 my $vars = $varstr ? [split /\s*,\s*/smx, $varstr] : [];
488              
489 4         7 for my $var (@{$vars}) {
  4         8  
490 1         3 $var =~ s/^[\!\$]//smx;
491 1         2 $subsymbols->{variables}->{$var} = shift @{$values};
  1         4  
492             }
493              
494 4         8 my $result = [];
495 4         15 $self->_parse_css($mixin_str, $result, $subsymbols);
496 4         4 push @{$ssubstash}, @{$result->[0]->{$proto}};
  4         41  
  4         10  
497              
498 4 50       11 $DEBUG and carp qq[DYNAMIC MIXIN $func];
499 4         14 next;
500             }
501              
502 43 100       114 if ($kv =~ /^\s*\@extend\s+(.*?)$/xms) {
503 1         3 my ($selector) = ($1, $2);
504 1         161 carp q[@extend not yet implemented]; ## no critic (RequireInterpolationOfMetachars)
505 1         96 next;
506             }
507              
508 42         146 my ($key, $value) = split /:/smx, $kv, 2;
509 42         104 $key =~ s/^\s+//smx;
510 42         69 $key =~ s/\s+$//smx;
511 42         118 $value =~ s/^\s+//smx;
512 42         77 $value =~ s/\s+$//smx;
513 42         54 push @{$ssubstash}, { $key => $value };
  42         166  
514             }
515              
516             #########
517             # post-process parent references '&'
518             #
519 37         79 my $parent_processed = [];
520             #carp qq[SUBSTASH=].Dumper($substash);
521 37         55 for my $child (@{$ssubstash}) {
  37         67  
522             #carp qq[CHILD=].Dumper($child);
523 59         77 my ($k) = keys %{$child};
  59         152  
524 59         121 my ($v) = $child->{$k};
525             #carp qq[post-process k=$k v=$v tokens=$tokens];
526 59         97 $k =~ s{(.*)&}{&$1$tokens}smx;
527             #carp qq[post-process kafter=$k];
528 59         67 push @{$parent_processed}, { $k => $v };
  59         214  
529             #carp Dumper($substash);
530             #carp Dumper({$tokens => $parent_processed});
531             }
532              
533 37         53 push @{$substash}, { $tokens => $parent_processed };
  37         186  
534             }
535 32         74 return 1;
536             }
537              
538             sub _stash2css {
539 77     77   143 my ($self, $stash, $symbols) = @_;
540 77         157 my $groups = [];
541 77         150 my $delayed = [];
542             #carp qq[STASH2CSS: ].Dumper($stash);
543 77         118 for my $stash_line (@{$stash}) {
  77         164  
544 93         135 for my $k (keys %{$stash_line}) {
  93         265  
545 93         146 my $vk = $k;
546 93         268 $vk =~ s/\s+/ /smx;
547              
548 93 100       375 if($k =~ /&/smx) {
549 6         24 ($vk) = $k =~ /&(.*)$/smx;
550              
551 6         19 $stash_line->{$vk} = $stash_line->{$k};
552 6         11 delete $stash_line->{$k};
553 6         12 $k = $vk;
554             }
555              
556 93         263 my $str = "$vk {\n";
557 93 50       306 if(!ref $stash_line->{$k}) {
558 0         0 $str .= sprintf q[ %s: %s], $vk, $stash_line->{$k};
559              
560             } else {
561              
562 93         129 for my $attr_line (@{$stash_line->{$k}}) {
  93         216  
563 148         194 for my $attr (sort keys %{$attr_line}) {
  148         486  
564 148         262 my $val = $attr_line->{$attr};
565              
566 148 100       398 if($attr =~ /^[+]/smx) {
567 3         7 $attr = q[];
568             }
569              
570 148 100       476 if($attr =~ /:$/smx) {
571             #########
572             # font:
573             # family: foo;
574             # size: bar;
575             #
576 3         7 my $rattr = $attr;
577 3         10 $rattr =~ s/:$//smx;
578 3         8 for my $val_line (@{$val}) {
  3         9  
579 9         12 for my $k2 (sort keys %{$val_line}) {
  9         29  
580 9         25 $str .= sprintf qq[ %s-%s: %s;\n], $rattr, $k2, $self->_expr($stash, $symbols, $val_line->{$k2});
581             }
582             }
583 3         11 next;
584             }
585              
586 145 100       341 if(ref $val) {
587 38 100       104 if($attr) {
588 35         108 $attr = sprintf q[ %s], $attr;
589             }
590 38 100       112 my $rattr = $k . ($attr ? $attr : q[]);
591              
592 38 100       127 if($k =~ /,/smx) {
593 1         6 $rattr = join q[, ], map { "$_$attr" } split /\s*,\s*/smx, $k;
  2         7  
594             }
595              
596 38 100       106 if($attr =~ /,/smx) {
597 3         11 $attr =~ s/^\s//smx;
598 3         17 $rattr = join q[, ], map { "$k $_" } split /\s*,\s*/smx, $attr;
  6         63  
599             }
600              
601             # TODO: What if both have ,?
602              
603 38         55 push @{$delayed}, $self->_stash2css([{$rattr => $val}], $symbols);
  38         295  
604 38         170 next;
605             }
606              
607 107         345 $str .= sprintf qq[ %s: %s;\n], $attr, $self->_expr($stash, $symbols, $val);
608             }
609             }
610             }
611              
612 93         165 $str .= "}\n";
613 93 100       414 if($str !~ /[{]\s*[}]/smx) {
614 83         96 push @{$groups}, $str;
  83         195  
615             }
616              
617 93         133 push @{$groups}, @{$delayed};
  93         148  
  93         151  
618 93         303 $delayed = [];
619             }
620             }
621              
622 77         116 return join "\n", @{$groups};
  77         597  
623             }
624              
625             sub _expr {
626 132     132   248 my ($self, $stash, $symbols, $expr) = @_;
627 132   100     651 my $vars = $symbols->{variables} || {};
628              
629 132 50       301 $expr =~ s/\!(\S+)/{$vars->{$1}||"\!$1"}/smxeg;
  6         12  
  6         42  
630 132 100       240 $expr =~ s/\$(\S+)/{$vars->{$1}||"\$$1"}/smxeg;
  14         18  
  14         87  
631              
632             {
633             # Functions
634 132         207 my $functions = Text::Sass::Functions->new;
  132         571  
635              
636             # TODO: should have rest, so that url() will work
637              
638 132         713 while ($expr =~ /^(.*?)((\S+)\s*[(]([^)]+)[)](.*)$)/smx) {
639 8         18 my $start = $1;
640 8         19 my $mstr = $2;
641 8         14 my $func = $3;
642 8         20 my $varstr = $4;
643 8         16 my $end = $5;
644              
645             #########
646             # We want hyphenated 'adjust-hue' to work
647             #
648 8         15 $func =~ s/\-/_/gsmx;
649 8 100       48 if (!$functions->can($func)) {
650 6         27 $start = $self->_expr($stash, $symbols, $start);
651 6         16 $end = $self->_expr($stash, $symbols, $end);
652              
653             #########
654             # not happy with this here. It probably at least belongs in Expr
655             # - and should include any other CSS stop-words
656             #
657 6 50       38 if($end =~ /repeat|left|top|right|bottom/smx) { ## no-repeat, repeat-x, repeat-y
658 6         10 $end = q[];
659             }
660              
661 6         13 $expr = $start . $mstr . $end;
662 6         13 last;
663             }
664              
665             #########
666             # TODO: Should support darken(#323, something(4+5, 5))
667             #
668 2         8 my @vars = split /,/smx, $varstr;
669 2         5 for my $var (@vars) {
670 4         9 $var =~ s/^\s//smx;
671 4         19 $var = $self->_expr($stash, $symbols, $var);
672             }
673              
674 2         10 my $res = $functions->$func(@vars);
675 2         271 $expr =~ s/\Q$mstr\E/$res/smx
676             }
677             }
678              
679 132         422 my @parts = split /\s+/smx, $expr;
680              
681 132         411 Readonly::Scalar my $BINARY_OP_PARTS => 3;
682 132 100       2925 if(scalar @parts == $BINARY_OP_PARTS) {
683 10         57 my $ret = Text::Sass::Expr->expr(@parts);
684 10 100       35 if (defined $ret) {
685 7         45 return $ret;
686             }
687             }
688              
689 125         766 return $expr;
690             }
691              
692             sub _stash2sass {
693 3     3   6 my ($self, $stash, $symbols) = @_;
694 3         7 my $groups = [];
695              
696             # TODO: Write symbols
697              
698 3         5 for my $stashline (@{$stash}) {
  3         13  
699 4         5 for my $k (keys %{$stashline}) {
  4         18  
700 4         10 my $str = "$k\n";
701              
702 4         8 for my $attrline (@{$stashline->{$k}}){
  4         11  
703 6         8 for my $attr (sort keys %{$attrline}) {
  6         24  
704 6         15 my $val = $attrline->{$attr};
705 6         28 $str .= sprintf qq[ %s: %s\n], $attr, $val;
706             }
707             }
708 4         8 push @{$groups}, $str;
  4         13  
709             }
710             }
711              
712 3         7 return join "\n", @{$groups};
  3         31  
713             }
714              
715             1;
716             __END__