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.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. 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