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,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
|
data/lib/sass/shared.rb
ADDED
|
@@ -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
|