haml 1.8.0 → 2.0.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/FAQ +138 -0
- data/MIT-LICENSE +1 -1
- data/{README → README.rdoc} +66 -3
- data/REVISION +1 -0
- data/Rakefile +115 -147
- data/VERSION +1 -1
- data/bin/css2sass +0 -0
- data/bin/haml +2 -1
- data/bin/html2haml +0 -0
- data/bin/sass +0 -0
- data/init.rb +6 -1
- data/lib/haml/buffer.rb +122 -64
- data/lib/haml/engine.rb +77 -46
- data/lib/haml/error.rb +15 -6
- data/lib/haml/exec.rb +61 -10
- data/lib/haml/filters.rb +229 -74
- data/lib/haml/helpers/action_view_extensions.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +109 -24
- data/lib/haml/helpers.rb +137 -76
- data/lib/haml/html.rb +8 -8
- data/lib/haml/precompiler.rb +280 -153
- data/lib/haml/template/patch.rb +10 -3
- data/lib/haml/template/plugin.rb +61 -10
- data/lib/haml/template.rb +14 -9
- data/lib/haml.rb +483 -214
- data/lib/sass/constant/color.rb +13 -13
- data/lib/sass/constant/literal.rb +8 -7
- data/lib/sass/constant/nil.rb +9 -0
- data/lib/sass/constant/number.rb +10 -10
- data/lib/sass/constant/operation.rb +4 -4
- data/lib/sass/constant/string.rb +3 -3
- data/lib/sass/constant.rb +46 -77
- data/lib/sass/css.rb +130 -56
- data/lib/sass/engine.rb +131 -43
- data/lib/sass/plugin/merb.rb +48 -12
- data/lib/sass/plugin/rails.rb +10 -4
- data/lib/sass/plugin.rb +33 -10
- data/lib/sass/tree/attr_node.rb +5 -5
- data/lib/sass/tree/directive_node.rb +2 -7
- data/lib/sass/tree/node.rb +1 -12
- data/lib/sass/tree/rule_node.rb +39 -31
- data/lib/sass/tree/value_node.rb +1 -1
- data/lib/sass.rb +194 -19
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +67 -80
- data/test/haml/engine_test.rb +368 -152
- data/test/haml/helper_test.rb +68 -16
- data/test/haml/html2haml_test.rb +3 -4
- data/test/haml/results/content_for_layout.xhtml +1 -2
- data/test/haml/results/eval_suppressed.xhtml +2 -4
- data/test/haml/results/filters.xhtml +38 -30
- data/test/haml/results/helpers.xhtml +4 -8
- data/test/haml/results/just_stuff.xhtml +8 -7
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +3 -7
- data/test/haml/results/partials.xhtml +1 -0
- data/test/haml/results/tag_parsing.xhtml +1 -6
- data/test/haml/results/very_basic.xhtml +2 -4
- data/test/haml/results/whitespace_handling.xhtml +13 -21
- data/test/haml/template_test.rb +42 -57
- data/test/haml/templates/_partial.haml +1 -0
- data/test/haml/templates/filters.haml +39 -21
- data/test/haml/templates/helpers.haml +10 -10
- data/test/haml/templates/just_stuff.haml +8 -3
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/partials.haml +1 -1
- data/test/haml/templates/tag_parsing.haml +0 -3
- data/test/haml/templates/whitespace_handling.haml +10 -10
- data/test/sass/engine_test.rb +97 -39
- data/test/sass/plugin_test.rb +4 -7
- data/test/sass/results/constants.css +2 -0
- data/test/sass/results/import.css +2 -2
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/templates/constants.sass +3 -0
- data/test/sass/templates/import.sass +4 -1
- data/test/sass/templates/importee.sass +4 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/test_helper.rb +18 -0
- metadata +70 -53
- data/lib/haml/helpers/action_view_mods.rb.rej +0 -30
- data/lib/haml/util.rb +0 -18
- data/lib/sass/constant.rb.rej +0 -42
- data/test/haml/runner.rb +0 -16
- data/test/profile.rb +0 -65
- data/test/sass/engine_test.rb.rej +0 -18
data/lib/sass/css.rb
CHANGED
|
@@ -6,11 +6,11 @@ module Sass
|
|
|
6
6
|
# :stopdoc:
|
|
7
7
|
module Tree
|
|
8
8
|
class Node
|
|
9
|
-
def to_sass
|
|
9
|
+
def to_sass(opts = {})
|
|
10
10
|
result = ''
|
|
11
11
|
|
|
12
12
|
children.each do |child|
|
|
13
|
-
result << "#{child.to_sass(0)}\n"
|
|
13
|
+
result << "#{child.to_sass(0, opts)}\n"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
result
|
|
@@ -18,17 +18,17 @@ module Sass
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
class ValueNode
|
|
21
|
-
def to_sass(tabs)
|
|
21
|
+
def to_sass(tabs, opts = {})
|
|
22
22
|
"#{value}\n"
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
class RuleNode
|
|
27
|
-
def to_sass(tabs)
|
|
28
|
-
str = "#{' ' * tabs}#{rule}\n"
|
|
27
|
+
def to_sass(tabs, opts = {})
|
|
28
|
+
str = "\n#{' ' * tabs}#{rule}#{children.any? { |c| c.is_a? AttrNode } ? "\n" : ''}"
|
|
29
29
|
|
|
30
30
|
children.each do |child|
|
|
31
|
-
str << "#{child.to_sass(tabs + 1)}"
|
|
31
|
+
str << "#{child.to_sass(tabs + 1, opts)}"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
str
|
|
@@ -36,8 +36,14 @@ module Sass
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
class AttrNode
|
|
39
|
-
def to_sass(tabs)
|
|
40
|
-
"#{' ' * tabs}
|
|
39
|
+
def to_sass(tabs, opts = {})
|
|
40
|
+
"#{' ' * tabs}#{opts[:alternate] ? '' : ':'}#{name}#{opts[:alternate] ? ':' : ''} #{value}\n"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class DirectiveNode
|
|
45
|
+
def to_sass(tabs, opts = {})
|
|
46
|
+
"#{' ' * tabs}#{value}#{children.map {|c| c.to_sass(tabs + 1, opts)}}\n"
|
|
41
47
|
end
|
|
42
48
|
end
|
|
43
49
|
end
|
|
@@ -46,25 +52,40 @@ module Sass
|
|
|
46
52
|
# It keeps the semantics and most of the efficiency of normal hashes
|
|
47
53
|
# while also keeping track of the order in which elements were set.
|
|
48
54
|
class OrderedHash
|
|
49
|
-
Node = Struct.new(
|
|
55
|
+
Node = Struct.new(:key, :value, :next, :prev)
|
|
50
56
|
include Enumerable
|
|
51
57
|
|
|
52
58
|
def initialize
|
|
53
59
|
@hash = {}
|
|
54
60
|
end
|
|
55
61
|
|
|
62
|
+
def initialize_copy(other)
|
|
63
|
+
@hash = other.instance_variable_get('@hash').clone
|
|
64
|
+
end
|
|
65
|
+
|
|
56
66
|
def [](key)
|
|
57
67
|
@hash[key] && @hash[key].value
|
|
58
68
|
end
|
|
59
69
|
|
|
60
70
|
def []=(key, value)
|
|
61
|
-
node = Node.new(key, value
|
|
71
|
+
node = Node.new(key, value)
|
|
72
|
+
|
|
73
|
+
if old = @hash[key]
|
|
74
|
+
if old.prev
|
|
75
|
+
old.prev.next = old.next
|
|
76
|
+
else # old is @first and @last
|
|
77
|
+
@first = @last = nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
62
81
|
if @first.nil?
|
|
63
82
|
@first = @last = node
|
|
64
83
|
else
|
|
84
|
+
node.prev = @last
|
|
65
85
|
@last.next = node
|
|
66
86
|
@last = node
|
|
67
87
|
end
|
|
88
|
+
|
|
68
89
|
@hash[key] = node
|
|
69
90
|
value
|
|
70
91
|
end
|
|
@@ -90,11 +111,12 @@ module Sass
|
|
|
90
111
|
|
|
91
112
|
# Creates a new instance of Sass::CSS that will compile the given document
|
|
92
113
|
# to a Sass string when +render+ is called.
|
|
93
|
-
def initialize(template)
|
|
114
|
+
def initialize(template, options = {})
|
|
94
115
|
if template.is_a? IO
|
|
95
116
|
template = template.read
|
|
96
117
|
end
|
|
97
118
|
|
|
119
|
+
@options = options
|
|
98
120
|
@template = StringScanner.new(template)
|
|
99
121
|
end
|
|
100
122
|
|
|
@@ -102,10 +124,10 @@ module Sass
|
|
|
102
124
|
# containing the CSS template.
|
|
103
125
|
def render
|
|
104
126
|
begin
|
|
105
|
-
build_tree.to_sass
|
|
127
|
+
build_tree.to_sass(@options).strip + "\n"
|
|
106
128
|
rescue Exception => err
|
|
107
129
|
line = @template.string[0...@template.pos].split("\n").size
|
|
108
|
-
|
|
130
|
+
|
|
109
131
|
err.backtrace.unshift "(css):#{line}"
|
|
110
132
|
raise err
|
|
111
133
|
end
|
|
@@ -116,46 +138,42 @@ module Sass
|
|
|
116
138
|
def build_tree
|
|
117
139
|
root = Tree::Node.new(nil)
|
|
118
140
|
whitespace
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
flatten_rules
|
|
124
|
-
fold_commas
|
|
141
|
+
rules root
|
|
142
|
+
expand_commas root
|
|
143
|
+
parent_ref_rules root
|
|
144
|
+
remove_parent_refs root
|
|
145
|
+
flatten_rules root
|
|
146
|
+
fold_commas root
|
|
125
147
|
root
|
|
126
148
|
end
|
|
127
149
|
|
|
128
|
-
def
|
|
129
|
-
while
|
|
130
|
-
|
|
131
|
-
whitespace
|
|
132
|
-
value = @template.scan /[^;]+/
|
|
133
|
-
assert_match /;/
|
|
150
|
+
def rules(root)
|
|
151
|
+
while r = rule
|
|
152
|
+
root << r
|
|
134
153
|
whitespace
|
|
135
|
-
|
|
136
|
-
if name == "import" && value =~ /^(url\()?"?([^\s\(\)\"]+)\.css"?\)?$/
|
|
137
|
-
value = $2
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
root << Tree::ValueNode.new("@#{name} #{value};", nil)
|
|
141
154
|
end
|
|
142
155
|
end
|
|
143
156
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
whitespace
|
|
157
|
+
def rule
|
|
158
|
+
return unless rule = @template.scan(/[^\{\};]+/)
|
|
159
|
+
rule.strip!
|
|
160
|
+
directive = rule[0] == ?@
|
|
149
161
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
rules = []
|
|
162
|
+
if directive
|
|
163
|
+
node = Tree::DirectiveNode.new(rule, nil)
|
|
164
|
+
return node if @template.scan(/;/)
|
|
154
165
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
assert_match /\{/
|
|
167
|
+
whitespace
|
|
168
|
+
|
|
169
|
+
rules(node)
|
|
170
|
+
return node
|
|
158
171
|
end
|
|
172
|
+
|
|
173
|
+
assert_match /\{/
|
|
174
|
+
node = Tree::RuleNode.new(rule, nil)
|
|
175
|
+
attributes(node)
|
|
176
|
+
return node
|
|
159
177
|
end
|
|
160
178
|
|
|
161
179
|
def attributes(rule)
|
|
@@ -164,12 +182,12 @@ module Sass
|
|
|
164
182
|
whitespace
|
|
165
183
|
|
|
166
184
|
assert_match /:/
|
|
167
|
-
|
|
185
|
+
|
|
168
186
|
value = ''
|
|
169
187
|
while @template.scan(/[^;\s\}]+/)
|
|
170
188
|
value << @template[0] << whitespace
|
|
171
189
|
end
|
|
172
|
-
|
|
190
|
+
|
|
173
191
|
assert_match /(;|(?=\}))/
|
|
174
192
|
rule << Tree::AttrNode.new(name, value, nil)
|
|
175
193
|
end
|
|
@@ -191,7 +209,10 @@ module Sass
|
|
|
191
209
|
|
|
192
210
|
def assert_match(re)
|
|
193
211
|
if !@template.scan(re)
|
|
194
|
-
|
|
212
|
+
line = @template.string[0..@template.pos].count "\n"
|
|
213
|
+
# Display basic regexps as plain old strings
|
|
214
|
+
expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
|
|
215
|
+
raise Exception.new("Invalid CSS on line #{line}: expected #{expected}")
|
|
195
216
|
end
|
|
196
217
|
whitespace
|
|
197
218
|
end
|
|
@@ -224,7 +245,22 @@ module Sass
|
|
|
224
245
|
root.children.flatten!
|
|
225
246
|
end
|
|
226
247
|
|
|
227
|
-
#
|
|
248
|
+
# Make rules use parent refs so that
|
|
249
|
+
#
|
|
250
|
+
# foo
|
|
251
|
+
# color: green
|
|
252
|
+
# foo.bar
|
|
253
|
+
# color: blue
|
|
254
|
+
#
|
|
255
|
+
# becomes
|
|
256
|
+
#
|
|
257
|
+
# foo
|
|
258
|
+
# color: green
|
|
259
|
+
# &.bar
|
|
260
|
+
# color: blue
|
|
261
|
+
#
|
|
262
|
+
# This has the side effect of nesting rules,
|
|
263
|
+
# so that
|
|
228
264
|
#
|
|
229
265
|
# foo
|
|
230
266
|
# color: green
|
|
@@ -237,29 +273,50 @@ module Sass
|
|
|
237
273
|
#
|
|
238
274
|
# foo
|
|
239
275
|
# color: green
|
|
240
|
-
# bar
|
|
276
|
+
# & bar
|
|
241
277
|
# color: red
|
|
242
|
-
# baz
|
|
278
|
+
# & baz
|
|
243
279
|
# color: blue
|
|
244
|
-
#
|
|
245
|
-
def
|
|
280
|
+
#
|
|
281
|
+
def parent_ref_rules(root)
|
|
246
282
|
rules = OrderedHash.new
|
|
247
283
|
root.children.select { |c| Tree::RuleNode === c }.each do |child|
|
|
248
284
|
root.children.delete child
|
|
249
|
-
first, rest = child.rule.
|
|
285
|
+
first, rest = child.rule.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
|
|
250
286
|
rules[first] ||= Tree::RuleNode.new(first, nil)
|
|
251
287
|
if rest
|
|
252
|
-
child.rule = rest
|
|
288
|
+
child.rule = "&" + rest
|
|
253
289
|
rules[first] << child
|
|
254
290
|
else
|
|
255
291
|
rules[first].children += child.children
|
|
256
292
|
end
|
|
257
293
|
end
|
|
258
294
|
|
|
259
|
-
rules.values.each { |v|
|
|
295
|
+
rules.values.each { |v| parent_ref_rules(v) }
|
|
260
296
|
root.children += rules.values
|
|
261
297
|
end
|
|
262
298
|
|
|
299
|
+
# Remove useless parent refs so that
|
|
300
|
+
#
|
|
301
|
+
# foo
|
|
302
|
+
# & bar
|
|
303
|
+
# color: blue
|
|
304
|
+
#
|
|
305
|
+
# becomes
|
|
306
|
+
#
|
|
307
|
+
# foo
|
|
308
|
+
# bar
|
|
309
|
+
# color: blue
|
|
310
|
+
#
|
|
311
|
+
def remove_parent_refs(root)
|
|
312
|
+
root.children.each do |child|
|
|
313
|
+
if child.is_a?(Tree::RuleNode)
|
|
314
|
+
child.rule.gsub! /^& /, ''
|
|
315
|
+
remove_parent_refs child
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
263
320
|
# Flatten rules so that
|
|
264
321
|
#
|
|
265
322
|
# foo
|
|
@@ -271,7 +328,18 @@ module Sass
|
|
|
271
328
|
#
|
|
272
329
|
# foo bar baz
|
|
273
330
|
# color: red
|
|
274
|
-
#
|
|
331
|
+
#
|
|
332
|
+
# and
|
|
333
|
+
#
|
|
334
|
+
# foo
|
|
335
|
+
# &.bar
|
|
336
|
+
# color: blue
|
|
337
|
+
#
|
|
338
|
+
# becomes
|
|
339
|
+
#
|
|
340
|
+
# foo.bar
|
|
341
|
+
# color: blue
|
|
342
|
+
#
|
|
275
343
|
def flatten_rules(root)
|
|
276
344
|
root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) }
|
|
277
345
|
end
|
|
@@ -279,7 +347,13 @@ module Sass
|
|
|
279
347
|
def flatten_rule(rule)
|
|
280
348
|
while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
|
|
281
349
|
child = rule.children.first
|
|
282
|
-
|
|
350
|
+
|
|
351
|
+
if child.rule[0] == ?&
|
|
352
|
+
rule.rule = child.rule.gsub /^&/, rule.rule
|
|
353
|
+
else
|
|
354
|
+
rule.rule = "#{rule.rule} #{child.rule}"
|
|
355
|
+
end
|
|
356
|
+
|
|
283
357
|
rule.children = child.children
|
|
284
358
|
end
|
|
285
359
|
|
data/lib/sass/engine.rb
CHANGED
|
@@ -6,7 +6,6 @@ require 'sass/tree/attr_node'
|
|
|
6
6
|
require 'sass/tree/directive_node'
|
|
7
7
|
require 'sass/constant'
|
|
8
8
|
require 'sass/error'
|
|
9
|
-
require 'haml/util'
|
|
10
9
|
|
|
11
10
|
module Sass
|
|
12
11
|
# This is the class where all the parsing and processing of the Sass
|
|
@@ -39,10 +38,16 @@ module Sass
|
|
|
39
38
|
|
|
40
39
|
# The character used to denote a compiler directive.
|
|
41
40
|
DIRECTIVE_CHAR = ?@
|
|
42
|
-
|
|
41
|
+
|
|
43
42
|
# Designates a non-parsed rule.
|
|
44
43
|
ESCAPE_CHAR = ?\\
|
|
45
44
|
|
|
45
|
+
# Designates block as mixin definition rather than CSS rules to output
|
|
46
|
+
MIXIN_DEFINITION_CHAR = ?=
|
|
47
|
+
|
|
48
|
+
# Includes named mixin declared using MIXIN_DEFINITION_CHAR
|
|
49
|
+
MIXIN_INCLUDE_CHAR = ?+
|
|
50
|
+
|
|
46
51
|
# The regex that matches and extracts data from
|
|
47
52
|
# attributes of the form <tt>:name attr</tt>.
|
|
48
53
|
ATTRIBUTE = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/
|
|
@@ -56,14 +61,14 @@ module Sass
|
|
|
56
61
|
|
|
57
62
|
# Creates a new instace of Sass::Engine that will compile the given
|
|
58
63
|
# template string when <tt>render</tt> is called.
|
|
59
|
-
# See README for available options.
|
|
64
|
+
# See README.rdoc for available options.
|
|
60
65
|
#
|
|
61
66
|
#--
|
|
62
67
|
#
|
|
63
68
|
# TODO: Add current options to REFRENCE. Remember :filename!
|
|
64
69
|
#
|
|
65
70
|
# When adding options, remember to add information about them
|
|
66
|
-
# to README!
|
|
71
|
+
# to README.rdoc!
|
|
67
72
|
#++
|
|
68
73
|
#
|
|
69
74
|
def initialize(template, options={})
|
|
@@ -71,9 +76,10 @@ module Sass
|
|
|
71
76
|
:style => :nested,
|
|
72
77
|
:load_paths => ['.']
|
|
73
78
|
}.merge! options
|
|
74
|
-
@template = template.split(/\n
|
|
79
|
+
@template = template.split(/\r\n|\r|\n/)
|
|
75
80
|
@lines = []
|
|
76
81
|
@constants = {"important" => "!important"}
|
|
82
|
+
@mixins = {}
|
|
77
83
|
end
|
|
78
84
|
|
|
79
85
|
# Processes the template and returns the result as a string.
|
|
@@ -96,16 +102,21 @@ module Sass
|
|
|
96
102
|
@constants
|
|
97
103
|
end
|
|
98
104
|
|
|
105
|
+
def mixins
|
|
106
|
+
@mixins
|
|
107
|
+
end
|
|
108
|
+
|
|
99
109
|
def render_to_tree
|
|
100
110
|
split_lines
|
|
101
111
|
|
|
102
112
|
root = Tree::Node.new(@options[:style])
|
|
103
113
|
index = 0
|
|
104
114
|
while @lines[index]
|
|
115
|
+
old_index = index
|
|
105
116
|
child, index = build_tree(index)
|
|
106
117
|
|
|
107
118
|
if child.is_a? Tree::Node
|
|
108
|
-
child.line =
|
|
119
|
+
child.line = old_index + 1
|
|
109
120
|
root << child
|
|
110
121
|
elsif child.is_a? Array
|
|
111
122
|
child.each do |c|
|
|
@@ -113,7 +124,7 @@ module Sass
|
|
|
113
124
|
end
|
|
114
125
|
end
|
|
115
126
|
end
|
|
116
|
-
@
|
|
127
|
+
@lines.clear
|
|
117
128
|
|
|
118
129
|
root
|
|
119
130
|
end
|
|
@@ -124,7 +135,7 @@ module Sass
|
|
|
124
135
|
# and computes the tabulation of the line.
|
|
125
136
|
def split_lines
|
|
126
137
|
@line = 0
|
|
127
|
-
old_tabs =
|
|
138
|
+
old_tabs = nil
|
|
128
139
|
@template.each_with_index do |line, index|
|
|
129
140
|
@line += 1
|
|
130
141
|
|
|
@@ -135,14 +146,16 @@ module Sass
|
|
|
135
146
|
end
|
|
136
147
|
|
|
137
148
|
if tabs # if line isn't blank
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line) if old_tabs.nil? && tabs > 0
|
|
150
|
+
|
|
151
|
+
if old_tabs && tabs - old_tabs > 1
|
|
152
|
+
raise SyntaxError.new("#{tabs * 2} spaces were used for indentation. Sass must be indented using two spaces.", @line)
|
|
140
153
|
end
|
|
141
154
|
@lines << [line.strip, tabs]
|
|
142
155
|
|
|
143
156
|
old_tabs = tabs
|
|
144
157
|
else
|
|
145
|
-
@lines << ['//', old_tabs]
|
|
158
|
+
@lines << ['//', old_tabs || 0]
|
|
146
159
|
end
|
|
147
160
|
end
|
|
148
161
|
|
|
@@ -151,18 +164,20 @@ module Sass
|
|
|
151
164
|
|
|
152
165
|
# Counts the tabulation of a line.
|
|
153
166
|
def count_tabs(line)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
return nil if line.strip.empty?
|
|
168
|
+
return nil unless spaces = line.index(/[^ ]/)
|
|
169
|
+
|
|
170
|
+
if spaces % 2 == 1
|
|
171
|
+
raise SyntaxError.new(<<END.strip, @line)
|
|
172
|
+
#{spaces} space#{spaces == 1 ? ' was' : 's were'} used for indentation. Sass must be indented using two spaces.
|
|
173
|
+
END
|
|
174
|
+
elsif line[spaces] == ?\t
|
|
175
|
+
raise SyntaxError.new(<<END.strip, @line)
|
|
176
|
+
A tab character was used for indentation. Sass must be indented using two spaces.
|
|
177
|
+
Are you sure you have soft tabs enabled in your editor?
|
|
178
|
+
END
|
|
165
179
|
end
|
|
180
|
+
spaces / 2
|
|
166
181
|
end
|
|
167
182
|
|
|
168
183
|
def build_tree(index)
|
|
@@ -177,15 +192,23 @@ module Sass
|
|
|
177
192
|
unless node.is_a? Tree::Node
|
|
178
193
|
if has_children
|
|
179
194
|
if node == :constant
|
|
180
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line)
|
|
195
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line + 1)
|
|
181
196
|
elsif node.is_a? Array
|
|
182
|
-
|
|
197
|
+
# arrays can either be full of import statements
|
|
198
|
+
# or attributes from mixin includes
|
|
199
|
+
# in either case they shouldn't have children.
|
|
200
|
+
# Need to peek into the array in order to give meaningful errors
|
|
201
|
+
directive_type = (node.first.is_a?(Tree::DirectiveNode) ? "import" : "mixin")
|
|
202
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath #{directive_type} directives.", @line + 1)
|
|
183
203
|
end
|
|
184
204
|
end
|
|
185
205
|
|
|
206
|
+
index = @line if node == :mixin
|
|
186
207
|
return node, index
|
|
187
208
|
end
|
|
188
209
|
|
|
210
|
+
node.line = @line
|
|
211
|
+
|
|
189
212
|
if node.is_a? Tree::CommentNode
|
|
190
213
|
while has_children
|
|
191
214
|
line, index = raw_next_line(index)
|
|
@@ -193,26 +216,52 @@ module Sass
|
|
|
193
216
|
|
|
194
217
|
has_children = has_children?(index, tabs)
|
|
195
218
|
end
|
|
196
|
-
else
|
|
197
|
-
while has_children
|
|
198
|
-
child, index = build_tree(index)
|
|
199
219
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
220
|
+
return node, index
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Resolve multiline rules
|
|
224
|
+
if node.is_a?(Tree::RuleNode)
|
|
225
|
+
if node.continued?
|
|
226
|
+
child, index = build_tree(index) if @lines[old_index = index]
|
|
227
|
+
if @lines[old_index].nil? || has_children?(old_index, tabs) || !child.is_a?(Tree::RuleNode)
|
|
228
|
+
raise SyntaxError.new("Rules can't end in commas.", @line)
|
|
207
229
|
end
|
|
208
230
|
|
|
209
|
-
|
|
231
|
+
node.add_rules child
|
|
210
232
|
end
|
|
233
|
+
node.children = child.children if child
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
while has_children
|
|
237
|
+
child, index = build_tree(index)
|
|
238
|
+
|
|
239
|
+
validate_and_append_child(node, child)
|
|
240
|
+
|
|
241
|
+
has_children = has_children?(index, tabs)
|
|
211
242
|
end
|
|
212
243
|
|
|
213
244
|
return node, index
|
|
214
245
|
end
|
|
215
246
|
|
|
247
|
+
def validate_and_append_child(parent, child)
|
|
248
|
+
case child
|
|
249
|
+
when :constant
|
|
250
|
+
raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
|
|
251
|
+
when :mixin
|
|
252
|
+
raise SyntaxError.new("Mixins may only be defined at the root of a document.", @line)
|
|
253
|
+
when Array
|
|
254
|
+
child.each do |c|
|
|
255
|
+
if c.is_a?(Tree::DirectiveNode)
|
|
256
|
+
raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
|
|
257
|
+
end
|
|
258
|
+
parent << c
|
|
259
|
+
end
|
|
260
|
+
when Tree::Node
|
|
261
|
+
parent << child
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
216
265
|
def has_children?(index, tabs)
|
|
217
266
|
next_line = ['//', 0]
|
|
218
267
|
while !next_line.nil? && next_line[0] == '//' && next_line[1] = 0
|
|
@@ -238,6 +287,14 @@ module Sass
|
|
|
238
287
|
parse_directive(line)
|
|
239
288
|
when ESCAPE_CHAR
|
|
240
289
|
Tree::RuleNode.new(line[1..-1], @options[:style])
|
|
290
|
+
when MIXIN_DEFINITION_CHAR
|
|
291
|
+
parse_mixin_definition(line)
|
|
292
|
+
when MIXIN_INCLUDE_CHAR
|
|
293
|
+
if line[1].nil? || line[1] == ?\s
|
|
294
|
+
Tree::RuleNode.new(line, @options[:style])
|
|
295
|
+
else
|
|
296
|
+
parse_mixin_include(line)
|
|
297
|
+
end
|
|
241
298
|
else
|
|
242
299
|
if line =~ ATTRIBUTE_ALTERNATE_MATCHER
|
|
243
300
|
parse_attribute(line, ATTRIBUTE_ALTERNATE)
|
|
@@ -259,7 +316,7 @@ module Sass
|
|
|
259
316
|
name, eq, value = line.scan(attribute_regx)[0]
|
|
260
317
|
|
|
261
318
|
if name.nil? || value.nil?
|
|
262
|
-
raise SyntaxError.new("Invalid attribute: \"#{line}\"", @line)
|
|
319
|
+
raise SyntaxError.new("Invalid attribute: \"#{line}\".", @line)
|
|
263
320
|
end
|
|
264
321
|
|
|
265
322
|
if eq.strip[0] == SCRIPT_CHAR
|
|
@@ -270,11 +327,18 @@ module Sass
|
|
|
270
327
|
end
|
|
271
328
|
|
|
272
329
|
def parse_constant(line)
|
|
273
|
-
name, value = line.scan(Sass::Constant::MATCH)[0]
|
|
330
|
+
name, op, value = line.scan(Sass::Constant::MATCH)[0]
|
|
274
331
|
unless name && value
|
|
275
|
-
raise SyntaxError.new("Invalid constant: \"#{line}\"", @line)
|
|
332
|
+
raise SyntaxError.new("Invalid constant: \"#{line}\".", @line)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
constant = Sass::Constant.parse(value, @constants, @line)
|
|
336
|
+
if op == '||='
|
|
337
|
+
@constants[name] ||= constant
|
|
338
|
+
else
|
|
339
|
+
@constants[name] = constant
|
|
276
340
|
end
|
|
277
|
-
|
|
341
|
+
|
|
278
342
|
:constant
|
|
279
343
|
end
|
|
280
344
|
|
|
@@ -291,14 +355,36 @@ module Sass
|
|
|
291
355
|
def parse_directive(line)
|
|
292
356
|
directive, value = line[1..-1].split(/\s+/, 2)
|
|
293
357
|
|
|
294
|
-
|
|
295
|
-
|
|
358
|
+
# If value begins with url( or ",
|
|
359
|
+
# it's a CSS @import rule and we don't want to touch it.
|
|
360
|
+
if directive == "import" && value !~ /^(url\(|")/
|
|
296
361
|
import(value)
|
|
297
362
|
else
|
|
298
363
|
Tree::DirectiveNode.new(line, @options[:style])
|
|
299
364
|
end
|
|
300
365
|
end
|
|
301
366
|
|
|
367
|
+
def parse_mixin_definition(line)
|
|
368
|
+
mixin_name = line[1..-1]
|
|
369
|
+
@mixins[mixin_name] = []
|
|
370
|
+
index = @line
|
|
371
|
+
line, tabs = @lines[index]
|
|
372
|
+
while !line.nil? && tabs > 0
|
|
373
|
+
child, index = build_tree(index)
|
|
374
|
+
validate_and_append_child(@mixins[mixin_name], child)
|
|
375
|
+
line, tabs = @lines[index]
|
|
376
|
+
end
|
|
377
|
+
:mixin
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def parse_mixin_include(line)
|
|
381
|
+
mixin_name = line[1..-1]
|
|
382
|
+
unless @mixins.has_key?(mixin_name)
|
|
383
|
+
raise SyntaxError.new("Undefined mixin '#{mixin_name}'.", @line)
|
|
384
|
+
end
|
|
385
|
+
@mixins[mixin_name]
|
|
386
|
+
end
|
|
387
|
+
|
|
302
388
|
def import(files)
|
|
303
389
|
nodes = []
|
|
304
390
|
|
|
@@ -312,7 +398,7 @@ module Sass
|
|
|
312
398
|
end
|
|
313
399
|
|
|
314
400
|
if filename =~ /\.css$/
|
|
315
|
-
nodes << Tree::
|
|
401
|
+
nodes << Tree::DirectiveNode.new("@import url(#{filename})", @options[:style])
|
|
316
402
|
else
|
|
317
403
|
File.open(filename) do |file|
|
|
318
404
|
new_options = @options.dup
|
|
@@ -321,6 +407,7 @@ module Sass
|
|
|
321
407
|
end
|
|
322
408
|
|
|
323
409
|
engine.constants.merge! @constants
|
|
410
|
+
engine.mixins.merge! @mixins
|
|
324
411
|
|
|
325
412
|
begin
|
|
326
413
|
root = engine.render_to_tree
|
|
@@ -333,6 +420,7 @@ module Sass
|
|
|
333
420
|
nodes << child
|
|
334
421
|
end
|
|
335
422
|
@constants = engine.constants
|
|
423
|
+
@mixins = engine.mixins
|
|
336
424
|
end
|
|
337
425
|
end
|
|
338
426
|
|
|
@@ -354,7 +442,7 @@ module Sass
|
|
|
354
442
|
|
|
355
443
|
if new_filename.nil?
|
|
356
444
|
if was_sass
|
|
357
|
-
raise Exception.new("File to import not found or unreadable: #{original_filename}")
|
|
445
|
+
raise Exception.new("File to import not found or unreadable: #{original_filename}.")
|
|
358
446
|
else
|
|
359
447
|
return filename + '.css'
|
|
360
448
|
end
|