lesslateral 1.2.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG +62 -0
  3. data/LICENSE +179 -0
  4. data/README.md +48 -0
  5. data/Rakefile +52 -0
  6. data/VERSION +1 -0
  7. data/bin/lessc +103 -0
  8. data/less.gemspec +134 -0
  9. data/lib/less.rb +36 -0
  10. data/lib/less/command.rb +108 -0
  11. data/lib/less/engine.rb +52 -0
  12. data/lib/less/engine/grammar/common.tt +29 -0
  13. data/lib/less/engine/grammar/entity.tt +144 -0
  14. data/lib/less/engine/grammar/less.tt +341 -0
  15. data/lib/less/engine/nodes.rb +9 -0
  16. data/lib/less/engine/nodes/element.rb +281 -0
  17. data/lib/less/engine/nodes/entity.rb +79 -0
  18. data/lib/less/engine/nodes/function.rb +93 -0
  19. data/lib/less/engine/nodes/literal.rb +171 -0
  20. data/lib/less/engine/nodes/property.rb +232 -0
  21. data/lib/less/engine/nodes/ruleset.rb +12 -0
  22. data/lib/less/engine/nodes/selector.rb +44 -0
  23. data/lib/less/ext.rb +60 -0
  24. data/lib/less/notification.rb +59 -0
  25. data/spec/command_spec.rb +102 -0
  26. data/spec/css/accessors.css +18 -0
  27. data/spec/css/big.css +3768 -0
  28. data/spec/css/colors.css +14 -0
  29. data/spec/css/comments.css +9 -0
  30. data/spec/css/css-3.css +21 -0
  31. data/spec/css/css.css +50 -0
  32. data/spec/css/dash-prefix.css +12 -0
  33. data/spec/css/functions.css +6 -0
  34. data/spec/css/import-with-extra-paths.css +8 -0
  35. data/spec/css/import-with-partial-in-extra-path.css +6 -0
  36. data/spec/css/import.css +12 -0
  37. data/spec/css/lazy-eval.css +1 -0
  38. data/spec/css/mixins-args.css +32 -0
  39. data/spec/css/mixins.css +28 -0
  40. data/spec/css/operations.css +28 -0
  41. data/spec/css/parens.css +20 -0
  42. data/spec/css/rulesets.css +17 -0
  43. data/spec/css/scope.css +11 -0
  44. data/spec/css/selectors.css +13 -0
  45. data/spec/css/strings.css +12 -0
  46. data/spec/css/variables.css +8 -0
  47. data/spec/css/whitespace.css +7 -0
  48. data/spec/engine_spec.rb +127 -0
  49. data/spec/less/accessors.less +20 -0
  50. data/spec/less/big.less +1264 -0
  51. data/spec/less/colors.less +35 -0
  52. data/spec/less/comments.less +46 -0
  53. data/spec/less/css-3.less +52 -0
  54. data/spec/less/css.less +104 -0
  55. data/spec/less/dash-prefix.less +21 -0
  56. data/spec/less/exceptions/mixed-units-error.less +3 -0
  57. data/spec/less/exceptions/name-error-1.0.less +3 -0
  58. data/spec/less/exceptions/syntax-error-1.0.less +3 -0
  59. data/spec/less/extra_import_path/extra.less +1 -0
  60. data/spec/less/extra_import_path/import/import-test-a.css +1 -0
  61. data/spec/less/extra_import_path/import/import-test-a.less +4 -0
  62. data/spec/less/functions.less +6 -0
  63. data/spec/less/hidden.less +25 -0
  64. data/spec/less/import-with-extra-paths.less +4 -0
  65. data/spec/less/import.less +8 -0
  66. data/spec/less/import/import-test-a.less +2 -0
  67. data/spec/less/import/import-test-b.less +8 -0
  68. data/spec/less/import/import-test-c.less +7 -0
  69. data/spec/less/import/import-test-d.css +1 -0
  70. data/spec/less/lazy-eval.less +6 -0
  71. data/spec/less/literal-css.less +11 -0
  72. data/spec/less/mixins-args.less +59 -0
  73. data/spec/less/mixins.less +43 -0
  74. data/spec/less/operations.less +39 -0
  75. data/spec/less/parens.less +26 -0
  76. data/spec/less/rulesets.less +30 -0
  77. data/spec/less/scope.less +32 -0
  78. data/spec/less/selectors.less +24 -0
  79. data/spec/less/strings.less +14 -0
  80. data/spec/less/variables.less +29 -0
  81. data/spec/less/whitespace.less +34 -0
  82. data/spec/spec.css +50 -0
  83. data/spec/spec_helper.rb +8 -0
  84. 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