haml 1.8.0 → 2.0.0
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/Rakefile +111 -147
- data/VERSION +1 -1
- data/bin/css2sass +0 -0
- data/bin/haml +0 -0
- data/bin/html2haml +0 -0
- data/bin/sass +0 -0
- data/init.rb +6 -1
- data/lib/haml/buffer.rb +121 -63
- data/lib/haml/engine.rb +67 -44
- data/lib/haml/error.rb +16 -6
- data/lib/haml/exec.rb +37 -7
- data/lib/haml/filters.rb +213 -68
- data/lib/haml/helpers/action_view_extensions.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +57 -9
- data/lib/haml/helpers.rb +105 -61
- data/lib/haml/html.rb +6 -6
- data/lib/haml/precompiler.rb +268 -132
- data/lib/haml/template/patch.rb +9 -2
- data/lib/haml/template/plugin.rb +61 -10
- data/lib/haml/template.rb +3 -6
- data/lib/haml.rb +464 -201
- data/lib/sass/constant/color.rb +13 -13
- data/lib/sass/constant/literal.rb +7 -7
- data/lib/sass/constant/number.rb +9 -9
- data/lib/sass/constant/operation.rb +4 -4
- data/lib/sass/constant/string.rb +3 -3
- data/lib/sass/constant.rb +22 -22
- data/lib/sass/css.rb +104 -31
- data/lib/sass/engine.rb +120 -39
- data/lib/sass/error.rb +1 -1
- data/lib/sass/plugin/merb.rb +48 -12
- data/lib/sass/plugin/rails.rb +10 -4
- data/lib/sass/plugin.rb +22 -3
- 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 +157 -12
- data/test/benchmark.rb +67 -80
- data/test/haml/engine_test.rb +318 -81
- data/test/haml/helper_test.rb +52 -16
- 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 +44 -15
- data/test/haml/results/helpers.xhtml +2 -3
- data/test/haml/results/just_stuff.xhtml +2 -6
- data/test/haml/results/nuke_inner_whitespace.xhtml +34 -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 +8 -15
- data/test/haml/templates/_partial.haml +1 -0
- data/test/haml/templates/filters.haml +48 -7
- data/test/haml/templates/helpers.haml +9 -9
- data/test/haml/templates/just_stuff.haml +1 -2
- data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/tag_parsing.haml +0 -3
- data/test/haml/test_helper.rb +15 -0
- data/test/sass/engine_test.rb +84 -34
- data/test/sass/plugin_test.rb +1 -1
- 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/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
- metadata +65 -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/constant/color.rb
CHANGED
|
@@ -21,9 +21,9 @@ module Sass::Constant # :nodoc:
|
|
|
21
21
|
'teal' => 0x008080,
|
|
22
22
|
'aqua' => 0x00ffff
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
REGEXP = /\##{"([0-9a-fA-F]{1,2})" * 3}/
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
def parse(value)
|
|
28
28
|
if (value =~ REGEXP)
|
|
29
29
|
@value = value.scan(REGEXP)[0].map { |num| num.ljust(2, num).to_i(16) }
|
|
@@ -32,7 +32,7 @@ module Sass::Constant # :nodoc:
|
|
|
32
32
|
@value = (0..2).map{ |n| color >> (n << 3) & 0xff }.reverse
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
def plus(other)
|
|
37
37
|
if other.is_a? Sass::Constant::String
|
|
38
38
|
Sass::Constant::String.from_value(self.to_s + other.to_s)
|
|
@@ -40,7 +40,7 @@ module Sass::Constant # :nodoc:
|
|
|
40
40
|
piecewise(other, :+)
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
def minus(other)
|
|
45
45
|
if other.is_a? Sass::Constant::String
|
|
46
46
|
raise NoMethodError.new(nil, :minus)
|
|
@@ -48,7 +48,7 @@ module Sass::Constant # :nodoc:
|
|
|
48
48
|
piecewise(other, :-)
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
def times(other)
|
|
53
53
|
if other.is_a? Sass::Constant::String
|
|
54
54
|
raise NoMethodError.new(nil, :times)
|
|
@@ -56,7 +56,7 @@ module Sass::Constant # :nodoc:
|
|
|
56
56
|
piecewise(other, :*)
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
def div(other)
|
|
61
61
|
if other.is_a? Sass::Constant::String
|
|
62
62
|
raise NoMethodError.new(nil, :div)
|
|
@@ -64,7 +64,7 @@ module Sass::Constant # :nodoc:
|
|
|
64
64
|
piecewise(other, :/)
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
def mod(other)
|
|
69
69
|
if other.is_a? Sass::Constant::String
|
|
70
70
|
raise NoMethodError.new(nil, :mod)
|
|
@@ -72,24 +72,24 @@ module Sass::Constant # :nodoc:
|
|
|
72
72
|
piecewise(other, :%)
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
def to_s
|
|
77
77
|
red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') }
|
|
78
78
|
"##{red}#{green}#{blue}"
|
|
79
79
|
end
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
protected
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
def self.filter_value(value)
|
|
84
84
|
value.map { |num| num.to_i }
|
|
85
85
|
end
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
private
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
def piecewise(other, operation)
|
|
90
90
|
other_num = other.is_a? Number
|
|
91
91
|
other_val = other.value
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
rgb = []
|
|
94
94
|
for i in (0...3)
|
|
95
95
|
res = @value[i].send(operation, other_num ? other_val : other_val[i])
|
|
@@ -13,7 +13,7 @@ class Sass::Constant::Literal # :nodoc:
|
|
|
13
13
|
|
|
14
14
|
# The regular expression matching colors.
|
|
15
15
|
COLOR = /^\# (?: [\da-f]{3} | [\da-f]{6} ) | #{html_color_matcher}/ix
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
def self.parse(value)
|
|
18
18
|
case value
|
|
19
19
|
when NUMBER
|
|
@@ -24,11 +24,11 @@ class Sass::Constant::Literal # :nodoc:
|
|
|
24
24
|
Sass::Constant::String.new(value)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
def initialize(value = nil)
|
|
29
29
|
self.parse(value) if value
|
|
30
30
|
end
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
def perform
|
|
33
33
|
self
|
|
34
34
|
end
|
|
@@ -36,15 +36,15 @@ class Sass::Constant::Literal # :nodoc:
|
|
|
36
36
|
def concat(other)
|
|
37
37
|
Sass::Constant::String.from_value("#{self.to_s} #{other.to_s}")
|
|
38
38
|
end
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
attr_reader :value
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
protected
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
def self.filter_value(value)
|
|
45
45
|
value
|
|
46
46
|
end
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
def self.from_value(value)
|
|
49
49
|
instance = self.new
|
|
50
50
|
instance.instance_variable_set('@value', self.filter_value(value))
|
data/lib/sass/constant/number.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Sass::Constant # :nodoc:
|
|
|
10
10
|
@value = first.empty? ? second.to_i : "#{first}#{second}".to_f
|
|
11
11
|
@unit = unit unless unit.empty?
|
|
12
12
|
end
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
def plus(other)
|
|
15
15
|
if other.is_a? Number
|
|
16
16
|
operate(other, :+)
|
|
@@ -20,7 +20,7 @@ module Sass::Constant # :nodoc:
|
|
|
20
20
|
Sass::Constant::String.from_value(self.to_s + other.to_s)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
def minus(other)
|
|
25
25
|
if other.is_a? Number
|
|
26
26
|
operate(other, :-)
|
|
@@ -28,7 +28,7 @@ module Sass::Constant # :nodoc:
|
|
|
28
28
|
raise NoMethodError.new(nil, :minus)
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
def times(other)
|
|
33
33
|
if other.is_a? Number
|
|
34
34
|
operate(other, :*)
|
|
@@ -38,7 +38,7 @@ module Sass::Constant # :nodoc:
|
|
|
38
38
|
raise NoMethodError.new(nil, :times)
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
def div(other)
|
|
43
43
|
if other.is_a? Number
|
|
44
44
|
operate(other, :/)
|
|
@@ -46,7 +46,7 @@ module Sass::Constant # :nodoc:
|
|
|
46
46
|
raise NoMethodError.new(nil, :div)
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
def mod(other)
|
|
51
51
|
if other.is_a? Number
|
|
52
52
|
operate(other, :%)
|
|
@@ -54,13 +54,13 @@ module Sass::Constant # :nodoc:
|
|
|
54
54
|
raise NoMethodError.new(nil, :mod)
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
def to_s
|
|
59
59
|
value = @value
|
|
60
60
|
value = value.to_i if value % 1 == 0.0
|
|
61
61
|
"#{value}#{@unit}"
|
|
62
62
|
end
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
protected
|
|
65
65
|
|
|
66
66
|
def self.from_value(value, unit=nil)
|
|
@@ -68,7 +68,7 @@ module Sass::Constant # :nodoc:
|
|
|
68
68
|
instance.instance_variable_set('@unit', unit)
|
|
69
69
|
instance
|
|
70
70
|
end
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
def operate(other, operation)
|
|
73
73
|
unit = nil
|
|
74
74
|
if other.unit.nil?
|
|
@@ -78,7 +78,7 @@ module Sass::Constant # :nodoc:
|
|
|
78
78
|
elsif other.unit == self.unit
|
|
79
79
|
unit = self.unit
|
|
80
80
|
else
|
|
81
|
-
raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}")
|
|
81
|
+
raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}.")
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
Number.from_value(self.value.send(operation, other.value), unit)
|
|
@@ -9,13 +9,13 @@ module Sass::Constant # :nodoc:
|
|
|
9
9
|
@operand2 = operand2
|
|
10
10
|
@operator = operator
|
|
11
11
|
end
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
def to_s
|
|
14
14
|
self.perform.to_s
|
|
15
15
|
end
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
protected
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def perform
|
|
20
20
|
literal1 = @operand1.perform
|
|
21
21
|
literal2 = @operand2.perform
|
|
@@ -23,7 +23,7 @@ module Sass::Constant # :nodoc:
|
|
|
23
23
|
literal1.send(@operator, literal2)
|
|
24
24
|
rescue NoMethodError => e
|
|
25
25
|
raise e unless e.name.to_s == @operator.to_s
|
|
26
|
-
raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\"")
|
|
26
|
+
raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\".")
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
data/lib/sass/constant/string.rb
CHANGED
|
@@ -2,11 +2,11 @@ require 'sass/constant/literal'
|
|
|
2
2
|
|
|
3
3
|
module Sass::Constant # :nodoc:
|
|
4
4
|
class String < Literal # :nodoc:
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
def parse(value)
|
|
7
7
|
@value = value
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
def plus(other)
|
|
11
11
|
Sass::Constant::String.from_value(self.to_s + other.to_s)
|
|
12
12
|
end
|
|
@@ -14,7 +14,7 @@ module Sass::Constant # :nodoc:
|
|
|
14
14
|
def funcall(other)
|
|
15
15
|
Sass::Constant::String.from_value("#{self.to_s}(#{other.to_s})")
|
|
16
16
|
end
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
def to_s
|
|
19
19
|
@value
|
|
20
20
|
end
|
data/lib/sass/constant.rb
CHANGED
|
@@ -8,13 +8,13 @@ module Sass
|
|
|
8
8
|
|
|
9
9
|
# Whitespace characters
|
|
10
10
|
WHITESPACE = [?\ , ?\t, ?\n, ?\r]
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
# The character used to escape values
|
|
13
13
|
ESCAPE_CHAR = ?\\
|
|
14
14
|
|
|
15
15
|
# The character used to open and close strings
|
|
16
16
|
STRING_CHAR = ?"
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
# A mapping of syntactically-significant characters
|
|
19
19
|
# to parsed symbols
|
|
20
20
|
SYMBOLS = {
|
|
@@ -31,14 +31,14 @@ module Sass
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
# The regular expression used to parse constants
|
|
34
|
-
MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }.join}]+)\s
|
|
35
|
-
|
|
34
|
+
MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }.join}]+)\s*((?:\|\|)?=)\s*(.+)/
|
|
35
|
+
|
|
36
36
|
# First-order operations
|
|
37
37
|
FIRST_ORDER = [:times, :div, :mod]
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
# Second-order operations
|
|
40
40
|
SECOND_ORDER = [:plus, :minus]
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
class << self
|
|
43
43
|
def parse(value, constants, line)
|
|
44
44
|
begin
|
|
@@ -46,28 +46,28 @@ module Sass
|
|
|
46
46
|
rescue Sass::SyntaxError => e
|
|
47
47
|
if e.message == "Constant arithmetic error"
|
|
48
48
|
e.instance_eval do
|
|
49
|
-
@message += ": #{value.dump}"
|
|
49
|
+
@message += ": #{value.dump}."
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
e.sass_line = line
|
|
53
53
|
raise e
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
private
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
def tokenize(value)
|
|
60
60
|
escaped = false
|
|
61
61
|
is_string = false
|
|
62
62
|
beginning_of_token = true
|
|
63
63
|
str = ''
|
|
64
64
|
to_return = []
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
reset_str = Proc.new do
|
|
67
67
|
to_return << str unless str.empty?
|
|
68
68
|
''
|
|
69
69
|
end
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
value.each_byte do |byte|
|
|
72
72
|
unless escaped
|
|
73
73
|
if byte == ESCAPE_CHAR
|
|
@@ -97,7 +97,7 @@ module Sass
|
|
|
97
97
|
str = reset_str.call
|
|
98
98
|
next
|
|
99
99
|
end
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
symbol = SYMBOLS[byte]
|
|
102
102
|
|
|
103
103
|
# Adjacent values without an operator should be concatenated
|
|
@@ -135,28 +135,28 @@ module Sass
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
escaped = false
|
|
140
140
|
beginning_of_token = false
|
|
141
141
|
str << byte.chr
|
|
142
142
|
end
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
if is_string
|
|
145
|
-
raise Sass::SyntaxError.new("Unterminated string: #{value.dump}")
|
|
145
|
+
raise Sass::SyntaxError.new("Unterminated string: #{value.dump}.")
|
|
146
146
|
end
|
|
147
147
|
str = reset_str.call
|
|
148
148
|
to_return
|
|
149
149
|
end
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
def parenthesize(value)
|
|
152
152
|
parenthesize_helper(0, value, value.length)[0]
|
|
153
153
|
end
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
def parenthesize_helper(i, value, value_len, return_after_expr = false)
|
|
156
156
|
to_return = []
|
|
157
157
|
beginning = i
|
|
158
158
|
token = value[i]
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
while i < value_len && token != :close
|
|
161
161
|
if token == :open
|
|
162
162
|
to_return.push(*value[beginning...i])
|
|
@@ -194,13 +194,13 @@ module Sass
|
|
|
194
194
|
else
|
|
195
195
|
i += 1
|
|
196
196
|
end
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
token = value[i]
|
|
199
199
|
end
|
|
200
200
|
to_return.push(*value[beginning...i])
|
|
201
201
|
return to_return, i + 1
|
|
202
202
|
end
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
#--
|
|
205
205
|
# TODO: Don't pass around original value;
|
|
206
206
|
# have Constant.parse automatically add it to exception.
|
|
@@ -234,10 +234,10 @@ module Sass
|
|
|
234
234
|
end
|
|
235
235
|
end
|
|
236
236
|
end
|
|
237
|
-
|
|
237
|
+
|
|
238
238
|
def get_constant(value, constants)
|
|
239
239
|
to_return = constants[value]
|
|
240
|
-
raise SyntaxError.new("Undefined constant: \"!#{value}\"") unless to_return
|
|
240
|
+
raise SyntaxError.new("Undefined constant: \"!#{value}\".") unless to_return
|
|
241
241
|
to_return
|
|
242
242
|
end
|
|
243
243
|
end
|
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
|
|
@@ -24,11 +24,11 @@ module Sass
|
|
|
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,8 @@ 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
41
|
end
|
|
42
42
|
end
|
|
43
43
|
end
|
|
@@ -46,25 +46,40 @@ module Sass
|
|
|
46
46
|
# It keeps the semantics and most of the efficiency of normal hashes
|
|
47
47
|
# while also keeping track of the order in which elements were set.
|
|
48
48
|
class OrderedHash
|
|
49
|
-
Node = Struct.new(
|
|
49
|
+
Node = Struct.new(:key, :value, :next, :prev)
|
|
50
50
|
include Enumerable
|
|
51
51
|
|
|
52
52
|
def initialize
|
|
53
53
|
@hash = {}
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
def initialize_copy(other)
|
|
57
|
+
@hash = other.instance_variable_get('@hash').clone
|
|
58
|
+
end
|
|
59
|
+
|
|
56
60
|
def [](key)
|
|
57
61
|
@hash[key] && @hash[key].value
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
def []=(key, value)
|
|
61
|
-
node = Node.new(key, value
|
|
65
|
+
node = Node.new(key, value)
|
|
66
|
+
|
|
67
|
+
if old = @hash[key]
|
|
68
|
+
if old.prev
|
|
69
|
+
old.prev.next = old.next
|
|
70
|
+
else # old is @first and @last
|
|
71
|
+
@first = @last = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
62
75
|
if @first.nil?
|
|
63
76
|
@first = @last = node
|
|
64
77
|
else
|
|
78
|
+
node.prev = @last
|
|
65
79
|
@last.next = node
|
|
66
80
|
@last = node
|
|
67
81
|
end
|
|
82
|
+
|
|
68
83
|
@hash[key] = node
|
|
69
84
|
value
|
|
70
85
|
end
|
|
@@ -90,11 +105,12 @@ module Sass
|
|
|
90
105
|
|
|
91
106
|
# Creates a new instance of Sass::CSS that will compile the given document
|
|
92
107
|
# to a Sass string when +render+ is called.
|
|
93
|
-
def initialize(template)
|
|
108
|
+
def initialize(template, options = {})
|
|
94
109
|
if template.is_a? IO
|
|
95
110
|
template = template.read
|
|
96
111
|
end
|
|
97
112
|
|
|
113
|
+
@options = options
|
|
98
114
|
@template = StringScanner.new(template)
|
|
99
115
|
end
|
|
100
116
|
|
|
@@ -102,10 +118,10 @@ module Sass
|
|
|
102
118
|
# containing the CSS template.
|
|
103
119
|
def render
|
|
104
120
|
begin
|
|
105
|
-
build_tree.to_sass
|
|
121
|
+
build_tree.to_sass(@options).lstrip
|
|
106
122
|
rescue Exception => err
|
|
107
123
|
line = @template.string[0...@template.pos].split("\n").size
|
|
108
|
-
|
|
124
|
+
|
|
109
125
|
err.backtrace.unshift "(css):#{line}"
|
|
110
126
|
raise err
|
|
111
127
|
end
|
|
@@ -116,12 +132,13 @@ module Sass
|
|
|
116
132
|
def build_tree
|
|
117
133
|
root = Tree::Node.new(nil)
|
|
118
134
|
whitespace
|
|
119
|
-
directives
|
|
120
|
-
rules
|
|
121
|
-
expand_commas
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
directives root
|
|
136
|
+
rules root
|
|
137
|
+
expand_commas root
|
|
138
|
+
parent_ref_rules root
|
|
139
|
+
remove_parent_refs root
|
|
140
|
+
flatten_rules root
|
|
141
|
+
fold_commas root
|
|
125
142
|
root
|
|
126
143
|
end
|
|
127
144
|
|
|
@@ -164,12 +181,12 @@ module Sass
|
|
|
164
181
|
whitespace
|
|
165
182
|
|
|
166
183
|
assert_match /:/
|
|
167
|
-
|
|
184
|
+
|
|
168
185
|
value = ''
|
|
169
186
|
while @template.scan(/[^;\s\}]+/)
|
|
170
187
|
value << @template[0] << whitespace
|
|
171
188
|
end
|
|
172
|
-
|
|
189
|
+
|
|
173
190
|
assert_match /(;|(?=\}))/
|
|
174
191
|
rule << Tree::AttrNode.new(name, value, nil)
|
|
175
192
|
end
|
|
@@ -191,7 +208,10 @@ module Sass
|
|
|
191
208
|
|
|
192
209
|
def assert_match(re)
|
|
193
210
|
if !@template.scan(re)
|
|
194
|
-
|
|
211
|
+
line = @template.string[0..@template.pos].count "\n"
|
|
212
|
+
# Display basic regexps as plain old strings
|
|
213
|
+
expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
|
|
214
|
+
raise Exception.new("Invalid CSS on line #{line}: expected #{expected}")
|
|
195
215
|
end
|
|
196
216
|
whitespace
|
|
197
217
|
end
|
|
@@ -224,7 +244,22 @@ module Sass
|
|
|
224
244
|
root.children.flatten!
|
|
225
245
|
end
|
|
226
246
|
|
|
227
|
-
#
|
|
247
|
+
# Make rules use parent refs so that
|
|
248
|
+
#
|
|
249
|
+
# foo
|
|
250
|
+
# color: green
|
|
251
|
+
# foo.bar
|
|
252
|
+
# color: blue
|
|
253
|
+
#
|
|
254
|
+
# becomes
|
|
255
|
+
#
|
|
256
|
+
# foo
|
|
257
|
+
# color: green
|
|
258
|
+
# &.bar
|
|
259
|
+
# color: blue
|
|
260
|
+
#
|
|
261
|
+
# This has the side effect of nesting rules,
|
|
262
|
+
# so that
|
|
228
263
|
#
|
|
229
264
|
# foo
|
|
230
265
|
# color: green
|
|
@@ -237,29 +272,50 @@ module Sass
|
|
|
237
272
|
#
|
|
238
273
|
# foo
|
|
239
274
|
# color: green
|
|
240
|
-
# bar
|
|
275
|
+
# & bar
|
|
241
276
|
# color: red
|
|
242
|
-
# baz
|
|
277
|
+
# & baz
|
|
243
278
|
# color: blue
|
|
244
|
-
#
|
|
245
|
-
def
|
|
279
|
+
#
|
|
280
|
+
def parent_ref_rules(root)
|
|
246
281
|
rules = OrderedHash.new
|
|
247
282
|
root.children.select { |c| Tree::RuleNode === c }.each do |child|
|
|
248
283
|
root.children.delete child
|
|
249
|
-
first, rest = child.rule.
|
|
284
|
+
first, rest = child.rule.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
|
|
250
285
|
rules[first] ||= Tree::RuleNode.new(first, nil)
|
|
251
286
|
if rest
|
|
252
|
-
child.rule = rest
|
|
287
|
+
child.rule = "&" + rest
|
|
253
288
|
rules[first] << child
|
|
254
289
|
else
|
|
255
290
|
rules[first].children += child.children
|
|
256
291
|
end
|
|
257
292
|
end
|
|
258
293
|
|
|
259
|
-
rules.values.each { |v|
|
|
294
|
+
rules.values.each { |v| parent_ref_rules(v) }
|
|
260
295
|
root.children += rules.values
|
|
261
296
|
end
|
|
262
297
|
|
|
298
|
+
# Remove useless parent refs so that
|
|
299
|
+
#
|
|
300
|
+
# foo
|
|
301
|
+
# & bar
|
|
302
|
+
# color: blue
|
|
303
|
+
#
|
|
304
|
+
# becomes
|
|
305
|
+
#
|
|
306
|
+
# foo
|
|
307
|
+
# bar
|
|
308
|
+
# color: blue
|
|
309
|
+
#
|
|
310
|
+
def remove_parent_refs(root)
|
|
311
|
+
root.children.each do |child|
|
|
312
|
+
if child.is_a?(Tree::RuleNode)
|
|
313
|
+
child.rule.gsub! /^& /, ''
|
|
314
|
+
remove_parent_refs child
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
263
319
|
# Flatten rules so that
|
|
264
320
|
#
|
|
265
321
|
# foo
|
|
@@ -271,7 +327,18 @@ module Sass
|
|
|
271
327
|
#
|
|
272
328
|
# foo bar baz
|
|
273
329
|
# color: red
|
|
274
|
-
#
|
|
330
|
+
#
|
|
331
|
+
# and
|
|
332
|
+
#
|
|
333
|
+
# foo
|
|
334
|
+
# &.bar
|
|
335
|
+
# color: blue
|
|
336
|
+
#
|
|
337
|
+
# becomes
|
|
338
|
+
#
|
|
339
|
+
# foo.bar
|
|
340
|
+
# color: blue
|
|
341
|
+
#
|
|
275
342
|
def flatten_rules(root)
|
|
276
343
|
root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) }
|
|
277
344
|
end
|
|
@@ -279,7 +346,13 @@ module Sass
|
|
|
279
346
|
def flatten_rule(rule)
|
|
280
347
|
while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
|
|
281
348
|
child = rule.children.first
|
|
282
|
-
|
|
349
|
+
|
|
350
|
+
if child.rule[0] == ?&
|
|
351
|
+
rule.rule = child.rule.gsub /^&/, rule.rule
|
|
352
|
+
else
|
|
353
|
+
rule.rule = "#{rule.rule} #{child.rule}"
|
|
354
|
+
end
|
|
355
|
+
|
|
283
356
|
rule.children = child.children
|
|
284
357
|
end
|
|
285
358
|
|