File Coverage

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