unboxed-less 1.2.12
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 +60 -0
- data/VERSION +1 -0
- data/bin/lessc +92 -0
- data/lib/ext.rb +31 -0
- data/lib/less.rb +32 -0
- data/lib/less/command.rb +98 -0
- data/lib/less/engine.rb +55 -0
- data/lib/less/engine/grammar/common.tt +29 -0
- data/lib/less/engine/grammar/entity.tt +130 -0
- data/lib/less/engine/grammar/less.tt +326 -0
- data/lib/less/engine/nodes.rb +9 -0
- data/lib/less/engine/nodes/element.rb +278 -0
- data/lib/less/engine/nodes/entity.rb +79 -0
- data/lib/less/engine/nodes/function.rb +84 -0
- data/lib/less/engine/nodes/literal.rb +171 -0
- data/lib/less/engine/nodes/property.rb +231 -0
- data/lib/less/engine/nodes/ruleset.rb +12 -0
- data/lib/less/engine/nodes/selector.rb +44 -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 +17 -0
- data/spec/css/css.css +50 -0
- data/spec/css/functions.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 +7 -0
- data/spec/css/whitespace.css +7 -0
- data/spec/engine_spec.rb +112 -0
- data/spec/less/accessors.less +20 -0
- data/spec/less/big.less +4810 -0
- data/spec/less/colors.less +35 -0
- data/spec/less/comments.less +46 -0
- data/spec/less/css-3.less +45 -0
- data/spec/less/css.less +104 -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/functions.less +6 -0
- data/spec/less/hidden.less +25 -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 +24 -0
- data/spec/less/whitespace.less +34 -0
- data/spec/spec.css +50 -0
- data/spec/spec_helper.rb +8 -0
- data/unboxed-less.gemspec +121 -0
- metadata +140 -0
@@ -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,278 @@
|
|
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
|
+
matched = false
|
52
|
+
stack, result = elements.dup, []
|
53
|
+
return self unless elements.size > 1
|
54
|
+
|
55
|
+
elements.each do
|
56
|
+
e = stack.first
|
57
|
+
result << e unless matched
|
58
|
+
|
59
|
+
matched = stack[1..-1].each do |ee|
|
60
|
+
if e.equiv? ee and e.elements.size == 0
|
61
|
+
self[e].set << ee
|
62
|
+
stack.shift
|
63
|
+
else
|
64
|
+
stack.shift
|
65
|
+
break false
|
66
|
+
end
|
67
|
+
end if stack.size > 1
|
68
|
+
end
|
69
|
+
@rules -= (elements - result)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Accessors for the different nodes in @rules
|
75
|
+
#
|
76
|
+
def identifiers; @rules.select {|r| r.kind_of? Property } end
|
77
|
+
def properties; @rules.select {|r| r.instance_of? Property } end
|
78
|
+
def variables; @rules.select {|r| r.instance_of? Variable } end
|
79
|
+
def elements; @rules.select {|r| r.kind_of? Element } end
|
80
|
+
def mixins; @rules.select {|r| r.instance_of? Mixin::Call} end
|
81
|
+
def parameters; [] end
|
82
|
+
|
83
|
+
# Select a child element
|
84
|
+
# TODO: Implement full selector syntax & merge with descend()
|
85
|
+
def [] key
|
86
|
+
case key
|
87
|
+
when Entity
|
88
|
+
@rules.find {|i| i.eql? key }
|
89
|
+
when String
|
90
|
+
@rules.find {|i| i.to_s == key }
|
91
|
+
else raise ArgumentError
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def == other
|
96
|
+
name == other.name
|
97
|
+
end
|
98
|
+
|
99
|
+
def eql? other
|
100
|
+
super and self.equiv? other
|
101
|
+
end
|
102
|
+
|
103
|
+
def equiv? other
|
104
|
+
rules.size == other.rules.size and
|
105
|
+
!rules.zip(other.rules).map do |a, b|
|
106
|
+
a.to_css == b.to_css
|
107
|
+
end.include?(false)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Same as above, except with a specific selector
|
111
|
+
# TODO: clean this up or implement it differently
|
112
|
+
def descend selector, element
|
113
|
+
if selector.is_a? Child
|
114
|
+
s = self[element.name].selector
|
115
|
+
self[element.name] if s.is_a? Child or s.is_a? Descendant
|
116
|
+
elsif selector.is_a? Descendant
|
117
|
+
self[element.name]
|
118
|
+
else
|
119
|
+
self[element.name] if self[element.name].selector.class == selector.class
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def mix arr = []
|
124
|
+
@rules += arr.map do |r|
|
125
|
+
n = r.copy
|
126
|
+
n.parent = self
|
127
|
+
n
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Add an arbitrary node to this element
|
133
|
+
#
|
134
|
+
def << obj
|
135
|
+
if obj.kind_of? Node::Entity
|
136
|
+
obj.parent = self
|
137
|
+
@rules << obj
|
138
|
+
else
|
139
|
+
raise ArgumentError, "argument can't be a #{obj.class}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def last; elements.last end
|
144
|
+
def first; elements.first end
|
145
|
+
def to_s; root?? '*' : name end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Entry point for the css conversion
|
149
|
+
#
|
150
|
+
def to_css path = [], env = nil
|
151
|
+
path << @selector.to_css << name unless root?
|
152
|
+
|
153
|
+
# puts "to_css env: #{env ? env.variables : "nil"}"
|
154
|
+
|
155
|
+
content = (properties + mixins).map do |i|
|
156
|
+
' ' * 2 + i.to_css(env)
|
157
|
+
end.compact.reject(&:empty?) * "\n"
|
158
|
+
|
159
|
+
content = content.include?("\n") ? "\n#{content}\n" : " #{content.strip} "
|
160
|
+
ruleset = if is_a?(Mixin::Def)
|
161
|
+
content.strip
|
162
|
+
else
|
163
|
+
!content.strip.empty??
|
164
|
+
"#{[path.reject(&:empty?).join.strip,
|
165
|
+
*@set.map(&:name)].uniq * ', '} {#{content}}\n" : ""
|
166
|
+
end
|
167
|
+
|
168
|
+
css = ruleset + elements.
|
169
|
+
reject {|e| e.is_a? Mixin::Def }.map do |i|
|
170
|
+
i.to_css(path, env)
|
171
|
+
end.reject(&:empty?).join
|
172
|
+
2.times {path.pop}
|
173
|
+
css
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Find the nearest node in the hierarchy or raise a NameError
|
178
|
+
#
|
179
|
+
def nearest ident, type = nil
|
180
|
+
ary = type || ident =~ /^[.#]/ ? :elements : :variables
|
181
|
+
result = path.map do |node|
|
182
|
+
node.send(ary).find {|i| i.to_s == ident }
|
183
|
+
end.compact.first
|
184
|
+
raise VariableNameError, ("#{ident} in #{self.to_s}") unless result
|
185
|
+
result
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Traverse the whole tree, returning each leaf (recursive)
|
190
|
+
#
|
191
|
+
def each path = [], &blk
|
192
|
+
elements.each do |element|
|
193
|
+
path << element
|
194
|
+
yield element, path if element.leaf?
|
195
|
+
element.each path, &blk
|
196
|
+
path.pop
|
197
|
+
end
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
def inspect depth = 0
|
202
|
+
indent = lambda {|i| '. ' * i }
|
203
|
+
put = lambda {|ary| ary.map {|i| indent[ depth + 1 ] + i.inspect } * "\n"}
|
204
|
+
|
205
|
+
(root?? "\n" : "") + [
|
206
|
+
indent[ depth ] + self.to_s,
|
207
|
+
put[ variables ],
|
208
|
+
put[ properties ],
|
209
|
+
put[ mixins ],
|
210
|
+
elements.map {|i| i.inspect( depth + 1 ) } * "\n"
|
211
|
+
].reject(&:empty?).join("\n") + "\n" + indent[ depth ]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
module Mixin
|
216
|
+
class Call
|
217
|
+
include Entity
|
218
|
+
|
219
|
+
def initialize mixin, params, parent
|
220
|
+
# puts "Initializing a Mixin::Call #{mixin}"
|
221
|
+
@mixin = mixin
|
222
|
+
self.parent = parent
|
223
|
+
@params = params.each do |e|
|
224
|
+
e.parent = self.parent
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def to_css env = nil
|
229
|
+
# puts "\n\n"
|
230
|
+
# puts "call .#{@mixin.name} #{@params} <#{@params.class}>"
|
231
|
+
@mixin.call(@params.map {|e| e.evaluate(env) })
|
232
|
+
end
|
233
|
+
|
234
|
+
def inspect
|
235
|
+
"#{@mixin.to_s} (#{@params})"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class Def < Element
|
240
|
+
attr_accessor :params
|
241
|
+
|
242
|
+
def initialize name, params = []
|
243
|
+
super name
|
244
|
+
@params = params.each do |param|
|
245
|
+
param.parent = self
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def call args = []
|
250
|
+
env = Element.new
|
251
|
+
|
252
|
+
@params.zip(args).each do |param, val|
|
253
|
+
env << (val ? Variable.new(param.to_s, Expression.new([val])) : param)
|
254
|
+
end
|
255
|
+
|
256
|
+
#b ? Node::Variable.new(a.to_s, Expression.new([b])) : a
|
257
|
+
|
258
|
+
# puts "#{self.inspect}"
|
259
|
+
# puts "env: #{env.variables} root?: #{env.root?}"
|
260
|
+
# puts "\nTOCSS"
|
261
|
+
to_css([], env)
|
262
|
+
end
|
263
|
+
|
264
|
+
def variables
|
265
|
+
params + super
|
266
|
+
end
|
267
|
+
|
268
|
+
def to_s
|
269
|
+
'.' + name
|
270
|
+
end
|
271
|
+
|
272
|
+
def to_css path, env
|
273
|
+
super(path, env)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Less
|
2
|
+
#
|
3
|
+
# Node::Entity
|
4
|
+
#
|
5
|
+
# Everything in the tree is an Entity
|
6
|
+
#
|
7
|
+
# Mixin/Class hierarchy
|
8
|
+
#
|
9
|
+
# - Entity
|
10
|
+
# - Element
|
11
|
+
# - Entity
|
12
|
+
# - Function
|
13
|
+
# - Keyword
|
14
|
+
# - Literal
|
15
|
+
# - Color
|
16
|
+
# - Number
|
17
|
+
# - String
|
18
|
+
# - FontFamily
|
19
|
+
# - Property
|
20
|
+
# - Variable
|
21
|
+
#
|
22
|
+
# TODO: Use delegate class -> @rules
|
23
|
+
#
|
24
|
+
module Node
|
25
|
+
module Entity
|
26
|
+
attr_accessor :parent
|
27
|
+
|
28
|
+
def initialize value, parent = nil
|
29
|
+
super value
|
30
|
+
@parent = parent
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Returns the path from any given node, to the root
|
35
|
+
#
|
36
|
+
# ex: ['color', 'p', '#header', 'body', '*']
|
37
|
+
#
|
38
|
+
def path node = self
|
39
|
+
path = []
|
40
|
+
while node do
|
41
|
+
path << node
|
42
|
+
node = node.parent
|
43
|
+
end
|
44
|
+
path
|
45
|
+
end
|
46
|
+
|
47
|
+
def root
|
48
|
+
path.last
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect; to_s end
|
52
|
+
def to_css; to_s end
|
53
|
+
def to_s; super end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# An anonymous node, for all the 'other' stuff
|
58
|
+
# which doesn't need any specific functionality.
|
59
|
+
#
|
60
|
+
class Anonymous < String
|
61
|
+
include Entity
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# + * - /
|
66
|
+
#
|
67
|
+
class Operator < String
|
68
|
+
def to_ruby
|
69
|
+
self
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Paren < String
|
74
|
+
def to_ruby
|
75
|
+
self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Less
|
2
|
+
#
|
3
|
+
# Functions useable from within the style-sheet go here
|
4
|
+
#
|
5
|
+
module Functions
|
6
|
+
def rgb *rgb
|
7
|
+
rgba rgb, 1.0
|
8
|
+
end
|
9
|
+
|
10
|
+
def hsl *args
|
11
|
+
hsla *[args, 1.0].flatten
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# RGBA to Node::Color
|
16
|
+
#
|
17
|
+
def rgba *rgba
|
18
|
+
Node::Color.new *rgba.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# HSLA to RGBA
|
23
|
+
#
|
24
|
+
def hsla h, s, l, a = 1.0
|
25
|
+
m2 = ( l <= 0.5 ) ? l * ( s + 1 ) : l + s - l * s
|
26
|
+
m1 = l * 2 - m2;
|
27
|
+
|
28
|
+
hue = lambda do |h|
|
29
|
+
h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h)
|
30
|
+
if h * 6 < 1 then m1 + (m2 - m1) * h * 6
|
31
|
+
elsif h * 2 < 1 then m2
|
32
|
+
elsif h * 3 < 2 then m1 + (m2 - m1) * (2/3 - h) * 6
|
33
|
+
else m1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
rgba hue[ h + 1/3 ], hue[ h ], hue[ h - 1/3 ], a
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.available
|
41
|
+
self.instance_methods.map(&:to_sym)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Node
|
46
|
+
#
|
47
|
+
# A CSS function, like rgb() or url()
|
48
|
+
#
|
49
|
+
# it calls functions from the Functions module
|
50
|
+
#
|
51
|
+
class Function < String
|
52
|
+
include Entity
|
53
|
+
include Functions
|
54
|
+
|
55
|
+
def initialize name, args
|
56
|
+
@args = if args.is_a? Array
|
57
|
+
args.map {|e| e.is_a?(Expression) ? e : Expression.new(e, self)}
|
58
|
+
else
|
59
|
+
[args]
|
60
|
+
end
|
61
|
+
|
62
|
+
super name
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_css env = nil
|
66
|
+
self.evaluate(env).to_css
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Call the function
|
71
|
+
#
|
72
|
+
# If the function isn't found, we just print it out,
|
73
|
+
# this is the case for url(), for example,
|
74
|
+
#
|
75
|
+
def evaluate context = nil
|
76
|
+
if Functions.available.include? self.to_sym
|
77
|
+
send to_sym, *@args
|
78
|
+
else
|
79
|
+
Node::Anonymous.new("#{to_sym}(#{@args.map(&:to_css) * ', '})")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|