File Coverage

blib/lib/Text/Sass.pm
Criterion Covered Total %
statement 450 459 98.0
branch 98 128 76.5
condition 6 8 75.0
subroutine 23 23 100.0
pod 4 4 100.0
total 581 622 93.4


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