File Coverage

blib/lib/CSS/LESSp.pm
Criterion Covered Total %
statement 183 423 43.2
branch 68 212 32.0
condition 27 57 47.3
subroutine 12 23 52.1
pod 14 14 100.0
total 304 729 41.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package CSS::LESSp;
3              
4 2     2   79586 use warnings;
  2         4  
  2         65  
5 2     2   10 use strict;
  2         4  
  2         74  
6 2     2   9 use Exporter;
  2         7  
  2         15194  
7              
8             our @ISA = qw(Exporter);
9             our @EXPORT_OK = qw('parse');
10             our $VERSION = '0.86';
11              
12             my $id = 1;
13              
14             #
15             # specs check
16             #
17             # accessors.less ( ok )
18             # big.less ( ok )
19             # colors.less ( ok ) exceptions : no hsl
20             # comments.less ( ok )
21             # css-3.less ( ok )
22             # css.less ( ok )
23             # dash-prefix.less ( ok )
24             # functions.less ( unsupported )
25             # hidden.less ( unsupported )
26             # import-with-extra-paths.less ( unsupported )
27             # import.less ( unsupported )
28             # lazy-eval.less ( unsupported )
29             # literal-css.less ( unsupported )
30             # mixins-args.less ( ok )
31             # mixins.less ( ok )
32             # operations.less ( ok )
33             # parens.less ( ok )
34             # rulesets.less ( ok )
35             # scope.less ( ok )
36             # selectors.less ( ok )
37             # strings.less ( ok )
38             # variables.less ( ok )
39             # whitespace.less ( ok ) exceptions : doesn't merge same lines
40             #
41              
42             sub new {
43 3     3 1 7 my $class = shift;
44 3         19 my $self = {
45             'variables' => {},
46             'rules' => [],
47             'type' => 'child',
48             'children' => []
49             };
50 3         8 bless $self, $class;
51 3         6 return $self;
52             }
53              
54             sub isFunction {
55 57     57 1 78 my ($self) = @_;
56 57 50       148 if ( $self->{'type'} eq 'function' ) { return 1; }
  0         0  
57 57         153 return 0;
58             }
59              
60             sub insertChild {
61 37     37 1 71 my ($self, $name, $parents) = @_;
62 37 50       77 my @parents = @{$parents} if $parents;
  37         85  
63 37         57 $id++;
64 37 100       109 $self->{'children'} = [] if !defined($self->{'children'});
65            
66 37         57 $name =~ s/\n/ /mg;
67            
68             #
69             # Find the full name
70 37         44 my $fullName = $name;
71 37 100       85 if ( defined($self->{'id'}) ) {
72 2 50       8 my @names = $name =~ /\,/ ? split(/\s*\,\s*/, $name) : ( $name );
73 2         4 for my $parent ( reverse @parents ) {
74 6 100       25 next if !defined($parent->{'name'});
75 2 50       7 my @prenames = $parent->{'name'} =~ /\,/ ? split(/\s*\,\s*/, $parent->{'name'}) : ($parent->{'name'});
76 2         4 my @postnames = ();
77 2         3 for my $prename ( @prenames ) {
78 2         4 for my $name ( @names ) {
79 2 50       12 push @postnames, $prename.( $name =~ /^\:/ ? "" : " " ).$name;
80             }
81             }
82 2         4 @names = @postnames;
83 2         5 $fullName = join(", ", @names);
84             }
85             }
86            
87 37         42 push @{$self->{'children'}}, {
  37         246  
88             'name' => $name,
89             'fullname' => $fullName,
90             'type' => 'child',
91             'id' => $id,
92             'variables' => {}
93             };
94 37         69 return bless $self->{'children'}->[$#{$self->{'children'}}];
  37         154  
95             }
96              
97             sub insertVariable {
98 0     0 1 0 my ($self, $variable, $value) = @_;
99            
100 0 0       0 if ( $self->isFunction ) {
101 0         0 push @{$self->{'variables'}}, { $variable => $value };
  0         0  
102             } else {
103 0         0 $self->{'variables'}->{$variable} = $value;
104             }
105            
106 0         0 return $self;
107             }
108              
109             sub insertRule {
110 57     57 1 98 my ($self, $property, $value) = @_;
111            
112 57 100       210 $self->{'rules'} = [] if !defined($self->{'rules'});
113            
114 57         554 push @{$self->{'rules'}}, { $property => $value };
  57         238  
115            
116 57         241 return $self;
117             }
118              
119             sub insertFunction {
120 0     0 1 0 my ($self, $name, $parents) = @_;
121 0 0       0 my @parents = @{$parents} if $parents;
  0         0  
122             #my @variables = @{$variables} if $variables;
123            
124 0         0 $name =~ s/\n/ /mg;
125 0 0       0 $self->{'functions'} = [] if !defined($self->{'functions'});
126            
127 0         0 push @{$self->{'functions'}}, {
  0         0  
128             'name' => $name,
129             'type' => 'function',
130             'variables' => []
131             };
132 0         0 return bless $self->{'functions'}->[$#{$self->{'functions'}}];
  0         0  
133             }
134              
135             sub getVariable {
136 0     0 1 0 my ($self, $variable, $parents) = @_;
137 0 0       0 my @parents = @{$parents} if $parents;
  0         0  
138            
139             #
140             # First try to see if the variable is here
141 0 0       0 if ( defined($self->{'variables'}->{$variable}) ) {
142 0         0 return $self->{'variables'}->{$variable};
143             }
144            
145             #
146             # If we have parents parse them too
147 0         0 for my $parent ( reverse @parents ) {
148 0 0       0 return $parent->{'variables'}->{$variable} if defined($parent->{'variables'}->{$variable});
149             }
150            
151 0         0 return 0;
152             }
153              
154             sub getSelector {
155 0     0 1 0 my ($self, $name, $parents) = @_;
156 0 0       0 my @parents = @{$parents} if $parents;
  0         0  
157            
158             #
159             # First try to see if it's a previous sibling
160 0 0       0 if ( defined($self->{'children'}) ) {
161 0         0 for my $style ( @{$self->{'children'}} ) {
  0         0  
162 0 0       0 return $style if ($style->{'name'} eq $name);
163             }
164             }
165              
166             #
167             # Next try to find from parents
168 0         0 for my $parent ( reverse @parents ) {
169 0         0 for my $style ( @{$parent->{'children'}} ) {
  0         0  
170 0 0       0 return $style if ($style->{'name'} eq $name);
171             }
172             }
173            
174             #
175             # Last we start search of everything if it is root
176 0 0       0 my $root = $parents[0] if !defined($parents[0]->{'id'});
177 0 0       0 if ( defined($root->{'children'}) ) {
178 0         0 my $return;
179             my $fullNameSelector;
180             $root->process(sub {
181 0     0   0 my ($style) = @_;
182 0 0 0     0 if ( defined($style->{'name'}) and $style->{'name'} eq $name ) {
183 0         0 $return = $style;
184             }
185 0 0 0     0 if ( defined($style->{'fullname'}) and $style->{'fullname'} eq $name ) {
186 0         0 $fullNameSelector = $style;
187             }
188 0         0 });
189 0 0       0 return $return if $return;
190 0 0       0 return $fullNameSelector if $fullNameSelector;
191             }
192            
193             #
194             # If we can't find ... then try for functions the same way
195 0 0       0 if ( defined($self->{'functions'}) ) {
196 0         0 for my $function ( @{$self->{'functions'}} ) {
  0         0  
197 0 0       0 return $function if ($function->{'name'} eq $name);
198             }
199             }
200 0         0 for my $parent ( reverse @parents ) {
201 0 0       0 if ( defined($parent->{'functions'}) ) {
202 0         0 for my $function ( @{$parent->{'functions'}} ) {
  0         0  
203 0 0       0 return $function if ($function->{'name'} eq $name);
204             }
205             }
206             }
207 0 0       0 if ( defined($root->{'children'}) ) {
208 0         0 my $return;
209             my $fullNameSelector;
210             $root->process(sub {
211 0     0   0 my ($style) = @_;
212 0 0       0 if ( defined($style->{'functions'}) ) {
213 0         0 for my $function ( @{$style->{'functions'}} ) {
  0         0  
214 0 0 0     0 if ( defined($function->{'name'}) and $function->{'name'} eq $name ) {
215 0         0 $return = $function;
216             }
217             }
218             }
219 0         0 });
220 0 0       0 return $return if $return;
221 0 0       0 return $fullNameSelector if $fullNameSelector;
222             }
223            
224 0         0 return 0;
225             }
226              
227             sub getValue {
228 0     0 1 0 my ($self, $property) = @_;
229 0         0 my $value;
230            
231 0 0       0 if ( defined($self->{'rules'}) ) {
232 0         0 for my $rule ( @{$self->{'rules'}} ) {
  0         0  
233 0 0       0 $value = join('', values %{$rule}) if join('', keys %{$rule}) eq $property;
  0         0  
  0         0  
234             }
235             }
236            
237 0         0 return $value;
238             }
239              
240             sub process {
241 3     3 1 5 my ($styles, $function) = @_;
242            
243 3         5 my @parents = ();
244 3         7 push @parents, $styles->{'children'};
245 3         13 $styles = $styles->{'children'};
246 3         5 my $level = 0;
247 3         5 push my @position, 0;
248            
249 3         4 while ( 1 ) {
250 37         54 my $style = $styles->[$position[$level]];
251 37         87 &$function($style, $level, (\@parents, \@position));
252 37 100       81 if ( defined($style->{'children'}) ) {
253 1         32 $level++;
254 1         3 $position[$level] = 0;
255 1         2 push @parents, $styles;
256 1         3 $styles = $style->{'children'};
257             } else {
258 36         47 while ( $position[$level] == $#{$styles} ) {
  37         99  
259 4 100       15 return 1 if $level == 0;
260 1         3 $styles = pop @parents;
261 1         4 $level--;
262             }
263 33         53 $position[$level]++;
264             }
265             }
266 0         0 return 0;
267             }
268              
269             sub copyTo {
270 0     0 1 0 my ($self, $targetSelector) = @_;
271 0         0 my ($rules, $children, $variables);
272            
273             # copy rules
274 0 0       0 if ( defined($self->{'rules'}) ) {
275 0 0       0 $targetSelector->{'rules'} = [] if !defined($targetSelector->{'rules'});
276 0         0 for my $rule ( @{$self->{'rules'}} ) {
  0         0  
277 0         0 push @{$targetSelector->{'rules'}}, {%{$rule}};
  0         0  
  0         0  
278             }
279             }
280            
281             # copy variables
282 0 0       0 if ( defined($self->{'variables'}) ) {
283 0         0 for my $variable ( keys %{$self->{'variables'}} ) {
  0         0  
284 0         0 $targetSelector->{'variables'}->{$variable} = $variables->{$variable};
285             }
286             }
287            
288             # copy children
289 0 0       0 if ( defined($self->{'children'}) ) {
290 0         0 my @targets = ();
291 0         0 push @targets, $targetSelector;
292 0         0 my $target = $targetSelector;
293 0         0 my $l = 0;
294            
295             $self->process(sub {
296 0     0   0 my ($style, $level) = @_;
297 0         0 $target = $target->insertChild($style->{'name'}, \@targets);
298             #
299             # adding variables
300 0 0       0 if ( defined($style->{'variables'}) ) {
301 0         0 for my $variable ( keys %{$style->{'variables'}} ) {
  0         0  
302 0         0 $target->insertVariable($variable, $style->{'variables'}->{$variable});
303             }
304             }
305             #
306             # adding rules
307 0 0       0 if ( defined($style->{'rules'}) ) {
308 0         0 for my $rule ( @{$style->{'rules'}} ) {
  0         0  
309 0         0 my $property = join("", keys %{$rule});
  0         0  
310 0         0 my $value = join("", values %{$rule});
  0         0  
311 0         0 $target->insertRule($property, $value);
312             }
313             }
314             #
315             # children leveling
316 0 0       0 if ( defined($style->{'children'}) ) {
317 0         0 push @targets, $target;
318             } else {
319 0 0       0 if ( $l != $level ) {
320 0         0 $l = $level;
321 0         0 $target = pop @targets;
322             }
323             }
324 0         0 });
325             }
326            
327 0         0 return 0;
328             }
329              
330             sub copyFunction {
331 0     0 1 0 my ($self, $targetSelector, $variables, $parents) = @_;
332 0         0 my ($rules, $children);
333 0         0 my @variables = @{$variables};
  0         0  
334 0         0 my @parents = @{$parents};
  0         0  
335            
336             # copy vars
337 0 0       0 if ( defined($self->{'variables'}) ) {
338 0         0 for ( my $position = 0; $position <= $#{$self->{'variables'}}; $position++ ) {
  0         0  
339 0         0 my $variable = join("",keys %{$self->{'variables'}->[$position]});
  0         0  
340 0         0 my $value = join("", values %{$self->{'variables'}->[$position]});
  0         0  
341 0 0       0 $value = $variables[$position] if $variables[$position];
342 0         0 $targetSelector->{'variables'}->{$variable} = $value;
343             }
344             }
345            
346             # copy rules
347 0 0       0 if ( defined($self->{'rules'}) ) {
348 0 0       0 $targetSelector->{'rules'} = [] if !defined($targetSelector->{'rules'});
349 0         0 for my $rule ( @{$self->{'rules'}} ) {
  0         0  
350 0         0 my $property = join("", keys %{$rule});
  0         0  
351 0         0 my $value = join("", values %{$rule});
  0         0  
352 0         0 while ( $value =~ /\@(\w+\-*\w*)/ ) {
353 0         0 my $word = $1;
354             # get the variable
355 0         0 my $var = $targetSelector->getVariable($word, \@parents);
356 0         0 $value =~ s/\@$word/$var/;
357             # if variable value is negative
358 0 0       0 if ( $var =~ s/^\-// ) {
359 0         0 $value =~ s/\-\s*\-$var/\+ $var/g;
360 0         0 $value =~ s/\+\s*\-$var/\- $var/g;
361             }
362             }
363             # parse for other stuff
364 0         0 $value = _parse_value($value);
365 0         0 push @{$targetSelector->{'rules'}}, {$property => $value};
  0         0  
366             }
367             }
368            
369             # copy children
370 0 0       0 if ( defined($self->{'children'}) ) {
371 0         0 my @targets = ();
372 0         0 push @targets, $targetSelector;
373 0         0 my $target = $targetSelector;
374 0         0 my $l = 0;
375            
376             $self->process(sub {
377 0     0   0 my ($style, $level) = @_;
378 0         0 $target = $target->insertChild($style->{'name'});
379             #
380             # adding variables
381 0 0       0 if ( defined($style->{'variables'}) ) {
382 0         0 for my $variable ( keys %{$style->{'variables'}} ) {
  0         0  
383 0         0 $target->insertVariable($variable, $style->{'variables'}->{$variable});
384             }
385             }
386             #
387             # adding rules
388 0 0       0 if ( defined($style->{'rules'}) ) {
389 0         0 for my $rule ( @{$style->{'rules'}} ) {
  0         0  
390 0         0 my $property = join("", keys %{$rule});
  0         0  
391 0         0 my $value = join("", values %{$rule});
  0         0  
392 0         0 $target->insertRule($property, $value);
393             }
394             }
395             #
396             # children leveling
397 0 0       0 if ( defined($style->{'children'}) ) {
398 0         0 push @targets, $target;
399             } else {
400 0 0       0 if ( $l != $level ) {
401 0         0 $l = $level;
402 0         0 $target = pop @targets;
403             }
404             }
405 0         0 });
406             }
407            
408 0         0 return 0;
409             }
410              
411             sub dump {
412 3     3 1 6 my ($self) = @_;
413 3         7 my @return = ();
414            
415 3 50       11 if ( defined($self->{'rules'}) ) {
416 3         5 for my $rule ( @{$self->{'rules'}} ) {
  3         9  
417 0         0 push @return, join('', keys %{$rule}).join('', values %{$rule}).";\n";
  0         0  
  0         0  
418             }
419 3         6 push @return, "\n";
420             }
421            
422             $self->process(sub {
423 37     37   69 my ($style, $level, @arrays) = @_;
424 37         38 my @parents = @{$arrays[0]};
  37         64  
425 37         50 my @position = @{$arrays[1]};
  37         54  
426 37 50       104 if ( defined($style->{'rules'}) ) {
427 37         92 push @return, $style->{'fullname'} ." { ";
428 37 100       37 push @return, "\n" if $#{$style->{'rules'}} > 0;
  37         97  
429 37         48 for my $rule ( @{$style->{'rules'}} ) {
  37         66  
430 57 100       59 push @return, "\t" if $#{$style->{'rules'}} > 0;
  57         454  
431 57         68 push @return, join('', keys %{$rule}).": ".join('', values %{$rule})."; ";
  57         275  
  57         152  
432 57 100       74 push @return, "\n" if $#{$style->{'rules'}} > 0;
  57         195  
433             }
434 37         108 push @return, "}\n";
435             }
436 3         32 });
437 3         182 return @return;
438             }
439              
440             sub _parse_value {
441 57     57   79 my $value = shift;
442             # convert rgb(255,255,255) to #ffffff ( hsl doesn't work right now )
443 57         132 while ( $value =~ /rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/ ) {
444 1         12 my $color = sprintf("#%0.2X%0.2X%0.2X", $1,$2,$3);
445 1         9 $value =~ s/rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/$color/;
446             }
447             # expand colors ( from #fff to #ffffff )
448 57         142 while ( $value =~ /\#([abcdef0123456789]{3})([^abcdef0123456789]|$)/i ) {
449 4         12 my $color = $1;
450 4         30 my $replace = join('', map { $_ x 2 } split(//, $color)); # equals to : for ( split(//, $color) ) { $replace .= $_ x 2; }
  12         48  
451 4         163 $value =~ s/\#$color/\#$replace/;
452             }
453             # expressions in brackets
454 57         160 while ( $value =~ /\(\s*(\-?\d+\s*((px|pt|em|cm|%))*\s*[\+\*\/\-]\s*\-?\d+\s*((px|pt|em|cm|%))*)\s*\)/ ) {
455 2         5 my $expression = my $eval = $1;
456 2         12 my ($removed) = $eval =~ m/(px|pt|em|cm|%)/;
457 2         8 $eval =~ s/(px|pt|em|cm|%)//;
458 2 50 33     123 if ( $eval !~ /[a-z]/i and defined(my $result = eval($eval)) ) {
459 2 50 33     13 $result .= "$removed" if defined $result and $removed;
460 2         59 $value =~ s/(\(\Q$expression\E\))/$result/;
461             };
462             }
463             # expression (+,-,*,/) ; whole expressions
464 57 100 66     476 if ( $value =~ /(\d+)\s*(px|pt|em|cm|%)*\s*(\+|\*|\/)\s*((\d+)\s*(px|pt|em|cm|%)*|\d+)/ or $value =~ /(\d+)\s*(px|pt|em|cm|%)*\s*(\-)\s+((\d+)\s*(px|pt|em|cm|%)*|\d+)/ ) {
465 1         3 my $eval = $value;
466 1 50       15 my $removed = $1 if $eval =~ s/(px|pt|em|cm|%)//g;
467 1 50       6 if ( $eval !~ /[a-z]/i ) {
468 0         0 my $result = eval($eval);
469 0 0 0     0 $result .= "$removed" if defined($result) and $removed;
470 0 0 0     0 $value = $result if !$@ and defined $result;
471             }
472             }
473             # expression with color
474 57 100 100     281 if ( $value =~ /\#[abcdef0123456789]{6}/i and $value =~ /(\+|\-|\*|\/)/ ) {
475 2         8 my @rgb = ( $value, $value, $value );
476 2         19 $rgb[0] =~ s/\#([abcdef0123456789]{2})[abcdef0123456789]{4}/\#$1/ig;
477 2         16 $rgb[1] =~ s/\#[abcdef0123456789]{2}([abcdef0123456789]{2})[abcdef0123456789]{2}/\#$1/ig;
478 2         16 $rgb[2] =~ s/\#[abcdef0123456789]{4}([abcdef0123456789]{2})/\#$1/ig;
479 2         6 my $return = "";
480 2         5 for ( @rgb ) {
481 6         19 while ( /\#([abcdef0123456789]{2})/i ) {
482 12         23 my $dec = hex($1);
483 12         156 s/\#$1/$dec/;
484             }
485 6 50       23 if ( !/[a-z]/i ) {
486 0         0 my $eval = eval;
487 0 0       0 if ( $eval < 0 ) { $eval = 0 };
  0         0  
488 0 0       0 if ( $eval > 255 ) { $eval = 255 };
  0         0  
489 0         0 $return .= sprintf("%0.2X", $eval);
490             }
491             }
492 2 50       14 $value = "#".lc $return if $return;
493             }
494 57         140 return $value;
495             }
496              
497             sub parse {
498 3     3 1 3856 my $self = shift;
499 3         5 my $string = shift;
500 3         12 my $styles = CSS::LESSp->new();
501 3         5 my $selector = $styles;
502 3         5 my @parents;
503 3         4 push @parents, $styles;
504             # real parsing
505 3         7 my $lastChar = my $buffer = my $mode = my $stop = "";
506 3         7 $string =~ s/^\xEF\xBB\xBF\x0A//; # removing special characters from front of file
507 3         497 for ( split //, $string ) {
508 2716         3348 $buffer .= $_;
509 2716 100       10870 if ( $mode ) {
510 573 100       1039 $buffer =~ s/.$// if $mode eq "delete";
511 573 100 100     1840 if ( length($stop) == 1 and $_ eq $stop ) { $mode = "" };
  41         52  
512 573 100 100     1435 if ( length($stop) == 2 and $lastChar.$_ eq $stop ) { $mode = "" } else { $lastChar = $_ };
  1         2  
  572         646  
513 573         698 next;
514             }
515 2143 100       4436 next if /\n/;
516             #
517             # The program
518             #
519 1981 100 100     9458 if ( /\}/ or /\;/ ) {
520             # clearing some buffer data
521 94         366 $buffer =~ s/.$//;
522 94         836 $buffer =~ s/^\s*|\s*$//g; # remove any spaces from front or back
523 94         139 $buffer =~ s/\s*\n\s*/ /g; # removes any new line
524 94 100       178 if ( $buffer ) {
525             # if it's a property and rule
526 57 50       317 if ( $buffer =~ s/^\s*([^:]*)\s*\:\s*(.*)\s*$// ) {
527 57         132 my $property = $1;
528 57         97 my $value = $2;
529 57         81 $value =~ s/\n/ /g;
530 57         289 $property =~ s/\s*$//; # remove any additional spaces left
531             # different rule-set property ( mixins )
532 57 50       146 if ( $value =~ /^(#|.)(.*)\[(.*)\]$/ ) {
533 0         0 my $targetSelectorName = $1.$2;
534 0         0 my $targetProperty = $3;
535 0         0 my $return;
536 0         0 $targetProperty =~ s/['"]//g;
537 0         0 $targetProperty =~ s/\s*//g;
538             # get the target mixin selector
539 0         0 my $targetSelector = $selector->getSelector($targetSelectorName, \@parents);
540             # get the value
541 0 0       0 if ( $targetProperty =~ /^\@/ ) {
542 0         0 $targetProperty =~ s/^\@//;
543 0         0 $return = $targetSelector->getVariable($targetProperty);
544             } else {
545 0         0 $return = $targetSelector->getValue($targetProperty);
546             }
547 0 0       0 $value = $return if $return;
548             }
549             # variable access
550 57 50       158 if ( !$selector->isFunction ) {
551 57         149 while ( $value =~ /\@(\w+\-*\w*)/ ) {
552 0         0 my $word = $1;
553             # get the variable
554 0         0 my $var = $selector->getVariable($word, \@parents);
555 0         0 $value =~ s/\@$word/$var/;
556             # if variable value is negative
557 0 0       0 if ( $var =~ s/^\-// ) {
558 0         0 $value =~ s/\-\s*\-$var/\+ $var/g;
559 0         0 $value =~ s/\+\s*\-$var/\- $var/g;
560             }
561             }
562             # parse for other stuff
563 57         173 $value = _parse_value($value);
564             }
565             # variable definition check ( after we have parsed the stuff )
566 57 50       132 if ( $property =~ /^\@(\w+\-*\w*)/ ) {
567 0         0 $selector->insertVariable($1, $value);
568 0         0 $property = $value = "";
569             } else {
570 57         130 $selector->insertRule($property, $value);
571             }
572             }
573             # nested rules (mixins)
574 57 50 33     259 if ( $buffer =~ s/^(\..*)$// or $buffer =~ s/^(\#.*)$// ) {
575 0         0 my $source = $1;
576 0         0 $source =~ s/\s*\:\s*/\:/g;
577             # for multiple mixins
578 0         0 while ( $source =~ /\(.*,.*\)/ ) {
579 0         0 $source =~ s/\(\s*(.*)\s*,\s*(.*)\s*\)/($1;$2)/
580             };
581 0 0       0 my @sources = $source =~ /\,/ ? split(/\s*\,\s*/, $source) : ( $source );
582 0         0 for my $source ( @sources ) {
583 0         0 my @vars; # this is if any functions are found
584 0         0 my $sourceSelector = $selector->getSelector($source , \@parents);
585 0 0 0     0 if ( $source =~ /\>/ && !$sourceSelector ) {
586 0         0 my ( $parent, $child ) = split(/\s*\>\s*/, $source);
587 0         0 my $pSelector = $selector->getSelector($parent, \@parents);
588 0 0       0 $sourceSelector = $pSelector->getSelector($child) if $pSelector;
589             }
590 0 0 0     0 if ( $source =~ /(.*)\((.*)\)/ && !$sourceSelector ) {
591 0         0 my $function = $1;
592 0         0 my $vars = $2;
593 0 0       0 @vars = $vars =~ /\;/ ? split(/\;/, $vars) : ( $vars );
594 0         0 $sourceSelector = $selector->getSelector($function, \@parents);
595             }
596 0 0       0 next if !$sourceSelector;
597 0 0       0 if ( $sourceSelector->isFunction ) {
598 0         0 $sourceSelector->copyFunction($selector, \@vars, \@parents);
599             } else {
600 0         0 $sourceSelector->copyTo($selector);
601             }
602             }
603             }
604             # other stuff
605 57 50       108 $selector->insertRule($buffer,"") if $buffer;
606             }
607 94 100       261 $selector = pop @parents if /\}/;
608 94         142 $buffer = "";
609             }
610             #
611             # selectors
612 1981 100       3978 if ( /\{/ ) {
613             # clearing some buffer data
614 37         351 $buffer =~ s/.$//g;
615 37         357 $buffer =~ s/^\s*|\s*$//g;
616             # insert the child
617 37 50       100 if ( $buffer ) {
618 37         61 push @parents, $selector;
619 37 50 66     166 if ( $buffer =~ /(.*)\s*\((\@*.*)\)/ and $buffer !~ /^.*[:].*\(/ ) {
620 0         0 my $function = $1;
621 0         0 my $vars = $2;
622 0         0 $function =~ s/^\s*|\s*$//g;
623 0         0 $selector = $selector->insertFunction($function, \@parents);
624 0 0       0 my @vars = $vars =~ /\,/ ? split(/\s*\,\s*/, $vars) : ( $vars );
625 0         0 for my $var ( @vars ) {
626 0 0       0 next if !$var;
627 0         0 my ($variable, $value) = split(/\s*\:\s*/, $var);
628 0         0 $variable =~ s/^\@//;
629 0         0 $selector->insertVariable($variable, $value);
630             }
631             } else {
632 37         186 $selector = $selector->insertChild($buffer, \@parents);
633             }
634             }
635 37         55 $buffer = "";
636             }
637             #
638             #
639             #
640 1981 100 100     11564 if ( /\"/ or /\'/ ) { $mode = "skip"; $stop = $_ };
  6         9  
  6         16  
641 1981 100       4042 if ( /\(/ ) { $mode = "skip"; $stop = ")" };
  35         38  
  35         49  
642 1981 50 66     17627 if ( $lastChar =~ /\// and /\// ) { $mode = "delete"; $stop = "\n"; $buffer =~ s/..$// }
  0         0  
  0         0  
  0         0  
643 1981 100 100     4243 if ( $lastChar =~ /\// and /\*/ ) { $mode = "delete"; $stop = "*/"; $buffer =~ s/..$// };
  1         2  
  1         2  
  1         5  
644 1981         2605 $lastChar = $_;
645             }
646 3         222 return $styles->dump();
647             }
648              
649             1;
650              
651             =head1 NAME
652              
653             CSS::LESSp - LESS for perl. Parse .less files and returns valid css (lesscss.org for more info about less files)
654              
655             =head1 SYNOPSIS
656              
657             use CSS::LESSp;
658              
659             my $buffer;
660             open(IN, "file.less");
661             for ( ) { $buffer .= $_ };
662             close(IN);
663            
664             my @css = CSS::LESSp->parse($buffer);
665              
666             print join("", @css);
667              
668             or you could simply use the lessp.pl tool in the package
669              
670             $ lessp.pl css.less > css.css
671              
672             =head1 DESCRIPTION
673              
674             This module is designed to parse and compile .less files in to .css files.
675              
676             About the documentation and syntax of less files please visit lesscss.org
677              
678             =head1 DIFFERENCE WITH THE ORIGINAL LESS FOR RUBY
679              
680             What is the benefits of LESS for perl ...
681              
682             It's extremely fast :
683            
684             # time ./lessp.pl big.less > big.css
685              
686             real 0m2.198s
687             user 0m2.174s
688             sys 0m0.020s
689            
690             # time lessc big.less big.css
691              
692             real 0m18.805s
693             user 0m18.437s
694             sys 0m0.184s
695            
696             =head1 METHODS
697              
698             =head3 parse
699              
700             Main parse method, returns array of the css file
701              
702             =head3 copyFunction
703              
704             =head3 copyTo
705              
706             =head3 dump
707             =head3 getSelector
708             =head3 getValue
709             =head3 getVariable
710             =head3 insertChild
711             =head3 insertFunction
712             =head3 insertRule
713             =head3 insertVariable
714             =head3 isFunction
715             =head3 new
716             =head3 process
717              
718             =head1 BUGS
719              
720             a ) You can not import other less files ...
721              
722             You can't do this
723              
724             @import url('/other.less')
725              
726             It might be added in future versions
727              
728             b ) You can not use hsl as a color
729              
730             You can't do this
731              
732             color: hsl(125,125,125);
733            
734             All other bugs should be reported via
735             L
736             or L.
737              
738             =head1 AUTHOR
739              
740             Ivan Drinchev
741              
742             =head1 CONTRIBUTORS
743              
744             People who've helped with this project :
745              
746             Michael Schout
747              
748             =head1 COPYRIGHT AND LICENSE
749              
750             Copyright (c) 2010.
751              
752             This library is free software; you can redistribute it and/or modify
753             it under the same terms as Perl itself.
754              
755             =cut