sass 3.3.0 → 3.4.25
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 +4 -4
- data/.yardopts +3 -1
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +148 -0
- data/MIT-LICENSE +1 -1
- data/README.md +76 -62
- data/Rakefile +104 -24
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/VERSION_NAME +1 -1
- data/bin/sass +1 -1
- data/bin/scss +1 -1
- data/extra/sass-spec-ref.sh +32 -0
- data/extra/update_watch.rb +1 -1
- data/lib/sass/cache_stores/filesystem.rb +9 -5
- data/lib/sass/cache_stores/memory.rb +4 -5
- data/lib/sass/callbacks.rb +2 -2
- data/lib/sass/css.rb +12 -13
- data/lib/sass/deprecation.rb +55 -0
- data/lib/sass/engine.rb +106 -70
- data/lib/sass/environment.rb +39 -19
- data/lib/sass/error.rb +17 -20
- data/lib/sass/exec/base.rb +199 -0
- data/lib/sass/exec/sass_convert.rb +283 -0
- data/lib/sass/exec/sass_scss.rb +440 -0
- data/lib/sass/exec.rb +5 -771
- data/lib/sass/features.rb +9 -2
- data/lib/sass/importers/base.rb +8 -3
- data/lib/sass/importers/filesystem.rb +30 -38
- data/lib/sass/logger/base.rb +8 -2
- data/lib/sass/logger/delayed.rb +50 -0
- data/lib/sass/logger.rb +8 -3
- data/lib/sass/media.rb +1 -4
- data/lib/sass/plugin/compiler.rb +224 -90
- data/lib/sass/plugin/configuration.rb +38 -22
- data/lib/sass/plugin/merb.rb +2 -2
- data/lib/sass/plugin/rack.rb +3 -3
- data/lib/sass/plugin/rails.rb +1 -1
- data/lib/sass/plugin/staleness_checker.rb +4 -4
- data/lib/sass/plugin.rb +6 -5
- data/lib/sass/script/css_lexer.rb +1 -1
- data/lib/sass/script/css_parser.rb +2 -3
- data/lib/sass/script/css_variable_warning.rb +52 -0
- data/lib/sass/script/functions.rb +739 -318
- data/lib/sass/script/lexer.rb +134 -54
- data/lib/sass/script/parser.rb +252 -56
- data/lib/sass/script/tree/funcall.rb +13 -6
- data/lib/sass/script/tree/interpolation.rb +127 -4
- data/lib/sass/script/tree/list_literal.rb +31 -4
- data/lib/sass/script/tree/literal.rb +4 -0
- data/lib/sass/script/tree/node.rb +21 -3
- data/lib/sass/script/tree/operation.rb +54 -1
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/tree/string_interpolation.rb +59 -38
- data/lib/sass/script/tree/variable.rb +1 -1
- data/lib/sass/script/tree.rb +1 -0
- data/lib/sass/script/value/base.rb +17 -14
- data/lib/sass/script/value/bool.rb +0 -5
- data/lib/sass/script/value/color.rb +78 -42
- data/lib/sass/script/value/helpers.rb +119 -2
- data/lib/sass/script/value/list.rb +0 -15
- data/lib/sass/script/value/map.rb +1 -1
- data/lib/sass/script/value/null.rb +0 -5
- data/lib/sass/script/value/number.rb +112 -31
- data/lib/sass/script/value/string.rb +102 -13
- data/lib/sass/script/value.rb +0 -1
- data/lib/sass/script.rb +3 -3
- data/lib/sass/scss/css_parser.rb +24 -4
- data/lib/sass/scss/parser.rb +290 -383
- data/lib/sass/scss/rx.rb +17 -9
- data/lib/sass/scss/static_parser.rb +306 -4
- data/lib/sass/scss.rb +0 -2
- data/lib/sass/selector/abstract_sequence.rb +35 -18
- data/lib/sass/selector/comma_sequence.rb +114 -19
- data/lib/sass/selector/pseudo.rb +266 -0
- data/lib/sass/selector/sequence.rb +146 -40
- data/lib/sass/selector/simple.rb +22 -33
- data/lib/sass/selector/simple_sequence.rb +122 -39
- data/lib/sass/selector.rb +57 -197
- data/lib/sass/shared.rb +2 -2
- data/lib/sass/source/map.rb +31 -14
- data/lib/sass/source/position.rb +4 -4
- data/lib/sass/stack.rb +2 -8
- data/lib/sass/supports.rb +10 -13
- data/lib/sass/tree/at_root_node.rb +1 -0
- data/lib/sass/tree/charset_node.rb +1 -1
- data/lib/sass/tree/comment_node.rb +1 -1
- data/lib/sass/tree/css_import_node.rb +9 -1
- data/lib/sass/tree/directive_node.rb +8 -2
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/extend_node.rb +1 -1
- data/lib/sass/tree/function_node.rb +9 -0
- data/lib/sass/tree/import_node.rb +6 -5
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/node.rb +5 -3
- data/lib/sass/tree/prop_node.rb +6 -7
- data/lib/sass/tree/rule_node.rb +26 -11
- data/lib/sass/tree/visitors/check_nesting.rb +56 -32
- data/lib/sass/tree/visitors/convert.rb +59 -44
- data/lib/sass/tree/visitors/cssize.rb +34 -30
- data/lib/sass/tree/visitors/deep_copy.rb +6 -1
- data/lib/sass/tree/visitors/extend.rb +15 -13
- data/lib/sass/tree/visitors/perform.rb +87 -50
- data/lib/sass/tree/visitors/set_options.rb +15 -1
- data/lib/sass/tree/visitors/to_css.rb +72 -43
- data/lib/sass/util/multibyte_string_scanner.rb +0 -2
- data/lib/sass/util/normalized_map.rb +0 -1
- data/lib/sass/util/subset_map.rb +2 -3
- data/lib/sass/util.rb +334 -154
- data/lib/sass/version.rb +7 -7
- data/lib/sass.rb +10 -8
- data/test/sass/cache_test.rb +62 -20
- data/test/sass/callbacks_test.rb +1 -1
- data/test/sass/compiler_test.rb +24 -11
- data/test/sass/conversion_test.rb +241 -50
- data/test/sass/css2sass_test.rb +73 -5
- data/test/sass/css_variable_test.rb +132 -0
- data/test/sass/encoding_test.rb +219 -0
- data/test/sass/engine_test.rb +343 -260
- data/test/sass/exec_test.rb +12 -2
- data/test/sass/extend_test.rb +333 -44
- data/test/sass/functions_test.rb +353 -260
- data/test/sass/importer_test.rb +40 -21
- data/test/sass/logger_test.rb +1 -1
- data/test/sass/more_results/more_import.css +1 -1
- data/test/sass/more_templates/more1.sass +10 -10
- data/test/sass/more_templates/more_import.sass +2 -2
- data/test/sass/plugin_test.rb +24 -21
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +4 -4
- data/test/sass/results/expanded.css +1 -1
- data/test/sass/results/import.css +1 -1
- data/test/sass/results/import_charset_ibm866.css +2 -2
- data/test/sass/results/mixins.css +17 -17
- data/test/sass/results/nested.css +1 -1
- data/test/sass/results/parent_ref.css +2 -2
- data/test/sass/results/script.css +5 -5
- data/test/sass/results/scss_import.css +1 -1
- data/test/sass/script_conversion_test.rb +71 -39
- data/test/sass/script_test.rb +714 -123
- data/test/sass/scss/css_test.rb +213 -30
- data/test/sass/scss/rx_test.rb +8 -4
- data/test/sass/scss/scss_test.rb +766 -22
- data/test/sass/source_map_test.rb +263 -95
- data/test/sass/superselector_test.rb +210 -0
- data/test/sass/templates/_partial.sass +1 -1
- data/test/sass/templates/basic.sass +10 -10
- data/test/sass/templates/bork1.sass +1 -1
- data/test/sass/templates/bork5.sass +1 -1
- data/test/sass/templates/compact.sass +10 -10
- data/test/sass/templates/complex.sass +187 -187
- data/test/sass/templates/compressed.sass +10 -10
- data/test/sass/templates/expanded.sass +10 -10
- data/test/sass/templates/import.sass +2 -2
- data/test/sass/templates/importee.sass +3 -3
- data/test/sass/templates/mixins.sass +22 -22
- data/test/sass/templates/multiline.sass +4 -4
- data/test/sass/templates/nested.sass +13 -13
- data/test/sass/templates/parent_ref.sass +12 -12
- data/test/sass/templates/script.sass +70 -70
- data/test/sass/templates/scss_import.scss +2 -1
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
- data/test/sass/templates/subdir/subdir.sass +3 -3
- data/test/sass/templates/units.sass +10 -10
- data/test/sass/test_helper.rb +1 -1
- data/test/sass/util/multibyte_string_scanner_test.rb +11 -3
- data/test/sass/util/normalized_map_test.rb +1 -1
- data/test/sass/util/subset_map_test.rb +2 -2
- data/test/sass/util_test.rb +46 -45
- data/test/sass/value_helpers_test.rb +5 -7
- data/test/sass-spec.yml +3 -0
- data/test/test_helper.rb +7 -6
- data/vendor/listen/CHANGELOG.md +1 -228
- data/vendor/listen/Gemfile +5 -15
- data/vendor/listen/README.md +111 -77
- data/vendor/listen/Rakefile +0 -42
- data/vendor/listen/lib/listen/adapter.rb +195 -82
- data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
- data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
- data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
- data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
- data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
- data/vendor/listen/lib/listen/directory_record.rb +96 -61
- data/vendor/listen/lib/listen/listener.rb +135 -37
- data/vendor/listen/lib/listen/turnstile.rb +9 -5
- data/vendor/listen/lib/listen/version.rb +1 -1
- data/vendor/listen/lib/listen.rb +33 -19
- data/vendor/listen/listen.gemspec +6 -0
- data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
- data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
- data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
- data/vendor/listen/spec/listen/listener_spec.rb +128 -39
- data/vendor/listen/spec/listen_spec.rb +15 -21
- data/vendor/listen/spec/spec_helper.rb +4 -0
- data/vendor/listen/spec/support/adapter_helper.rb +52 -15
- data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
- data/vendor/listen/spec/support/listeners_helper.rb +30 -7
- metadata +310 -300
- data/CONTRIBUTING +0 -3
- data/ext/mkrf_conf.rb +0 -27
- data/lib/sass/script/value/deprecated_false.rb +0 -55
- data/lib/sass/scss/script_lexer.rb +0 -15
- data/lib/sass/scss/script_parser.rb +0 -25
- data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
- data/vendor/listen/lib/listen/multi_listener.rb +0 -143
- data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
- data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
data/lib/sass/util.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require 'erb'
|
2
3
|
require 'set'
|
3
4
|
require 'enumerator'
|
@@ -12,12 +13,14 @@ require 'sass/util/subset_map'
|
|
12
13
|
|
13
14
|
module Sass
|
14
15
|
# A module containing various useful functions.
|
16
|
+
# @comment
|
17
|
+
# rubocop:disable ModuleLength
|
15
18
|
module Util
|
16
19
|
extend self
|
17
20
|
|
18
21
|
# An array of ints representing the Ruby version number.
|
19
22
|
# @api public
|
20
|
-
|
23
|
+
RUBY_VERSION_COMPONENTS = RUBY_VERSION.split(".").map {|s| s.to_i}
|
21
24
|
|
22
25
|
# The Ruby engine we're running under. Defaults to `"ruby"`
|
23
26
|
# if the top-level constant is undefined.
|
@@ -137,6 +140,18 @@ module Sass
|
|
137
140
|
[[value, range.first].max, range.last].min
|
138
141
|
end
|
139
142
|
|
143
|
+
# Like [Fixnum.round], but leaves rooms for slight floating-point
|
144
|
+
# differences.
|
145
|
+
#
|
146
|
+
# @param value [Numeric]
|
147
|
+
# @return [Numeric]
|
148
|
+
def round(value)
|
149
|
+
# If the number is within epsilon of X.5, round up (or down for negative
|
150
|
+
# numbers).
|
151
|
+
return value.round if (value % 1) - 0.5 <= -1 * Script::Value::Number.epsilon
|
152
|
+
value > 0 ? value.ceil : value.floor
|
153
|
+
end
|
154
|
+
|
140
155
|
# Concatenates all strings that are adjacent in an array,
|
141
156
|
# while leaving other elements as they are.
|
142
157
|
#
|
@@ -162,6 +177,43 @@ module Sass
|
|
162
177
|
end
|
163
178
|
end
|
164
179
|
|
180
|
+
# Non-destructively replaces all occurrences of a subsequence in an array
|
181
|
+
# with another subsequence.
|
182
|
+
#
|
183
|
+
# @example
|
184
|
+
# replace_subseq([1, 2, 3, 4, 5], [2, 3], [:a, :b])
|
185
|
+
# #=> [1, :a, :b, 4, 5]
|
186
|
+
#
|
187
|
+
# @param arr [Array] The array whose subsequences will be replaced.
|
188
|
+
# @param subseq [Array] The subsequence to find and replace.
|
189
|
+
# @param replacement [Array] The sequence that `subseq` will be replaced with.
|
190
|
+
# @return [Array] `arr` with `subseq` replaced with `replacement`.
|
191
|
+
def replace_subseq(arr, subseq, replacement)
|
192
|
+
new = []
|
193
|
+
matched = []
|
194
|
+
i = 0
|
195
|
+
arr.each do |elem|
|
196
|
+
if elem != subseq[i]
|
197
|
+
new.push(*matched)
|
198
|
+
matched = []
|
199
|
+
i = 0
|
200
|
+
new << elem
|
201
|
+
next
|
202
|
+
end
|
203
|
+
|
204
|
+
if i == subseq.length - 1
|
205
|
+
matched = []
|
206
|
+
i = 0
|
207
|
+
new.push(*replacement)
|
208
|
+
else
|
209
|
+
matched << elem
|
210
|
+
i += 1
|
211
|
+
end
|
212
|
+
end
|
213
|
+
new.push(*matched)
|
214
|
+
new
|
215
|
+
end
|
216
|
+
|
165
217
|
# Intersperses a value in an enumerable, as would be done with `Array#join`
|
166
218
|
# but without concatenating the array together afterwards.
|
167
219
|
#
|
@@ -227,7 +279,7 @@ module Sass
|
|
227
279
|
# # [2, 4, 5]]
|
228
280
|
def paths(arrs)
|
229
281
|
arrs.inject([[]]) do |paths, arr|
|
230
|
-
|
282
|
+
arr.map {|e| paths.map {|path| path + [e]}}.flatten(1)
|
231
283
|
end
|
232
284
|
end
|
233
285
|
|
@@ -260,7 +312,7 @@ module Sass
|
|
260
312
|
# @return [Array]
|
261
313
|
def hash_to_a(hash)
|
262
314
|
return hash.to_a unless ruby1_8? || defined?(Test::Unit)
|
263
|
-
hash.sort_by {|k,
|
315
|
+
hash.sort_by {|k, _v| k}
|
264
316
|
end
|
265
317
|
|
266
318
|
# Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed
|
@@ -287,6 +339,18 @@ module Sass
|
|
287
339
|
arr
|
288
340
|
end
|
289
341
|
|
342
|
+
# Like `String.upcase`, but only ever upcases ASCII letters.
|
343
|
+
def upcase(string)
|
344
|
+
return string.upcase unless ruby2_4?
|
345
|
+
string.upcase(:ascii)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Like `String.downcase`, but only ever downcases ASCII letters.
|
349
|
+
def downcase(string)
|
350
|
+
return string.downcase unless ruby2_4?
|
351
|
+
string.downcase(:ascii)
|
352
|
+
end
|
353
|
+
|
290
354
|
# Returns a sub-array of `minuend` containing only elements that are also in
|
291
355
|
# `subtrahend`. Ensures that the return value has the same order as
|
292
356
|
# `minuend`, even on Rubinius where that's not guaranteed by `Array#-`.
|
@@ -300,6 +364,18 @@ module Sass
|
|
300
364
|
minuend.select {|e| set.include?(e)}
|
301
365
|
end
|
302
366
|
|
367
|
+
# Returns the maximum of `val1` and `val2`. We use this over \{Array.max} to
|
368
|
+
# avoid unnecessary garbage collection.
|
369
|
+
def max(val1, val2)
|
370
|
+
val1 > val2 ? val1 : val2
|
371
|
+
end
|
372
|
+
|
373
|
+
# Returns the minimum of `val1` and `val2`. We use this over \{Array.min} to
|
374
|
+
# avoid unnecessary garbage collection.
|
375
|
+
def min(val1, val2)
|
376
|
+
val1 <= val2 ? val1 : val2
|
377
|
+
end
|
378
|
+
|
303
379
|
# Returns a string description of the character that caused an
|
304
380
|
# `Encoding::UndefinedConversionError`.
|
305
381
|
#
|
@@ -354,13 +430,13 @@ module Sass
|
|
354
430
|
# Returns information about the caller of the previous method.
|
355
431
|
#
|
356
432
|
# @param entry [String] An entry in the `#caller` list, or a similarly formatted string
|
357
|
-
# @return [[String,
|
433
|
+
# @return [[String, Integer, (String, nil)]]
|
358
434
|
# An array containing the filename, line, and method name of the caller.
|
359
435
|
# The method name may be nil
|
360
436
|
def caller_info(entry = nil)
|
361
437
|
# JRuby evaluates `caller` incorrectly when it's in an actual default argument.
|
362
438
|
entry ||= caller[1]
|
363
|
-
info = entry.scan(/^(
|
439
|
+
info = entry.scan(/^((?:[A-Za-z]:)?.*?):(-?.*?)(?::.*`(.+)')?$/).first
|
364
440
|
info[1] = info[1].to_i
|
365
441
|
# This is added by Rubinius to designate a block, but we don't care about it.
|
366
442
|
info[2].sub!(/ \{\}\Z/, '') if info[2]
|
@@ -448,7 +524,7 @@ module Sass
|
|
448
524
|
#
|
449
525
|
# @param msg [String]
|
450
526
|
def sass_warn(msg)
|
451
|
-
msg
|
527
|
+
msg += "\n" unless ruby1?
|
452
528
|
Sass.logger.warn(msg)
|
453
529
|
end
|
454
530
|
|
@@ -507,8 +583,17 @@ module Sass
|
|
507
583
|
#
|
508
584
|
# @return [Boolean]
|
509
585
|
def listen_geq_2?
|
510
|
-
|
511
|
-
|
586
|
+
return @listen_geq_2 if defined?(@listen_geq_2)
|
587
|
+
@listen_geq_2 =
|
588
|
+
begin
|
589
|
+
# Make sure we're loading listen/version from the same place that
|
590
|
+
# we're loading listen itself.
|
591
|
+
load_listen!
|
592
|
+
require 'listen/version'
|
593
|
+
version_geq(::Listen::VERSION, '2.0.0')
|
594
|
+
rescue LoadError
|
595
|
+
false
|
596
|
+
end
|
512
597
|
end
|
513
598
|
|
514
599
|
# Returns an ActionView::Template* class.
|
@@ -563,7 +648,7 @@ module Sass
|
|
563
648
|
|
564
649
|
# Returns an array of ints representing the JRuby version number.
|
565
650
|
#
|
566
|
-
# @return [Array<
|
651
|
+
# @return [Array<Integer>]
|
567
652
|
def jruby_version
|
568
653
|
@jruby_version ||= ::JRUBY_VERSION.split(".").map {|s| s.to_i}
|
569
654
|
end
|
@@ -572,7 +657,7 @@ module Sass
|
|
572
657
|
#
|
573
658
|
# @param path [String]
|
574
659
|
def glob(path)
|
575
|
-
path = path.
|
660
|
+
path = path.tr('\\', '/') if windows?
|
576
661
|
if block_given?
|
577
662
|
Dir.glob(path) {|f| yield(f)}
|
578
663
|
else
|
@@ -583,13 +668,99 @@ module Sass
|
|
583
668
|
# Like `Pathname.new`, but normalizes Windows paths to always use backslash
|
584
669
|
# separators.
|
585
670
|
#
|
586
|
-
# `Pathname
|
671
|
+
# `Pathname#relative_path_from` can break if the two pathnames aren't
|
587
672
|
# consistent in their slash style.
|
673
|
+
#
|
674
|
+
# @param path [String]
|
675
|
+
# @return [Pathname]
|
588
676
|
def pathname(path)
|
589
677
|
path = path.tr("/", "\\") if windows?
|
590
678
|
Pathname.new(path)
|
591
679
|
end
|
592
680
|
|
681
|
+
# Like `Pathname#cleanpath`, but normalizes Windows paths to always use
|
682
|
+
# backslash separators. Normally, `Pathname#cleanpath` actually does the
|
683
|
+
# reverse -- it will convert backslashes to forward slashes, which can break
|
684
|
+
# `Pathname#relative_path_from`.
|
685
|
+
#
|
686
|
+
# @param path [String, Pathname]
|
687
|
+
# @return [Pathname]
|
688
|
+
def cleanpath(path)
|
689
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
690
|
+
pathname(path.cleanpath.to_s)
|
691
|
+
end
|
692
|
+
|
693
|
+
# Returns `path` with all symlinks resolved.
|
694
|
+
#
|
695
|
+
# @param path [String, Pathname]
|
696
|
+
# @return [Pathname]
|
697
|
+
def realpath(path)
|
698
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
699
|
+
|
700
|
+
# Explicitly DON'T run #pathname here. We don't want to convert
|
701
|
+
# to Windows directory separators because we're comparing these
|
702
|
+
# against the paths returned by Listen, which use forward
|
703
|
+
# slashes everywhere.
|
704
|
+
begin
|
705
|
+
path.realpath
|
706
|
+
rescue SystemCallError
|
707
|
+
# If [path] doesn't actually exist, don't bail, just
|
708
|
+
# return the original.
|
709
|
+
path
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
# Returns `path` relative to `from`.
|
714
|
+
#
|
715
|
+
# This is like `Pathname#relative_path_from` except it accepts both strings
|
716
|
+
# and pathnames, it handles Windows path separators correctly, and it throws
|
717
|
+
# an error rather than crashing if the paths use different encodings
|
718
|
+
# (https://github.com/ruby/ruby/pull/713).
|
719
|
+
#
|
720
|
+
# @param path [String, Pathname]
|
721
|
+
# @param from [String, Pathname]
|
722
|
+
# @return [Pathname?]
|
723
|
+
def relative_path_from(path, from)
|
724
|
+
pathname(path.to_s).relative_path_from(pathname(from.to_s))
|
725
|
+
rescue NoMethodError => e
|
726
|
+
raise e unless e.name == :zero?
|
727
|
+
|
728
|
+
# Work around https://github.com/ruby/ruby/pull/713.
|
729
|
+
path = path.to_s
|
730
|
+
from = from.to_s
|
731
|
+
raise ArgumentError("Incompatible path encodings: #{path.inspect} is #{path.encoding}, " +
|
732
|
+
"#{from.inspect} is #{from.encoding}")
|
733
|
+
end
|
734
|
+
|
735
|
+
# Converts `path` to a "file:" URI. This handles Windows paths correctly.
|
736
|
+
#
|
737
|
+
# @param path [String, Pathname]
|
738
|
+
# @return [String]
|
739
|
+
def file_uri_from_path(path)
|
740
|
+
path = path.to_s if path.is_a?(Pathname)
|
741
|
+
path = path.tr('\\', '/') if windows?
|
742
|
+
path = Sass::Util.escape_uri(path)
|
743
|
+
return path.start_with?('/') ? "file://" + path : path unless windows?
|
744
|
+
return "file:///" + path.tr("\\", "/") if path =~ %r{^[a-zA-Z]:[/\\]}
|
745
|
+
return "file:" + path.tr("\\", "/") if path =~ %r{\\\\[^\\]+\\[^\\/]+}
|
746
|
+
path.tr("\\", "/")
|
747
|
+
end
|
748
|
+
|
749
|
+
# Retries a filesystem operation if it fails on Windows. Windows
|
750
|
+
# has weird and flaky locking rules that can cause operations to fail.
|
751
|
+
#
|
752
|
+
# @yield [] The filesystem operation.
|
753
|
+
def retry_on_windows
|
754
|
+
return yield unless windows?
|
755
|
+
|
756
|
+
begin
|
757
|
+
yield
|
758
|
+
rescue SystemCallError
|
759
|
+
sleep 0.1
|
760
|
+
yield
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
593
764
|
# Prepare a value for a destructuring assignment (e.g. `a, b =
|
594
765
|
# val`). This works around a performance bug when using
|
595
766
|
# ActiveSupport, and only needs to be called when `val` is likely
|
@@ -610,7 +781,7 @@ module Sass
|
|
610
781
|
# @return [Boolean]
|
611
782
|
def ruby1?
|
612
783
|
return @ruby1 if defined?(@ruby1)
|
613
|
-
@ruby1 =
|
784
|
+
@ruby1 = RUBY_VERSION_COMPONENTS[0] <= 1
|
614
785
|
end
|
615
786
|
|
616
787
|
# Whether or not this is running under Ruby 1.8 or lower.
|
@@ -624,16 +795,28 @@ module Sass
|
|
624
795
|
# We have to fall back to 1.8 behavior.
|
625
796
|
return @ruby1_8 if defined?(@ruby1_8)
|
626
797
|
@ruby1_8 = ironruby? ||
|
627
|
-
(
|
798
|
+
(RUBY_VERSION_COMPONENTS[0] == 1 && RUBY_VERSION_COMPONENTS[1] < 9)
|
799
|
+
end
|
800
|
+
|
801
|
+
# Whether or not this is running under Ruby 1.9.2 exactly.
|
802
|
+
#
|
803
|
+
# @return [Boolean]
|
804
|
+
def ruby1_9_2?
|
805
|
+
return @ruby1_9_2 if defined?(@ruby1_9_2)
|
806
|
+
@ruby1_9_2 = RUBY_VERSION_COMPONENTS == [1, 9, 2]
|
628
807
|
end
|
629
808
|
|
630
|
-
# Whether or not this is running under Ruby
|
631
|
-
# Note that lower versions are not officially supported.
|
809
|
+
# Whether or not this is running under Ruby 2.4 or higher.
|
632
810
|
#
|
633
811
|
# @return [Boolean]
|
634
|
-
def
|
635
|
-
return @
|
636
|
-
@
|
812
|
+
def ruby2_4?
|
813
|
+
return @ruby2_4 if defined?(@ruby2_4)
|
814
|
+
@ruby2_4 =
|
815
|
+
if RUBY_VERSION_COMPONENTS[0] == 2
|
816
|
+
RUBY_VERSION_COMPONENTS[1] >= 4
|
817
|
+
else
|
818
|
+
RUBY_VERSION_COMPONENTS[0] > 2
|
819
|
+
end
|
637
820
|
end
|
638
821
|
|
639
822
|
# Wehter or not this is running under JRuby 1.6 or lower.
|
@@ -677,117 +860,80 @@ module Sass
|
|
677
860
|
end
|
678
861
|
|
679
862
|
return Hash[pairs_or_hash] unless ruby1_8?
|
680
|
-
(pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*flatten(
|
863
|
+
(pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*pairs_or_hash.flatten(1)]
|
681
864
|
end
|
682
865
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
# @yield [msg] A block in which an encoding error can be raised.
|
690
|
-
# Only yields if there is an encoding error
|
691
|
-
# @yieldparam msg [String] The error message to be raised
|
692
|
-
# @return [String] `str`, potentially with encoding gotchas like BOMs removed
|
693
|
-
def check_encoding(str)
|
694
|
-
if ruby1_8?
|
695
|
-
return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
|
696
|
-
elsif str.valid_encoding?
|
697
|
-
# Get rid of the Unicode BOM if possible
|
698
|
-
if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
|
699
|
-
return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
|
700
|
-
else
|
701
|
-
return str
|
702
|
-
end
|
703
|
-
end
|
704
|
-
|
705
|
-
encoding = str.encoding
|
706
|
-
newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
|
707
|
-
str.force_encoding("binary").split(newlines).each_with_index do |line, i|
|
708
|
-
begin
|
709
|
-
line.encode(encoding)
|
710
|
-
rescue Encoding::UndefinedConversionError => e
|
711
|
-
yield <<MSG.rstrip, i + 1
|
712
|
-
Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}
|
713
|
-
MSG
|
714
|
-
end
|
715
|
-
end
|
716
|
-
str
|
866
|
+
unless ruby1_8?
|
867
|
+
CHARSET_REGEXP = /\A@charset "([^"]+)"/
|
868
|
+
bom = "\uFEFF"
|
869
|
+
UTF_8_BOM = bom.encode("UTF-8").force_encoding('BINARY')
|
870
|
+
UTF_16BE_BOM = bom.encode("UTF-16BE").force_encoding('BINARY')
|
871
|
+
UTF_16LE_BOM = bom.encode("UTF-16LE").force_encoding('BINARY')
|
717
872
|
end
|
718
873
|
|
719
874
|
# Like {\#check\_encoding}, but also checks for a `@charset` declaration
|
720
875
|
# at the beginning of the file and uses that encoding if it exists.
|
721
876
|
#
|
722
|
-
#
|
723
|
-
# If a `@charset` declaration exists,
|
724
|
-
# we assume that that's the original encoding of the document.
|
725
|
-
# Otherwise, we use whatever encoding Ruby has.
|
726
|
-
# Then we convert that to UTF-8 to process internally.
|
727
|
-
# The UTF-8 end result is what's returned by this method.
|
877
|
+
# Sass follows CSS's decoding rules.
|
728
878
|
#
|
729
879
|
# @param str [String] The string of which to check the encoding
|
730
|
-
# @yield [msg] A block in which an encoding error can be raised.
|
731
|
-
# Only yields if there is an encoding error
|
732
|
-
# @yieldparam msg [String] The error message to be raised
|
733
880
|
# @return [(String, Encoding)] The original string encoded as UTF-8,
|
734
881
|
# and the source encoding of the string (or `nil` under Ruby 1.8)
|
735
882
|
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
736
883
|
# cannot be converted to UTF-8
|
737
884
|
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
885
|
+
# @raise [Sass::SyntaxError] If the document declares an encoding that
|
886
|
+
# doesn't match its contents, or it doesn't declare an encoding and its
|
887
|
+
# contents are invalid in the native encoding.
|
888
|
+
def check_sass_encoding(str)
|
889
|
+
# On Ruby 1.8 we can't do anything complicated with encodings.
|
890
|
+
# Instead, we just strip out a UTF-8 BOM if it exists and
|
891
|
+
# sanitize according to Section 3.3 of CSS Syntax Level 3. We
|
892
|
+
# don't sanitize null characters since they might be components
|
893
|
+
# of other characters.
|
894
|
+
if ruby1_8?
|
895
|
+
return str.gsub(/\A\xEF\xBB\xBF/, '').gsub(/\r\n?|\f/, "\n"), nil
|
745
896
|
end
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
897
|
+
|
898
|
+
# Determine the fallback encoding following section 3.2 of CSS Syntax Level 3 and Encodings:
|
899
|
+
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#determine-the-fallback-encoding
|
900
|
+
# http://encoding.spec.whatwg.org/#decode
|
901
|
+
binary = str.dup.force_encoding("BINARY")
|
902
|
+
if binary.start_with?(UTF_8_BOM)
|
903
|
+
binary.slice! 0, UTF_8_BOM.length
|
904
|
+
str = binary.force_encoding('UTF-8')
|
905
|
+
elsif binary.start_with?(UTF_16BE_BOM)
|
906
|
+
binary.slice! 0, UTF_16BE_BOM.length
|
907
|
+
str = binary.force_encoding('UTF-16BE')
|
908
|
+
elsif binary.start_with?(UTF_16LE_BOM)
|
909
|
+
binary.slice! 0, UTF_16LE_BOM.length
|
910
|
+
str = binary.force_encoding('UTF-16LE')
|
911
|
+
elsif binary =~ CHARSET_REGEXP
|
912
|
+
charset = $1.force_encoding('US-ASCII')
|
913
|
+
# Ruby 1.9.2 doesn't recognize a UTF-16 encoding without an endian marker.
|
914
|
+
if ruby1_9_2? && charset.downcase == 'utf-16'
|
915
|
+
encoding = Encoding.find('UTF-8')
|
916
|
+
else
|
917
|
+
encoding = Encoding.find(charset)
|
918
|
+
if encoding.name == 'UTF-16' || encoding.name == 'UTF-16BE'
|
919
|
+
encoding = Encoding.find('UTF-8')
|
754
920
|
end
|
755
921
|
end
|
756
|
-
str.force_encoding(
|
757
|
-
elsif
|
758
|
-
|
922
|
+
str = binary.force_encoding(encoding)
|
923
|
+
elsif str.encoding.name == "ASCII-8BIT"
|
924
|
+
# Normally we want to fall back on believing the Ruby string
|
925
|
+
# encoding, but if that's just binary we want to make sure
|
926
|
+
# it's valid UTF-8.
|
927
|
+
str = str.force_encoding('utf-8')
|
759
928
|
end
|
760
929
|
|
761
|
-
str
|
762
|
-
return str.encode("UTF-8"), str.encoding
|
763
|
-
end
|
930
|
+
find_encoding_error(str) unless str.valid_encoding?
|
764
931
|
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
# We could automatically add in any non-ASCII-compatible encodings here,
|
772
|
-
# but there's not really a good way to do that
|
773
|
-
# without manually checking that each encoding
|
774
|
-
# encodes all ASCII characters properly,
|
775
|
-
# which takes long enough to affect the startup time of the CLI.
|
776
|
-
ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
|
777
|
-
|
778
|
-
CHARSET_REGEXPS = Hash.new do |h, e|
|
779
|
-
h[e] =
|
780
|
-
begin
|
781
|
-
# /\A(?:\uFEFF)?@charset "(.*?)"|\A(\uFEFF)/
|
782
|
-
Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
|
783
|
-
_enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
|
784
|
-
_enc("\uFEFF", e)})/)
|
785
|
-
rescue Encoding::ConverterNotFoundError => _
|
786
|
-
nil # JRuby on Java 5 doesn't support UTF-32
|
787
|
-
rescue
|
788
|
-
# /\A@charset "(.*?)"/
|
789
|
-
Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
|
790
|
-
end
|
932
|
+
begin
|
933
|
+
# If the string is valid, preprocess it according to section 3.3 of CSS Syntax Level 3.
|
934
|
+
return str.encode("UTF-8").gsub(/\r\n?|\f/, "\n").tr("\u0000", "�"), str.encoding
|
935
|
+
rescue EncodingError
|
936
|
+
find_encoding_error(str)
|
791
937
|
end
|
792
938
|
end
|
793
939
|
|
@@ -820,7 +966,7 @@ MSG
|
|
820
966
|
# A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
|
821
967
|
#
|
822
968
|
# @param enum [Enumerable] The enumerable to get the enumerator for
|
823
|
-
# @param n [
|
969
|
+
# @param n [Integer] The size of each cons
|
824
970
|
# @return [Enumerator] The consed enumerator
|
825
971
|
def enum_cons(enum, n)
|
826
972
|
ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
|
@@ -829,7 +975,7 @@ MSG
|
|
829
975
|
# A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
|
830
976
|
#
|
831
977
|
# @param enum [Enumerable] The enumerable to get the enumerator for
|
832
|
-
# @param n [
|
978
|
+
# @param n [Integer] The size of each slice
|
833
979
|
# @return [Enumerator] The consed enumerator
|
834
980
|
def enum_slice(enum, n)
|
835
981
|
ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
|
@@ -856,22 +1002,11 @@ MSG
|
|
856
1002
|
# Returns the ASCII code of the given character.
|
857
1003
|
#
|
858
1004
|
# @param c [String] All characters but the first are ignored.
|
859
|
-
# @return [
|
1005
|
+
# @return [Integer] The ASCII code of `c`.
|
860
1006
|
def ord(c)
|
861
1007
|
ruby1_8? ? c[0] : c.ord
|
862
1008
|
end
|
863
1009
|
|
864
|
-
# Flattens the first `n` nested arrays in a cross-version manner.
|
865
|
-
#
|
866
|
-
# @param arr [Array] The array to flatten
|
867
|
-
# @param n [Fixnum] The number of levels to flatten
|
868
|
-
# @return [Array] The flattened array
|
869
|
-
def flatten(arr, n)
|
870
|
-
return arr.flatten(n) unless ruby1_8_6?
|
871
|
-
return arr if n == 0
|
872
|
-
arr.inject([]) {|res, e| e.is_a?(Array) ? res.concat(flatten(e, n - 1)) : res << e}
|
873
|
-
end
|
874
|
-
|
875
1010
|
# Flattens the first level of nested arrays in `arrs`. Unlike
|
876
1011
|
# `Array#flatten`, this orders the result by taking the first
|
877
1012
|
# values from each array in order, then the second, and so on.
|
@@ -890,27 +1025,6 @@ MSG
|
|
890
1025
|
result
|
891
1026
|
end
|
892
1027
|
|
893
|
-
# Returns the hash code for a set in a cross-version manner.
|
894
|
-
# Aggravatingly, this is order-dependent in Ruby 1.8.6.
|
895
|
-
#
|
896
|
-
# @param set [Set]
|
897
|
-
# @return [Fixnum] The order-independent hashcode of `set`
|
898
|
-
def set_hash(set)
|
899
|
-
return set.hash unless ruby1_8_6?
|
900
|
-
set.map {|e| e.hash}.uniq.sort.hash
|
901
|
-
end
|
902
|
-
|
903
|
-
# Tests the hash-equality of two sets in a cross-version manner.
|
904
|
-
# Aggravatingly, this is order-dependent in Ruby 1.8.6.
|
905
|
-
#
|
906
|
-
# @param set1 [Set]
|
907
|
-
# @param set2 [Set]
|
908
|
-
# @return [Boolean] Whether or not the sets are hashcode equal
|
909
|
-
def set_eql?(set1, set2)
|
910
|
-
return set1.eql?(set2) unless ruby1_8_6?
|
911
|
-
set1.to_a.uniq.sort_by {|e| e.hash}.eql?(set2.to_a.uniq.sort_by {|e| e.hash})
|
912
|
-
end
|
913
|
-
|
914
1028
|
# Like `Object#inspect`, but preserves non-ASCII characters rather than
|
915
1029
|
# escaping them under Ruby 1.9.2. This is necessary so that the
|
916
1030
|
# precompiled Haml template can be `#encode`d into `@options[:encoding]`
|
@@ -919,7 +1033,7 @@ MSG
|
|
919
1033
|
# @param obj {Object}
|
920
1034
|
# @return {String}
|
921
1035
|
def inspect_obj(obj)
|
922
|
-
return obj.inspect unless version_geq(
|
1036
|
+
return obj.inspect unless version_geq(RUBY_VERSION, "1.9.2")
|
923
1037
|
return ':' + inspect_obj(obj.to_s) if obj.is_a?(Symbol)
|
924
1038
|
return obj.inspect unless obj.is_a?(String)
|
925
1039
|
'"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
|
@@ -1013,11 +1127,11 @@ MSG
|
|
1013
1127
|
|
1014
1128
|
# Converts the argument into a valid JSON value.
|
1015
1129
|
#
|
1016
|
-
# @param v [
|
1130
|
+
# @param v [Integer, String, Array, Boolean, nil]
|
1017
1131
|
# @return [String]
|
1018
1132
|
def json_value_of(v)
|
1019
1133
|
case v
|
1020
|
-
when
|
1134
|
+
when Integer
|
1021
1135
|
v.to_s
|
1022
1136
|
when String
|
1023
1137
|
"\"" + json_escape_string(v) + "\""
|
@@ -1039,7 +1153,7 @@ MSG
|
|
1039
1153
|
VLQ_BASE_MASK = VLQ_BASE - 1
|
1040
1154
|
VLQ_CONTINUATION_BIT = VLQ_BASE
|
1041
1155
|
|
1042
|
-
BASE64_DIGITS = ('A'..'Z').to_a
|
1156
|
+
BASE64_DIGITS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+', '/']
|
1043
1157
|
BASE64_DIGIT_MAP = begin
|
1044
1158
|
map = {}
|
1045
1159
|
Sass::Util.enum_with_index(BASE64_DIGITS).map do |digit, i|
|
@@ -1050,7 +1164,7 @@ MSG
|
|
1050
1164
|
|
1051
1165
|
# Encodes `value` as VLQ (http://en.wikipedia.org/wiki/VLQ).
|
1052
1166
|
#
|
1053
|
-
# @param value [
|
1167
|
+
# @param value [Integer]
|
1054
1168
|
# @return [String] The encoded value
|
1055
1169
|
def encode_vlq(value)
|
1056
1170
|
if value < 0
|
@@ -1128,15 +1242,25 @@ MSG
|
|
1128
1242
|
# rename operation.
|
1129
1243
|
#
|
1130
1244
|
# @param filename [String] The file to write to.
|
1245
|
+
# @param perms [Integer] The permissions used for creating this file.
|
1246
|
+
# Will be masked by the process umask. Defaults to readable/writeable
|
1247
|
+
# by all users however the umask usually changes this to only be writable
|
1248
|
+
# by the process's user.
|
1131
1249
|
# @yieldparam tmpfile [Tempfile] The temp file that can be written to.
|
1132
1250
|
# @return The value returned by the block.
|
1133
|
-
def atomic_create_and_write_file(filename)
|
1251
|
+
def atomic_create_and_write_file(filename, perms = 0666)
|
1134
1252
|
require 'tempfile'
|
1135
1253
|
tmpfile = Tempfile.new(File.basename(filename), File.dirname(filename))
|
1136
1254
|
tmpfile.binmode if tmpfile.respond_to?(:binmode)
|
1137
1255
|
result = yield tmpfile
|
1138
1256
|
tmpfile.close
|
1139
1257
|
ATOMIC_WRITE_MUTEX.synchronize do
|
1258
|
+
begin
|
1259
|
+
File.chmod(perms & ~File.umask, tmpfile.path)
|
1260
|
+
rescue Errno::EPERM
|
1261
|
+
# If we don't have permissions to chmod the file, don't let that crash
|
1262
|
+
# the compilation. See issue 1215.
|
1263
|
+
end
|
1140
1264
|
File.rename tmpfile.path, filename
|
1141
1265
|
end
|
1142
1266
|
result
|
@@ -1147,15 +1271,71 @@ MSG
|
|
1147
1271
|
tmpfile.unlink if tmpfile
|
1148
1272
|
end
|
1149
1273
|
|
1274
|
+
def load_listen!
|
1275
|
+
if defined?(gem)
|
1276
|
+
begin
|
1277
|
+
gem 'listen', '>= 1.1.0', '< 3.0.0'
|
1278
|
+
require 'listen'
|
1279
|
+
rescue Gem::LoadError
|
1280
|
+
dir = scope("vendor/listen/lib")
|
1281
|
+
$LOAD_PATH.unshift dir
|
1282
|
+
begin
|
1283
|
+
require 'listen'
|
1284
|
+
rescue LoadError => e
|
1285
|
+
if version_geq(RUBY_VERSION, "1.9.3")
|
1286
|
+
version_constraint = "~> 3.0"
|
1287
|
+
else
|
1288
|
+
version_constraint = "~> 1.1"
|
1289
|
+
end
|
1290
|
+
e.message << "\n" <<
|
1291
|
+
"Run \"gem install listen --version '#{version_constraint}'\" to get it."
|
1292
|
+
raise e
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
else
|
1296
|
+
begin
|
1297
|
+
require 'listen'
|
1298
|
+
rescue LoadError => e
|
1299
|
+
dir = scope("vendor/listen/lib")
|
1300
|
+
if $LOAD_PATH.include?(dir)
|
1301
|
+
raise e unless File.exist?(scope(".git"))
|
1302
|
+
e.message << "\n" <<
|
1303
|
+
'Run "git submodule update --init" to get the bundled version.'
|
1304
|
+
else
|
1305
|
+
$LOAD_PATH.unshift dir
|
1306
|
+
retry
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
end
|
1310
|
+
end
|
1311
|
+
|
1150
1312
|
private
|
1151
1313
|
|
1152
|
-
|
1314
|
+
def find_encoding_error(str)
|
1315
|
+
encoding = str.encoding
|
1316
|
+
cr = Regexp.quote("\r".encode(encoding).force_encoding('BINARY'))
|
1317
|
+
lf = Regexp.quote("\n".encode(encoding).force_encoding('BINARY'))
|
1318
|
+
ff = Regexp.quote("\f".encode(encoding).force_encoding('BINARY'))
|
1319
|
+
line_break = /#{cr}#{lf}?|#{ff}|#{lf}/
|
1320
|
+
|
1321
|
+
str.force_encoding("binary").split(line_break).each_with_index do |line, i|
|
1322
|
+
begin
|
1323
|
+
line.encode(encoding)
|
1324
|
+
rescue Encoding::UndefinedConversionError => e
|
1325
|
+
raise Sass::SyntaxError.new(
|
1326
|
+
"Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}",
|
1327
|
+
:line => i + 1)
|
1328
|
+
end
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
# We shouldn't get here, but it's possible some weird encoding stuff causes it.
|
1332
|
+
return str, str.encoding
|
1333
|
+
end
|
1153
1334
|
|
1154
1335
|
# Calculates the memoization table for the Least Common Subsequence algorithm.
|
1155
1336
|
# Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
|
1156
1337
|
def lcs_table(x, y)
|
1157
1338
|
# This method does not take a block as an explicit parameter for performance reasons.
|
1158
|
-
# rubocop:enable LineLength
|
1159
1339
|
c = Array.new(x.size) {[]}
|
1160
1340
|
x.size.times {|i| c[i][0] = 0}
|
1161
1341
|
y.size.times {|j| c[0][j] = 0}
|
@@ -1171,12 +1351,12 @@ MSG
|
|
1171
1351
|
end
|
1172
1352
|
c
|
1173
1353
|
end
|
1174
|
-
# rubocop:disable ParameterLists
|
1354
|
+
# rubocop:disable ParameterLists
|
1175
1355
|
|
1176
1356
|
# Computes a single longest common subsequence for arrays x and y.
|
1177
1357
|
# Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
|
1178
1358
|
def lcs_backtrace(c, x, y, i, j, &block)
|
1179
|
-
# rubocop:enable ParameterList
|
1359
|
+
# rubocop:enable ParameterList
|
1180
1360
|
return [] if i == 0 || j == 0
|
1181
1361
|
if (v = yield(x[i], y[j]))
|
1182
1362
|
return lcs_backtrace(c, x, y, i - 1, j - 1, &block) << v
|