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,327 @@
1
+ require 'sass/selector/simple'
2
+ require 'sass/selector/abstract_sequence'
3
+ require 'sass/selector/comma_sequence'
4
+ require 'sass/selector/pseudo'
5
+ require 'sass/selector/sequence'
6
+ require 'sass/selector/simple_sequence'
7
+
8
+ module Sass
9
+ # A namespace for nodes in the parse tree for selectors.
10
+ #
11
+ # {CommaSequence} is the toplevel selector,
12
+ # representing a comma-separated sequence of {Sequence}s,
13
+ # such as `foo bar, baz bang`.
14
+ # {Sequence} is the next level,
15
+ # representing {SimpleSequence}s separated by combinators (e.g. descendant or child),
16
+ # such as `foo bar` or `foo > bar baz`.
17
+ # {SimpleSequence} is a sequence of selectors that all apply to a single element,
18
+ # such as `foo.bar[attr=val]`.
19
+ # Finally, {Simple} is the superclass of the simplest selectors,
20
+ # such as `.foo` or `#bar`.
21
+ module Selector
22
+ # The base used for calculating selector specificity. The spec says this
23
+ # should be "sufficiently high"; it's extremely unlikely that any single
24
+ # selector sequence will contain 1,000 simple selectors.
25
+ SPECIFICITY_BASE = 1_000
26
+
27
+ # A parent-referencing selector (`&` in Sass).
28
+ # The function of this is to be replaced by the parent selector
29
+ # in the nested hierarchy.
30
+ class Parent < Simple
31
+ # The identifier following the `&`. `nil` indicates no suffix.
32
+ #
33
+ # @return [String, nil]
34
+ attr_reader :suffix
35
+
36
+ # @param name [String, nil] See \{#suffix}
37
+ def initialize(suffix = nil)
38
+ @suffix = suffix
39
+ end
40
+
41
+ # @see Selector#to_s
42
+ def to_s(opts = {})
43
+ "&" + (@suffix || '')
44
+ end
45
+
46
+ # Always raises an exception.
47
+ #
48
+ # @raise [Sass::SyntaxError] Parent selectors should be resolved before unification
49
+ # @see Selector#unify
50
+ def unify(sels)
51
+ raise Sass::SyntaxError.new("[BUG] Cannot unify parent selectors.")
52
+ end
53
+ end
54
+
55
+ # A class selector (e.g. `.foo`).
56
+ class Class < Simple
57
+ # The class name.
58
+ #
59
+ # @return [String]
60
+ attr_reader :name
61
+
62
+ # @param name [String] The class name
63
+ def initialize(name)
64
+ @name = name
65
+ end
66
+
67
+ # @see Selector#to_s
68
+ def to_s(opts = {})
69
+ "." + @name
70
+ end
71
+
72
+ # @see AbstractSequence#specificity
73
+ def specificity
74
+ SPECIFICITY_BASE
75
+ end
76
+ end
77
+
78
+ # An id selector (e.g. `#foo`).
79
+ class Id < Simple
80
+ # The id name.
81
+ #
82
+ # @return [String]
83
+ attr_reader :name
84
+
85
+ # @param name [String] The id name
86
+ def initialize(name)
87
+ @name = name
88
+ end
89
+
90
+ def unique?
91
+ true
92
+ end
93
+
94
+ # @see Selector#to_s
95
+ def to_s(opts = {})
96
+ "#" + @name
97
+ end
98
+
99
+ # Returns `nil` if `sels` contains an {Id} selector
100
+ # with a different name than this one.
101
+ #
102
+ # @see Selector#unify
103
+ def unify(sels)
104
+ return if sels.any? {|sel2| sel2.is_a?(Id) && name != sel2.name}
105
+ super
106
+ end
107
+
108
+ # @see AbstractSequence#specificity
109
+ def specificity
110
+ SPECIFICITY_BASE**2
111
+ end
112
+ end
113
+
114
+ # A placeholder selector (e.g. `%foo`).
115
+ # This exists to be replaced via `@extend`.
116
+ # Rulesets using this selector will not be printed, but can be extended.
117
+ # Otherwise, this acts just like a class selector.
118
+ class Placeholder < Simple
119
+ # The placeholder name.
120
+ #
121
+ # @return [String]
122
+ attr_reader :name
123
+
124
+ # @param name [String] The placeholder name
125
+ def initialize(name)
126
+ @name = name
127
+ end
128
+
129
+ # @see Selector#to_s
130
+ def to_s(opts = {})
131
+ "%" + @name
132
+ end
133
+
134
+ # @see AbstractSequence#specificity
135
+ def specificity
136
+ SPECIFICITY_BASE
137
+ end
138
+ end
139
+
140
+ # A universal selector (`*` in CSS).
141
+ class Universal < Simple
142
+ # The selector namespace. `nil` means the default namespace, `""` means no
143
+ # namespace, `"*"` means any namespace.
144
+ #
145
+ # @return [String, nil]
146
+ attr_reader :namespace
147
+
148
+ # @param namespace [String, nil] See \{#namespace}
149
+ def initialize(namespace)
150
+ @namespace = namespace
151
+ end
152
+
153
+ # @see Selector#to_s
154
+ def to_s(opts = {})
155
+ @namespace ? "#{@namespace}|*" : "*"
156
+ end
157
+
158
+ # Unification of a universal selector is somewhat complicated,
159
+ # especially when a namespace is specified.
160
+ # If there is no namespace specified
161
+ # or any namespace is specified (namespace `"*"`),
162
+ # then `sel` is returned without change
163
+ # (unless it's empty, in which case `"*"` is required).
164
+ #
165
+ # If a namespace is specified
166
+ # but `sel` does not specify a namespace,
167
+ # then the given namespace is applied to `sel`,
168
+ # either by adding this {Universal} selector
169
+ # or applying this namespace to an existing {Element} selector.
170
+ #
171
+ # If both this selector *and* `sel` specify namespaces,
172
+ # those namespaces are unified via {Simple#unify_namespaces}
173
+ # and the unified namespace is used, if possible.
174
+ #
175
+ # @todo There are lots of cases that this documentation specifies;
176
+ # make sure we thoroughly test **all of them**.
177
+ # @todo Keep track of whether a default namespace has been declared
178
+ # and handle namespace-unspecified selectors accordingly.
179
+ # @todo If any branch of a CommaSequence ends up being just `"*"`,
180
+ # then all other branches should be eliminated
181
+ #
182
+ # @see Selector#unify
183
+ def unify(sels)
184
+ name =
185
+ case sels.first
186
+ when Universal; :universal
187
+ when Element; sels.first.name
188
+ else
189
+ return [self] + sels unless namespace.nil? || namespace == '*'
190
+ return sels unless sels.empty?
191
+ return [self]
192
+ end
193
+
194
+ ns, accept = unify_namespaces(namespace, sels.first.namespace)
195
+ return unless accept
196
+ [name == :universal ? Universal.new(ns) : Element.new(name, ns)] + sels[1..-1]
197
+ end
198
+
199
+ # @see AbstractSequence#specificity
200
+ def specificity
201
+ 0
202
+ end
203
+ end
204
+
205
+ # An element selector (e.g. `h1`).
206
+ class Element < Simple
207
+ # The element name.
208
+ #
209
+ # @return [String]
210
+ attr_reader :name
211
+
212
+ # The selector namespace. `nil` means the default namespace, `""` means no
213
+ # namespace, `"*"` means any namespace.
214
+ #
215
+ # @return [String, nil]
216
+ attr_reader :namespace
217
+
218
+ # @param name [String] The element name
219
+ # @param namespace [String, nil] See \{#namespace}
220
+ def initialize(name, namespace)
221
+ @name = name
222
+ @namespace = namespace
223
+ end
224
+
225
+ # @see Selector#to_s
226
+ def to_s(opts = {})
227
+ @namespace ? "#{@namespace}|#{@name}" : @name
228
+ end
229
+
230
+ # Unification of an element selector is somewhat complicated,
231
+ # especially when a namespace is specified.
232
+ # First, if `sel` contains another {Element} with a different \{#name},
233
+ # then the selectors can't be unified and `nil` is returned.
234
+ #
235
+ # Otherwise, if `sel` doesn't specify a namespace,
236
+ # or it specifies any namespace (via `"*"`),
237
+ # then it's returned with this element selector
238
+ # (e.g. `.foo` becomes `a.foo` or `svg|a.foo`).
239
+ # Similarly, if this selector doesn't specify a namespace,
240
+ # the namespace from `sel` is used.
241
+ #
242
+ # If both this selector *and* `sel` specify namespaces,
243
+ # those namespaces are unified via {Simple#unify_namespaces}
244
+ # and the unified namespace is used, if possible.
245
+ #
246
+ # @todo There are lots of cases that this documentation specifies;
247
+ # make sure we thoroughly test **all of them**.
248
+ # @todo Keep track of whether a default namespace has been declared
249
+ # and handle namespace-unspecified selectors accordingly.
250
+ #
251
+ # @see Selector#unify
252
+ def unify(sels)
253
+ case sels.first
254
+ when Universal;
255
+ when Element; return unless name == sels.first.name
256
+ else return [self] + sels
257
+ end
258
+
259
+ ns, accept = unify_namespaces(namespace, sels.first.namespace)
260
+ return unless accept
261
+ [Element.new(name, ns)] + sels[1..-1]
262
+ end
263
+
264
+ # @see AbstractSequence#specificity
265
+ def specificity
266
+ 1
267
+ end
268
+ end
269
+
270
+ # An attribute selector (e.g. `[href^="http://"]`).
271
+ class Attribute < Simple
272
+ # The attribute name.
273
+ #
274
+ # @return [Array<String, Sass::Script::Tree::Node>]
275
+ attr_reader :name
276
+
277
+ # The attribute namespace. `nil` means the default namespace, `""` means
278
+ # no namespace, `"*"` means any namespace.
279
+ #
280
+ # @return [String, nil]
281
+ attr_reader :namespace
282
+
283
+ # The matching operator, e.g. `"="` or `"^="`.
284
+ #
285
+ # @return [String]
286
+ attr_reader :operator
287
+
288
+ # The right-hand side of the operator.
289
+ #
290
+ # @return [String]
291
+ attr_reader :value
292
+
293
+ # Flags for the attribute selector (e.g. `i`).
294
+ #
295
+ # @return [String]
296
+ attr_reader :flags
297
+
298
+ # @param name [String] The attribute name
299
+ # @param namespace [String, nil] See \{#namespace}
300
+ # @param operator [String] The matching operator, e.g. `"="` or `"^="`
301
+ # @param value [String] See \{#value}
302
+ # @param flags [String] See \{#flags}
303
+ def initialize(name, namespace, operator, value, flags)
304
+ @name = name
305
+ @namespace = namespace
306
+ @operator = operator
307
+ @value = value
308
+ @flags = flags
309
+ end
310
+
311
+ # @see Selector#to_s
312
+ def to_s(opts = {})
313
+ res = "["
314
+ res << @namespace << "|" if @namespace
315
+ res << @name
316
+ res << @operator << @value if @value
317
+ res << " " << @flags if @flags
318
+ res << "]"
319
+ end
320
+
321
+ # @see AbstractSequence#specificity
322
+ def specificity
323
+ SPECIFICITY_BASE
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,76 @@
1
+ module Sass
2
+ # This module contains functionality that's shared between Haml and Sass.
3
+ module Shared
4
+ extend self
5
+
6
+ # Scans through a string looking for the interoplation-opening `#{`
7
+ # and, when it's found, yields the scanner to the calling code
8
+ # so it can handle it properly.
9
+ #
10
+ # The scanner will have any backslashes immediately in front of the `#{`
11
+ # as the second capture group (`scan[2]`),
12
+ # and the text prior to that as the first (`scan[1]`).
13
+ #
14
+ # @yieldparam scan [StringScanner] The scanner scanning through the string
15
+ # @return [String] The text remaining in the scanner after all `#{`s have been processed
16
+ def handle_interpolation(str)
17
+ scan = Sass::Util::MultibyteStringScanner.new(str)
18
+ yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
19
+ scan.rest
20
+ end
21
+
22
+ # Moves a scanner through a balanced pair of characters.
23
+ # For example:
24
+ #
25
+ # Foo (Bar (Baz bang) bop) (Bang (bop bip))
26
+ # ^ ^
27
+ # from to
28
+ #
29
+ # @param scanner [StringScanner] The string scanner to move
30
+ # @param start [Character] The character opening the balanced pair.
31
+ # A `Fixnum` in 1.8, a `String` in 1.9
32
+ # @param finish [Character] The character closing the balanced pair.
33
+ # A `Fixnum` in 1.8, a `String` in 1.9
34
+ # @param count [Integer] The number of opening characters matched
35
+ # before calling this method
36
+ # @return [(String, String)] The string matched within the balanced pair
37
+ # and the rest of the string.
38
+ # `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
39
+ def balance(scanner, start, finish, count = 0)
40
+ str = ''
41
+ scanner = Sass::Util::MultibyteStringScanner.new(scanner) unless scanner.is_a? StringScanner
42
+ regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
43
+ while scanner.scan(regexp)
44
+ str << scanner.matched
45
+ count += 1 if scanner.matched[-1] == start
46
+ count -= 1 if scanner.matched[-1] == finish
47
+ return [str, scanner.rest] if count == 0
48
+ end
49
+ end
50
+
51
+ # Formats a string for use in error messages about indentation.
52
+ #
53
+ # @param indentation [String] The string used for indentation
54
+ # @param was [Boolean] Whether or not to add `"was"` or `"were"`
55
+ # (depending on how many characters were in `indentation`)
56
+ # @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)
57
+ def human_indentation(indentation, was = false)
58
+ if !indentation.include?(?\t)
59
+ noun = 'space'
60
+ elsif !indentation.include?(?\s)
61
+ noun = 'tab'
62
+ else
63
+ return indentation.inspect + (was ? ' was' : '')
64
+ end
65
+
66
+ singular = indentation.length == 1
67
+ if was
68
+ was = singular ? ' was' : ' were'
69
+ else
70
+ was = ''
71
+ end
72
+
73
+ "#{indentation.length} #{noun}#{'s' unless singular}#{was}"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,209 @@
1
+ module Sass::Source
2
+ class Map
3
+ # A mapping from one source range to another. Indicates that `input` was
4
+ # compiled to `output`.
5
+ #
6
+ # @!attribute input
7
+ # @return [Sass::Source::Range] The source range in the input document.
8
+ #
9
+ # @!attribute output
10
+ # @return [Sass::Source::Range] The source range in the output document.
11
+ class Mapping < Struct.new(:input, :output)
12
+ # @return [String] A string representation of the mapping.
13
+ def inspect
14
+ "#{input.inspect} => #{output.inspect}"
15
+ end
16
+ end
17
+
18
+ # The mapping data ordered by the location in the target.
19
+ #
20
+ # @return [Array<Mapping>]
21
+ attr_reader :data
22
+
23
+ def initialize
24
+ @data = []
25
+ end
26
+
27
+ # Adds a new mapping from one source range to another. Multiple invocations
28
+ # of this method should have each `output` range come after all previous ranges.
29
+ #
30
+ # @param input [Sass::Source::Range]
31
+ # The source range in the input document.
32
+ # @param output [Sass::Source::Range]
33
+ # The source range in the output document.
34
+ def add(input, output)
35
+ @data.push(Mapping.new(input, output))
36
+ end
37
+
38
+ # Shifts all output source ranges forward one or more lines.
39
+ #
40
+ # @param delta [Integer] The number of lines to shift the ranges forward.
41
+ def shift_output_lines(delta)
42
+ return if delta == 0
43
+ @data.each do |m|
44
+ m.output.start_pos.line += delta
45
+ m.output.end_pos.line += delta
46
+ end
47
+ end
48
+
49
+ # Shifts any output source ranges that lie on the first line forward one or
50
+ # more characters on that line.
51
+ #
52
+ # @param delta [Integer] The number of characters to shift the ranges
53
+ # forward.
54
+ def shift_output_offsets(delta)
55
+ return if delta == 0
56
+ @data.each do |m|
57
+ break if m.output.start_pos.line > 1
58
+ m.output.start_pos.offset += delta
59
+ m.output.end_pos.offset += delta if m.output.end_pos.line > 1
60
+ end
61
+ end
62
+
63
+ # Returns the standard JSON representation of the source map.
64
+ #
65
+ # If the `:css_uri` option isn't specified, the `:css_path` and
66
+ # `:sourcemap_path` options must both be specified. Any options may also be
67
+ # specified alongside the `:css_uri` option. If `:css_uri` isn't specified,
68
+ # it will be inferred from `:css_path` and `:sourcemap_path` using the
69
+ # assumption that the local file system has the same layout as the server.
70
+ #
71
+ # Regardless of which options are passed to this method, source stylesheets
72
+ # that are imported using a non-default importer will only be linked to in
73
+ # the source map if their importers implement
74
+ # \{Sass::Importers::Base#public\_url\}.
75
+ #
76
+ # @option options :css_uri [String]
77
+ # The publicly-visible URI of the CSS output file.
78
+ # @option options :css_path [String]
79
+ # The local path of the CSS output file.
80
+ # @option options :sourcemap_path [String]
81
+ # The (eventual) local path of the sourcemap file.
82
+ # @option options :type [Symbol]
83
+ # `:auto` (default), `:file`, or `:inline`.
84
+ # @return [String] The JSON string.
85
+ # @raise [ArgumentError] If neither `:css_uri` nor `:css_path` and
86
+ # `:sourcemap_path` are specified.
87
+ def to_json(options)
88
+ css_uri, css_path, sourcemap_path =
89
+ options[:css_uri], options[:css_path], options[:sourcemap_path]
90
+ unless css_uri || (css_path && sourcemap_path)
91
+ raise ArgumentError.new("Sass::Source::Map#to_json requires either " \
92
+ "the :css_uri option or both the :css_path and :soucemap_path options.")
93
+ end
94
+ css_path &&= Sass::Util.pathname(File.absolute_path(css_path))
95
+ sourcemap_path &&= Sass::Util.pathname(File.absolute_path(sourcemap_path))
96
+ css_uri ||= Sass::Util.file_uri_from_path(
97
+ Sass::Util.relative_path_from(css_path, sourcemap_path.dirname))
98
+
99
+ result = "{\n"
100
+ write_json_field(result, "version", 3, true)
101
+
102
+ source_uri_to_id = {}
103
+ id_to_source_uri = {}
104
+ id_to_contents = {} if options[:type] == :inline
105
+ next_source_id = 0
106
+ line_data = []
107
+ segment_data_for_line = []
108
+
109
+ # These track data necessary for the delta coding.
110
+ previous_target_line = nil
111
+ previous_target_offset = 1
112
+ previous_source_line = 1
113
+ previous_source_offset = 1
114
+ previous_source_id = 0
115
+
116
+ @data.each do |m|
117
+ file, importer = m.input.file, m.input.importer
118
+
119
+ next unless importer
120
+
121
+ if options[:type] == :inline
122
+ source_uri = file
123
+ else
124
+ sourcemap_dir = sourcemap_path && sourcemap_path.dirname.to_s
125
+ sourcemap_dir = nil if options[:type] == :file
126
+ source_uri = importer.public_url(file, sourcemap_dir)
127
+ next unless source_uri
128
+ end
129
+
130
+ current_source_id = source_uri_to_id[source_uri]
131
+ unless current_source_id
132
+ current_source_id = next_source_id
133
+ next_source_id += 1
134
+
135
+ source_uri_to_id[source_uri] = current_source_id
136
+ id_to_source_uri[current_source_id] = source_uri
137
+
138
+ if options[:type] == :inline
139
+ id_to_contents[current_source_id] =
140
+ importer.find(file, {}).instance_variable_get('@template')
141
+ end
142
+ end
143
+
144
+ [
145
+ [m.input.start_pos, m.output.start_pos],
146
+ [m.input.end_pos, m.output.end_pos]
147
+ ].each do |source_pos, target_pos|
148
+ if previous_target_line != target_pos.line
149
+ line_data.push(segment_data_for_line.join(",")) unless segment_data_for_line.empty?
150
+ (target_pos.line - 1 - (previous_target_line || 0)).times {line_data.push("")}
151
+ previous_target_line = target_pos.line
152
+ previous_target_offset = 1
153
+ segment_data_for_line = []
154
+ end
155
+
156
+ # `segment` is a data chunk for a single position mapping.
157
+ segment = ""
158
+
159
+ # Field 1: zero-based starting offset.
160
+ segment << Sass::Util.encode_vlq(target_pos.offset - previous_target_offset)
161
+ previous_target_offset = target_pos.offset
162
+
163
+ # Field 2: zero-based index into the "sources" list.
164
+ segment << Sass::Util.encode_vlq(current_source_id - previous_source_id)
165
+ previous_source_id = current_source_id
166
+
167
+ # Field 3: zero-based starting line in the original source.
168
+ segment << Sass::Util.encode_vlq(source_pos.line - previous_source_line)
169
+ previous_source_line = source_pos.line
170
+
171
+ # Field 4: zero-based starting offset in the original source.
172
+ segment << Sass::Util.encode_vlq(source_pos.offset - previous_source_offset)
173
+ previous_source_offset = source_pos.offset
174
+
175
+ segment_data_for_line.push(segment)
176
+
177
+ previous_target_line = target_pos.line
178
+ end
179
+ end
180
+ line_data.push(segment_data_for_line.join(","))
181
+ write_json_field(result, "mappings", line_data.join(";"))
182
+
183
+ source_names = []
184
+ (0...next_source_id).each {|id| source_names.push(id_to_source_uri[id].to_s)}
185
+ write_json_field(result, "sources", source_names)
186
+
187
+ if options[:type] == :inline
188
+ write_json_field(result, "sourcesContent",
189
+ (0...next_source_id).map {|id| id_to_contents[id]})
190
+ end
191
+
192
+ write_json_field(result, "names", [])
193
+ write_json_field(result, "file", css_uri)
194
+
195
+ result << "\n}"
196
+ result
197
+ end
198
+
199
+ private
200
+
201
+ def write_json_field(out, name, value, is_first = false)
202
+ out << (is_first ? "" : ",\n") <<
203
+ "\"" <<
204
+ Sass::Util.json_escape_string(name) <<
205
+ "\": " <<
206
+ Sass::Util.json_value_of(value)
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,39 @@
1
+ module Sass::Source
2
+ class Position
3
+ # The one-based line of the document associated with the position.
4
+ #
5
+ # @return [Integer]
6
+ attr_accessor :line
7
+
8
+ # The one-based offset in the line of the document associated with the
9
+ # position.
10
+ #
11
+ # @return [Integer]
12
+ attr_accessor :offset
13
+
14
+ # @param line [Integer] The source line
15
+ # @param offset [Integer] 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
+
26
+ # @param str [String] The string to move through.
27
+ # @return [Position] The source position after proceeding forward through
28
+ # `str`.
29
+ def after(str)
30
+ newlines = str.count("\n")
31
+ Position.new(line + newlines,
32
+ if newlines == 0
33
+ offset + str.length
34
+ else
35
+ str.length - str.rindex("\n") - 1
36
+ end)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
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
+ # The importer that imported the file in which this source range appears.
20
+ # This is nil for target ranges.
21
+ #
22
+ # @return [Sass::Importers::Base]
23
+ attr_accessor :importer
24
+
25
+ # @param start_pos [Sass::Source::Position] See \{#start_pos}
26
+ # @param end_pos [Sass::Source::Position] See \{#end_pos}
27
+ # @param file [String] See \{#file}
28
+ # @param importer [Sass::Importers::Base] See \{#importer}
29
+ def initialize(start_pos, end_pos, file, importer = nil)
30
+ @start_pos = start_pos
31
+ @end_pos = end_pos
32
+ @file = file
33
+ @importer = importer
34
+ end
35
+
36
+ # @return [String] A string representation of the source range.
37
+ def inspect
38
+ "(#{start_pos.inspect} to #{end_pos.inspect}#{" in #{@file}" if @file})"
39
+ end
40
+ end
41
+ end