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.
Files changed (88) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.md +24 -6
  3. data/Rakefile +28 -37
  4. data/VERSION +1 -1
  5. data/bin/lessc +41 -11
  6. data/less.gemspec +85 -16
  7. data/lib/less/command.rb +47 -28
  8. data/lib/less/engine/grammar/common.tt +29 -0
  9. data/lib/less/engine/grammar/entity.tt +144 -0
  10. data/lib/less/engine/grammar/less.tt +341 -0
  11. data/lib/less/engine/nodes/element.rb +281 -0
  12. data/lib/less/engine/nodes/entity.rb +79 -0
  13. data/lib/less/engine/nodes/function.rb +93 -0
  14. data/lib/less/engine/nodes/literal.rb +171 -0
  15. data/lib/less/engine/nodes/property.rb +232 -0
  16. data/lib/less/engine/nodes/ruleset.rb +12 -0
  17. data/lib/less/engine/nodes/selector.rb +44 -0
  18. data/lib/less/engine/nodes.rb +9 -0
  19. data/lib/less/engine.rb +44 -140
  20. data/lib/less/ext.rb +60 -0
  21. data/lib/less.rb +25 -13
  22. data/spec/command_spec.rb +3 -7
  23. data/spec/css/accessors.css +18 -0
  24. data/spec/css/big.css +3768 -0
  25. data/spec/css/colors.css +14 -0
  26. data/spec/css/comments.css +9 -0
  27. data/spec/css/css-3.css +21 -0
  28. data/spec/css/css.css +50 -0
  29. data/spec/css/dash-prefix.css +12 -0
  30. data/spec/css/functions.css +6 -0
  31. data/spec/css/import-with-extra-paths.css +8 -0
  32. data/spec/css/import-with-partial-in-extra-path.css +6 -0
  33. data/spec/css/import.css +12 -0
  34. data/spec/css/lazy-eval.css +1 -0
  35. data/spec/css/mixins-args.css +32 -0
  36. data/spec/css/mixins.css +28 -0
  37. data/spec/css/operations.css +28 -0
  38. data/spec/css/parens.css +20 -0
  39. data/spec/css/rulesets.css +17 -0
  40. data/spec/css/scope.css +11 -0
  41. data/spec/css/selectors.css +13 -0
  42. data/spec/css/strings.css +12 -0
  43. data/spec/css/variables.css +8 -0
  44. data/spec/css/whitespace.css +7 -0
  45. data/spec/engine_spec.rb +107 -15
  46. data/spec/less/accessors.less +20 -0
  47. data/spec/less/big.less +1264 -0
  48. data/spec/less/colors.less +35 -0
  49. data/spec/less/comments.less +46 -0
  50. data/spec/less/css-3.less +52 -0
  51. data/spec/less/css.less +104 -0
  52. data/spec/less/dash-prefix.less +21 -0
  53. data/spec/less/exceptions/mixed-units-error.less +3 -0
  54. data/spec/less/exceptions/name-error-1.0.less +3 -0
  55. data/spec/less/exceptions/syntax-error-1.0.less +3 -0
  56. data/spec/less/extra_import_path/extra.less +1 -0
  57. data/spec/less/extra_import_path/import/import-test-a.css +1 -0
  58. data/spec/less/extra_import_path/import/import-test-a.less +4 -0
  59. data/spec/less/functions.less +6 -0
  60. data/spec/less/hidden.less +25 -0
  61. data/spec/less/import/import-test-a.less +2 -0
  62. data/spec/less/import/import-test-b.less +8 -0
  63. data/spec/less/import/import-test-c.less +7 -0
  64. data/spec/less/import/import-test-d.css +1 -0
  65. data/spec/less/import-with-extra-paths.less +4 -0
  66. data/spec/less/import.less +8 -0
  67. data/spec/less/lazy-eval.less +6 -0
  68. data/spec/less/literal-css.less +11 -0
  69. data/spec/less/mixins-args.less +59 -0
  70. data/spec/less/mixins.less +43 -0
  71. data/spec/less/operations.less +39 -0
  72. data/spec/less/parens.less +26 -0
  73. data/spec/less/rulesets.less +30 -0
  74. data/spec/less/scope.less +32 -0
  75. data/spec/less/selectors.less +24 -0
  76. data/spec/less/strings.less +14 -0
  77. data/spec/less/variables.less +29 -0
  78. data/spec/less/whitespace.less +34 -0
  79. data/spec/spec.css +50 -25
  80. data/spec/spec_helper.rb +4 -1
  81. metadata +96 -15
  82. data/lib/less/tree.rb +0 -82
  83. data/spec/css/less-0.8.5.css +0 -24
  84. data/spec/css/less-0.8.6.css +0 -24
  85. data/spec/css/less-0.8.7.css +0 -24
  86. data/spec/css/less-0.8.8.css +0 -25
  87. data/spec/spec.less +0 -128
  88. data/spec/tree_spec.rb +0 -5
