lesslateral 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/.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
|