less 1.0.16 → 1.1.3
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/Rakefile +17 -6
- data/VERSION +1 -1
- data/bin/lessc +22 -0
- data/less.gemspec +6 -4
- data/lib/ext.rb +62 -0
- data/lib/less.rb +2 -64
- data/lib/less/command.rb +22 -6
- data/lib/less/engine.rb +7 -3
- data/lib/less/engine/grammar/common.tt +29 -0
- data/lib/less/engine/grammar/entity.tt +139 -0
- data/lib/less/engine/grammar/less.tt +271 -0
- data/lib/less/engine/nodes/function.rb +12 -9
- data/spec/css/parens.css +10 -1
- data/spec/less/parens.less +8 -2
- metadata +6 -4
- data/lib/less/engine/less.tt +0 -422
- data/lib/less/engine/parser.rb +0 -4296
@@ -0,0 +1,271 @@
|
|
1
|
+
module Less
|
2
|
+
grammar StyleSheet
|
3
|
+
include Common
|
4
|
+
include Entity
|
5
|
+
|
6
|
+
rule primary
|
7
|
+
(declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
|
8
|
+
end
|
9
|
+
|
10
|
+
rule comment
|
11
|
+
ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# div, .class, body > p {...}
|
16
|
+
#
|
17
|
+
rule ruleset
|
18
|
+
selectors "{" ws primary ws "}" ws {
|
19
|
+
def build env
|
20
|
+
# Build the ruleset for each selector
|
21
|
+
selectors.build(env, :ruleset).each do |sel|
|
22
|
+
primary.build sel
|
23
|
+
end
|
24
|
+
end
|
25
|
+
} / ws selectors ';' ws {
|
26
|
+
def build env
|
27
|
+
selectors.build(env, :mixin).each do |path|
|
28
|
+
rules = path.inject(env.root) do |current, node|
|
29
|
+
current.descend(node.selector, node) or raise MixinNameError, path.join
|
30
|
+
end.rules
|
31
|
+
env.rules += rules
|
32
|
+
end
|
33
|
+
end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
rule import
|
38
|
+
"@import" S url:(string / url) medias? s ';' ws {
|
39
|
+
def build env
|
40
|
+
path = File.join(env.root.file, url.value)
|
41
|
+
path += '.less' unless path =~ /\.(le|c)ss$/
|
42
|
+
if File.exist? path
|
43
|
+
imported = Less::Engine.new(File.new(path)).to_tree
|
44
|
+
env.rules += imported.rules
|
45
|
+
else
|
46
|
+
raise ImportError, path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
rule url
|
53
|
+
'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
|
54
|
+
def build env = nil
|
55
|
+
Node::String.new CGI.unescape(path.text_value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def value
|
59
|
+
build
|
60
|
+
end
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
rule medias
|
65
|
+
[-a-z]+ (s ',' s [a-z]+)*
|
66
|
+
end
|
67
|
+
|
68
|
+
rule selectors
|
69
|
+
ws selector tail:(s ',' ws selector)* ws {
|
70
|
+
def build env, method
|
71
|
+
all.map do |e|
|
72
|
+
e.send(method, env) if e.respond_to? method
|
73
|
+
end.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def all
|
77
|
+
[selector] + tail.elements.map {|e| e.selector }
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# div > p a {...}
|
84
|
+
#
|
85
|
+
rule selector
|
86
|
+
sel:(s select element s)+ arguments? {
|
87
|
+
def ruleset env
|
88
|
+
sel.elements.inject(env) do |node, e|
|
89
|
+
node << Node::Element.new(e.element.text_value, e.select.text_value)
|
90
|
+
node.last
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def mixin env
|
95
|
+
sel.elements.map do |e|
|
96
|
+
Node::Element.new(e.element.text_value, e.select.text_value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# @my-var: 12px;
|
104
|
+
# height: 100%;
|
105
|
+
#
|
106
|
+
rule declaration
|
107
|
+
ws name:(ident / variable) s ':' s expressions s (';'/ ws &'}') ws {
|
108
|
+
def build env
|
109
|
+
env << (name.text_value =~ /^@/ ?
|
110
|
+
Node::Variable : Node::Property).new(name.text_value, expressions.build(env), env)
|
111
|
+
end
|
112
|
+
# Empty rule
|
113
|
+
} / ws ident s ':' s ';' ws
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# An operation or compound value
|
118
|
+
#
|
119
|
+
rule expressions
|
120
|
+
# Operation
|
121
|
+
expression tail:(operator expression)+ {
|
122
|
+
def build env
|
123
|
+
all.map {|e| e.build(env) }.dissolve
|
124
|
+
end
|
125
|
+
|
126
|
+
def all
|
127
|
+
[expression] + tail.elements.map {|i| [i.operator, i.expression] }.flatten.compact
|
128
|
+
end
|
129
|
+
# Space-delimited expressions
|
130
|
+
} / expression tail:(WS expression)* {
|
131
|
+
def build env
|
132
|
+
all.map {|e| e.build(env) if e.respond_to? :build }.compact
|
133
|
+
end
|
134
|
+
|
135
|
+
def all
|
136
|
+
[expression] + tail.elements.map {|f| f.expression }
|
137
|
+
end
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
rule expression
|
142
|
+
'(' s expressions s ')' {
|
143
|
+
def build env
|
144
|
+
Node::Expression.new(['('] + expressions.build(env).flatten + [')'])
|
145
|
+
end
|
146
|
+
} / entity '' {
|
147
|
+
def build env
|
148
|
+
entity.method(:build).arity.zero?? entity.build : entity.build(env)
|
149
|
+
end
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# An identifier
|
155
|
+
#
|
156
|
+
rule ident
|
157
|
+
'*'? '-'? [-a-z0-9_]+
|
158
|
+
end
|
159
|
+
|
160
|
+
rule variable
|
161
|
+
'@' [-a-zA-Z0-9_]+ {
|
162
|
+
def build
|
163
|
+
Node::Variable.new(text_value)
|
164
|
+
end
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# div / .class / #id / input[type="text"] / lang(fr)
|
170
|
+
#
|
171
|
+
rule element
|
172
|
+
(class_id / tag / ident) attribute* ('(' ident? attribute* ')')? / attribute+ / '@media' / '@font-face'
|
173
|
+
end
|
174
|
+
|
175
|
+
rule class_id
|
176
|
+
tag? (class / id)+
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# [type="text"]
|
181
|
+
#
|
182
|
+
rule attribute
|
183
|
+
'[' tag ([|~*$^]? '=') (tag / string) ']' / '[' (tag / string) ']'
|
184
|
+
end
|
185
|
+
|
186
|
+
rule class
|
187
|
+
'.' [_a-z] [-a-zA-Z0-9_]*
|
188
|
+
end
|
189
|
+
|
190
|
+
rule id
|
191
|
+
'#' [_a-z] [-a-zA-Z0-9_]*
|
192
|
+
end
|
193
|
+
|
194
|
+
rule tag
|
195
|
+
[a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
|
196
|
+
end
|
197
|
+
|
198
|
+
rule select
|
199
|
+
(s [+>~] s / s ':' / S)?
|
200
|
+
end
|
201
|
+
|
202
|
+
# TODO: Merge this with attribute rule
|
203
|
+
rule accessor
|
204
|
+
ident:(class_id / tag) '[' attr:(string / variable) ']' {
|
205
|
+
def build env
|
206
|
+
env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
|
207
|
+
end
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
211
|
+
rule operator
|
212
|
+
S [-+*/] S {
|
213
|
+
def build env
|
214
|
+
Node::Operator.new(text_value.strip)
|
215
|
+
end
|
216
|
+
} / [-+*/] {
|
217
|
+
def build env
|
218
|
+
Node::Operator.new(text_value)
|
219
|
+
end
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# Functions and arguments
|
225
|
+
#
|
226
|
+
rule function
|
227
|
+
name:([-a-zA-Z_]+) arguments {
|
228
|
+
def build
|
229
|
+
Node::Function.new(name.text_value, [arguments.build].flatten)
|
230
|
+
end
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
rule arguments
|
235
|
+
'(' s argument s tail:(',' s argument s)* ')' {
|
236
|
+
def build
|
237
|
+
all.map do |e|
|
238
|
+
e.build if e.respond_to? :build
|
239
|
+
end.compact
|
240
|
+
end
|
241
|
+
|
242
|
+
def all
|
243
|
+
[argument] + tail.elements.map {|e| e.argument }
|
244
|
+
end
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
rule argument
|
249
|
+
color / number unit {
|
250
|
+
def build
|
251
|
+
Node::Number.new number.text_value, unit.text_value
|
252
|
+
end
|
253
|
+
} / string {
|
254
|
+
def build
|
255
|
+
Node::String.new text_value
|
256
|
+
end
|
257
|
+
} / [a-zA-Z]+ '=' dimension {
|
258
|
+
def build
|
259
|
+
Node::Anonymous.new text_value
|
260
|
+
end
|
261
|
+
} / [-a-zA-Z0-9_%$/.&=:;#+?]+ {
|
262
|
+
def build
|
263
|
+
Node::String.new text_value
|
264
|
+
end
|
265
|
+
} / function / keyword other:(S keyword)* {
|
266
|
+
def build
|
267
|
+
end
|
268
|
+
}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -36,6 +36,10 @@ module Less
|
|
36
36
|
|
37
37
|
rgba hue[ h + 1/3 ], hue[ h ], hue[ h - 1/3 ], a
|
38
38
|
end
|
39
|
+
|
40
|
+
def self.available
|
41
|
+
self.instance_methods.map(&:to_sym)
|
42
|
+
end
|
39
43
|
end
|
40
44
|
|
41
45
|
module Node
|
@@ -45,31 +49,30 @@ module Less
|
|
45
49
|
# it calls functions from the Functions module
|
46
50
|
#
|
47
51
|
class Function < ::String
|
48
|
-
include Functions
|
49
52
|
include Entity
|
53
|
+
include Functions
|
50
54
|
|
51
55
|
def initialize name, *args
|
52
56
|
@args = args.flatten
|
53
57
|
super name
|
54
58
|
end
|
55
|
-
|
59
|
+
|
56
60
|
def to_css
|
57
61
|
self.evaluate.to_css
|
58
62
|
end
|
59
63
|
|
60
64
|
#
|
61
65
|
# Call the function
|
62
|
-
#
|
63
|
-
def evaluate
|
64
|
-
send self.to_sym, *@args
|
65
|
-
end
|
66
|
-
|
67
66
|
#
|
68
67
|
# If the function isn't found, we just print it out,
|
69
68
|
# this is the case for url(), for example,
|
70
69
|
#
|
71
|
-
def
|
72
|
-
|
70
|
+
def evaluate
|
71
|
+
if Functions.available.include? self.to_sym
|
72
|
+
send to_sym, *@args
|
73
|
+
else
|
74
|
+
Node::Anonymous.new("#{to_sym}(#{@args.map(&:to_css) * ', '})")
|
75
|
+
end
|
73
76
|
end
|
74
77
|
end
|
75
78
|
end
|
data/spec/css/parens.css
CHANGED
data/spec/less/parens.less
CHANGED
@@ -9,7 +9,13 @@
|
|
9
9
|
.more-parens {
|
10
10
|
@var: (2 * 2);
|
11
11
|
padding: (2 * @var) 4 4 (@var * 1px);
|
12
|
-
|
12
|
+
width: (@var * @var) * 6;
|
13
|
+
height: (7 * 7) + (8 * 8);
|
14
|
+
margin: 4 * (5 + 5) / 2 - (@var * 2);
|
13
15
|
//margin: (6 * 6)px;
|
14
|
-
//height: (7 * 7) + (8 * 8);
|
15
16
|
}
|
17
|
+
|
18
|
+
.nested-parens {
|
19
|
+
width: 2 * (4 * (2 + (1 + 6))) - 1;
|
20
|
+
height: ((2+3)*(2+3) / (9-4)) + 1;
|
21
|
+
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: less
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- cloudhead
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-27 00:00:00 -04:00
|
13
13
|
default_executable: lessc
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -30,11 +30,14 @@ files:
|
|
30
30
|
- VERSION
|
31
31
|
- bin/lessc
|
32
32
|
- less.gemspec
|
33
|
+
- lib/ext.rb
|
33
34
|
- lib/less.rb
|
34
35
|
- lib/less/command.rb
|
35
36
|
- lib/less/engine.rb
|
36
37
|
- lib/less/engine/builder.rb
|
37
|
-
- lib/less/engine/
|
38
|
+
- lib/less/engine/grammar/common.tt
|
39
|
+
- lib/less/engine/grammar/entity.tt
|
40
|
+
- lib/less/engine/grammar/less.tt
|
38
41
|
- lib/less/engine/nodes.rb
|
39
42
|
- lib/less/engine/nodes/element.rb
|
40
43
|
- lib/less/engine/nodes/entity.rb
|
@@ -43,7 +46,6 @@ files:
|
|
43
46
|
- lib/less/engine/nodes/property.rb
|
44
47
|
- lib/less/engine/nodes/ruleset.rb
|
45
48
|
- lib/less/engine/nodes/selector.rb
|
46
|
-
- lib/less/engine/parser.rb
|
47
49
|
- lib/vendor/treetop/.gitignore
|
48
50
|
- lib/vendor/treetop/LICENSE
|
49
51
|
- lib/vendor/treetop/README
|
data/lib/less/engine/less.tt
DELETED
@@ -1,422 +0,0 @@
|
|
1
|
-
grammar Less
|
2
|
-
rule primary
|
3
|
-
(declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
|
4
|
-
end
|
5
|
-
|
6
|
-
rule comment
|
7
|
-
ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
|
8
|
-
end
|
9
|
-
|
10
|
-
#
|
11
|
-
# div, .class, body > p {...}
|
12
|
-
#
|
13
|
-
rule ruleset
|
14
|
-
selectors "{" ws primary ws "}" ws {
|
15
|
-
def build env
|
16
|
-
# Build the ruleset for each selector
|
17
|
-
selectors.build(env, :ruleset).each do |sel|
|
18
|
-
primary.build sel
|
19
|
-
end
|
20
|
-
end
|
21
|
-
} / ws selectors ';' ws {
|
22
|
-
def build env
|
23
|
-
selectors.build(env, :mixin).each do |path|
|
24
|
-
rules = path.inject(env.root) do |current, node|
|
25
|
-
current.descend(node.selector, node) or raise MixinNameError, path.join
|
26
|
-
end.rules
|
27
|
-
env.rules += rules
|
28
|
-
end
|
29
|
-
end
|
30
|
-
}
|
31
|
-
end
|
32
|
-
|
33
|
-
rule import
|
34
|
-
"@import" S url:(string / url) medias? s ';' ws {
|
35
|
-
def build env
|
36
|
-
path = File.join(env.root.file, url.value)
|
37
|
-
path += '.less' unless path =~ /\.(le|c)ss$/
|
38
|
-
if File.exist? path
|
39
|
-
imported = Less::Engine.new(File.new(path)).to_tree
|
40
|
-
env.rules += imported.rules
|
41
|
-
else
|
42
|
-
raise ImportError, path
|
43
|
-
end
|
44
|
-
end
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
rule url
|
49
|
-
'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
|
50
|
-
def build env = nil
|
51
|
-
Node::String.new CGI.unescape(path.text_value)
|
52
|
-
end
|
53
|
-
|
54
|
-
def value
|
55
|
-
build
|
56
|
-
end
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
rule medias
|
61
|
-
[-a-z]+ (s ',' s [a-z]+)*
|
62
|
-
end
|
63
|
-
|
64
|
-
rule selectors
|
65
|
-
ws selector tail:(s ',' ws selector)* ws {
|
66
|
-
def build env, method
|
67
|
-
all.map do |e|
|
68
|
-
e.send(method, env) if e.respond_to? method
|
69
|
-
end.compact
|
70
|
-
end
|
71
|
-
|
72
|
-
def all
|
73
|
-
[selector] + tail.elements.map {|e| e.selector }
|
74
|
-
end
|
75
|
-
}
|
76
|
-
end
|
77
|
-
|
78
|
-
#
|
79
|
-
# div > p a {...}
|
80
|
-
#
|
81
|
-
rule selector
|
82
|
-
sel:(s select element s)+ arguments? {
|
83
|
-
def ruleset env
|
84
|
-
sel.elements.inject(env) do |node, e|
|
85
|
-
node << Node::Element.new(e.element.text_value, e.select.text_value)
|
86
|
-
node.last
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def mixin env
|
91
|
-
sel.elements.map do |e|
|
92
|
-
Node::Element.new(e.element.text_value, e.select.text_value)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
}
|
96
|
-
end
|
97
|
-
|
98
|
-
#
|
99
|
-
# @my-var: 12px;
|
100
|
-
# height: 100%;
|
101
|
-
#
|
102
|
-
rule declaration
|
103
|
-
ws name:(ident / variable) s ':' s expressions s (';'/ ws &'}') ws {
|
104
|
-
def build env
|
105
|
-
env << (name.text_value =~ /^@/ ?
|
106
|
-
Node::Variable : Node::Property).new(name.text_value, expressions.build(env), env)
|
107
|
-
|
108
|
-
end
|
109
|
-
# Empty rule
|
110
|
-
} / ws ident s ':' s ';' ws
|
111
|
-
end
|
112
|
-
|
113
|
-
rule expressions
|
114
|
-
expression+ {
|
115
|
-
def build env
|
116
|
-
elements.map do |e|
|
117
|
-
e.build(env) if e.respond_to? :build
|
118
|
-
end.compact
|
119
|
-
end
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
|
-
#
|
124
|
-
# An operation or compound value
|
125
|
-
#
|
126
|
-
rule expression
|
127
|
-
s '(' s expressions s ')' s {
|
128
|
-
def build env
|
129
|
-
Node::Expression.new(['('] + expressions.build(env).flatten + [')'])
|
130
|
-
end
|
131
|
-
} / entity tail:(operator entity)* ws {
|
132
|
-
def build env
|
133
|
-
exp = all.map do |e|
|
134
|
-
e.method(:build).arity.zero??
|
135
|
-
e.build : e.build(env) if e.respond_to? :build
|
136
|
-
end.dissolve
|
137
|
-
exp.is_a?(Array) ? Node::Expression.new(exp) : exp
|
138
|
-
end
|
139
|
-
|
140
|
-
def all
|
141
|
-
[entity] + tail.elements.map {|i| [i.operator, i.entity] }.flatten.compact
|
142
|
-
end
|
143
|
-
}
|
144
|
-
end
|
145
|
-
|
146
|
-
#
|
147
|
-
# Entity: Any whitespace delimited token
|
148
|
-
#
|
149
|
-
rule entity
|
150
|
-
function / fonts / keyword / accessor / variable / literal / important
|
151
|
-
end
|
152
|
-
|
153
|
-
rule fonts
|
154
|
-
font family:(s ',' s font)+ {
|
155
|
-
def build
|
156
|
-
Node::FontFamily.new(all.map(&:build))
|
157
|
-
end
|
158
|
-
|
159
|
-
def all
|
160
|
-
[font] + family.elements.map {|f| f.font }
|
161
|
-
end
|
162
|
-
}
|
163
|
-
end
|
164
|
-
|
165
|
-
rule font
|
166
|
-
[a-zA-Z] [-a-zA-Z0-9]* {
|
167
|
-
def build
|
168
|
-
Node::Keyword.new(text_value)
|
169
|
-
end
|
170
|
-
} / string {
|
171
|
-
def build
|
172
|
-
Node::String.new(text_value)
|
173
|
-
end
|
174
|
-
}
|
175
|
-
end
|
176
|
-
|
177
|
-
#
|
178
|
-
# An identifier
|
179
|
-
#
|
180
|
-
rule ident
|
181
|
-
'*'? '-'? [-a-z0-9_]+
|
182
|
-
end
|
183
|
-
|
184
|
-
rule variable
|
185
|
-
'@' [-a-zA-Z0-9_]+ {
|
186
|
-
def build
|
187
|
-
Node::Variable.new(text_value)
|
188
|
-
end
|
189
|
-
}
|
190
|
-
end
|
191
|
-
|
192
|
-
#
|
193
|
-
# div / .class / #id / input[type="text"] / lang(fr)
|
194
|
-
#
|
195
|
-
rule element
|
196
|
-
(class_id / tag / ident) attribute* ('(' ident? attribute* ')')? / attribute+ / '@media' / '@font-face'
|
197
|
-
end
|
198
|
-
|
199
|
-
rule class_id
|
200
|
-
tag? (class / id)+
|
201
|
-
end
|
202
|
-
|
203
|
-
#
|
204
|
-
# [type="text"]
|
205
|
-
#
|
206
|
-
rule attribute
|
207
|
-
'[' tag ([|~*$^]? '=') (tag / string) ']' / '[' (tag / string) ']'
|
208
|
-
end
|
209
|
-
|
210
|
-
rule class
|
211
|
-
'.' [_a-z] [-a-zA-Z0-9_]*
|
212
|
-
end
|
213
|
-
|
214
|
-
rule id
|
215
|
-
'#' [_a-z] [-a-zA-Z0-9_]*
|
216
|
-
end
|
217
|
-
|
218
|
-
rule tag
|
219
|
-
[a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
|
220
|
-
end
|
221
|
-
|
222
|
-
rule select
|
223
|
-
(s [+>~] s / s ':' / S)?
|
224
|
-
end
|
225
|
-
|
226
|
-
# TODO: Merge this with attribute rule
|
227
|
-
rule accessor
|
228
|
-
ident:(class_id / tag) '[' attr:(string / variable) ']' {
|
229
|
-
def build env
|
230
|
-
env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
|
231
|
-
end
|
232
|
-
}
|
233
|
-
end
|
234
|
-
|
235
|
-
rule operator
|
236
|
-
S [-+*/] S {
|
237
|
-
def build
|
238
|
-
Node::Operator.new(text_value.strip)
|
239
|
-
end
|
240
|
-
} / [-+*/] {
|
241
|
-
def build
|
242
|
-
Node::Operator.new(text_value)
|
243
|
-
end
|
244
|
-
}
|
245
|
-
end
|
246
|
-
|
247
|
-
#
|
248
|
-
# Tokens which don't need to be evaluated
|
249
|
-
#
|
250
|
-
rule literal
|
251
|
-
color / (dimension / [-a-z]+) '/' dimension {
|
252
|
-
def build
|
253
|
-
Node::Anonymous.new(text_value)
|
254
|
-
end
|
255
|
-
} / number unit {
|
256
|
-
def build
|
257
|
-
Node::Number.new(number.text_value, unit.text_value)
|
258
|
-
end
|
259
|
-
} / string {
|
260
|
-
def build
|
261
|
-
Node::String.new(text_value)
|
262
|
-
end
|
263
|
-
}
|
264
|
-
end
|
265
|
-
|
266
|
-
# !important
|
267
|
-
rule important
|
268
|
-
'!important' {
|
269
|
-
def build
|
270
|
-
Node::Keyword.new(text_value)
|
271
|
-
end
|
272
|
-
}
|
273
|
-
end
|
274
|
-
|
275
|
-
#
|
276
|
-
# `blue`, `small`, `normal` etc.
|
277
|
-
#
|
278
|
-
rule keyword
|
279
|
-
[-a-zA-Z]+ !ns {
|
280
|
-
def build
|
281
|
-
Node::Keyword.new(text_value)
|
282
|
-
end
|
283
|
-
}
|
284
|
-
end
|
285
|
-
|
286
|
-
#
|
287
|
-
# 'hello world' / "hello world"
|
288
|
-
#
|
289
|
-
rule string
|
290
|
-
"'" content:(!"'" . )* "'" {
|
291
|
-
def value
|
292
|
-
content.text_value
|
293
|
-
end
|
294
|
-
} / ["] content:(!["] . )* ["] {
|
295
|
-
def value
|
296
|
-
content.text_value
|
297
|
-
end
|
298
|
-
}
|
299
|
-
end
|
300
|
-
|
301
|
-
#
|
302
|
-
# Numbers & Units
|
303
|
-
#
|
304
|
-
rule dimension
|
305
|
-
number unit
|
306
|
-
end
|
307
|
-
|
308
|
-
rule number
|
309
|
-
'-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
|
310
|
-
end
|
311
|
-
|
312
|
-
rule unit
|
313
|
-
('px'/'em'/'pc'/'%'/'pt'/'cm'/'mm')?
|
314
|
-
end
|
315
|
-
|
316
|
-
#
|
317
|
-
# Color
|
318
|
-
#
|
319
|
-
rule color
|
320
|
-
'#' rgb {
|
321
|
-
def build
|
322
|
-
Node::Color.new(*rgb.build)
|
323
|
-
end
|
324
|
-
} / fn:(('hsl'/'rgb') 'a'?) arguments {
|
325
|
-
def build
|
326
|
-
Node::Function.new(fn.text_value, arguments.build.flatten)
|
327
|
-
end
|
328
|
-
}
|
329
|
-
end
|
330
|
-
|
331
|
-
#
|
332
|
-
# 00ffdd / 0fd
|
333
|
-
#
|
334
|
-
rule rgb
|
335
|
-
r:(hex hex) g:(hex hex) b:(hex hex) {
|
336
|
-
def build
|
337
|
-
[r.text_value, g.text_value, b.text_value]
|
338
|
-
end
|
339
|
-
} / r:hex g:hex b:hex {
|
340
|
-
def build
|
341
|
-
[r.text_value, g.text_value, b.text_value].map {|c| c * 2 }
|
342
|
-
end
|
343
|
-
}
|
344
|
-
end
|
345
|
-
|
346
|
-
rule hex
|
347
|
-
[a-fA-F0-9]
|
348
|
-
end
|
349
|
-
|
350
|
-
#
|
351
|
-
# Functions and arguments
|
352
|
-
#
|
353
|
-
rule function
|
354
|
-
name:([-a-zA-Z_]+) arguments {
|
355
|
-
def build
|
356
|
-
Node::Function.new(name.text_value, [arguments.build].flatten)
|
357
|
-
end
|
358
|
-
}
|
359
|
-
end
|
360
|
-
|
361
|
-
rule arguments
|
362
|
-
'(' s argument s tail:(',' s argument s)* ')' {
|
363
|
-
def build
|
364
|
-
all.map do |e|
|
365
|
-
e.build if e.respond_to? :build
|
366
|
-
end.compact
|
367
|
-
end
|
368
|
-
|
369
|
-
def all
|
370
|
-
[argument] + tail.elements.map {|e| e.argument }
|
371
|
-
end
|
372
|
-
}
|
373
|
-
end
|
374
|
-
|
375
|
-
rule argument
|
376
|
-
color {
|
377
|
-
def build
|
378
|
-
Node::Color.new text_value
|
379
|
-
end
|
380
|
-
} / number unit {
|
381
|
-
def build
|
382
|
-
Node::Number.new number.text_value, unit.text_value
|
383
|
-
end
|
384
|
-
} / string {
|
385
|
-
def build
|
386
|
-
Node::String.new text_value
|
387
|
-
end
|
388
|
-
} / [a-zA-Z]+ '=' dimension {
|
389
|
-
def build
|
390
|
-
Node::Anonymous.new text_value
|
391
|
-
end
|
392
|
-
} / [-a-zA-Z0-9_%$/.&=:;#+?]+ {
|
393
|
-
def build
|
394
|
-
Node::String.new text_value
|
395
|
-
end
|
396
|
-
}
|
397
|
-
end
|
398
|
-
|
399
|
-
#
|
400
|
-
# Whitespace
|
401
|
-
#
|
402
|
-
rule s
|
403
|
-
[ ]*
|
404
|
-
end
|
405
|
-
|
406
|
-
rule S
|
407
|
-
[ ]+
|
408
|
-
end
|
409
|
-
|
410
|
-
rule ws
|
411
|
-
[\n ]*
|
412
|
-
end
|
413
|
-
|
414
|
-
rule WS
|
415
|
-
[\n ]+
|
416
|
-
end
|
417
|
-
|
418
|
-
# Non-space char
|
419
|
-
rule ns
|
420
|
-
![ ;\n] .
|
421
|
-
end
|
422
|
-
end
|