sass 3.3.0.alpha.16 → 3.3.0.alpha.50
Sign up to get free protection for your applications and to get access to all the features.
- data/REVISION +1 -1
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/lib/sass/engine.rb +135 -31
- data/lib/sass/exec.rb +38 -9
- data/lib/sass/plugin/compiler.rb +26 -13
- data/lib/sass/script/lexer.rb +40 -18
- data/lib/sass/script/node.rb +10 -0
- data/lib/sass/script/parser.rb +50 -18
- data/lib/sass/scss/parser.rb +107 -68
- data/lib/sass/scss/static_parser.rb +1 -1
- data/lib/sass/source/map.rb +159 -0
- data/lib/sass/source/position.rb +26 -0
- data/lib/sass/source/range.rb +34 -0
- data/lib/sass/tree/css_import_node.rb +1 -1
- data/lib/sass/tree/node.rb +19 -3
- data/lib/sass/tree/prop_node.rb +11 -1
- data/lib/sass/tree/root_node.rb +19 -3
- data/lib/sass/tree/visitors/perform.rb +9 -1
- data/lib/sass/tree/visitors/to_css.rb +191 -64
- data/lib/sass/util.rb +94 -0
- data/test/sass/source_map_test.rb +837 -0
- data/test/sass/util_test.rb +41 -0
- metadata +9 -4
@@ -42,7 +42,7 @@ module Sass
|
|
42
42
|
def interp_ident(ident = IDENT); s = tok(ident) and [s]; end
|
43
43
|
def use_css_import?; true; end
|
44
44
|
|
45
|
-
def special_directive(name)
|
45
|
+
def special_directive(name, start_pos)
|
46
46
|
return unless %w[media import charset -moz-document].include?(name)
|
47
47
|
super
|
48
48
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Sass::Source
|
4
|
+
class Map
|
5
|
+
# A mapping from one source range to another. Indicates that `input` was
|
6
|
+
# compiled to `output`.
|
7
|
+
#
|
8
|
+
# @!attribute input
|
9
|
+
# @return [Sass::Source::Range] The source range in the input document.
|
10
|
+
#
|
11
|
+
# @!attribute output
|
12
|
+
# @return [Sass::Source::Range] The source range in the output document.
|
13
|
+
class Mapping < Struct.new(:input, :output)
|
14
|
+
# @return [String] A string representation of the mapping.
|
15
|
+
def inspect
|
16
|
+
"#{input.inspect} => #{output.inspect}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# The mapping data ordered by the location in the target.
|
21
|
+
#
|
22
|
+
# @return [Array<Mapping>]
|
23
|
+
attr_reader :data
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@data = []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Adds a new mapping from one source range to another. Multiple invocations
|
30
|
+
# of this method should have each `output` range come after all previous ranges.
|
31
|
+
#
|
32
|
+
# @param input [Sass::Source::Range]
|
33
|
+
# The source range in the input document.
|
34
|
+
# @param output [Sass::Source::Range]
|
35
|
+
# The source range in the output document.
|
36
|
+
def add(input, output)
|
37
|
+
@data.push(Mapping.new(input, output))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Shifts all output source ranges forward one or more lines.
|
41
|
+
#
|
42
|
+
# @param delta [Fixnum] The number of lines to shift the ranges forward.
|
43
|
+
def shift_output_lines(delta)
|
44
|
+
return if delta == 0
|
45
|
+
@data.each do |m|
|
46
|
+
m.output.start_pos.line += delta
|
47
|
+
m.output.end_pos.line += delta
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Shifts any output source ranges that lie on the first line forward one or
|
52
|
+
# more characters on that line.
|
53
|
+
#
|
54
|
+
# @param delta [Fixnum] The number of characters to shift the ranges
|
55
|
+
# forward.
|
56
|
+
def shift_output_offsets(delta)
|
57
|
+
return if delta == 0
|
58
|
+
@data.each do |m|
|
59
|
+
break if m.output.start_pos.line > 1
|
60
|
+
m.output.start_pos.offset += delta
|
61
|
+
m.output.end_pos.offset += delta if m.output.end_pos.line > 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the standard JSON representation of the source map.
|
66
|
+
#
|
67
|
+
# @param target_filename [String] The filename of the output file; that is,
|
68
|
+
# the target of the mapping. This should be relative to the working
|
69
|
+
# directory.
|
70
|
+
# @return [String] The JSON string.
|
71
|
+
def to_json(target_filename)
|
72
|
+
result = "{\n"
|
73
|
+
write_json_field(result, "version", "3", true)
|
74
|
+
|
75
|
+
source_pathname_to_id = {}
|
76
|
+
id_to_source_pathname = {}
|
77
|
+
next_source_id = 0
|
78
|
+
line_data = []
|
79
|
+
segment_data_for_line = []
|
80
|
+
|
81
|
+
# These track data necessary for the delta coding.
|
82
|
+
previous_target_line = nil
|
83
|
+
previous_target_offset = 1
|
84
|
+
previous_source_line = 1
|
85
|
+
previous_source_offset = 1
|
86
|
+
previous_source_id = 0
|
87
|
+
|
88
|
+
target_pathname = Pathname.pwd.join(Pathname.new(target_filename)).cleanpath
|
89
|
+
@data.each do |m|
|
90
|
+
source_pathname = Pathname.pwd.join(Pathname.new(m.input.file)).cleanpath
|
91
|
+
source_pathname = source_pathname.relative_path_from(target_pathname.dirname)
|
92
|
+
current_source_id = source_pathname_to_id[source_pathname]
|
93
|
+
unless current_source_id
|
94
|
+
current_source_id = next_source_id
|
95
|
+
next_source_id += 1
|
96
|
+
|
97
|
+
source_pathname_to_id[source_pathname] = current_source_id
|
98
|
+
id_to_source_pathname[current_source_id] = source_pathname
|
99
|
+
end
|
100
|
+
|
101
|
+
[
|
102
|
+
[m.input.start_pos, m.output.start_pos],
|
103
|
+
[m.input.end_pos, m.output.end_pos]
|
104
|
+
].each do |source_pos, target_pos|
|
105
|
+
if previous_target_line != target_pos.line
|
106
|
+
line_data.push(segment_data_for_line.join(",")) unless segment_data_for_line.empty?
|
107
|
+
(target_pos.line - 1 - (previous_target_line || 0)).times {line_data.push("")}
|
108
|
+
previous_target_line = target_pos.line
|
109
|
+
previous_target_offset = 1
|
110
|
+
segment_data_for_line = []
|
111
|
+
end
|
112
|
+
|
113
|
+
# `segment` is a data chunk for a single position mapping.
|
114
|
+
segment = ""
|
115
|
+
|
116
|
+
# Field 1: zero-based starting offset.
|
117
|
+
segment << Sass::Util.encode_vlq(target_pos.offset - previous_target_offset)
|
118
|
+
previous_target_offset = target_pos.offset
|
119
|
+
|
120
|
+
# Field 2: zero-based index into the "sources" list.
|
121
|
+
segment << Sass::Util.encode_vlq(current_source_id - previous_source_id)
|
122
|
+
previous_source_id = current_source_id
|
123
|
+
|
124
|
+
# Field 3: zero-based starting line in the original source.
|
125
|
+
segment << Sass::Util.encode_vlq(source_pos.line - previous_source_line)
|
126
|
+
previous_source_line = source_pos.line
|
127
|
+
|
128
|
+
# Field 4: zero-based starting offset in the original source.
|
129
|
+
segment << Sass::Util.encode_vlq(source_pos.offset - previous_source_offset)
|
130
|
+
previous_source_offset = source_pos.offset
|
131
|
+
|
132
|
+
segment_data_for_line.push(segment)
|
133
|
+
|
134
|
+
previous_target_line = target_pos.line
|
135
|
+
end
|
136
|
+
end
|
137
|
+
line_data.push(segment_data_for_line.join(","))
|
138
|
+
write_json_field(result, "mappings", line_data.join(";"))
|
139
|
+
|
140
|
+
source_names = []
|
141
|
+
(0...next_source_id).each {|id| source_names.push(id_to_source_pathname[id].to_s)}
|
142
|
+
write_json_field(result, "sources", source_names)
|
143
|
+
write_json_field(result, "file", target_pathname.basename.to_s)
|
144
|
+
|
145
|
+
result << "\n}"
|
146
|
+
result
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def write_json_field(out, name, value, is_first = false)
|
152
|
+
out << (is_first ? "" : ",\n") <<
|
153
|
+
"\"" <<
|
154
|
+
Sass::Util.json_escape_string(name) <<
|
155
|
+
"\": " <<
|
156
|
+
Sass::Util.json_value_of(value)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Sass::Source
|
2
|
+
class Position
|
3
|
+
# The one-based line of the document associated with the position.
|
4
|
+
#
|
5
|
+
# @return [Fixnum]
|
6
|
+
attr_accessor :line
|
7
|
+
|
8
|
+
# The one-based offset in the line of the document associated with the
|
9
|
+
# position.
|
10
|
+
#
|
11
|
+
# @return [Fixnum]
|
12
|
+
attr_accessor :offset
|
13
|
+
|
14
|
+
# @param line [Fixnum] The source line
|
15
|
+
# @param offset [Fixnum] The source offset
|
16
|
+
def initialize(line, offset)
|
17
|
+
@line = line
|
18
|
+
@offset = offset
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] A string representation of the source position.
|
22
|
+
def inspect
|
23
|
+
"#{line.inspect}:#{offset.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sass::Source
|
2
|
+
class Range
|
3
|
+
# The starting position of the range in the document (inclusive).
|
4
|
+
#
|
5
|
+
# @return [Sass::Source::Position]
|
6
|
+
attr_accessor :start_pos
|
7
|
+
|
8
|
+
# The ending position of the range in the document (exclusive).
|
9
|
+
#
|
10
|
+
# @return [Sass::Source::Position]
|
11
|
+
attr_accessor :end_pos
|
12
|
+
|
13
|
+
# The file in which this source range appears. This can be nil if the file
|
14
|
+
# is unknown or not yet generated.
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :file
|
18
|
+
|
19
|
+
# @param start_pos [Sass::Source::Position] See \{#start_pos}
|
20
|
+
# @param end_pos [Sass::Source::Position] See \{#end_pos}
|
21
|
+
# @param file [String] See \{#file}
|
22
|
+
def initialize(start_pos, end_pos, file)
|
23
|
+
raise 'hell' if end_pos.is_a?(Fixnum)
|
24
|
+
@start_pos = start_pos
|
25
|
+
@end_pos = end_pos
|
26
|
+
@file = file
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String] A string representation of the source range.
|
30
|
+
def inspect
|
31
|
+
"(#{start_pos.inspect} to #{end_pos.inspect}#{" in #{@file}" if @file})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -30,7 +30,7 @@ module Sass::Tree
|
|
30
30
|
|
31
31
|
# @param uri [String, Sass::Script::Node] See \{#uri}
|
32
32
|
# @param query [Array<String, Sass::Script::Node>] See \{#query}
|
33
|
-
def initialize(uri, query =
|
33
|
+
def initialize(uri, query = [])
|
34
34
|
@uri = uri
|
35
35
|
@query = query
|
36
36
|
super('')
|
data/lib/sass/tree/node.rb
CHANGED
@@ -46,6 +46,11 @@ module Sass
|
|
46
46
|
# @return [Fixnum]
|
47
47
|
attr_accessor :line
|
48
48
|
|
49
|
+
# The source range in the document on which this node appeared.
|
50
|
+
#
|
51
|
+
# @return [Sass::Source::Range]
|
52
|
+
attr_accessor :source_range
|
53
|
+
|
49
54
|
# The name of the document on which this node appeared.
|
50
55
|
#
|
51
56
|
# @return [String]
|
@@ -127,10 +132,21 @@ module Sass
|
|
127
132
|
|
128
133
|
# Computes the CSS corresponding to this static CSS tree.
|
129
134
|
#
|
130
|
-
# @return [String
|
135
|
+
# @return [String] The resulting CSS
|
136
|
+
# @see Sass::Tree
|
137
|
+
def css
|
138
|
+
Sass::Tree::Visitors::ToCss.new.visit(self)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Computes the CSS corresponding to this static CSS tree, along with
|
142
|
+
# the respective source map.
|
143
|
+
#
|
144
|
+
# @return [(String, Sass::Source::Map)] The resulting CSS and the source map
|
131
145
|
# @see Sass::Tree
|
132
|
-
def
|
133
|
-
Sass::Tree::Visitors::ToCss.
|
146
|
+
def css_with_sourcemap
|
147
|
+
visitor = Sass::Tree::Visitors::ToCss.new(:build_source_mapping)
|
148
|
+
result = visitor.visit(self)
|
149
|
+
return result, visitor.source_mapping
|
134
150
|
end
|
135
151
|
|
136
152
|
# Returns a representation of the node for debugging purposes.
|
data/lib/sass/tree/prop_node.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Sass::Tree
|
2
|
-
# A static node
|
2
|
+
# A static node representing a CSS property.
|
3
3
|
#
|
4
4
|
# @see Sass::Tree
|
5
5
|
class PropNode < Node
|
@@ -42,6 +42,16 @@ module Sass::Tree
|
|
42
42
|
# @return [Fixnum]
|
43
43
|
attr_accessor :tabs
|
44
44
|
|
45
|
+
# The source range in which the property name appears.
|
46
|
+
#
|
47
|
+
# @return [Sass::Source::Range]
|
48
|
+
attr_accessor :name_source_range
|
49
|
+
|
50
|
+
# The source range in which the property value appears.
|
51
|
+
#
|
52
|
+
# @return [Sass::Source::Range]
|
53
|
+
attr_accessor :value_source_range
|
54
|
+
|
45
55
|
# @param name [Array<String, Sass::Script::Node>] See \{#name}
|
46
56
|
# @param value [Sass::Script::Node] See \{#value}
|
47
57
|
# @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
|
data/lib/sass/tree/root_node.rb
CHANGED
@@ -13,15 +13,31 @@ module Sass
|
|
13
13
|
@template = template
|
14
14
|
end
|
15
15
|
|
16
|
-
# Runs the dynamic Sass code
|
17
|
-
#
|
16
|
+
# Runs the dynamic Sass code and computes the CSS for the tree.
|
17
|
+
#
|
18
|
+
# @return [String] The compiled CSS.
|
18
19
|
def render
|
20
|
+
css_tree.css
|
21
|
+
end
|
22
|
+
|
23
|
+
# Runs the dynamic Sass code and computes the CSS for the tree, along with
|
24
|
+
# the sourcemap.
|
25
|
+
#
|
26
|
+
# @return [(String, Sass::Source::Map)] The compiled CSS, as well as
|
27
|
+
# the source map. @see #render
|
28
|
+
def render_with_sourcemap
|
29
|
+
css_tree.css_with_sourcemap
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def css_tree
|
19
35
|
Visitors::CheckNesting.visit(self)
|
20
36
|
result = Visitors::Perform.visit(self)
|
21
37
|
Visitors::CheckNesting.visit(result) # Check again to validate mixins
|
22
38
|
result, extends = Visitors::Cssize.visit(result)
|
23
39
|
Visitors::Extend.visit(result, extends)
|
24
|
-
result
|
40
|
+
result
|
25
41
|
end
|
26
42
|
end
|
27
43
|
end
|
@@ -213,7 +213,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
213
213
|
# or parses and includes the imported Sass file.
|
214
214
|
def visit_import(node)
|
215
215
|
if path = node.css_import?
|
216
|
-
|
216
|
+
resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
|
217
|
+
resolved_node.source_range = node.source_range
|
218
|
+
return resolved_node
|
217
219
|
end
|
218
220
|
file = node.imported_file
|
219
221
|
handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
|
@@ -295,6 +297,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
295
297
|
node.resolved_name = run_interp(node.name)
|
296
298
|
val = node.value.perform(@environment)
|
297
299
|
node.resolved_value = val.to_s
|
300
|
+
node.value_source_range = val.source_range if val.source_range
|
298
301
|
yield
|
299
302
|
end
|
300
303
|
|
@@ -323,6 +326,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
323
326
|
var = @environment.var(node.name)
|
324
327
|
return [] if node.guarded && var && !var.null?
|
325
328
|
val = node.expr.perform(@environment)
|
329
|
+
if node.expr.source_range
|
330
|
+
val.source_range = node.expr.source_range
|
331
|
+
else
|
332
|
+
val.source_range = node.source_range
|
333
|
+
end
|
326
334
|
@environment.set_var(node.name, val)
|
327
335
|
[]
|
328
336
|
end
|
@@ -1,11 +1,25 @@
|
|
1
1
|
# A visitor for converting a Sass tree into CSS.
|
2
2
|
class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
3
|
-
|
3
|
+
# The source mapping for the generated CSS file. This is only set if
|
4
|
+
# `build_source_mapping` is passed to the constructor and \{#render} has been
|
5
|
+
# run.
|
6
|
+
attr_reader :source_mapping
|
4
7
|
|
5
|
-
|
8
|
+
# @param build_source_mapping [Boolean] Whether to build a
|
9
|
+
# \{Sass::Source::Map} while creating the CSS output. The mapping will
|
10
|
+
# be available from \{#source\_mapping} after the visitor has completed.
|
11
|
+
def initialize(build_source_mapping = false)
|
6
12
|
@tabs = 0
|
13
|
+
@line = 1
|
14
|
+
@offset = 1
|
15
|
+
@result = ""
|
16
|
+
@source_mapping = Sass::Source::Map.new if build_source_mapping
|
7
17
|
end
|
8
18
|
|
19
|
+
# Runs the visitor on `node`.
|
20
|
+
#
|
21
|
+
# @param node [Sass::Tree::Node] The root node of the tree to convert to CSS>
|
22
|
+
# @return [String] The CSS output.
|
9
23
|
def visit(node)
|
10
24
|
super
|
11
25
|
rescue Sass::SyntaxError => e
|
@@ -13,6 +27,8 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
13
27
|
raise e
|
14
28
|
end
|
15
29
|
|
30
|
+
protected
|
31
|
+
|
16
32
|
def with_tabs(tabs)
|
17
33
|
old_tabs, @tabs = @tabs, tabs
|
18
34
|
yield
|
@@ -20,40 +36,116 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
20
36
|
@tabs = old_tabs
|
21
37
|
end
|
22
38
|
|
39
|
+
# Associate all output produced in a block with a given node. Used for source
|
40
|
+
# mapping.
|
41
|
+
def for_node(node, attr_prefix = nil)
|
42
|
+
return yield unless @source_mapping
|
43
|
+
start_pos = Sass::Source::Position.new(@line, @offset)
|
44
|
+
yield
|
45
|
+
|
46
|
+
range_attr = attr_prefix ? :"#{attr_prefix}_source_range" : :source_range
|
47
|
+
return if node.invisible? || !node.send(range_attr)
|
48
|
+
source_range = node.send(range_attr)
|
49
|
+
target_end_pos = Sass::Source::Position.new(@line, @offset)
|
50
|
+
target_range = Sass::Source::Range.new(start_pos, target_end_pos, nil)
|
51
|
+
@source_mapping.add(source_range, target_range)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Move the output cursor back `chars` characters.
|
55
|
+
def erase!(chars)
|
56
|
+
return if chars == 0
|
57
|
+
str = @result.slice!(-chars..-1)
|
58
|
+
newlines = str.count("\n")
|
59
|
+
if newlines > 0
|
60
|
+
@line -= newlines
|
61
|
+
@offset = @result[@result.rindex("\n") || 0..-1].size
|
62
|
+
else
|
63
|
+
@offset -= chars
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Avoid allocating lots of new strings for `#output`. This is important
|
68
|
+
# because `#output` is called all the time.
|
69
|
+
NEWLINE = "\n"
|
70
|
+
|
71
|
+
# Add `s` to the output string and update the line and offset information
|
72
|
+
# accordingly.
|
73
|
+
def output(s)
|
74
|
+
if @lstrip
|
75
|
+
s = s.gsub(/\A\s+/, "")
|
76
|
+
@lstrip = false
|
77
|
+
end
|
78
|
+
|
79
|
+
newlines = s.count(NEWLINE)
|
80
|
+
if newlines > 0
|
81
|
+
@line += newlines
|
82
|
+
@offset = s[s.rindex(NEWLINE)..-1].size
|
83
|
+
else
|
84
|
+
@offset += s.size
|
85
|
+
end
|
86
|
+
|
87
|
+
@result << s
|
88
|
+
end
|
89
|
+
|
90
|
+
# Strip all trailing whitespace from the output string.
|
91
|
+
def rstrip!
|
92
|
+
erase! @result.length - 1 - (@result.rindex(/[^\s]/) || -1)
|
93
|
+
end
|
94
|
+
|
95
|
+
# lstrip the first output in the given block.
|
96
|
+
def lstrip
|
97
|
+
old_lstrip = @lstrip
|
98
|
+
@lstrip = true
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
@lstrip = @lstrip && old_lstrip
|
102
|
+
end
|
103
|
+
|
104
|
+
# Prepend `prefix` to the output string.
|
105
|
+
def prepend!(prefix)
|
106
|
+
@result.insert 0, prefix
|
107
|
+
return unless @source_mapping
|
108
|
+
|
109
|
+
line_delta = prefix.count("\n")
|
110
|
+
offset_delta = prefix.gsub(/.*\n/, '').size
|
111
|
+
@source_mapping.shift_output_offsets(offset_delta)
|
112
|
+
@source_mapping.shift_output_lines(line_delta)
|
113
|
+
end
|
114
|
+
|
23
115
|
def visit_root(node)
|
24
|
-
result = String.new
|
25
116
|
node.children.each do |child|
|
26
117
|
next if child.invisible?
|
27
|
-
|
28
|
-
|
118
|
+
visit(child)
|
119
|
+
output "\n" unless node.style == :compressed
|
29
120
|
end
|
30
|
-
|
31
|
-
return "" if result.empty?
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
begin
|
36
|
-
encoding = node.children.first.name
|
37
|
-
# Default to big-endian encoding, because we have to decide somehow
|
38
|
-
encoding << 'BE' if encoding =~ /\Autf-(16|32)\Z/i
|
39
|
-
result = result.encode(Encoding.find(encoding))
|
40
|
-
rescue EncodingError
|
41
|
-
end
|
42
|
-
end
|
121
|
+
rstrip!
|
122
|
+
return "" if @result.empty?
|
123
|
+
|
124
|
+
output "\n"
|
125
|
+
return @result if Sass::Util.ruby1_8? || @result.ascii_only?
|
43
126
|
|
44
|
-
|
45
|
-
|
46
|
-
|
127
|
+
if node.children.first.is_a?(Sass::Tree::CharsetNode)
|
128
|
+
begin
|
129
|
+
encoding = node.children.first.name
|
130
|
+
# Default to big-endian encoding, because we have to decide somehow
|
131
|
+
encoding << 'BE' if encoding =~ /\Autf-(16|32)\Z/i
|
132
|
+
@result = @result.encode(Encoding.find(encoding))
|
133
|
+
rescue EncodingError
|
134
|
+
end
|
47
135
|
end
|
48
|
-
|
136
|
+
|
137
|
+
prepend! "@charset \"#{@result.encoding.name}\";#{
|
138
|
+
node.style == :compressed ? '' : "\n"
|
139
|
+
}".encode(@result.encoding)
|
140
|
+
@result
|
49
141
|
rescue Sass::SyntaxError => e
|
50
142
|
e.sass_template ||= node.template
|
51
143
|
raise e
|
52
144
|
end
|
53
145
|
|
54
146
|
def visit_charset(node)
|
55
|
-
"@charset \"#{node.name}\";"
|
56
|
-
end
|
147
|
+
for_node(node) {output("@charset \"#{node.name}\";")}
|
148
|
+
end
|
57
149
|
|
58
150
|
def visit_comment(node)
|
59
151
|
return if node.invisible?
|
@@ -62,55 +154,74 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
62
154
|
content = node.resolved_value.gsub(/^/, spaces)
|
63
155
|
content.gsub!(%r{^(\s*)//(.*)$}) {|md| "#{$1}/*#{$2} */"} if node.type == :silent
|
64
156
|
content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && node.type != :loud
|
65
|
-
content
|
157
|
+
for_node(node) {output(content)}
|
66
158
|
end
|
67
159
|
|
68
160
|
def visit_directive(node)
|
69
161
|
was_in_directive = @in_directive
|
70
162
|
tab_str = ' ' * @tabs
|
71
|
-
|
72
|
-
|
163
|
+
if !node.has_children || node.children.empty?
|
164
|
+
output(tab_str)
|
165
|
+
for_node(node) {output(node.resolved_value)}
|
166
|
+
output(!node.has_children ? ";" : " {}")
|
167
|
+
return
|
168
|
+
end
|
169
|
+
|
73
170
|
@in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
171
|
+
output(tab_str) if node.style != :compressed
|
172
|
+
for_node(node) {output(node.resolved_value)}
|
173
|
+
output(node.style == :compressed ? "{" : " {")
|
174
|
+
output(node.style == :compact ? ' ' : "\n") if node.style != :compressed
|
175
|
+
|
79
176
|
was_prop = false
|
80
177
|
first = true
|
81
178
|
node.children.each do |child|
|
82
179
|
next if child.invisible?
|
83
180
|
if node.style == :compact
|
84
181
|
if child.is_a?(Sass::Tree::PropNode)
|
85
|
-
with_tabs(first || was_prop ? 0 : @tabs + 1)
|
182
|
+
with_tabs(first || was_prop ? 0 : @tabs + 1) do
|
183
|
+
visit(child)
|
184
|
+
output(' ')
|
185
|
+
end
|
86
186
|
else
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
187
|
+
if was_prop
|
188
|
+
erase! 1
|
189
|
+
output "\n"
|
190
|
+
end
|
191
|
+
|
192
|
+
if first
|
193
|
+
lstrip {with_tabs(@tabs + 1) {visit(child)}}
|
194
|
+
else
|
195
|
+
with_tabs(@tabs + 1) {visit(child)}
|
196
|
+
end
|
197
|
+
|
198
|
+
rstrip!
|
199
|
+
output "\n"
|
91
200
|
end
|
92
201
|
was_prop = child.is_a?(Sass::Tree::PropNode)
|
93
202
|
first = false
|
94
203
|
elsif node.style == :compressed
|
95
|
-
|
204
|
+
output(was_prop ? ";" : "")
|
205
|
+
with_tabs(0) {visit(child)}
|
96
206
|
was_prop = child.is_a?(Sass::Tree::PropNode)
|
97
207
|
else
|
98
|
-
|
208
|
+
with_tabs(@tabs + 1) {visit(child)}
|
209
|
+
output "\n"
|
99
210
|
end
|
100
211
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
212
|
+
rstrip!
|
213
|
+
output(if node.style == :compressed
|
214
|
+
"}"
|
215
|
+
else
|
216
|
+
(node.style == :expanded ? "\n" : " ") + "}\n"
|
217
|
+
end)
|
106
218
|
ensure
|
107
219
|
@in_directive = was_in_directive
|
108
220
|
end
|
109
221
|
|
110
222
|
def visit_media(node)
|
111
|
-
|
112
|
-
|
113
|
-
str
|
223
|
+
with_tabs(@tabs + node.tabs) {visit_directive(node)}
|
224
|
+
erase! 1 unless node.style == :compressed || node.group_end || @result[-1] != ?\n
|
114
225
|
end
|
115
226
|
|
116
227
|
def visit_supports(node)
|
@@ -124,10 +235,15 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
124
235
|
def visit_prop(node)
|
125
236
|
return if node.resolved_value.empty?
|
126
237
|
tab_str = ' ' * (@tabs + node.tabs)
|
238
|
+
output(tab_str)
|
239
|
+
for_node(node, :name) {output(node.resolved_name)}
|
127
240
|
if node.style == :compressed
|
128
|
-
"
|
241
|
+
output(":");
|
242
|
+
for_node(node, :value) {output(node.resolved_value)}
|
129
243
|
else
|
130
|
-
"
|
244
|
+
output(": ")
|
245
|
+
for_node(node, :value) {output(node.resolved_value)}
|
246
|
+
output(";")
|
131
247
|
end
|
132
248
|
end
|
133
249
|
|
@@ -158,18 +274,18 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
158
274
|
joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
|
159
275
|
total_rule = total_indent << joined_rules
|
160
276
|
|
161
|
-
to_return = ''
|
162
277
|
old_spaces = ' ' * @tabs
|
163
278
|
spaces = ' ' * (@tabs + 1)
|
164
279
|
if node.style != :compressed
|
165
280
|
if node.options[:debug_info] && !@in_directive
|
166
|
-
|
281
|
+
visit(debug_info_rule(node.debug_info, node.options))
|
282
|
+
output "\n"
|
167
283
|
elsif node.options[:trace_selectors]
|
168
|
-
|
169
|
-
|
170
|
-
|
284
|
+
output("#{old_spaces}/* ")
|
285
|
+
output(node.stack_trace.join("\n #{old_spaces}"))
|
286
|
+
output(" */\n")
|
171
287
|
elsif node.options[:line_comments]
|
172
|
-
|
288
|
+
output("#{old_spaces}/* line #{node.line}")
|
173
289
|
|
174
290
|
if node.filename
|
175
291
|
relative_filename = if node.options[:css_filename]
|
@@ -181,26 +297,37 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
181
297
|
end
|
182
298
|
end
|
183
299
|
relative_filename ||= node.filename
|
184
|
-
|
300
|
+
output(", #{relative_filename}")
|
185
301
|
end
|
186
302
|
|
187
|
-
|
303
|
+
output(" */\n")
|
188
304
|
end
|
189
305
|
end
|
190
306
|
|
307
|
+
end_props, trailer, tabs = '', '', 0
|
191
308
|
if node.style == :compact
|
192
|
-
|
193
|
-
|
309
|
+
separator, end_props, bracket = ' ', ' ', ' { '
|
310
|
+
trailer = "\n" if node.group_end
|
194
311
|
elsif node.style == :compressed
|
195
|
-
|
196
|
-
to_return << "#{total_rule}{#{properties}}"
|
312
|
+
separator, bracket = ';', '{'
|
197
313
|
else
|
198
|
-
|
314
|
+
tabs = @tabs + 1
|
315
|
+
separator, bracket = "\n", " {\n"
|
316
|
+
trailer = "\n" if node.group_end
|
199
317
|
end_props = (node.style == :expanded ? "\n" + old_spaces : ' ')
|
200
|
-
|
318
|
+
end
|
319
|
+
output(total_rule)
|
320
|
+
output(bracket)
|
321
|
+
|
322
|
+
with_tabs(tabs) do
|
323
|
+
node.children.each_with_index do |child, i|
|
324
|
+
output(separator) if i > 0
|
325
|
+
visit(child)
|
326
|
+
end
|
201
327
|
end
|
202
328
|
|
203
|
-
|
329
|
+
output(end_props)
|
330
|
+
output("}" + trailer)
|
204
331
|
end
|
205
332
|
end
|
206
333
|
|