less 0.8.8 → 1.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +62 -0
- data/README.md +24 -6
- data/Rakefile +28 -37
- data/VERSION +1 -1
- data/bin/lessc +41 -11
- data/less.gemspec +85 -16
- data/lib/less/command.rb +47 -28
- data/lib/less/engine/grammar/common.tt +29 -0
- data/lib/less/engine/grammar/entity.tt +144 -0
- data/lib/less/engine/grammar/less.tt +341 -0
- data/lib/less/engine/nodes/element.rb +281 -0
- data/lib/less/engine/nodes/entity.rb +79 -0
- data/lib/less/engine/nodes/function.rb +93 -0
- data/lib/less/engine/nodes/literal.rb +171 -0
- data/lib/less/engine/nodes/property.rb +232 -0
- data/lib/less/engine/nodes/ruleset.rb +12 -0
- data/lib/less/engine/nodes/selector.rb +44 -0
- data/lib/less/engine/nodes.rb +9 -0
- data/lib/less/engine.rb +44 -140
- data/lib/less/ext.rb +60 -0
- data/lib/less.rb +25 -13
- data/spec/command_spec.rb +3 -7
- data/spec/css/accessors.css +18 -0
- data/spec/css/big.css +3768 -0
- data/spec/css/colors.css +14 -0
- data/spec/css/comments.css +9 -0
- data/spec/css/css-3.css +21 -0
- data/spec/css/css.css +50 -0
- data/spec/css/dash-prefix.css +12 -0
- data/spec/css/functions.css +6 -0
- data/spec/css/import-with-extra-paths.css +8 -0
- data/spec/css/import-with-partial-in-extra-path.css +6 -0
- data/spec/css/import.css +12 -0
- data/spec/css/lazy-eval.css +1 -0
- data/spec/css/mixins-args.css +32 -0
- data/spec/css/mixins.css +28 -0
- data/spec/css/operations.css +28 -0
- data/spec/css/parens.css +20 -0
- data/spec/css/rulesets.css +17 -0
- data/spec/css/scope.css +11 -0
- data/spec/css/selectors.css +13 -0
- data/spec/css/strings.css +12 -0
- data/spec/css/variables.css +8 -0
- data/spec/css/whitespace.css +7 -0
- data/spec/engine_spec.rb +107 -15
- data/spec/less/accessors.less +20 -0
- data/spec/less/big.less +1264 -0
- data/spec/less/colors.less +35 -0
- data/spec/less/comments.less +46 -0
- data/spec/less/css-3.less +52 -0
- data/spec/less/css.less +104 -0
- data/spec/less/dash-prefix.less +21 -0
- data/spec/less/exceptions/mixed-units-error.less +3 -0
- data/spec/less/exceptions/name-error-1.0.less +3 -0
- data/spec/less/exceptions/syntax-error-1.0.less +3 -0
- data/spec/less/extra_import_path/extra.less +1 -0
- data/spec/less/extra_import_path/import/import-test-a.css +1 -0
- data/spec/less/extra_import_path/import/import-test-a.less +4 -0
- data/spec/less/functions.less +6 -0
- data/spec/less/hidden.less +25 -0
- data/spec/less/import/import-test-a.less +2 -0
- data/spec/less/import/import-test-b.less +8 -0
- data/spec/less/import/import-test-c.less +7 -0
- data/spec/less/import/import-test-d.css +1 -0
- data/spec/less/import-with-extra-paths.less +4 -0
- data/spec/less/import.less +8 -0
- data/spec/less/lazy-eval.less +6 -0
- data/spec/less/literal-css.less +11 -0
- data/spec/less/mixins-args.less +59 -0
- data/spec/less/mixins.less +43 -0
- data/spec/less/operations.less +39 -0
- data/spec/less/parens.less +26 -0
- data/spec/less/rulesets.less +30 -0
- data/spec/less/scope.less +32 -0
- data/spec/less/selectors.less +24 -0
- data/spec/less/strings.less +14 -0
- data/spec/less/variables.less +29 -0
- data/spec/less/whitespace.less +34 -0
- data/spec/spec.css +50 -25
- data/spec/spec_helper.rb +4 -1
- metadata +96 -15
- data/lib/less/tree.rb +0 -82
- data/spec/css/less-0.8.5.css +0 -24
- data/spec/css/less-0.8.6.css +0 -24
- data/spec/css/less-0.8.7.css +0 -24
- data/spec/css/less-0.8.8.css +0 -25
- data/spec/spec.less +0 -128
- data/spec/tree_spec.rb +0 -5
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
module Less
|
|
2
|
+
module Node
|
|
3
|
+
class Property < String
|
|
4
|
+
include Entity
|
|
5
|
+
|
|
6
|
+
attr_accessor :value
|
|
7
|
+
|
|
8
|
+
def initialize key, value = nil, parent = nil
|
|
9
|
+
super key, parent
|
|
10
|
+
value = if value.is_a? Array
|
|
11
|
+
value.each {|v| v.parent = self if v.respond_to? :parent }.
|
|
12
|
+
map {|v| v.is_a?(Expression) ? v : Expression.new(v, self) }
|
|
13
|
+
elsif value.nil?
|
|
14
|
+
[]
|
|
15
|
+
else
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
@value = value.is_a?(Expression) ? value : Expression.new(value, self)
|
|
19
|
+
@value.parent = self
|
|
20
|
+
@value.delimiter = ','
|
|
21
|
+
# puts "new property #{to_s}: #{value} => #{@value}, contains: #{@value[0].class}"
|
|
22
|
+
# puts
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parent= obj
|
|
26
|
+
@parent = obj
|
|
27
|
+
value.parent = self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def copy
|
|
31
|
+
clone.tap {|c| c.value = value.copy }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def << token
|
|
35
|
+
token = Node::Anonymous.new(*token) unless token.is_a? Entity or token.respond_to? :to_ruby
|
|
36
|
+
token.parent = self if token.respond_to? :parent
|
|
37
|
+
@value << token
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def empty?; !@value || @value.empty? end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
self + (empty?? "" : ": `#{value.map {|i| i.to_s } * ' | '}`")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def == other
|
|
47
|
+
self.to_s == other.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def eql? other
|
|
51
|
+
self == other and value.eql? other.value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_s
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def nearest node
|
|
59
|
+
parent.nearest node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def evaluate env = nil
|
|
63
|
+
# puts "evaluating property `#{to_s}`: #{value.inspect}"
|
|
64
|
+
if value.is_a?(Expression) #Value
|
|
65
|
+
# puts "value is a Value"
|
|
66
|
+
value.map {|e| e.evaluate(env) } #6
|
|
67
|
+
else
|
|
68
|
+
# puts "value is a #{value.class}"
|
|
69
|
+
[value.evaluate(env)]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_css env = nil
|
|
76
|
+
# puts "property.to_css `#{to_s}` env:#{env ? env.variables : "nil"}"
|
|
77
|
+
val = evaluate(env)
|
|
78
|
+
"#{self}: #{if val.respond_to? :to_css
|
|
79
|
+
val.to_css
|
|
80
|
+
else
|
|
81
|
+
# p val
|
|
82
|
+
# puts "#{val.class} #{val.first.class}"
|
|
83
|
+
val.map {|i| i.to_css }.join(", ")
|
|
84
|
+
end};"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Variable < Property
|
|
89
|
+
attr_reader :declaration
|
|
90
|
+
|
|
91
|
+
def initialize key, value = nil, parent = nil
|
|
92
|
+
@declaration = value ? true : false
|
|
93
|
+
super key.delete('@'), value, parent
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def inspect
|
|
97
|
+
"@#{super}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def to_s
|
|
101
|
+
"@#{super}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def evaluate env = nil
|
|
105
|
+
if declaration
|
|
106
|
+
# puts "evaluating DEC"
|
|
107
|
+
value.evaluate #2
|
|
108
|
+
else
|
|
109
|
+
# puts "evaluating #{to_s} par: #{parent} env: #{env ? env.variables : "nil"}"
|
|
110
|
+
begin
|
|
111
|
+
var = (env || self.parent).nearest(to_s) #3
|
|
112
|
+
rescue VariableNameError
|
|
113
|
+
var = self.parent.nearest(to_s)
|
|
114
|
+
end
|
|
115
|
+
var.evaluate
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def to_ruby
|
|
120
|
+
evaluate.to_ruby
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def to_css env = nil
|
|
124
|
+
val = evaluate env
|
|
125
|
+
if val.respond_to? :to_css
|
|
126
|
+
env ? val.to_css(env) : val.to_css
|
|
127
|
+
else
|
|
128
|
+
val.map {|i| env ? i.to_css(env) : i.to_css }.join ', '
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class Expression < Array
|
|
134
|
+
attr_accessor :parent, :delimiter
|
|
135
|
+
|
|
136
|
+
def initialize ary, parent = nil, delimiter = ' '
|
|
137
|
+
self.parent = parent
|
|
138
|
+
self.delimiter = delimiter
|
|
139
|
+
# puts "new expression #{ary} |#{delimiter}|"
|
|
140
|
+
super(ary.is_a?(Array) ? ary : [ary].flatten)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def expressions; select {|i| i.kind_of? Expression } end
|
|
144
|
+
def variables; select {|i| i.kind_of? Variable } end
|
|
145
|
+
def operators; select {|i| i.is_a? Operator } end
|
|
146
|
+
def entities; select {|i| i.kind_of? Entity } end
|
|
147
|
+
def literals; select {|i| i.kind_of? Literal } end
|
|
148
|
+
|
|
149
|
+
def parent= obj
|
|
150
|
+
@parent = obj
|
|
151
|
+
each {|e| e.parent = obj if e.respond_to? :parent }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def inspect
|
|
155
|
+
'[' + map {|i| i.inspect }.join(', ') + ']'
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def delimiter= d
|
|
159
|
+
@delimiter = d.strip + ' '
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def flatten
|
|
163
|
+
self
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def terminal?
|
|
167
|
+
expressions.empty? #&& variables.empty?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def to_css env = nil
|
|
171
|
+
# puts "TOCSS, delim: |#{@delimiter}|"
|
|
172
|
+
map do |i|
|
|
173
|
+
i.parent = self.parent
|
|
174
|
+
i.respond_to?(:to_css) ? i.to_css() : i.to_s
|
|
175
|
+
end * @delimiter
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def to_ruby
|
|
179
|
+
map do |i|
|
|
180
|
+
i.respond_to?(:to_ruby) ? i.to_ruby : i.to_s
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
#
|
|
185
|
+
# Evaluates the expression and instantiates a new Literal with the result
|
|
186
|
+
# ex: [#111, +, #111] will evaluate to a Color node, with value #222
|
|
187
|
+
#
|
|
188
|
+
def evaluate env = nil
|
|
189
|
+
# puts "expression #{self.inspect} env: #{env ? env.variables : "nil"}"
|
|
190
|
+
if size > 2 or !terminal?
|
|
191
|
+
# puts " SIZE > 2 or !terminal"
|
|
192
|
+
|
|
193
|
+
# puts "--- sub evaluation ---"
|
|
194
|
+
|
|
195
|
+
# Replace self with an evaluated sub-expression
|
|
196
|
+
evaled = self.class.new(map {|e| e.respond_to?(:evaluate) ? e.evaluate(env) : e }, parent, delimiter) #5
|
|
197
|
+
|
|
198
|
+
# puts "======================"
|
|
199
|
+
# puts "evaled => #{evaled.inspect}"
|
|
200
|
+
|
|
201
|
+
unit = evaled.literals.map do |node|
|
|
202
|
+
node.unit
|
|
203
|
+
end.compact.uniq.tap do |ary|
|
|
204
|
+
raise MixedUnitsError, evaled * ' ' if ary.size > 1 && !evaled.operators.empty?
|
|
205
|
+
end.join
|
|
206
|
+
|
|
207
|
+
entity = evaled.literals.find {|e| e.unit == unit } || evaled.literals.first || evaled.entities.first
|
|
208
|
+
result = evaled.operators.empty?? evaled : eval(evaled.to_ruby.join)
|
|
209
|
+
|
|
210
|
+
# puts "entity is a #{entity.class}"
|
|
211
|
+
# puts "delimiter is |#{@delimiter}|"
|
|
212
|
+
|
|
213
|
+
case result
|
|
214
|
+
when Entity then result
|
|
215
|
+
when Expression then result.one?? result.first : self.class.new(result, parent, delimiter)
|
|
216
|
+
else entity.class.new(result, *(unit if entity.class == Node::Number))
|
|
217
|
+
end
|
|
218
|
+
elsif size == 1
|
|
219
|
+
if first.is_a? Variable
|
|
220
|
+
first.evaluate(env)
|
|
221
|
+
elsif first.is_a? Function
|
|
222
|
+
first.evaluate(env)
|
|
223
|
+
else
|
|
224
|
+
first
|
|
225
|
+
end
|
|
226
|
+
else
|
|
227
|
+
self
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Less
|
|
2
|
+
module Node
|
|
3
|
+
class Selector < String
|
|
4
|
+
include Entity
|
|
5
|
+
|
|
6
|
+
Selectors = {
|
|
7
|
+
:Descendant => '',
|
|
8
|
+
:Child => '>',
|
|
9
|
+
:Adjacent => '+',
|
|
10
|
+
:PseudoClass => ':',
|
|
11
|
+
:PseudoElement => '::',
|
|
12
|
+
:Sibling => '~'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
super Selectors[ self.class.to_s.split('::').last.to_sym ]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.[] key
|
|
20
|
+
Node.const_get(Selectors.find {|k, v| v == key }.first)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Descendant < Selector
|
|
25
|
+
def to_css; " " end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Child < Selector
|
|
29
|
+
def to_css; " #{self} " end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Adjacent < Selector
|
|
33
|
+
def to_css; " #{self} " end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class PseudoClass < Selector
|
|
37
|
+
def to_css; self end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class PseudoElement < Selector
|
|
41
|
+
def to_css; self end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
|
2
|
+
|
|
3
|
+
require 'engine/nodes/entity'
|
|
4
|
+
require 'engine/nodes/function'
|
|
5
|
+
require 'engine/nodes/ruleset'
|
|
6
|
+
require 'engine/nodes/element'
|
|
7
|
+
require 'engine/nodes/property'
|
|
8
|
+
require 'engine/nodes/literal'
|
|
9
|
+
require 'engine/nodes/selector'
|
data/lib/less/engine.rb
CHANGED
|
@@ -1,148 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
class Engine < String
|
|
3
|
-
# Compound properties, such as `border: 1px solid black`
|
|
4
|
-
COMPOUND = ['font', 'background', 'border']
|
|
5
|
-
REGEX = {
|
|
6
|
-
:path => /([#.][->#.\w ]+)?( ?> ?)?/, # #header > .title
|
|
7
|
-
:selector => /[-\w #.,>*:\(\)]/, # .cow .milk > a
|
|
8
|
-
:variable => /@([-\w]+)/, # @milk-white
|
|
9
|
-
:property => /@[-\w]+|[-a-z]+/, # font-size
|
|
10
|
-
:color => /#([a-zA-Z0-9]{3,6})\b/, # #f0f0f0
|
|
11
|
-
:number => /\d+(?>\.\d+)?/, # 24.8
|
|
12
|
-
:unit => /px|em|pt|cm|mm|%/ # em
|
|
13
|
-
}
|
|
14
|
-
REGEX[:numeric] = /#{REGEX[:number]}(#{REGEX[:unit]})?/
|
|
15
|
-
REGEX[:operand] = /#{REGEX[:color]}|#{REGEX[:numeric]}/
|
|
16
|
-
|
|
17
|
-
def initialize s
|
|
18
|
-
super
|
|
19
|
-
@tree = Tree.new self.hashify
|
|
20
|
-
end
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
|
21
2
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
# Evaluate mixins
|
|
45
|
-
#
|
|
46
|
-
@tree = @tree.traverse :branch do |path, node|
|
|
47
|
-
if node.include? :mixins
|
|
48
|
-
node[:mixins].each do |m|
|
|
49
|
-
@tree.find( :mixin, m.delete(' ').split('>') ).each {|k, v| node[ k ] = v }
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Call `evaluate` on variables, such as '@dark: @light / 2'
|
|
55
|
-
@tree = @tree.traverse :branch do |path, node|
|
|
56
|
-
node.vars.each do |key, value|
|
|
57
|
-
evaluate key, value, path, node.vars
|
|
58
|
-
end if node.vars?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Call `evaluate` on css properties, such as 'font-size: @big'
|
|
62
|
-
@tree = @tree.traverse :leaf do |key, value, path, node|
|
|
63
|
-
evaluate key, value, path, node
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
# Evaluate operations (2+2)
|
|
68
|
-
#
|
|
69
|
-
# Units are: 1px, 1em, 1%, #111
|
|
70
|
-
@tree = @tree.traverse :leaf do |key, value, path, node|
|
|
71
|
-
node[ key ] = value.gsub /(#{REGEX[:operand]}(\s?)[-+\/*](\4))+(#{REGEX[:operand]})/ do |operation|
|
|
72
|
-
raise CompoundOperationError, "#{key}: #{value}" if COMPOUND.include? key # Disallow operations on compound declarations
|
|
73
|
-
if (unit = operation.scan(/#{REGEX[:numeric]}|(#)/i).flatten.compact.uniq).size <= 1
|
|
74
|
-
unit = unit.join
|
|
75
|
-
operation = if unit == '#'
|
|
76
|
-
evaluate = lambda do |v|
|
|
77
|
-
result = eval v
|
|
78
|
-
unit + ( result.zero?? '000' : result.to_s(16) )
|
|
79
|
-
end
|
|
80
|
-
operation.gsub REGEX[:color] do
|
|
81
|
-
hex = $1 * ( $1.size < 6 ? 6 / $1.size : 1 )
|
|
82
|
-
hex.to_i(16)
|
|
83
|
-
end.delete unit
|
|
84
|
-
else
|
|
85
|
-
evaluate = lambda {|v| eval( v ).to_s + unit }
|
|
86
|
-
operation.gsub REGEX[:unit], ''
|
|
87
|
-
end.to_s
|
|
88
|
-
next if operation.match /[a-z]/i
|
|
89
|
-
evaluate.call operation
|
|
90
|
-
else
|
|
91
|
-
raise MixedUnitsError, value
|
|
92
|
-
end
|
|
93
|
-
end
|
|
3
|
+
require 'engine/nodes'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'engine/parser'
|
|
7
|
+
rescue LoadError
|
|
8
|
+
Treetop.load File.join(LESS_GRAMMAR, 'less.tt')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module Less
|
|
12
|
+
class Engine
|
|
13
|
+
attr_reader :css, :less
|
|
14
|
+
|
|
15
|
+
def initialize obj, options = {}
|
|
16
|
+
@less = if obj.is_a? File
|
|
17
|
+
@path = File.dirname File.expand_path(obj.path)
|
|
18
|
+
obj.read
|
|
19
|
+
elsif obj.is_a? String
|
|
20
|
+
obj.dup
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, "argument must be an instance of File or String!"
|
|
94
23
|
end
|
|
24
|
+
|
|
25
|
+
@options = options
|
|
26
|
+
@parser = StyleSheetParser.new
|
|
95
27
|
end
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
value = if var.include? '>'
|
|
108
|
-
@tree.find :var, var.split('>') # Try finding it in a specific namespace
|
|
109
|
-
else
|
|
110
|
-
node.var(var) or @tree.nearest var, path # Try local first, then nearest scope
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
if value
|
|
114
|
-
# Substitute variable with value
|
|
115
|
-
node[ key ] = node[ key ].gsub /#{REGEX[:path]}@#{name}/, value
|
|
116
|
-
else
|
|
117
|
-
node.delete key # Discard the declaration if the variable wasn't found
|
|
118
|
-
end
|
|
119
|
-
end
|
|
28
|
+
|
|
29
|
+
def parse build = true, env = Node::Element.new
|
|
30
|
+
root = @parser.parse(self.prepare)
|
|
31
|
+
|
|
32
|
+
return root unless build
|
|
33
|
+
|
|
34
|
+
if root
|
|
35
|
+
@tree = root.build env.tap {|e| e.file = @path }
|
|
36
|
+
else
|
|
37
|
+
raise SyntaxError, @parser.failure_message(@options[:color])
|
|
120
38
|
end
|
|
39
|
+
|
|
40
|
+
@tree
|
|
121
41
|
end
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
42
|
+
alias :to_tree :parse
|
|
43
|
+
|
|
44
|
+
def to_css
|
|
45
|
+
@css || @css = self.parse.group.to_css
|
|
125
46
|
end
|
|
126
|
-
|
|
127
|
-
def
|
|
128
|
-
|
|
129
|
-
# Parse the LESS structure into a hash
|
|
130
|
-
#
|
|
131
|
-
###
|
|
132
|
-
# less: color: black;
|
|
133
|
-
# hashify: "color" => "black"
|
|
134
|
-
#
|
|
135
|
-
hash = self.gsub(/\t+/, ' '). # Tabs
|
|
136
|
-
gsub(/\r\n/, "\n"). # m$
|
|
137
|
-
gsub(/\/\/.*/, ''). # Comments //
|
|
138
|
-
gsub(/\/\*.*?\*\//m, ''). # Comments /*
|
|
139
|
-
gsub(/"/, "'"). # " => '
|
|
140
|
-
gsub(/("|')(.+?)(\1)/) { $1 + CGI.escape( $2 ) + $1 }. # Escape string values
|
|
141
|
-
gsub(/(#{REGEX[:property]}):\s*(.+?)\s*(;|(?=\}))/,'"\1"=>"\2",'). # Rules
|
|
142
|
-
gsub(/\}/, "},"). # Closing }
|
|
143
|
-
gsub(/( *)(#{REGEX[:selector]}+?)[ \n]*(?=\{)/m, '\1"\2"=>'). # Selectors
|
|
144
|
-
gsub(/([.#][->\w .#]+);/, '"\\1" => :mixin,') # Mixins
|
|
145
|
-
eval "{" + hash + "}" # Return {hash}
|
|
47
|
+
|
|
48
|
+
def prepare
|
|
49
|
+
@less.gsub(/\r\n/, "\n").gsub(/\t/, ' ')
|
|
146
50
|
end
|
|
147
51
|
end
|
|
148
|
-
end
|
|
52
|
+
end
|
data/lib/less/ext.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Treetop
|
|
2
|
+
module Runtime
|
|
3
|
+
class CompiledParser
|
|
4
|
+
def failure_message color
|
|
5
|
+
o = color ? Mutter.new.clear : lambda {|i, *args| i }
|
|
6
|
+
return nil unless (tf = terminal_failures) && tf.size > 0
|
|
7
|
+
msg = "on line #{failure_line}: expected " + (
|
|
8
|
+
tf.size == 1 ?
|
|
9
|
+
o[tf[0].expected_string, :yellow] :
|
|
10
|
+
"one of #{o[tf.map {|f| f.expected_string }.uniq * ' ', :yellow]}"
|
|
11
|
+
)
|
|
12
|
+
f = input[failure_index]
|
|
13
|
+
got = case f
|
|
14
|
+
when "\n" then o['\n', :cyan]
|
|
15
|
+
when nil then o["EOF", :cyan]
|
|
16
|
+
when ' ' then o["white-space", :cyan]
|
|
17
|
+
else o[f.chr, :yellow]
|
|
18
|
+
end
|
|
19
|
+
msg += " got #{got} after:\n\n#{input[index...failure_index]}\n"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Object
|
|
26
|
+
def verbose
|
|
27
|
+
$verbose = true
|
|
28
|
+
yield
|
|
29
|
+
ensure
|
|
30
|
+
$verbose = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def tap
|
|
34
|
+
yield self
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Array
|
|
40
|
+
def dissolve
|
|
41
|
+
ary = flatten.compact
|
|
42
|
+
case ary.size
|
|
43
|
+
when 0 then []
|
|
44
|
+
when 1 then first
|
|
45
|
+
else ary
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def one?
|
|
50
|
+
size == 1
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
unless :symbol.respond_to?(:to_proc)
|
|
55
|
+
class Symbol
|
|
56
|
+
def to_proc
|
|
57
|
+
Proc.new {|*args| args.shift.__send__(self, *args) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/less.rb
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
|
2
|
-
|
|
3
1
|
require 'cgi'
|
|
2
|
+
require 'treetop'
|
|
3
|
+
require 'mutter'
|
|
4
|
+
require 'delegate'
|
|
5
|
+
|
|
6
|
+
LESS_ROOT = File.expand_path(File.dirname(__FILE__))
|
|
7
|
+
LESS_PARSER = File.join(LESS_ROOT, 'less', 'engine', 'parser.rb')
|
|
8
|
+
LESS_GRAMMAR = File.join(LESS_ROOT, 'less', 'engine', 'grammar')
|
|
4
9
|
|
|
10
|
+
$LESS_LOAD_PATH = []
|
|
11
|
+
|
|
12
|
+
$:.unshift File.dirname(__FILE__)
|
|
13
|
+
|
|
14
|
+
require 'less/ext'
|
|
5
15
|
require 'less/command'
|
|
6
16
|
require 'less/engine'
|
|
7
|
-
require 'less/tree'
|
|
8
17
|
|
|
9
18
|
module Less
|
|
10
|
-
MixedUnitsError
|
|
11
|
-
PathError
|
|
12
|
-
|
|
19
|
+
MixedUnitsError = Class.new(RuntimeError)
|
|
20
|
+
PathError = Class.new(RuntimeError)
|
|
21
|
+
VariableNameError = Class.new(NameError)
|
|
22
|
+
MixinNameError = Class.new(NameError)
|
|
23
|
+
SyntaxError = Class.new(RuntimeError)
|
|
24
|
+
ImportError = Class.new(RuntimeError)
|
|
25
|
+
CompileError = Class.new(RuntimeError)
|
|
26
|
+
|
|
27
|
+
$verbose = false
|
|
13
28
|
|
|
14
29
|
def self.version
|
|
15
|
-
File.read( File.join( File.dirname(__FILE__), '..', 'VERSION') )
|
|
30
|
+
File.read( File.join( File.dirname(__FILE__), '..', 'VERSION') ).strip
|
|
16
31
|
end
|
|
17
|
-
end
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def to_tree
|
|
22
|
-
Less::Tree.new self
|
|
33
|
+
def self.parse less
|
|
34
|
+
Engine.new(less).to_css
|
|
23
35
|
end
|
|
24
|
-
end
|
|
36
|
+
end
|
data/spec/command_spec.rb
CHANGED
|
@@ -2,11 +2,11 @@ require File.dirname(__FILE__) + '/spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module LessCommandSpecHelper
|
|
4
4
|
def required_options
|
|
5
|
-
{:source => File.dirname(__FILE__) + '/
|
|
5
|
+
{:source => File.dirname(__FILE__) + '/less/css.less', :destination => File.dirname(__FILE__) + '/spec.css' }
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def valid_options
|
|
9
|
-
{:destination => File.dirname(__FILE__) + '/spec.css', :watch => true, :
|
|
9
|
+
{:destination => File.dirname(__FILE__) + '/spec.css', :watch => true, :debug => false}.merge(required_options)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -48,10 +48,6 @@ describe Less::Command do
|
|
|
48
48
|
@command.options[:watch].should == valid_options[:watch]
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
it "should set the chain" do
|
|
52
|
-
@command.options[:chain].should == valid_options[:chain]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
51
|
it "should set the debug" do
|
|
56
52
|
@command.options[:debug].should == valid_options[:debug]
|
|
57
53
|
end
|
|
@@ -80,7 +76,7 @@ describe Less::Command do
|
|
|
80
76
|
end
|
|
81
77
|
|
|
82
78
|
it "should attempt to re-compile" do
|
|
83
|
-
@command.should_receive(:
|
|
79
|
+
@command.should_receive(:parse).with().once
|
|
84
80
|
end
|
|
85
81
|
end
|
|
86
82
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.magic-box {
|
|
2
|
+
content: "gold";
|
|
3
|
+
width: 60cm;
|
|
4
|
+
height: 40cm;
|
|
5
|
+
}
|
|
6
|
+
.magic-box #lock { color: silver; }
|
|
7
|
+
#accessors {
|
|
8
|
+
content: "gold";
|
|
9
|
+
width: 60cm;
|
|
10
|
+
}
|
|
11
|
+
.unlock {
|
|
12
|
+
content: "gold";
|
|
13
|
+
width: 60cm;
|
|
14
|
+
height: 40cm;
|
|
15
|
+
color: silver;
|
|
16
|
+
border-color: orange;
|
|
17
|
+
}
|
|
18
|
+
.unlock #lock { color: silver; }
|