haml-edge 2.3.179 → 2.3.180
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/EDGE_GEM_VERSION +1 -1
- data/README.md +88 -149
- data/VERSION +1 -1
- data/bin/css2sass +7 -1
- data/bin/sass-convert +7 -0
- data/lib/haml/exec.rb +95 -22
- data/lib/haml/template.rb +1 -1
- data/lib/haml/util.rb +50 -0
- data/lib/sass.rb +1 -1
- data/lib/sass/css.rb +38 -210
- data/lib/sass/engine.rb +121 -47
- data/lib/sass/files.rb +28 -19
- data/lib/sass/plugin.rb +32 -43
- data/lib/sass/repl.rb +1 -1
- data/lib/sass/script.rb +25 -6
- data/lib/sass/script/bool.rb +1 -0
- data/lib/sass/script/color.rb +2 -2
- data/lib/sass/script/css_lexer.rb +22 -0
- data/lib/sass/script/css_parser.rb +28 -0
- data/lib/sass/script/funcall.rb +17 -9
- data/lib/sass/script/functions.rb +46 -1
- data/lib/sass/script/interpolation.rb +42 -0
- data/lib/sass/script/lexer.rb +142 -34
- data/lib/sass/script/literal.rb +28 -12
- data/lib/sass/script/node.rb +57 -1
- data/lib/sass/script/number.rb +18 -3
- data/lib/sass/script/operation.rb +44 -8
- data/lib/sass/script/parser.rb +149 -24
- data/lib/sass/script/string.rb +50 -2
- data/lib/sass/script/unary_operation.rb +25 -10
- data/lib/sass/script/variable.rb +20 -11
- data/lib/sass/scss.rb +14 -0
- data/lib/sass/scss/css_parser.rb +39 -0
- data/lib/sass/scss/parser.rb +683 -0
- data/lib/sass/scss/rx.rb +112 -0
- data/lib/sass/scss/script_lexer.rb +13 -0
- data/lib/sass/scss/script_parser.rb +25 -0
- data/lib/sass/tree/comment_node.rb +58 -16
- data/lib/sass/tree/debug_node.rb +7 -2
- data/lib/sass/tree/directive_node.rb +38 -34
- data/lib/sass/tree/for_node.rb +6 -0
- data/lib/sass/tree/if_node.rb +13 -0
- data/lib/sass/tree/import_node.rb +26 -7
- data/lib/sass/tree/mixin_def_node.rb +18 -0
- data/lib/sass/tree/mixin_node.rb +16 -1
- data/lib/sass/tree/node.rb +98 -27
- data/lib/sass/tree/prop_node.rb +97 -20
- data/lib/sass/tree/root_node.rb +37 -0
- data/lib/sass/tree/rule_node.rb +88 -60
- data/lib/sass/tree/variable_node.rb +9 -5
- data/lib/sass/tree/while_node.rb +4 -0
- data/test/haml/results/filters.xhtml +1 -1
- data/test/haml/util_test.rb +28 -0
- data/test/sass/conversion_test.rb +884 -0
- data/test/sass/css2sass_test.rb +46 -21
- data/test/sass/engine_test.rb +680 -160
- data/test/sass/functions_test.rb +27 -0
- data/test/sass/more_results/more_import.css +1 -1
- data/test/sass/more_templates/more_import.sass +3 -3
- data/test/sass/plugin_test.rb +28 -8
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +5 -5
- data/test/sass/results/compressed.css +1 -1
- data/test/sass/results/expanded.css +1 -1
- data/test/sass/results/import.css +3 -1
- data/test/sass/results/mixins.css +12 -12
- data/test/sass/results/nested.css +1 -1
- data/test/sass/results/parent_ref.css +4 -4
- data/test/sass/results/script.css +3 -3
- data/test/sass/results/scss_import.css +15 -0
- data/test/sass/results/scss_importee.css +2 -0
- data/test/sass/script_conversion_test.rb +153 -0
- data/test/sass/script_test.rb +44 -54
- data/test/sass/scss/css_test.rb +811 -0
- data/test/sass/scss/rx_test.rb +156 -0
- data/test/sass/scss/scss_test.rb +871 -0
- data/test/sass/scss/test_helper.rb +37 -0
- data/test/sass/templates/alt.sass +2 -2
- data/test/sass/templates/bork1.sass +1 -1
- data/test/sass/templates/import.sass +4 -4
- data/test/sass/templates/importee.sass +3 -3
- data/test/sass/templates/line_numbers.sass +1 -1
- data/test/sass/templates/mixins.sass +2 -2
- data/test/sass/templates/nested_mixin_bork.sass +1 -1
- data/test/sass/templates/options.sass +1 -1
- data/test/sass/templates/parent_ref.sass +2 -2
- data/test/sass/templates/script.sass +69 -69
- data/test/sass/templates/scss_import.scss +10 -0
- data/test/sass/templates/scss_importee.scss +1 -0
- data/test/sass/templates/units.sass +10 -10
- data/test/test_helper.rb +4 -4
- metadata +27 -2
data/lib/haml/template.rb
CHANGED
@@ -77,7 +77,7 @@ if Haml::Util.rails_root
|
|
77
77
|
FileUtils.cp(haml_init_file, rails_init_file) unless FileUtils.cmp(rails_init_file, haml_init_file)
|
78
78
|
end
|
79
79
|
rescue SystemCallError
|
80
|
-
|
80
|
+
Haml::Util.haml_warn <<END
|
81
81
|
HAML WARNING:
|
82
82
|
#{rails_init_file} is out of date and couldn't be automatically updated.
|
83
83
|
Please run `haml --rails #{File.expand_path(Haml::Util.rails_root)}' to update it.
|
data/lib/haml/util.rb
CHANGED
@@ -135,6 +135,18 @@ module Haml
|
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
+
# Destructively strips whitespace from the beginning and end
|
139
|
+
# of the first and last elements, respectively,
|
140
|
+
# in the array (if those elements are strings).
|
141
|
+
#
|
142
|
+
# @param arr [Array]
|
143
|
+
# @return [Array] `arr`
|
144
|
+
def strip_string_array(arr)
|
145
|
+
arr.first.lstrip! if arr.first.is_a?(String)
|
146
|
+
arr.last.rstrip! if arr.last.is_a?(String)
|
147
|
+
arr
|
148
|
+
end
|
149
|
+
|
138
150
|
# Returns information about the caller of the previous method.
|
139
151
|
#
|
140
152
|
# @param entry [String] An entry in the `#caller` list, or a similarly formatted string
|
@@ -156,6 +168,26 @@ module Haml
|
|
156
168
|
$stderr = the_real_stderr
|
157
169
|
end
|
158
170
|
|
171
|
+
@@silence_warnings = false
|
172
|
+
# Silences all Haml warnings within a block.
|
173
|
+
#
|
174
|
+
# @yield A block in which no Haml warnings will be printed
|
175
|
+
def silence_haml_warnings
|
176
|
+
old_silence_warnings = @@silence_warnings
|
177
|
+
@@silence_warnings = true
|
178
|
+
yield
|
179
|
+
ensure
|
180
|
+
@@silence_warnings = old_silence_warnings
|
181
|
+
end
|
182
|
+
|
183
|
+
# The same as `Kernel#warn`, but is silenced by \{#silence\_haml\_warnings}.
|
184
|
+
#
|
185
|
+
# @param msg [String]
|
186
|
+
def haml_warn(msg)
|
187
|
+
return if @@silence_warnings
|
188
|
+
warn(msg)
|
189
|
+
end
|
190
|
+
|
159
191
|
## Cross Rails Version Compatibility
|
160
192
|
|
161
193
|
# Returns the root of the Rails application,
|
@@ -326,6 +358,24 @@ MSG
|
|
326
358
|
ruby1_8? ? enum.enum_with_index : enum.each_with_index
|
327
359
|
end
|
328
360
|
|
361
|
+
# A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
|
362
|
+
#
|
363
|
+
# @param enum [Enumerable] The enumerable to get the enumerator for
|
364
|
+
# @param n [Fixnum] The size of each cons
|
365
|
+
# @return [Enumerator] The consed enumerator
|
366
|
+
def enum_cons(enum, n)
|
367
|
+
ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
|
368
|
+
end
|
369
|
+
|
370
|
+
# A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
|
371
|
+
#
|
372
|
+
# @param enum [Enumerable] The enumerable to get the enumerator for
|
373
|
+
# @param n [Fixnum] The size of each slice
|
374
|
+
# @return [Enumerator] The consed enumerator
|
375
|
+
def enum_slice(enum, n)
|
376
|
+
ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
|
377
|
+
end
|
378
|
+
|
329
379
|
# Returns the ASCII code of the given character.
|
330
380
|
#
|
331
381
|
# @param c [String] All characters but the first are ignored.
|
data/lib/sass.rb
CHANGED
@@ -5,7 +5,7 @@ require 'haml/version'
|
|
5
5
|
|
6
6
|
# The module that contains everything Sass-related:
|
7
7
|
#
|
8
|
-
# * {Sass::Engine} is the class used to render Sass within Ruby code.
|
8
|
+
# * {Sass::Engine} is the class used to render Sass/SCSS within Ruby code.
|
9
9
|
# * {Sass::Plugin} is interfaces with web frameworks (Rails and Merb in particular).
|
10
10
|
# * {Sass::SyntaxError} is raised when Sass encounters an error.
|
11
11
|
# * {Sass::CSS} handles conversion of CSS to Sass.
|
data/lib/sass/css.rb
CHANGED
@@ -1,96 +1,49 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../sass'
|
2
2
|
require 'sass/tree/node'
|
3
|
+
require 'sass/scss/css_parser'
|
3
4
|
require 'strscan'
|
4
5
|
|
5
6
|
module Sass
|
6
|
-
|
7
|
-
class Node
|
8
|
-
# Converts a node to Sass code that will generate it.
|
9
|
-
#
|
10
|
-
# @param tabs [Fixnum] The amount of tabulation to use for the Sass code
|
11
|
-
# @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
|
12
|
-
# @return [String] The Sass code corresponding to the node
|
13
|
-
def to_sass(tabs = 0, opts = {})
|
14
|
-
result = ''
|
15
|
-
|
16
|
-
children.each do |child|
|
17
|
-
result << "#{' ' * tabs}#{child.to_sass(0, opts)}\n"
|
18
|
-
end
|
19
|
-
|
20
|
-
result
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class RuleNode
|
25
|
-
# @see Node#to_sass
|
26
|
-
def to_sass(tabs, opts = {})
|
27
|
-
name = rules.first
|
28
|
-
name = "\\" + name if name[0] == ?:
|
29
|
-
str = "\n#{' ' * tabs}#{name}#{children.any? { |c| c.is_a? PropNode } ? "\n" : ''}"
|
30
|
-
|
31
|
-
children.each do |child|
|
32
|
-
str << "#{child.to_sass(tabs + 1, opts)}"
|
33
|
-
end
|
34
|
-
|
35
|
-
str
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class PropNode
|
40
|
-
# @see Node#to_sass
|
41
|
-
def to_sass(tabs, opts = {})
|
42
|
-
"#{' ' * tabs}#{opts[:old] ? ':' : ''}#{name}#{opts[:old] ? '' : ':'} #{value}\n"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class DirectiveNode
|
47
|
-
# @see Node#to_sass
|
48
|
-
def to_sass(tabs, opts = {})
|
49
|
-
"#{' ' * tabs}#{value}#{children.map {|c| c.to_sass(tabs + 1, opts)}}\n"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# This class converts CSS documents into Sass templates.
|
7
|
+
# This class converts CSS documents into Sass or SCSS templates.
|
55
8
|
# It works by parsing the CSS document into a {Sass::Tree} structure,
|
56
9
|
# and then applying various transformations to the structure
|
57
|
-
# to produce more concise and idiomatic Sass.
|
10
|
+
# to produce more concise and idiomatic Sass/SCSS.
|
58
11
|
#
|
59
12
|
# Example usage:
|
60
13
|
#
|
61
|
-
# Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
|
14
|
+
# Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
|
15
|
+
# Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
|
62
16
|
class CSS
|
63
17
|
# @param template [String] The CSS code
|
64
18
|
# @option options :old [Boolean] (false)
|
65
19
|
# Whether or not to output old property syntax
|
66
20
|
# (`:color blue` as opposed to `color: blue`).
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# Used for error reporting
|
21
|
+
# This is only meaningful when generating Sass code,
|
22
|
+
# rather than SCSS.
|
70
23
|
def initialize(template, options = {})
|
71
24
|
if template.is_a? IO
|
72
25
|
template = template.read
|
73
26
|
end
|
74
27
|
|
75
|
-
@line = 1
|
76
28
|
@options = options.dup
|
77
29
|
# Backwards compatibility
|
78
30
|
@options[:old] = true if @options[:alternate] == false
|
79
|
-
@
|
31
|
+
@template = template
|
80
32
|
end
|
81
33
|
|
82
|
-
# Converts the CSS template into Sass code.
|
34
|
+
# Converts the CSS template into Sass or SCSS code.
|
83
35
|
#
|
84
|
-
# @
|
36
|
+
# @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
|
37
|
+
# @return [String] The resulting Sass or SCSS code
|
85
38
|
# @raise [Sass::SyntaxError] if there's an error parsing the CSS template
|
86
|
-
def render
|
87
|
-
@template
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
build_tree.
|
39
|
+
def render(fmt = :sass)
|
40
|
+
Haml::Util.check_encoding(@template) do |msg, line|
|
41
|
+
raise Sass::SyntaxError.new(msg, :line => line)
|
42
|
+
end
|
43
|
+
|
44
|
+
build_tree.send("to_#{fmt}", @options).strip + "\n"
|
92
45
|
rescue Sass::SyntaxError => err
|
93
|
-
err.modify_backtrace(:filename => @options[:filename] || '(css)'
|
46
|
+
err.modify_backtrace(:filename => @options[:filename] || '(css)')
|
94
47
|
raise err
|
95
48
|
end
|
96
49
|
|
@@ -100,9 +53,7 @@ module Sass
|
|
100
53
|
#
|
101
54
|
# @return [Tree::Node] The root node of the parsed tree
|
102
55
|
def build_tree
|
103
|
-
root =
|
104
|
-
whitespace
|
105
|
-
rules root
|
56
|
+
root = Sass::SCSS::CssParser.new(@template).parse
|
106
57
|
expand_commas root
|
107
58
|
parent_ref_rules root
|
108
59
|
remove_parent_refs root
|
@@ -111,133 +62,6 @@ module Sass
|
|
111
62
|
root
|
112
63
|
end
|
113
64
|
|
114
|
-
# Parses a set of CSS rules.
|
115
|
-
#
|
116
|
-
# @param root [Tree::Node] The parent node of the rules
|
117
|
-
def rules(root)
|
118
|
-
while r = rule
|
119
|
-
root << r
|
120
|
-
whitespace
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# Parses a single CSS rule.
|
125
|
-
#
|
126
|
-
# @return [Tree::Node] The parsed rule
|
127
|
-
def rule
|
128
|
-
rule = ""
|
129
|
-
loop do
|
130
|
-
token = scan(/(?:[^\{\};\/\s]|\/[^*])+/)
|
131
|
-
if token.nil?
|
132
|
-
return if rule.empty?
|
133
|
-
break
|
134
|
-
end
|
135
|
-
rule << token
|
136
|
-
break unless @template.match?(/\s|\/\*/)
|
137
|
-
whitespace
|
138
|
-
rule << " "
|
139
|
-
end
|
140
|
-
|
141
|
-
rule.strip!
|
142
|
-
directive = rule[0] == ?@
|
143
|
-
|
144
|
-
if directive
|
145
|
-
node = Tree::DirectiveNode.new(rule)
|
146
|
-
return node if scan(/;/)
|
147
|
-
|
148
|
-
assert_match /\{/
|
149
|
-
whitespace
|
150
|
-
|
151
|
-
rules(node)
|
152
|
-
return node
|
153
|
-
end
|
154
|
-
|
155
|
-
assert_match /\{/
|
156
|
-
node = Tree::RuleNode.new(rule)
|
157
|
-
properties(node)
|
158
|
-
return node
|
159
|
-
end
|
160
|
-
|
161
|
-
# Parses a set of CSS properties within a rule.
|
162
|
-
#
|
163
|
-
# @param rule [Tree::RuleNode] The parent node of the properties
|
164
|
-
def properties(rule)
|
165
|
-
while scan(/[^:\}\s]+/)
|
166
|
-
name = @template[0]
|
167
|
-
whitespace
|
168
|
-
|
169
|
-
assert_match /:/
|
170
|
-
|
171
|
-
value = ''
|
172
|
-
while scan(/[^;\s\}]+/)
|
173
|
-
value << @template[0] << whitespace
|
174
|
-
end
|
175
|
-
|
176
|
-
assert_match /(;|(?=\}))/
|
177
|
-
rule << Tree::PropNode.new(name, value, nil)
|
178
|
-
end
|
179
|
-
|
180
|
-
assert_match /\}/
|
181
|
-
end
|
182
|
-
|
183
|
-
# Moves the scanner over a section of whitespace or comments.
|
184
|
-
#
|
185
|
-
# @return [String] The ignored whitespace
|
186
|
-
def whitespace
|
187
|
-
space = scan(/\s*/) || ''
|
188
|
-
|
189
|
-
# If we've hit a comment,
|
190
|
-
# go past it and look for more whitespace
|
191
|
-
if scan(/\/\*/)
|
192
|
-
scan_until(/\*\//)
|
193
|
-
return space + whitespace
|
194
|
-
end
|
195
|
-
return space
|
196
|
-
end
|
197
|
-
|
198
|
-
# Moves the scanner over a regular expression,
|
199
|
-
# raising an exception if it doesn't match.
|
200
|
-
#
|
201
|
-
# @param re [Regexp] The regular expression to assert
|
202
|
-
def assert_match(re)
|
203
|
-
if scan(re)
|
204
|
-
whitespace
|
205
|
-
return
|
206
|
-
end
|
207
|
-
|
208
|
-
pos = @template.pos
|
209
|
-
|
210
|
-
after = @template.string[[pos - 15, 0].max...pos].gsub(/.*\n/m, '')
|
211
|
-
after = "..." + after if pos >= 15
|
212
|
-
|
213
|
-
# Display basic regexps as plain old strings
|
214
|
-
string = re.source.gsub(/\\(.)/, '\1')
|
215
|
-
expected = re.source == Regexp.escape(string) ? string.inspect : re.inspect
|
216
|
-
|
217
|
-
was = @template.rest[0...15].gsub(/\n.*/m, '')
|
218
|
-
was += "..." if @template.rest.size >= 15
|
219
|
-
raise Sass::SyntaxError.new(
|
220
|
-
"Invalid CSS after #{after.inspect}: expected #{expected}, was #{was.inspect}")
|
221
|
-
end
|
222
|
-
|
223
|
-
# Identical to `@template.scan`, except that it increments the line count.
|
224
|
-
# `@template.scan` should never be called directly;
|
225
|
-
# this should be used instead.
|
226
|
-
def scan(re)
|
227
|
-
str = @template.scan(re)
|
228
|
-
@line += str.count "\n" if str
|
229
|
-
str
|
230
|
-
end
|
231
|
-
|
232
|
-
# Identical to `@template.scan_until`, except that it increments the line count.
|
233
|
-
# `@template.scan_until` should never be called directly;
|
234
|
-
# this should be used instead.
|
235
|
-
def scan_until(re)
|
236
|
-
str = @template.scan_until(re)
|
237
|
-
@line += str.count "\n" if str
|
238
|
-
str
|
239
|
-
end
|
240
|
-
|
241
65
|
# Transform
|
242
66
|
#
|
243
67
|
# foo, bar, baz
|
@@ -255,9 +79,9 @@ module Sass
|
|
255
79
|
# @param root [Tree::Node] The parent node
|
256
80
|
def expand_commas(root)
|
257
81
|
root.children.map! do |child|
|
258
|
-
next child unless Tree::RuleNode === child && child.
|
259
|
-
child.
|
260
|
-
node = Tree::RuleNode.new(rule.strip)
|
82
|
+
next child unless Tree::RuleNode === child && child.rule.first.include?(',')
|
83
|
+
child.rule.first.split(',').map do |rule|
|
84
|
+
node = Tree::RuleNode.new([rule.strip])
|
261
85
|
node.children = child.children
|
262
86
|
node
|
263
87
|
end
|
@@ -301,22 +125,26 @@ module Sass
|
|
301
125
|
# @param root [Tree::Node] The parent node
|
302
126
|
def parent_ref_rules(root)
|
303
127
|
current_rule = nil
|
304
|
-
root.children.
|
305
|
-
|
306
|
-
|
128
|
+
root.children.map! do |child|
|
129
|
+
next child unless child.is_a?(Tree::RuleNode)
|
130
|
+
|
131
|
+
first, rest = child.rule.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
|
307
132
|
|
308
|
-
if current_rule.nil? || current_rule.
|
309
|
-
current_rule = Tree::RuleNode.new(first)
|
310
|
-
root << current_rule
|
133
|
+
if current_rule.nil? || current_rule.rule.first != first
|
134
|
+
current_rule = Tree::RuleNode.new([first])
|
311
135
|
end
|
312
136
|
|
313
137
|
if rest
|
314
|
-
child.
|
138
|
+
child.rule = ["&" + rest]
|
315
139
|
current_rule << child
|
316
140
|
else
|
317
141
|
current_rule.children += child.children
|
318
142
|
end
|
143
|
+
|
144
|
+
current_rule
|
319
145
|
end
|
146
|
+
root.children.compact!
|
147
|
+
root.children.uniq!
|
320
148
|
|
321
149
|
root.children.each { |v| parent_ref_rules(v) }
|
322
150
|
end
|
@@ -337,7 +165,7 @@ module Sass
|
|
337
165
|
def remove_parent_refs(root)
|
338
166
|
root.children.each do |child|
|
339
167
|
if child.is_a?(Tree::RuleNode)
|
340
|
-
child.
|
168
|
+
child.rule.first.gsub! /^& +/, ''
|
341
169
|
remove_parent_refs child
|
342
170
|
end
|
343
171
|
end
|
@@ -378,10 +206,10 @@ module Sass
|
|
378
206
|
while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
|
379
207
|
child = rule.children.first
|
380
208
|
|
381
|
-
if child.
|
382
|
-
rule.
|
209
|
+
if child.rule.first[0] == ?&
|
210
|
+
rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
|
383
211
|
else
|
384
|
-
rule.
|
212
|
+
rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
|
385
213
|
end
|
386
214
|
|
387
215
|
rule.children = child.children
|
@@ -411,7 +239,7 @@ module Sass
|
|
411
239
|
next child unless child.is_a?(Tree::RuleNode)
|
412
240
|
|
413
241
|
if prev_rule && prev_rule.children == child.children
|
414
|
-
prev_rule.
|
242
|
+
prev_rule.rule.first << ", #{child.rule.first}"
|
415
243
|
next nil
|
416
244
|
end
|
417
245
|
|
data/lib/sass/engine.rb
CHANGED
@@ -16,6 +16,7 @@ require 'sass/tree/debug_node'
|
|
16
16
|
require 'sass/tree/import_node'
|
17
17
|
require 'sass/environment'
|
18
18
|
require 'sass/script'
|
19
|
+
require 'sass/scss'
|
19
20
|
require 'sass/error'
|
20
21
|
require 'sass/files'
|
21
22
|
require 'haml/shared'
|
@@ -123,7 +124,7 @@ module Sass
|
|
123
124
|
# The regex that matches and extracts data from
|
124
125
|
# properties of the form `name: prop`.
|
125
126
|
# @private
|
126
|
-
PROPERTY_NEW = /^([^\s=:"]+)
|
127
|
+
PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
|
127
128
|
|
128
129
|
# The regex that matches and extracts data from
|
129
130
|
# properties of the form `:name prop`.
|
@@ -136,6 +137,7 @@ module Sass
|
|
136
137
|
:load_paths => ['.'],
|
137
138
|
:cache => true,
|
138
139
|
:cache_location => './.sass-cache',
|
140
|
+
:syntax => :sass,
|
139
141
|
}.freeze
|
140
142
|
|
141
143
|
# @param template [String] The Sass template.
|
@@ -174,8 +176,13 @@ module Sass
|
|
174
176
|
def to_tree
|
175
177
|
@template = check_encoding(@template) {|msg, line| raise Sass::SyntaxError.new(msg, :line => line)}
|
176
178
|
|
177
|
-
|
178
|
-
|
179
|
+
if @options[:syntax] == :scss
|
180
|
+
root = Sass::SCSS::Parser.new(@template).parse
|
181
|
+
else
|
182
|
+
root = Tree::RootNode.new(@template)
|
183
|
+
append_children(root, tree(tabulate(@template)).first, true)
|
184
|
+
end
|
185
|
+
|
179
186
|
root.options = @options
|
180
187
|
root
|
181
188
|
rescue SyntaxError => e
|
@@ -286,11 +293,7 @@ MSG
|
|
286
293
|
node.line = line.index
|
287
294
|
node.filename = line.filename
|
288
295
|
|
289
|
-
|
290
|
-
node.lines = line.children
|
291
|
-
else
|
292
|
-
append_children(node, line.children, false)
|
293
|
-
end
|
296
|
+
append_children(node, line.children, false)
|
294
297
|
end
|
295
298
|
|
296
299
|
node_or_nodes
|
@@ -298,6 +301,7 @@ MSG
|
|
298
301
|
|
299
302
|
def append_children(parent, children, root)
|
300
303
|
continued_rule = nil
|
304
|
+
continued_comment = nil
|
301
305
|
children.each do |line|
|
302
306
|
child = build_tree(parent, line, root)
|
303
307
|
|
@@ -320,6 +324,17 @@ MSG
|
|
320
324
|
continued_rule, child = nil, continued_rule
|
321
325
|
end
|
322
326
|
|
327
|
+
if child.is_a?(Tree::CommentNode) && child.silent
|
328
|
+
if continued_comment &&
|
329
|
+
child.line == continued_comment.line +
|
330
|
+
continued_comment.value.count("\n") + 1
|
331
|
+
continued_comment.value << "\n" << child.value
|
332
|
+
next
|
333
|
+
end
|
334
|
+
|
335
|
+
continued_comment = child
|
336
|
+
end
|
337
|
+
|
323
338
|
check_for_no_children(child)
|
324
339
|
validate_and_append_child(parent, child, line, root)
|
325
340
|
end
|
@@ -331,17 +346,6 @@ MSG
|
|
331
346
|
end
|
332
347
|
|
333
348
|
def validate_and_append_child(parent, child, line, root)
|
334
|
-
unless root
|
335
|
-
case child
|
336
|
-
when Tree::MixinDefNode
|
337
|
-
raise SyntaxError.new("Mixins may only be defined at the root of a document.",
|
338
|
-
:line => line.index)
|
339
|
-
when Tree::ImportNode
|
340
|
-
raise SyntaxError.new("Import directives may only be used at the root of a document.",
|
341
|
-
:line => line.index)
|
342
|
-
end
|
343
|
-
end
|
344
|
-
|
345
349
|
case child
|
346
350
|
when Array
|
347
351
|
child.each {|c| validate_and_append_child(parent, c, line, root)}
|
@@ -352,18 +356,10 @@ MSG
|
|
352
356
|
|
353
357
|
def check_for_no_children(node)
|
354
358
|
return unless node.is_a?(Tree::RuleNode) && node.children.empty?
|
355
|
-
|
359
|
+
Haml::Util.haml_warn(<<WARNING.strip)
|
356
360
|
WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
|
361
|
-
Selector
|
362
|
-
#{node.rules.join("\n ")}
|
363
|
-
doesn't have any properties and will not be rendered.
|
364
|
-
LONG
|
365
|
-
|
366
|
-
warn(warning.strip)
|
361
|
+
This selector doesn't have any properties and will not be rendered.
|
362
|
+
WARNING
|
367
363
|
end
|
368
364
|
|
369
365
|
def parse_line(parent, line, root)
|
@@ -376,23 +372,23 @@ LONG
|
|
376
372
|
# which begin with ::,
|
377
373
|
# as well as pseudo-classes
|
378
374
|
# if we're using the new property syntax
|
379
|
-
Tree::RuleNode.new(line.text)
|
375
|
+
Tree::RuleNode.new(parse_interp(line.text))
|
380
376
|
else
|
381
377
|
parse_property(line, PROPERTY_OLD)
|
382
378
|
end
|
383
|
-
when
|
379
|
+
when ?!, ?$
|
384
380
|
parse_variable(line)
|
385
381
|
when COMMENT_CHAR
|
386
382
|
parse_comment(line.text)
|
387
383
|
when DIRECTIVE_CHAR
|
388
384
|
parse_directive(parent, line, root)
|
389
385
|
when ESCAPE_CHAR
|
390
|
-
Tree::RuleNode.new(line.text[1..-1])
|
386
|
+
Tree::RuleNode.new(parse_interp(line.text[1..-1]))
|
391
387
|
when MIXIN_DEFINITION_CHAR
|
392
388
|
parse_mixin_definition(line)
|
393
389
|
when MIXIN_INCLUDE_CHAR
|
394
390
|
if line.text[1].nil? || line.text[1] == ?\s
|
395
|
-
Tree::RuleNode.new(line.text)
|
391
|
+
Tree::RuleNode.new(parse_interp(line.text))
|
396
392
|
else
|
397
393
|
parse_mixin_include(line, root)
|
398
394
|
end
|
@@ -400,7 +396,7 @@ LONG
|
|
400
396
|
if line.text =~ PROPERTY_NEW_MATCHER
|
401
397
|
parse_property(line, PROPERTY_NEW)
|
402
398
|
else
|
403
|
-
Tree::RuleNode.new(line.text)
|
399
|
+
Tree::RuleNode.new(parse_interp(line.text))
|
404
400
|
end
|
405
401
|
end
|
406
402
|
end
|
@@ -411,29 +407,51 @@ LONG
|
|
411
407
|
raise SyntaxError.new("Invalid property: \"#{line.text}\".",
|
412
408
|
:line => @line) if name.nil? || value.nil?
|
413
409
|
|
414
|
-
|
415
|
-
|
410
|
+
if value.strip.empty?
|
411
|
+
expr = Sass::Script::String.new("")
|
416
412
|
else
|
417
|
-
value
|
413
|
+
expr = parse_script(value, :offset => line.offset + line.text.index(value))
|
414
|
+
|
415
|
+
if eq.strip[0] == SCRIPT_CHAR
|
416
|
+
expr.context = :equals
|
417
|
+
Script.equals_warning("properties", name,
|
418
|
+
Sass::Tree::PropNode.val_to_sass(expr), false,
|
419
|
+
@line, line.offset + 1, @options[:filename])
|
420
|
+
end
|
418
421
|
end
|
419
|
-
Tree::PropNode.new(
|
422
|
+
Tree::PropNode.new(
|
423
|
+
parse_interp(name), expr,
|
424
|
+
property_regx == PROPERTY_OLD ? :old : :new)
|
420
425
|
end
|
421
426
|
|
422
427
|
def parse_variable(line)
|
423
|
-
name, op, value = line.text.scan(Script::MATCH)[0]
|
428
|
+
name, op, value, default = line.text.scan(Script::MATCH)[0]
|
429
|
+
guarded = op =~ /^\|\|/
|
424
430
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
|
425
431
|
:line => @line + 1) unless line.children.empty?
|
426
432
|
raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
|
427
433
|
:line => @line) unless name && value
|
434
|
+
Script.var_warning(name, @line, line.offset + 1, @options[:filename]) if line.text[0] == ?!
|
435
|
+
|
436
|
+
expr = parse_script(value, :offset => line.offset + line.text.index(value))
|
437
|
+
if op =~ /=$/
|
438
|
+
expr.context = :equals
|
439
|
+
type = guarded ? "variable defaults" : "variables"
|
440
|
+
Script.equals_warning(type, "$#{name}", expr.to_sass,
|
441
|
+
guarded, @line, line.offset + 1, @options[:filename])
|
442
|
+
end
|
428
443
|
|
429
|
-
Tree::VariableNode.new(name,
|
444
|
+
Tree::VariableNode.new(name, expr, default || guarded)
|
430
445
|
end
|
431
446
|
|
432
447
|
def parse_comment(line)
|
433
448
|
if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
|
434
|
-
|
449
|
+
silent = line[1] == SASS_COMMENT_CHAR
|
450
|
+
Tree::CommentNode.new(
|
451
|
+
format_comment_text(line[2..-1], silent),
|
452
|
+
silent)
|
435
453
|
else
|
436
|
-
Tree::RuleNode.new(line)
|
454
|
+
Tree::RuleNode.new(parse_interp(line))
|
437
455
|
end
|
438
456
|
end
|
439
457
|
|
@@ -447,6 +465,10 @@ LONG
|
|
447
465
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
|
448
466
|
:line => @line + 1) unless line.children.empty?
|
449
467
|
value.split(/,\s*/).map {|f| Tree::ImportNode.new(f)}
|
468
|
+
elsif directive == "mixin"
|
469
|
+
parse_mixin_definition(line)
|
470
|
+
elsif directive == "include"
|
471
|
+
parse_mixin_include(line, root)
|
450
472
|
elsif directive == "for"
|
451
473
|
parse_for(line, root, value)
|
452
474
|
elsif directive == "else"
|
@@ -482,14 +504,18 @@ LONG
|
|
482
504
|
raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
|
483
505
|
end
|
484
506
|
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
507
|
+
if var.slice!(0) == ?!
|
508
|
+
offset = line.offset + line.text.index("!" + var) + 1
|
509
|
+
Script.var_warning(var, @line, offset, @options[:filename])
|
510
|
+
end
|
485
511
|
|
486
512
|
parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
|
487
513
|
parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
|
488
|
-
Tree::ForNode.new(var
|
514
|
+
Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
|
489
515
|
end
|
490
516
|
|
491
517
|
def parse_else(parent, line, text)
|
492
|
-
previous = parent.last
|
518
|
+
previous = parent.children.last
|
493
519
|
raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
|
494
520
|
|
495
521
|
if text
|
@@ -505,8 +531,10 @@ LONG
|
|
505
531
|
nil
|
506
532
|
end
|
507
533
|
|
534
|
+
# @private
|
535
|
+
MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
508
536
|
def parse_mixin_definition(line)
|
509
|
-
name, arg_string = line.text.scan(
|
537
|
+
name, arg_string = line.text.scan(MIXIN_DEF_RE).first
|
510
538
|
raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
|
511
539
|
|
512
540
|
offset = line.offset + line.text.size - arg_string.size
|
@@ -516,8 +544,10 @@ LONG
|
|
516
544
|
Tree::MixinDefNode.new(name, args)
|
517
545
|
end
|
518
546
|
|
547
|
+
# @private
|
548
|
+
MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
519
549
|
def parse_mixin_include(line, root)
|
520
|
-
name, arg_string = line.text.scan(
|
550
|
+
name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
|
521
551
|
raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
|
522
552
|
|
523
553
|
offset = line.offset + line.text.size - arg_string.size
|
@@ -533,5 +563,49 @@ LONG
|
|
533
563
|
offset = options[:offset] || 0
|
534
564
|
Script.parse(script, line, offset, @options)
|
535
565
|
end
|
566
|
+
|
567
|
+
def format_comment_text(text, silent)
|
568
|
+
content = text.split("\n")
|
569
|
+
|
570
|
+
if content.first && content.first.strip.empty?
|
571
|
+
removed_first = true
|
572
|
+
content.shift
|
573
|
+
end
|
574
|
+
|
575
|
+
return silent ? "//" : "/* */" if content.empty?
|
576
|
+
content.map! {|l| (l.empty? ? "" : " ") + l}
|
577
|
+
content.first.gsub!(/^ /, '') unless removed_first
|
578
|
+
content.last.gsub!(%r{ ?\*/ *$}, '')
|
579
|
+
if silent
|
580
|
+
"//" + content.join("\n//")
|
581
|
+
else
|
582
|
+
"/*" + content.join("\n *") + " */"
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def parse_interp(text)
|
587
|
+
self.class.parse_interp(text, @line, :filename => @filename)
|
588
|
+
end
|
589
|
+
|
590
|
+
# It's important that this have strings (at least)
|
591
|
+
# at the beginning, the end, and between each Script::Node.
|
592
|
+
#
|
593
|
+
# @private
|
594
|
+
def self.parse_interp(text, line, options)
|
595
|
+
res = []
|
596
|
+
rest = Haml::Shared.handle_interpolation text do |scan|
|
597
|
+
escapes = scan[2].size
|
598
|
+
res << scan.matched[0...-2 - escapes]
|
599
|
+
if escapes % 2 == 1
|
600
|
+
res << "\\" * (escapes - 1) << '#{'
|
601
|
+
else
|
602
|
+
res << "\\" * [0, escapes - 1].max
|
603
|
+
res << Script::Parser.new(
|
604
|
+
scan, line, scan.pos - scan.matched_size, options).
|
605
|
+
parse_interpolated
|
606
|
+
end
|
607
|
+
end
|
608
|
+
res << rest
|
609
|
+
end
|
536
610
|
end
|
537
611
|
end
|