sass4 4.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.
- checksums.yaml +7 -0
- data/.yardopts +13 -0
- data/AGENTS.md +534 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +148 -0
- data/MIT-LICENSE +20 -0
- data/README.md +242 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/sass +13 -0
- data/bin/sass-convert +12 -0
- data/bin/scss +13 -0
- data/extra/sass-spec-ref.sh +40 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +18 -0
- data/lib/sass/cache_stores/base.rb +88 -0
- data/lib/sass/cache_stores/chain.rb +34 -0
- data/lib/sass/cache_stores/filesystem.rb +60 -0
- data/lib/sass/cache_stores/memory.rb +46 -0
- data/lib/sass/cache_stores/null.rb +25 -0
- data/lib/sass/cache_stores.rb +15 -0
- data/lib/sass/callbacks.rb +67 -0
- data/lib/sass/css.rb +407 -0
- data/lib/sass/deprecation.rb +55 -0
- data/lib/sass/engine.rb +1236 -0
- data/lib/sass/environment.rb +236 -0
- data/lib/sass/error.rb +198 -0
- data/lib/sass/exec/base.rb +188 -0
- data/lib/sass/exec/sass_convert.rb +283 -0
- data/lib/sass/exec/sass_scss.rb +436 -0
- data/lib/sass/exec.rb +9 -0
- data/lib/sass/features.rb +48 -0
- data/lib/sass/importers/base.rb +182 -0
- data/lib/sass/importers/deprecated_path.rb +51 -0
- data/lib/sass/importers/filesystem.rb +221 -0
- data/lib/sass/importers.rb +23 -0
- data/lib/sass/logger/base.rb +47 -0
- data/lib/sass/logger/delayed.rb +50 -0
- data/lib/sass/logger/log_level.rb +45 -0
- data/lib/sass/logger.rb +17 -0
- data/lib/sass/media.rb +210 -0
- data/lib/sass/plugin/compiler.rb +552 -0
- data/lib/sass/plugin/configuration.rb +134 -0
- data/lib/sass/plugin/generic.rb +15 -0
- data/lib/sass/plugin/merb.rb +48 -0
- data/lib/sass/plugin/rack.rb +60 -0
- data/lib/sass/plugin/rails.rb +47 -0
- data/lib/sass/plugin/staleness_checker.rb +199 -0
- data/lib/sass/plugin.rb +134 -0
- data/lib/sass/railtie.rb +10 -0
- data/lib/sass/repl.rb +57 -0
- data/lib/sass/root.rb +7 -0
- data/lib/sass/script/css_lexer.rb +33 -0
- data/lib/sass/script/css_parser.rb +36 -0
- data/lib/sass/script/functions.rb +3103 -0
- data/lib/sass/script/lexer.rb +518 -0
- data/lib/sass/script/parser.rb +1164 -0
- data/lib/sass/script/tree/funcall.rb +314 -0
- data/lib/sass/script/tree/interpolation.rb +220 -0
- data/lib/sass/script/tree/list_literal.rb +119 -0
- data/lib/sass/script/tree/literal.rb +49 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/tree/node.rb +119 -0
- data/lib/sass/script/tree/operation.rb +149 -0
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/tree/string_interpolation.rb +125 -0
- data/lib/sass/script/tree/unary_operation.rb +69 -0
- data/lib/sass/script/tree/variable.rb +57 -0
- data/lib/sass/script/tree.rb +16 -0
- data/lib/sass/script/value/arg_list.rb +36 -0
- data/lib/sass/script/value/base.rb +258 -0
- data/lib/sass/script/value/bool.rb +35 -0
- data/lib/sass/script/value/callable.rb +25 -0
- data/lib/sass/script/value/color.rb +704 -0
- data/lib/sass/script/value/function.rb +19 -0
- data/lib/sass/script/value/helpers.rb +298 -0
- data/lib/sass/script/value/list.rb +135 -0
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/null.rb +44 -0
- data/lib/sass/script/value/number.rb +564 -0
- data/lib/sass/script/value/string.rb +138 -0
- data/lib/sass/script/value.rb +13 -0
- data/lib/sass/script.rb +66 -0
- data/lib/sass/scss/css_parser.rb +61 -0
- data/lib/sass/scss/parser.rb +1343 -0
- data/lib/sass/scss/rx.rb +134 -0
- data/lib/sass/scss/static_parser.rb +351 -0
- data/lib/sass/scss.rb +14 -0
- data/lib/sass/selector/abstract_sequence.rb +112 -0
- data/lib/sass/selector/comma_sequence.rb +195 -0
- data/lib/sass/selector/pseudo.rb +291 -0
- data/lib/sass/selector/sequence.rb +661 -0
- data/lib/sass/selector/simple.rb +124 -0
- data/lib/sass/selector/simple_sequence.rb +348 -0
- data/lib/sass/selector.rb +327 -0
- data/lib/sass/shared.rb +76 -0
- data/lib/sass/source/map.rb +209 -0
- data/lib/sass/source/position.rb +39 -0
- data/lib/sass/source/range.rb +41 -0
- data/lib/sass/stack.rb +140 -0
- data/lib/sass/supports.rb +225 -0
- data/lib/sass/tree/at_root_node.rb +83 -0
- data/lib/sass/tree/charset_node.rb +22 -0
- data/lib/sass/tree/comment_node.rb +82 -0
- data/lib/sass/tree/content_node.rb +9 -0
- data/lib/sass/tree/css_import_node.rb +68 -0
- data/lib/sass/tree/debug_node.rb +18 -0
- data/lib/sass/tree/directive_node.rb +59 -0
- data/lib/sass/tree/each_node.rb +24 -0
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/extend_node.rb +43 -0
- data/lib/sass/tree/for_node.rb +36 -0
- data/lib/sass/tree/function_node.rb +44 -0
- data/lib/sass/tree/if_node.rb +52 -0
- data/lib/sass/tree/import_node.rb +75 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/media_node.rb +48 -0
- data/lib/sass/tree/mixin_def_node.rb +38 -0
- data/lib/sass/tree/mixin_node.rb +52 -0
- data/lib/sass/tree/node.rb +240 -0
- data/lib/sass/tree/prop_node.rb +162 -0
- data/lib/sass/tree/return_node.rb +19 -0
- data/lib/sass/tree/root_node.rb +44 -0
- data/lib/sass/tree/rule_node.rb +153 -0
- data/lib/sass/tree/supports_node.rb +38 -0
- data/lib/sass/tree/trace_node.rb +33 -0
- data/lib/sass/tree/variable_node.rb +36 -0
- data/lib/sass/tree/visitors/base.rb +72 -0
- data/lib/sass/tree/visitors/check_nesting.rb +173 -0
- data/lib/sass/tree/visitors/convert.rb +350 -0
- data/lib/sass/tree/visitors/cssize.rb +362 -0
- data/lib/sass/tree/visitors/deep_copy.rb +107 -0
- data/lib/sass/tree/visitors/extend.rb +64 -0
- data/lib/sass/tree/visitors/perform.rb +572 -0
- data/lib/sass/tree/visitors/set_options.rb +139 -0
- data/lib/sass/tree/visitors/to_css.rb +440 -0
- data/lib/sass/tree/warn_node.rb +18 -0
- data/lib/sass/tree/while_node.rb +18 -0
- data/lib/sass/util/multibyte_string_scanner.rb +151 -0
- data/lib/sass/util/normalized_map.rb +122 -0
- data/lib/sass/util/subset_map.rb +109 -0
- data/lib/sass/util/test.rb +9 -0
- data/lib/sass/util.rb +1137 -0
- data/lib/sass/version.rb +120 -0
- data/lib/sass.rb +102 -0
- data/rails/init.rb +1 -0
- metadata +283 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# A visitor for converting a Sass tree into CSS.
|
|
2
|
+
class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
3
|
+
# The source mapping for the generated CSS file. This is only set if
|
|
4
|
+
# `build_source_mapping` is passed to the constructor and \{Sass::Engine#render} has been
|
|
5
|
+
# run.
|
|
6
|
+
attr_reader :source_mapping
|
|
7
|
+
|
|
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)
|
|
12
|
+
@tabs = 0
|
|
13
|
+
@line = 1
|
|
14
|
+
@offset = 1
|
|
15
|
+
@result = String.new("")
|
|
16
|
+
@source_mapping = build_source_mapping ? Sass::Source::Map.new : nil
|
|
17
|
+
@lstrip = nil
|
|
18
|
+
@in_directive = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Runs the visitor on `node`.
|
|
22
|
+
#
|
|
23
|
+
# @param node [Sass::Tree::Node] The root node of the tree to convert to CSS>
|
|
24
|
+
# @return [String] The CSS output.
|
|
25
|
+
def visit(node)
|
|
26
|
+
super
|
|
27
|
+
rescue Sass::SyntaxError => e
|
|
28
|
+
e.modify_backtrace(:filename => node.filename, :line => node.line)
|
|
29
|
+
raise e
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def with_tabs(tabs)
|
|
35
|
+
old_tabs, @tabs = @tabs, tabs
|
|
36
|
+
yield
|
|
37
|
+
ensure
|
|
38
|
+
@tabs = old_tabs
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Associate all output produced in a block with a given node. Used for source
|
|
42
|
+
# mapping.
|
|
43
|
+
def for_node(node, attr_prefix = nil)
|
|
44
|
+
return yield unless @source_mapping
|
|
45
|
+
start_pos = Sass::Source::Position.new(@line, @offset)
|
|
46
|
+
yield
|
|
47
|
+
|
|
48
|
+
range_attr = attr_prefix ? :"#{attr_prefix}_source_range" : :source_range
|
|
49
|
+
return if node.invisible? || !node.send(range_attr)
|
|
50
|
+
source_range = node.send(range_attr)
|
|
51
|
+
target_end_pos = Sass::Source::Position.new(@line, @offset)
|
|
52
|
+
target_range = Sass::Source::Range.new(start_pos, target_end_pos, nil)
|
|
53
|
+
@source_mapping.add(source_range, target_range)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def trailing_semicolon?
|
|
57
|
+
@result.end_with?(";") && !@result.end_with?('\;')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Move the output cursor back `chars` characters.
|
|
61
|
+
def erase!(chars)
|
|
62
|
+
return if chars == 0
|
|
63
|
+
str = @result.slice!(-chars..-1)
|
|
64
|
+
newlines = str.count("\n")
|
|
65
|
+
if newlines > 0
|
|
66
|
+
@line -= newlines
|
|
67
|
+
@offset = @result[@result.rindex("\n") || 0..-1].size
|
|
68
|
+
else
|
|
69
|
+
@offset -= chars
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Avoid allocating lots of new strings for `#output`. This is important
|
|
74
|
+
# because `#output` is called all the time.
|
|
75
|
+
NEWLINE = "\n"
|
|
76
|
+
|
|
77
|
+
# Add `s` to the output string and update the line and offset information
|
|
78
|
+
# accordingly.
|
|
79
|
+
def output(s)
|
|
80
|
+
if @lstrip
|
|
81
|
+
s = s.gsub(/\A\s+/, "")
|
|
82
|
+
@lstrip = false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
newlines = s.count(NEWLINE)
|
|
86
|
+
if newlines > 0
|
|
87
|
+
@line += newlines
|
|
88
|
+
@offset = s[s.rindex(NEWLINE)..-1].size
|
|
89
|
+
else
|
|
90
|
+
@offset += s.size
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@result << s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Strip all trailing whitespace from the output string.
|
|
97
|
+
def rstrip!
|
|
98
|
+
erase! @result.length - 1 - (@result.rindex(/[^\s]/) || -1)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# lstrip the first output in the given block.
|
|
102
|
+
def lstrip
|
|
103
|
+
old_lstrip = @lstrip
|
|
104
|
+
@lstrip = true
|
|
105
|
+
yield
|
|
106
|
+
ensure
|
|
107
|
+
@lstrip &&= old_lstrip
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Prepend `prefix` to the output string.
|
|
111
|
+
def prepend!(prefix)
|
|
112
|
+
@result.insert 0, prefix
|
|
113
|
+
return unless @source_mapping
|
|
114
|
+
|
|
115
|
+
line_delta = prefix.count("\n")
|
|
116
|
+
offset_delta = prefix.gsub(/.*\n/, '').size
|
|
117
|
+
@source_mapping.shift_output_offsets(offset_delta)
|
|
118
|
+
@source_mapping.shift_output_lines(line_delta)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def visit_root(node)
|
|
122
|
+
node.children.each do |child|
|
|
123
|
+
next if child.invisible?
|
|
124
|
+
visit(child)
|
|
125
|
+
next if node.style == :compressed
|
|
126
|
+
output "\n"
|
|
127
|
+
next unless child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles?
|
|
128
|
+
output "\n"
|
|
129
|
+
end
|
|
130
|
+
rstrip!
|
|
131
|
+
if node.style == :compressed && trailing_semicolon?
|
|
132
|
+
erase! 1
|
|
133
|
+
end
|
|
134
|
+
return "" if @result.empty?
|
|
135
|
+
|
|
136
|
+
output "\n"
|
|
137
|
+
|
|
138
|
+
unless @result.ascii_only?
|
|
139
|
+
if node.style == :compressed
|
|
140
|
+
# A byte order mark is sufficient to tell browsers that this
|
|
141
|
+
# file is UTF-8 encoded, and will override any other detection
|
|
142
|
+
# methods as per http://encoding.spec.whatwg.org/#decode-and-encode.
|
|
143
|
+
prepend! "\uFEFF"
|
|
144
|
+
else
|
|
145
|
+
prepend! "@charset \"UTF-8\";\n"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
@result
|
|
150
|
+
rescue Sass::SyntaxError => e
|
|
151
|
+
e.sass_template ||= node.template
|
|
152
|
+
raise e
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def visit_charset(node)
|
|
156
|
+
for_node(node) {output("@charset \"#{node.name}\";")}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def visit_comment(node)
|
|
160
|
+
return if node.invisible?
|
|
161
|
+
spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
|
|
162
|
+
output(spaces)
|
|
163
|
+
|
|
164
|
+
content = node.resolved_value.split("\n").join("\n" + spaces)
|
|
165
|
+
if node.type == :silent
|
|
166
|
+
content.gsub!(%r{^(\s*)//(.*)$}) {"#{$1}/*#{$2} */"}
|
|
167
|
+
end
|
|
168
|
+
if (node.style == :compact || node.style == :compressed) && node.type != :loud
|
|
169
|
+
content.gsub!(%r{\n +(\* *(?!/))?}, ' ')
|
|
170
|
+
end
|
|
171
|
+
for_node(node) {output(content)}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def visit_directive(node)
|
|
175
|
+
was_in_directive = @in_directive
|
|
176
|
+
tab_str = ' ' * @tabs
|
|
177
|
+
if !node.has_children || node.children.empty?
|
|
178
|
+
output(tab_str)
|
|
179
|
+
for_node(node) {output(node.resolved_value)}
|
|
180
|
+
if node.has_children
|
|
181
|
+
output("#{' ' unless node.style == :compressed}{}")
|
|
182
|
+
elsif node.children.empty?
|
|
183
|
+
output(";")
|
|
184
|
+
end
|
|
185
|
+
return
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
@in_directive ||= !node.is_a?(Sass::Tree::MediaNode)
|
|
189
|
+
output(tab_str) if node.style != :compressed
|
|
190
|
+
for_node(node) {output(node.resolved_value)}
|
|
191
|
+
output(node.style == :compressed ? "{" : " {")
|
|
192
|
+
output(node.style == :compact ? ' ' : "\n") if node.style != :compressed
|
|
193
|
+
|
|
194
|
+
had_children = true
|
|
195
|
+
first = true
|
|
196
|
+
node.children.each do |child|
|
|
197
|
+
next if child.invisible?
|
|
198
|
+
if node.style == :compact
|
|
199
|
+
if child.is_a?(Sass::Tree::PropNode)
|
|
200
|
+
with_tabs(first || !had_children ? 0 : @tabs + 1) do
|
|
201
|
+
visit(child)
|
|
202
|
+
output(' ')
|
|
203
|
+
end
|
|
204
|
+
else
|
|
205
|
+
unless had_children
|
|
206
|
+
erase! 1
|
|
207
|
+
output "\n"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if first
|
|
211
|
+
lstrip {with_tabs(@tabs + 1) {visit(child)}}
|
|
212
|
+
else
|
|
213
|
+
with_tabs(@tabs + 1) {visit(child)}
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
rstrip!
|
|
217
|
+
output "\n"
|
|
218
|
+
end
|
|
219
|
+
had_children = child.has_children
|
|
220
|
+
first = false
|
|
221
|
+
elsif node.style == :compressed
|
|
222
|
+
unless had_children
|
|
223
|
+
output(";") unless trailing_semicolon?
|
|
224
|
+
end
|
|
225
|
+
with_tabs(0) {visit(child)}
|
|
226
|
+
had_children = child.has_children
|
|
227
|
+
else
|
|
228
|
+
with_tabs(@tabs + 1) {visit(child)}
|
|
229
|
+
output "\n"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
rstrip!
|
|
233
|
+
if node.style == :compressed && trailing_semicolon?
|
|
234
|
+
erase! 1
|
|
235
|
+
end
|
|
236
|
+
if node.style == :expanded
|
|
237
|
+
output("\n#{tab_str}")
|
|
238
|
+
elsif node.style != :compressed
|
|
239
|
+
output(" ")
|
|
240
|
+
end
|
|
241
|
+
output("}")
|
|
242
|
+
ensure
|
|
243
|
+
@in_directive = was_in_directive
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def visit_media(node)
|
|
247
|
+
with_tabs(@tabs + node.tabs) {visit_directive(node)}
|
|
248
|
+
output("\n") if node.style != :compressed && node.group_end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def visit_supports(node)
|
|
252
|
+
visit_media(node)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def visit_cssimport(node)
|
|
256
|
+
visit_directive(node)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def visit_prop(node)
|
|
260
|
+
return if node.resolved_value.empty? && !node.custom_property?
|
|
261
|
+
tab_str = ' ' * (@tabs + node.tabs)
|
|
262
|
+
output(tab_str)
|
|
263
|
+
for_node(node, :name) {output(node.resolved_name)}
|
|
264
|
+
output(":")
|
|
265
|
+
output(" ") unless node.style == :compressed || node.custom_property?
|
|
266
|
+
for_node(node, :value) do
|
|
267
|
+
output(if node.custom_property?
|
|
268
|
+
format_custom_property_value(node)
|
|
269
|
+
else
|
|
270
|
+
node.resolved_value
|
|
271
|
+
end)
|
|
272
|
+
end
|
|
273
|
+
output(";") unless node.style == :compressed
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def visit_rule(node)
|
|
277
|
+
with_tabs(@tabs + node.tabs) do
|
|
278
|
+
rule_separator = node.style == :compressed ? ',' : ', '
|
|
279
|
+
line_separator =
|
|
280
|
+
case node.style
|
|
281
|
+
when :nested, :expanded; "\n"
|
|
282
|
+
when :compressed; ""
|
|
283
|
+
else; " "
|
|
284
|
+
end
|
|
285
|
+
rule_indent = ' ' * @tabs
|
|
286
|
+
per_rule_indent, total_indent = if [:nested, :expanded].include?(node.style)
|
|
287
|
+
[rule_indent, '']
|
|
288
|
+
else
|
|
289
|
+
['', rule_indent]
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
joined_rules = node.resolved_rules.members.map do |seq|
|
|
293
|
+
next if seq.invisible?
|
|
294
|
+
rule_part = seq.to_s(style: node.style, placeholder: false)
|
|
295
|
+
if node.style == :compressed
|
|
296
|
+
rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
|
|
297
|
+
rule_part.gsub!(/\s*([+>])\s*/m, '\1')
|
|
298
|
+
rule_part.gsub!(/nth([^( ]*)\(([^)]*)\)/m) do |match|
|
|
299
|
+
if match.include?(' of ')
|
|
300
|
+
match.gsub(/\s+/, ' ').gsub(/\s+of\s+/, ' of ')
|
|
301
|
+
else
|
|
302
|
+
match.tr(" \t\n", "")
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
rule_part = Sass::Util.strip_except_escapes(rule_part)
|
|
306
|
+
end
|
|
307
|
+
rule_part
|
|
308
|
+
end.compact.join(rule_separator)
|
|
309
|
+
|
|
310
|
+
joined_rules.lstrip!
|
|
311
|
+
joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
|
|
312
|
+
|
|
313
|
+
old_spaces = ' ' * @tabs
|
|
314
|
+
if node.style != :compressed
|
|
315
|
+
if node.options[:debug_info] && !@in_directive
|
|
316
|
+
visit(debug_info_rule(node.debug_info, node.options))
|
|
317
|
+
output "\n"
|
|
318
|
+
elsif node.options[:trace_selectors]
|
|
319
|
+
output("#{old_spaces}/* ")
|
|
320
|
+
output(node.stack_trace.gsub("\n", "\n #{old_spaces}"))
|
|
321
|
+
output(" */\n")
|
|
322
|
+
elsif node.options[:line_comments]
|
|
323
|
+
output("#{old_spaces}/* line #{node.line}")
|
|
324
|
+
|
|
325
|
+
if node.filename
|
|
326
|
+
relative_filename =
|
|
327
|
+
if node.options[:css_filename]
|
|
328
|
+
begin
|
|
329
|
+
Sass::Util.relative_path_from(
|
|
330
|
+
node.filename, File.dirname(node.options[:css_filename])).to_s
|
|
331
|
+
rescue ArgumentError
|
|
332
|
+
nil
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
relative_filename ||= node.filename
|
|
336
|
+
output(", #{relative_filename}")
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
output(" */\n")
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
end_props, trailer, tabs = '', '', 0
|
|
344
|
+
if node.style == :compact
|
|
345
|
+
separator, end_props, bracket = ' ', ' ', ' { '
|
|
346
|
+
trailer = "\n" if node.group_end
|
|
347
|
+
elsif node.style == :compressed
|
|
348
|
+
separator, bracket = ';', '{'
|
|
349
|
+
else
|
|
350
|
+
tabs = @tabs + 1
|
|
351
|
+
separator, bracket = "\n", " {\n"
|
|
352
|
+
trailer = "\n" if node.group_end
|
|
353
|
+
end_props = (node.style == :expanded ? "\n" + old_spaces : ' ')
|
|
354
|
+
end
|
|
355
|
+
output(total_indent + per_rule_indent)
|
|
356
|
+
for_node(node, :selector) {output(joined_rules)}
|
|
357
|
+
output(bracket)
|
|
358
|
+
|
|
359
|
+
with_tabs(tabs) do
|
|
360
|
+
node.children.each_with_index do |child, i|
|
|
361
|
+
if i > 0
|
|
362
|
+
if separator.start_with?(";") && trailing_semicolon?
|
|
363
|
+
erase! 1
|
|
364
|
+
end
|
|
365
|
+
output(separator)
|
|
366
|
+
end
|
|
367
|
+
visit(child)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
if node.style == :compressed && trailing_semicolon?
|
|
371
|
+
erase! 1
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
output(end_props)
|
|
375
|
+
output("}" + trailer)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def visit_keyframerule(node)
|
|
380
|
+
visit_directive(node)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
private
|
|
384
|
+
|
|
385
|
+
# Reformats the value of `node` so that it's nicely indented, preserving its
|
|
386
|
+
# existing relative indentation.
|
|
387
|
+
#
|
|
388
|
+
# @param node [Sass::Script::Tree::PropNode] A custom property node.
|
|
389
|
+
# @return [String]
|
|
390
|
+
def format_custom_property_value(node)
|
|
391
|
+
value = node.resolved_value.sub(/\n[ \t\r\f\n]*\Z/, ' ')
|
|
392
|
+
if node.style == :compact || node.style == :compressed || !value.include?("\n")
|
|
393
|
+
# Folding not involving newlines was done in the parser. We can safely
|
|
394
|
+
# fold newlines here because tokens like strings can't contain literal
|
|
395
|
+
# newlines, so we know any adjacent whitespace is tokenized as whitespace.
|
|
396
|
+
return node.resolved_value.gsub(/[ \t\r\f]*\n[ \t\r\f\n]*/, ' ')
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Find the smallest amount of indentation in the custom property and use
|
|
400
|
+
# that as the base indentation level.
|
|
401
|
+
lines = value.split("\n")
|
|
402
|
+
indented_lines = lines[1..-1]
|
|
403
|
+
min_indentation = indented_lines.
|
|
404
|
+
map {|line| line[/^[ \t]*/]}.
|
|
405
|
+
reject {|line| line.empty?}.
|
|
406
|
+
min_by {|line| line.length}
|
|
407
|
+
|
|
408
|
+
# Limit the base indentation to the same indentation level as the node name
|
|
409
|
+
# so that if *every* line is indented relative to the property name that's
|
|
410
|
+
# preserved.
|
|
411
|
+
if node.name_source_range
|
|
412
|
+
base_indentation = min_indentation[0...node.name_source_range.start_pos.offset - 1]
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
lines.first + "\n" + indented_lines.join("\n").gsub(/^#{base_indentation}/, ' ' * @tabs)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def debug_info_rule(debug_info, options)
|
|
419
|
+
node = Sass::Tree::DirectiveNode.resolved("@media -sass-debug-info")
|
|
420
|
+
debug_info.map {|k, v| [k.to_s, v.to_s]}.to_a.each do |k, v|
|
|
421
|
+
rule = Sass::Tree::RuleNode.new([""])
|
|
422
|
+
rule.resolved_rules = Sass::Selector::CommaSequence.new(
|
|
423
|
+
[Sass::Selector::Sequence.new(
|
|
424
|
+
[Sass::Selector::SimpleSequence.new(
|
|
425
|
+
[Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)],
|
|
426
|
+
false)
|
|
427
|
+
])
|
|
428
|
+
])
|
|
429
|
+
prop = Sass::Tree::PropNode.new([""], [""], :new)
|
|
430
|
+
prop.resolved_name = "font-family"
|
|
431
|
+
prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
|
|
432
|
+
rule << prop
|
|
433
|
+
node << rule
|
|
434
|
+
end
|
|
435
|
+
node.options = options.merge(:debug_info => false,
|
|
436
|
+
:line_comments => false,
|
|
437
|
+
:style => :compressed)
|
|
438
|
+
node
|
|
439
|
+
end
|
|
440
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Sass
|
|
2
|
+
module Tree
|
|
3
|
+
# A dynamic node representing a Sass `@warn` statement.
|
|
4
|
+
#
|
|
5
|
+
# @see Sass::Tree
|
|
6
|
+
class WarnNode < Node
|
|
7
|
+
# The expression to print.
|
|
8
|
+
# @return [Script::Tree::Node]
|
|
9
|
+
attr_accessor :expr
|
|
10
|
+
|
|
11
|
+
# @param expr [Script::Tree::Node] The expression to print
|
|
12
|
+
def initialize(expr)
|
|
13
|
+
@expr = expr
|
|
14
|
+
super()
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'sass/tree/node'
|
|
2
|
+
|
|
3
|
+
module Sass::Tree
|
|
4
|
+
# A dynamic node representing a Sass `@while` loop.
|
|
5
|
+
#
|
|
6
|
+
# @see Sass::Tree
|
|
7
|
+
class WhileNode < Node
|
|
8
|
+
# The parse tree for the continuation expression.
|
|
9
|
+
# @return [Script::Tree::Node]
|
|
10
|
+
attr_accessor :expr
|
|
11
|
+
|
|
12
|
+
# @param expr [Script::Tree::Node] See \{#expr}
|
|
13
|
+
def initialize(expr)
|
|
14
|
+
@expr = expr
|
|
15
|
+
super()
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
|
|
3
|
+
if Sass::Util.rbx?
|
|
4
|
+
# Rubinius's StringScanner class implements some of its methods in terms of
|
|
5
|
+
# others, which causes us to double-count bytes in some cases if we do
|
|
6
|
+
# straightforward inheritance. To work around this, we use a delegate class.
|
|
7
|
+
require 'delegate'
|
|
8
|
+
class Sass::Util::MultibyteStringScanner < DelegateClass(StringScanner)
|
|
9
|
+
def initialize(str)
|
|
10
|
+
super(StringScanner.new(str))
|
|
11
|
+
@mb_pos = 0
|
|
12
|
+
@mb_matched_size = nil
|
|
13
|
+
@mb_last_pos = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def is_a?(klass)
|
|
17
|
+
__getobj__.is_a?(klass) || super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
class Sass::Util::MultibyteStringScanner < StringScanner
|
|
22
|
+
def initialize(str)
|
|
23
|
+
super
|
|
24
|
+
@mb_pos = 0
|
|
25
|
+
@mb_matched_size = nil
|
|
26
|
+
@mb_last_pos = nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# A wrapper of the native StringScanner class that works correctly with
|
|
32
|
+
# multibyte character encodings. The native class deals only in bytes, not
|
|
33
|
+
# characters, for methods like [#pos] and [#matched_size]. This class deals
|
|
34
|
+
# only in characters, instead.
|
|
35
|
+
class Sass::Util::MultibyteStringScanner
|
|
36
|
+
def self.new(str)
|
|
37
|
+
return StringScanner.new(str) if str.ascii_only?
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
alias_method :byte_pos, :pos
|
|
42
|
+
alias_method :byte_matched_size, :matched_size
|
|
43
|
+
|
|
44
|
+
def check(pattern); _match super; end
|
|
45
|
+
def check_until(pattern); _matched super; end
|
|
46
|
+
def getch; _forward _match super; end
|
|
47
|
+
def match?(pattern); _size check(pattern); end
|
|
48
|
+
def matched_size; @mb_matched_size; end
|
|
49
|
+
def peek(len); string[@mb_pos, len]; end
|
|
50
|
+
alias_method :peep, :peek
|
|
51
|
+
def pos; @mb_pos; end
|
|
52
|
+
alias_method :pointer, :pos
|
|
53
|
+
def rest_size; rest.size; end
|
|
54
|
+
def scan(pattern); _forward _match super; end
|
|
55
|
+
def scan_until(pattern); _forward _matched super; end
|
|
56
|
+
def skip(pattern); _size scan(pattern); end
|
|
57
|
+
def skip_until(pattern); _matched _size scan_until(pattern); end
|
|
58
|
+
|
|
59
|
+
def get_byte
|
|
60
|
+
raise "MultibyteStringScanner doesn't support #get_byte."
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def getbyte
|
|
64
|
+
raise "MultibyteStringScanner doesn't support #getbyte."
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def pos=(n)
|
|
68
|
+
@mb_last_pos = nil
|
|
69
|
+
|
|
70
|
+
# We set position kind of a lot during parsing, so we want it to be as
|
|
71
|
+
# efficient as possible. This is complicated by the fact that UTF-8 is a
|
|
72
|
+
# variable-length encoding, so it's difficult to find the byte length that
|
|
73
|
+
# corresponds to a given character length.
|
|
74
|
+
#
|
|
75
|
+
# Our heuristic here is to try to count the fewest possible characters. So
|
|
76
|
+
# if the new position is close to the current one, just count the
|
|
77
|
+
# characters between the two; if the new position is closer to the
|
|
78
|
+
# beginning of the string, just count the characters from there.
|
|
79
|
+
if @mb_pos - n < @mb_pos / 2
|
|
80
|
+
# New position is close to old position
|
|
81
|
+
byte_delta = @mb_pos > n ? -string[n...@mb_pos].bytesize : string[@mb_pos...n].bytesize
|
|
82
|
+
super(byte_pos + byte_delta)
|
|
83
|
+
else
|
|
84
|
+
# New position is close to BOS
|
|
85
|
+
super(string[0...n].bytesize)
|
|
86
|
+
end
|
|
87
|
+
@mb_pos = n
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def reset
|
|
91
|
+
@mb_pos = 0
|
|
92
|
+
@mb_matched_size = nil
|
|
93
|
+
@mb_last_pos = nil
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def scan_full(pattern, advance_pointer_p, return_string_p)
|
|
98
|
+
res = _match super(pattern, advance_pointer_p, true)
|
|
99
|
+
_forward res if advance_pointer_p
|
|
100
|
+
return res if return_string_p
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def search_full(pattern, advance_pointer_p, return_string_p)
|
|
104
|
+
res = super(pattern, advance_pointer_p, true)
|
|
105
|
+
_forward res if advance_pointer_p
|
|
106
|
+
_matched((res if return_string_p))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def string=(str)
|
|
110
|
+
@mb_pos = 0
|
|
111
|
+
@mb_matched_size = nil
|
|
112
|
+
@mb_last_pos = nil
|
|
113
|
+
super
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def terminate
|
|
117
|
+
@mb_pos = string.size
|
|
118
|
+
@mb_matched_size = nil
|
|
119
|
+
@mb_last_pos = nil
|
|
120
|
+
super
|
|
121
|
+
end
|
|
122
|
+
alias_method :clear, :terminate
|
|
123
|
+
|
|
124
|
+
def unscan
|
|
125
|
+
super
|
|
126
|
+
@mb_pos = @mb_last_pos
|
|
127
|
+
@mb_last_pos = @mb_matched_size = nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def _size(str)
|
|
133
|
+
str && str.size
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def _match(str)
|
|
137
|
+
@mb_matched_size = str && str.size
|
|
138
|
+
str
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def _matched(res)
|
|
142
|
+
_match matched
|
|
143
|
+
res
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def _forward(str)
|
|
147
|
+
@mb_last_pos = @mb_pos
|
|
148
|
+
@mb_pos += str.size if str
|
|
149
|
+
str
|
|
150
|
+
end
|
|
151
|
+
end
|