lesslateral 1.2.21
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG +62 -0
- data/LICENSE +179 -0
- data/README.md +48 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/bin/lessc +103 -0
- data/less.gemspec +134 -0
- data/lib/less.rb +36 -0
- data/lib/less/command.rb +108 -0
- data/lib/less/engine.rb +52 -0
- 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.rb +9 -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/ext.rb +60 -0
- data/lib/less/notification.rb +59 -0
- data/spec/command_spec.rb +102 -0
- 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 +127 -0
- 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-with-extra-paths.less +4 -0
- data/spec/less/import.less +8 -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/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 -0
- data/spec/spec_helper.rb +8 -0
- metadata +159 -0
@@ -0,0 +1,341 @@
|
|
1
|
+
require 'grammar/common'
|
2
|
+
require 'grammar/entity'
|
3
|
+
|
4
|
+
module Less
|
5
|
+
grammar StyleSheet
|
6
|
+
include Common
|
7
|
+
include Entity
|
8
|
+
|
9
|
+
rule primary
|
10
|
+
(import / declaration / ruleset / mixin / comment)* {
|
11
|
+
def build env = Less::Element.new
|
12
|
+
elements.map do |e|
|
13
|
+
e.build env if e.respond_to? :build
|
14
|
+
end; env
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
rule comment
|
20
|
+
ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# div, .class, body > p {...}
|
25
|
+
#
|
26
|
+
rule ruleset
|
27
|
+
selectors "{" ws primary ws "}" s hide:(';'?) ws {
|
28
|
+
def build env
|
29
|
+
# Build the ruleset for each selector
|
30
|
+
selectors.build(env, :ruleset).each do |sel|
|
31
|
+
sel.hide unless hide.empty?
|
32
|
+
primary.build sel
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# Mixin Declaration
|
36
|
+
} / ws '.' name:[-a-zA-Z0-9_]+ ws parameters ws "{" ws primary ws "}" ws {
|
37
|
+
def build env
|
38
|
+
env << Node::Mixin::Def.new(name.text_value, parameters.build(env))
|
39
|
+
primary.build env.last
|
40
|
+
end
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
rule mixin
|
45
|
+
name:('.' [-a-zA-Z0-9_]+) args:(arguments) s ';' ws {
|
46
|
+
def build env
|
47
|
+
definition = env.nearest(name.text_value, :mixin) or raise MixinNameError, "#{name.text_value}() in #{env}"
|
48
|
+
params = args.build.map {|i| Node::Expression.new i } unless args.empty?
|
49
|
+
env << Node::Mixin::Call.new(definition, params || [], env)
|
50
|
+
end
|
51
|
+
} / ws selectors ';' ws {
|
52
|
+
def build env
|
53
|
+
selectors.build(env, :mixin).each do |path|
|
54
|
+
rules = path.inject(env.root) do |current, node|
|
55
|
+
current.descend(node.selector, node) or raise MixinNameError, "#{selectors.text_value} in #{env}"
|
56
|
+
end.rules
|
57
|
+
env.rules += rules
|
58
|
+
end
|
59
|
+
end
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
rule selectors
|
64
|
+
ws selector tail:(s ',' ws selector)* ws {
|
65
|
+
def build env, method
|
66
|
+
all.map do |e|
|
67
|
+
e.send(method, env) if e.respond_to? method
|
68
|
+
end.compact
|
69
|
+
end
|
70
|
+
|
71
|
+
def all
|
72
|
+
[selector] + tail.elements.map {|e| e.selector }
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# div > p a {...}
|
79
|
+
#
|
80
|
+
rule selector
|
81
|
+
sel:(s select element s)+ '' {
|
82
|
+
def ruleset env
|
83
|
+
sel.elements.inject(env) do |node, e|
|
84
|
+
node << Node::Element.new(e.element.text_value, e.select.text_value)
|
85
|
+
node.last
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def mixin env
|
90
|
+
sel.elements.map do |e|
|
91
|
+
Node::Element.new(e.element.text_value, e.select.text_value)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
rule parameters
|
98
|
+
'(' s ')' {
|
99
|
+
def build env
|
100
|
+
[]
|
101
|
+
end
|
102
|
+
} / '(' parameter tail:(s ',' s parameter)* ')' {
|
103
|
+
def build env
|
104
|
+
all.map do |e|
|
105
|
+
e.build(env)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def all
|
110
|
+
[parameter] + tail.elements.map {|e| e.parameter }
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
rule parameter
|
116
|
+
variable s ':' s expressions {
|
117
|
+
def build env
|
118
|
+
Node::Variable.new(variable.text_value, expressions.build(env), env)
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
rule import
|
124
|
+
ws "@import" S url:(string / url) medias? s ';' ws {
|
125
|
+
def build env
|
126
|
+
standard_path = File.join(env.root.file || Dir.pwd, url.value)
|
127
|
+
|
128
|
+
# Compile a list of possible paths for this file
|
129
|
+
paths = $LESS_LOAD_PATH.map { |p| File.join(p, url.value) } + [standard_path]
|
130
|
+
# Standardize and make uniq
|
131
|
+
paths = paths.map do |p|
|
132
|
+
p = File.expand_path(p)
|
133
|
+
p += '.less' unless p =~ /\.(le|c)ss$/
|
134
|
+
p
|
135
|
+
end.uniq
|
136
|
+
|
137
|
+
# Use the first that exists if any
|
138
|
+
if path = paths.detect {|p| File.exists?(p)}
|
139
|
+
unless env.root.imported.include?(path)
|
140
|
+
env.root.imported << path
|
141
|
+
env.rules += Less::Engine.new(File.new(path)).to_tree.rules
|
142
|
+
end
|
143
|
+
else
|
144
|
+
raise ImportError, standard_path
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
rule url
|
152
|
+
'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
|
153
|
+
def build env = nil
|
154
|
+
Node::Function.new('url', value)
|
155
|
+
end
|
156
|
+
|
157
|
+
def value
|
158
|
+
Node::Quoted.new CGI.unescape(path.text_value)
|
159
|
+
end
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
rule medias
|
164
|
+
[-a-z]+ (s ',' s [a-z]+)*
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# @my-var: 12px;
|
169
|
+
# height: 100%;
|
170
|
+
#
|
171
|
+
rule declaration
|
172
|
+
ws name:(ident / variable) s ':' ws expressions tail:(ws ',' ws expressions)* s (';'/ ws &'}') ws {
|
173
|
+
def build env
|
174
|
+
result = all.map {|e| e.build(env) if e.respond_to? :build }.compact
|
175
|
+
env << (name.text_value =~ /^@/ ?
|
176
|
+
Node::Variable : Node::Property).new(name.text_value, result, env)
|
177
|
+
end
|
178
|
+
|
179
|
+
def all
|
180
|
+
[expressions] + tail.elements.map {|f| f.expressions }
|
181
|
+
end
|
182
|
+
# Empty rule
|
183
|
+
} / ws ident s ':' s ';' ws
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# An operation or compound value
|
188
|
+
#
|
189
|
+
rule expressions
|
190
|
+
# Operation
|
191
|
+
expression tail:(operator expression)+ {
|
192
|
+
def build env = nil
|
193
|
+
all.map {|e| e.build(env) }.dissolve
|
194
|
+
end
|
195
|
+
|
196
|
+
def all
|
197
|
+
[expression] + tail.elements.map {|i| [i.operator, i.expression] }.flatten.compact
|
198
|
+
end
|
199
|
+
# Space-delimited expressions
|
200
|
+
} / expression tail:(WS expression)* i:important? {
|
201
|
+
def build env = nil
|
202
|
+
all.map {|e| e.build(env) if e.respond_to? :build }.compact
|
203
|
+
end
|
204
|
+
|
205
|
+
def all
|
206
|
+
[expression] + tail.elements.map {|f| f.expression } + [i]
|
207
|
+
end
|
208
|
+
# Catch-all rule
|
209
|
+
} / [-a-zA-Z0-9_.&*/=:,+? \[\]()#%]+ {
|
210
|
+
def build env
|
211
|
+
[Node::Anonymous.new(text_value)]
|
212
|
+
end
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
rule expression
|
217
|
+
'(' s expressions s ')' {
|
218
|
+
def build env = nil
|
219
|
+
Node::Expression.new(['('] + expressions.build(env).flatten + [')'])
|
220
|
+
end
|
221
|
+
} / entity '' {
|
222
|
+
def build env = nil
|
223
|
+
e = entity.method(:build).arity.zero?? entity.build : entity.build(env)
|
224
|
+
e.respond_to?(:dissolve) ? e.dissolve : e
|
225
|
+
end
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
# !important
|
230
|
+
rule important
|
231
|
+
s '!' s 'important' {
|
232
|
+
def build env = nil
|
233
|
+
Node::Keyword.new(text_value.strip)
|
234
|
+
end
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
#
|
239
|
+
# An identifier
|
240
|
+
#
|
241
|
+
rule ident
|
242
|
+
'*'? '-'? [-a-z_] [-a-z0-9_]*
|
243
|
+
end
|
244
|
+
|
245
|
+
rule variable
|
246
|
+
'@' [-a-zA-Z0-9_]+ {
|
247
|
+
def build
|
248
|
+
Node::Variable.new(text_value)
|
249
|
+
end
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# div / .class / #id / input[type="text"] / lang(fr)
|
255
|
+
#
|
256
|
+
rule element
|
257
|
+
((class / id / tag / ident) attribute* ('(' [a-zA-Z]+ ')' / '(' (pseudo_exp / selector / [0-9]+) ')' )?)+
|
258
|
+
/ attribute+ / '@media' / '@font-face'
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# 4n+1
|
263
|
+
#
|
264
|
+
rule pseudo_exp
|
265
|
+
'-'? ([0-9]+)? 'n' ([-+] [0-9]+)?
|
266
|
+
end
|
267
|
+
|
268
|
+
#
|
269
|
+
# [type="text"]
|
270
|
+
#
|
271
|
+
rule attribute
|
272
|
+
'[' tag ([|~*$^]? '=') (string / [-a-zA-Z_0-9]+) ']' / '[' (tag / string) ']'
|
273
|
+
end
|
274
|
+
|
275
|
+
rule class
|
276
|
+
'.' [_a-zA-Z] [-a-zA-Z0-9_]*
|
277
|
+
end
|
278
|
+
|
279
|
+
rule id
|
280
|
+
'#' [_a-zA-Z] [-a-zA-Z0-9_]*
|
281
|
+
end
|
282
|
+
|
283
|
+
rule tag
|
284
|
+
[a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
|
285
|
+
end
|
286
|
+
|
287
|
+
rule select
|
288
|
+
(s [+>~] s / '::' / s ':' / S)?
|
289
|
+
end
|
290
|
+
|
291
|
+
# TODO: Merge this with attribute rule
|
292
|
+
rule accessor
|
293
|
+
ident:(class / id / tag) '[' attr:(string / variable) ']' {
|
294
|
+
def build env
|
295
|
+
env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
|
296
|
+
end
|
297
|
+
}
|
298
|
+
end
|
299
|
+
|
300
|
+
rule operator
|
301
|
+
S [-+*/] S {
|
302
|
+
def build env
|
303
|
+
Node::Operator.new(text_value.strip)
|
304
|
+
end
|
305
|
+
} / [-+*/] {
|
306
|
+
def build env
|
307
|
+
Node::Operator.new(text_value)
|
308
|
+
end
|
309
|
+
}
|
310
|
+
end
|
311
|
+
|
312
|
+
#
|
313
|
+
# Functions and arguments
|
314
|
+
#
|
315
|
+
rule function
|
316
|
+
name:([-a-zA-Z_]+) arguments {
|
317
|
+
def build
|
318
|
+
Node::Function.new(name.text_value, arguments.build)
|
319
|
+
end
|
320
|
+
}
|
321
|
+
end
|
322
|
+
|
323
|
+
rule arguments
|
324
|
+
'(' s expressions s tail:(',' s expressions s)* ')' {
|
325
|
+
def build
|
326
|
+
all.map do |e|
|
327
|
+
e.build if e.respond_to? :build
|
328
|
+
end.compact
|
329
|
+
end
|
330
|
+
|
331
|
+
def all
|
332
|
+
[expressions] + tail.elements.map {|e| e.expressions }
|
333
|
+
end
|
334
|
+
} / '(' s ')' {
|
335
|
+
def build
|
336
|
+
[]
|
337
|
+
end
|
338
|
+
}
|
339
|
+
end
|
340
|
+
end
|
341
|
+
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'
|
@@ -0,0 +1,281 @@
|
|
1
|
+
module Less
|
2
|
+
module Node
|
3
|
+
#
|
4
|
+
# Element
|
5
|
+
#
|
6
|
+
# div {...}
|
7
|
+
#
|
8
|
+
# TODO: Look into making @rules its own hash-like class
|
9
|
+
# TODO: Look into whether selector should be child by default
|
10
|
+
#
|
11
|
+
class Element
|
12
|
+
include Enumerable
|
13
|
+
include Entity
|
14
|
+
|
15
|
+
attr_accessor :rules, :selector, :file,
|
16
|
+
:set, :imported, :name
|
17
|
+
|
18
|
+
def initialize name = "", selector = ''
|
19
|
+
@name = name
|
20
|
+
@set, @imported = [], []
|
21
|
+
@rules = [] # Holds all the nodes under this element's hierarchy
|
22
|
+
@selector = Selector[selector.strip].new # descendant | child | adjacent
|
23
|
+
end
|
24
|
+
|
25
|
+
def class?; name =~ /^\./ end
|
26
|
+
def id?; name =~ /^#/ end
|
27
|
+
def universal?; name == '*' end
|
28
|
+
|
29
|
+
def tag?
|
30
|
+
not id? || class? || universal?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Top-most node?
|
34
|
+
def root?
|
35
|
+
parent.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
@rules.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def leaf?
|
43
|
+
elements.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Group similar rulesets together
|
47
|
+
# This is horrible, horrible code,
|
48
|
+
# but it'll have to do until I find
|
49
|
+
# a proper way to do it.
|
50
|
+
def group
|
51
|
+
elements = self.elements.reject {|e| e.is_a?(Mixin::Def) }
|
52
|
+
return self unless elements.size > 1
|
53
|
+
|
54
|
+
stack, result, matched = elements.dup, [], false
|
55
|
+
|
56
|
+
elements.each do
|
57
|
+
e = stack.first
|
58
|
+
result << e unless matched
|
59
|
+
|
60
|
+
matched = stack[1..-1].each do |ee|
|
61
|
+
if e.equiv? ee and e.elements.size == 0
|
62
|
+
self[e].set << ee
|
63
|
+
stack.shift
|
64
|
+
else
|
65
|
+
stack.shift
|
66
|
+
break false
|
67
|
+
end
|
68
|
+
end if stack.size > 1
|
69
|
+
end
|
70
|
+
@rules -= (elements - result)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Accessors for the different nodes in @rules
|
76
|
+
#
|
77
|
+
def identifiers; @rules.select {|r| r.kind_of? Property } end
|
78
|
+
def properties; @rules.select {|r| r.instance_of? Property } end
|
79
|
+
def variables; @rules.select {|r| r.instance_of? Variable } end
|
80
|
+
def elements; @rules.select {|r| r.kind_of? Element } end
|
81
|
+
def mixins; @rules.select {|r| r.instance_of? Mixin::Call} end
|
82
|
+
def parameters; [] end
|
83
|
+
|
84
|
+
# Select a child element
|
85
|
+
# TODO: Implement full selector syntax & merge with descend()
|
86
|
+
def [] key
|
87
|
+
case key
|
88
|
+
when Entity
|
89
|
+
@rules.find {|i| i.eql? key }
|
90
|
+
when String
|
91
|
+
@rules.find {|i| i.to_s == key }
|
92
|
+
else raise ArgumentError
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def == other
|
97
|
+
name == other.name
|
98
|
+
end
|
99
|
+
|
100
|
+
def eql? other
|
101
|
+
super and self.equiv? other
|
102
|
+
end
|
103
|
+
|
104
|
+
def equiv? other
|
105
|
+
rules.size == other.rules.size and
|
106
|
+
!rules.zip(other.rules).map do |a, b|
|
107
|
+
a.to_css == b.to_css
|
108
|
+
end.include?(false)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Same as above, except with a specific selector
|
112
|
+
# TODO: clean this up or implement it differently
|
113
|
+
def descend selector, element
|
114
|
+
if selector.is_a? Child
|
115
|
+
s = self[element.name].selector
|
116
|
+
self[element.name] if s.is_a? Child or s.is_a? Descendant
|
117
|
+
elsif selector.is_a? Descendant
|
118
|
+
self[element.name]
|
119
|
+
else
|
120
|
+
self[element.name] if self[element.name].selector.class == selector.class
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Add an arbitrary node to this element
|
126
|
+
#
|
127
|
+
def << obj
|
128
|
+
if obj.kind_of? Node::Entity
|
129
|
+
obj.parent = self
|
130
|
+
@rules << obj
|
131
|
+
else
|
132
|
+
raise ArgumentError, "argument can't be a #{obj.class}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def last; elements.last end
|
137
|
+
def first; elements.first end
|
138
|
+
def to_s; root?? '*' : name end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Entry point for the css conversion
|
142
|
+
#
|
143
|
+
def to_css path = [], env = nil
|
144
|
+
path << @selector.to_css << name unless root?
|
145
|
+
|
146
|
+
# puts "to_css env: #{env ? env.variables : "nil"}"
|
147
|
+
content = @rules.select do |r|
|
148
|
+
r.is_a?(Mixin::Call) || r.instance_of?(Property)
|
149
|
+
end.map do |i|
|
150
|
+
' ' * 2 + i.to_css(env)
|
151
|
+
end.compact.reject(&:empty?) * "\n"
|
152
|
+
|
153
|
+
content = content.include?("\n") ? "\n#{content}\n" : " #{content.strip} "
|
154
|
+
|
155
|
+
ruleset = if is_a?(Mixin::Def)
|
156
|
+
content.strip
|
157
|
+
else
|
158
|
+
!content.strip.empty??
|
159
|
+
"#{[path.reject(&:empty?).join.strip,
|
160
|
+
*@set.map(&:name)].uniq * ', '} {#{content}}\n" : ""
|
161
|
+
end
|
162
|
+
|
163
|
+
ruleset + elements.reject {|e| e.is_a?(Mixin::Def) }.map do |i|
|
164
|
+
i.to_css(path, env)
|
165
|
+
end.reject(&:empty?).join
|
166
|
+
|
167
|
+
ensure
|
168
|
+
2.times { path.pop }
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Find the nearest node in the hierarchy or raise a NameError
|
173
|
+
#
|
174
|
+
def nearest ident, type = nil
|
175
|
+
ary = type || ident =~ /^[.#]/ ? :elements : :variables
|
176
|
+
path.map do |node|
|
177
|
+
node.send(ary).find {|i| i.to_s == ident }
|
178
|
+
end.compact.first.tap do |result|
|
179
|
+
raise VariableNameError, ("#{ident} in #{self.to_s}") if result.nil? && type != :mixin
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# Traverse the whole tree, returning each leaf (recursive)
|
185
|
+
#
|
186
|
+
def each path = [], &blk
|
187
|
+
elements.each do |element|
|
188
|
+
path << element
|
189
|
+
yield element, path if element.leaf?
|
190
|
+
element.each path, &blk
|
191
|
+
path.pop
|
192
|
+
end
|
193
|
+
self
|
194
|
+
end
|
195
|
+
|
196
|
+
def inspect depth = 0
|
197
|
+
indent = lambda {|i| '. ' * i }
|
198
|
+
put = lambda {|ary| ary.map {|i| indent[ depth + 1 ] + i.inspect } * "\n"}
|
199
|
+
|
200
|
+
(root?? "\n" : "") + [
|
201
|
+
indent[ depth ] + self.to_s,
|
202
|
+
put[ variables ],
|
203
|
+
put[ properties ],
|
204
|
+
put[ mixins ],
|
205
|
+
elements.map {|i| i.inspect( depth + 1 ) } * "\n"
|
206
|
+
].reject(&:empty?).join("\n") + "\n" + indent[ depth ]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
module Mixin
|
211
|
+
class Call
|
212
|
+
include Entity
|
213
|
+
|
214
|
+
def initialize mixin, params, parent
|
215
|
+
# puts "Initializing a Mixin::Call #{mixin}"
|
216
|
+
@mixin = mixin
|
217
|
+
self.parent = parent
|
218
|
+
@params = params.each do |e|
|
219
|
+
e.parent = self.parent
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def to_css env = nil
|
224
|
+
# puts "\n\n"
|
225
|
+
# puts "call .#{@mixin.name} #{@params} <#{@params.class}>"
|
226
|
+
@mixin.call(@params.map {|e| e.evaluate(env) })
|
227
|
+
end
|
228
|
+
|
229
|
+
def inspect
|
230
|
+
"#{@mixin.to_s} (#{@params})"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class Def < Element
|
235
|
+
attr_accessor :params
|
236
|
+
|
237
|
+
def initialize name, params = []
|
238
|
+
super name
|
239
|
+
@params = params.each do |param|
|
240
|
+
param.parent = self
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def call args = []
|
245
|
+
if e = @rules.find {|r| r.is_a? Element }
|
246
|
+
raise CompileError, "#{e} in #{self.inspect}: can't nest selectors inside a dynamic mixin."
|
247
|
+
end
|
248
|
+
|
249
|
+
env = Element.new
|
250
|
+
|
251
|
+
@params.zip(args).each do |param, val|
|
252
|
+
env << (val ? Variable.new(param.to_s, Expression.new([val])) : param)
|
253
|
+
end
|
254
|
+
|
255
|
+
#b ? Node::Variable.new(a.to_s, Expression.new([b])) : a
|
256
|
+
|
257
|
+
# puts "#{self.inspect}"
|
258
|
+
# puts "env: #{env.variables} root?: #{env.root?}"
|
259
|
+
# puts "\nTOCSS"
|
260
|
+
to_css([], env)
|
261
|
+
end
|
262
|
+
|
263
|
+
def variables
|
264
|
+
params + super
|
265
|
+
end
|
266
|
+
|
267
|
+
def to_s
|
268
|
+
'.' + name
|
269
|
+
end
|
270
|
+
|
271
|
+
def inspect
|
272
|
+
".#{name}()"
|
273
|
+
end
|
274
|
+
|
275
|
+
def to_css path, env
|
276
|
+
super(path, env)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|