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