@@ -0,0 +1,144 @@
1
+ require 'grammar/common'
2
+
3
+ module Less
4
+ module StyleSheet
5
+ grammar Entity
6
+ #
7
+ # Entity: Any whitespace delimited token
8
+ #
9
+ rule entity
10
+ url / alpha / function / accessor / keyword / variable / literal / font
11
+ end
12
+
13
+ rule fonts
14
+ font family:(s ',' s font)+ {
15
+ def build
16
+ Node::FontFamily.new(all.map(&:build))
17
+ end
18
+
19
+ def all
20
+ [font] + family.elements.map {|f| f.font }
21
+ end
22
+ }
23
+ end
24
+
25
+ rule font
26
+ [a-zA-Z] [-a-zA-Z0-9]* !ns {
27
+ def build
28
+ Node::Keyword.new(text_value)
29
+ end
30
+ } / string {
31
+ def build
32
+ Node::Quoted.new(text_value)
33
+ end
34
+ }
35
+ end
36
+
37
+ #
38
+ # Tokens which don't need to be evaluated
39
+ #
40
+ rule literal
41
+ color / (dimension / [-a-z]+) '/' dimension {
42
+ def build
43
+ Node::Anonymous.new(text_value)
44
+ end
45
+ } / number unit {
46
+ def build
47
+ Node::Number.new(number.text_value, unit.text_value)
48
+ end
49
+ } / string {
50
+ def build
51
+ Node::Quoted.new(text_value)
52
+ end
53
+ }
54
+ end
55
+
56
+ #
57
+ # `blue`, `small`, `normal` etc.
58
+ #
59
+ rule keyword
60
+ [-a-zA-Z]+ !ns {
61
+ def build
62
+ Node::Keyword.new(text_value)
63
+ end
64
+ }
65
+ end
66
+
67
+ #
68
+ # 'hello world' / "hello world"
69
+ #
70
+ rule string
71
+ "'" content:(!"'" . )* "'" {
72
+ def value
73
+ content.text_value
74
+ end
75
+ } / ["] content:(!["] . )* ["] {
76
+ def value
77
+ content.text_value
78
+ end
79
+ }
80
+ end
81
+
82
+ #
83
+ # Numbers & Units
84
+ #
85
+ rule dimension
86
+ number unit
87
+ end
88
+
89
+ rule number
90
+ '-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
91
+ end
92
+
93
+ rule unit
94
+ ('px'/'em'/'pc'/'%'/'ex'/'in'/'deg'/'s'/'pt'/'cm'/'mm')?
95
+ end
96
+
97
+ #
98
+ # Color
99
+ #
100
+ rule color
101
+ '#' rgb {
102
+ def build
103
+ Node::Color.new(*rgb.build)
104
+ end
105
+ } / fn:(('hsl'/'rgb') 'a'?) arguments {
106
+ def build
107
+ Node::Function.new(fn.text_value, arguments.build.flatten)
108
+ end
109
+ }
110
+ end
111
+
112
+ #
113
+ # 00ffdd / 0fd
114
+ #
115
+ rule rgb
116
+ r:(hex hex) g:(hex hex) b:(hex hex) {
117
+ def build
118
+ [r.text_value, g.text_value, b.text_value]
119
+ end
120
+ } / r:hex g:hex b:hex {
121
+ def build
122
+ [r.text_value, g.text_value, b.text_value].map {|c| c * 2 }
123
+ end
124
+ }
125
+ end
126
+
127
+ rule hex
128
+ [a-fA-F0-9]
129
+ end
130
+
131
+ #
132
+ # Special case for IE alpha filter
133
+ #
134
+ rule alpha
135
+ "alpha" '(' "opacity=" variable ')' {
136
+ def build env
137
+ var = variable.text_value
138
+ Node::Quoted.new(text_value.sub(var, env.nearest(var).evaluate.to_i.to_s))
139
+ end
140
+ }
141
+ end
142
+ end
143
+ end
144
+ end
@@ -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