sass 3.1.11 → 3.1.12
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/sass/engine.rb +1 -1
- data/lib/sass/environment.rb +19 -5
- data/lib/sass/exec.rb +1 -1
- data/lib/sass/repl.rb +0 -1
- data/lib/sass/script/functions.rb +1 -1
- data/lib/sass/script/number.rb +1 -1
- data/lib/sass/script/variable.rb +0 -1
- data/lib/sass/scss/parser.rb +23 -9
- data/lib/sass/scss/rx.rb +8 -4
- data/lib/sass/tree/media_node.rb +4 -4
- data/lib/sass/tree/visitors/convert.rb +3 -3
- data/lib/sass/tree/visitors/cssize.rb +3 -1
- data/lib/sass/tree/visitors/perform.rb +16 -4
- data/test/sass/conversion_test.rb +2 -2
- data/test/sass/engine_test.rb +78 -0
- data/test/sass/scss/scss_test.rb +25 -0
- data/test/test_helper.rb +1 -0
- metadata +4 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.
|
1
|
+
3.1.12
|
data/lib/sass/engine.rb
CHANGED
@@ -683,7 +683,7 @@ WARNING
|
|
683
683
|
:line => @line + 1) unless line.children.empty?
|
684
684
|
Tree::CharsetNode.new(name)
|
685
685
|
elsif directive == "media"
|
686
|
-
Tree::MediaNode.new(value)
|
686
|
+
Tree::MediaNode.new(value.split(',').map {|s| s.strip})
|
687
687
|
else
|
688
688
|
Tree::DirectiveNode.new(line.text)
|
689
689
|
end
|
data/lib/sass/environment.rb
CHANGED
@@ -26,7 +26,7 @@ module Sass
|
|
26
26
|
unless parent
|
27
27
|
@stack = []
|
28
28
|
@mixins_in_use = Set.new
|
29
|
-
|
29
|
+
@files_in_use = Set.new
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -59,7 +59,8 @@ module Sass
|
|
59
59
|
else
|
60
60
|
stack.push(top_of_stack = frame_info)
|
61
61
|
end
|
62
|
-
mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin]
|
62
|
+
mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin]
|
63
|
+
files_in_use << top_of_stack[:filename] if top_of_stack[:filename]
|
63
64
|
end
|
64
65
|
|
65
66
|
# Like \{#push\_frame}, but next time a stack frame is pushed,
|
@@ -72,9 +73,8 @@ module Sass
|
|
72
73
|
|
73
74
|
# Pop a stack frame from the mixin/include stack.
|
74
75
|
def pop_frame
|
75
|
-
|
76
|
-
|
77
|
-
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
|
76
|
+
pop_and_unuse if stack.last && stack.last[:prepared]
|
77
|
+
pop_and_unuse
|
78
78
|
end
|
79
79
|
|
80
80
|
# A list of stack frames in the mixin/include stack.
|
@@ -93,6 +93,13 @@ module Sass
|
|
93
93
|
@mixins_in_use ||= @parent.mixins_in_use
|
94
94
|
end
|
95
95
|
|
96
|
+
# A set of names of files currently present in the stack.
|
97
|
+
#
|
98
|
+
# @return [Set<String>] The filenames.
|
99
|
+
def files_in_use
|
100
|
+
@files_in_use ||= @parent.files_in_use
|
101
|
+
end
|
102
|
+
|
96
103
|
def stack_trace
|
97
104
|
trace = []
|
98
105
|
stack.reverse.each_with_index do |entry, i|
|
@@ -106,6 +113,13 @@ module Sass
|
|
106
113
|
|
107
114
|
private
|
108
115
|
|
116
|
+
def pop_and_unuse
|
117
|
+
popped = stack.pop
|
118
|
+
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
|
119
|
+
files_in_use.delete(popped[:filename]) if popped && popped[:filename]
|
120
|
+
popped
|
121
|
+
end
|
122
|
+
|
109
123
|
def parent_options
|
110
124
|
@parent_options ||= @parent && @parent.options
|
111
125
|
end
|
data/lib/sass/exec.rb
CHANGED
@@ -389,7 +389,7 @@ MSG
|
|
389
389
|
dirs.map! {|from, to| [from, to || from]}
|
390
390
|
::Sass::Plugin.options[:template_location] = dirs
|
391
391
|
|
392
|
-
::Sass::Plugin.
|
392
|
+
::Sass::Plugin.on_updated_stylesheet do |_, css|
|
393
393
|
if File.exists? css
|
394
394
|
puts_action :overwrite, :yellow, css
|
395
395
|
else
|
data/lib/sass/repl.rb
CHANGED
@@ -968,7 +968,7 @@ module Sass::Script
|
|
968
968
|
#
|
969
969
|
# Finally, the weight of color1 is renormalized to be within [0, 1]
|
970
970
|
# and the weight of color2 is given by 1 minus the weight of color1.
|
971
|
-
p = weight.value/100.0
|
971
|
+
p = (weight.value/100.0).to_f
|
972
972
|
w = p*2 - 1
|
973
973
|
a = color1.alpha - color2.alpha
|
974
974
|
|
data/lib/sass/script/number.rb
CHANGED
data/lib/sass/script/variable.rb
CHANGED
data/lib/sass/scss/parser.rb
CHANGED
@@ -287,20 +287,23 @@ module Sass
|
|
287
287
|
def use_css_import?; false; end
|
288
288
|
|
289
289
|
def media_directive
|
290
|
-
|
291
|
-
block(node(Sass::Tree::MediaNode.new(val)), :directive)
|
290
|
+
block(node(Sass::Tree::MediaNode.new(media_query_list)), :directive)
|
292
291
|
end
|
293
292
|
|
294
293
|
# http://www.w3.org/TR/css3-mediaqueries/#syntax
|
295
294
|
def media_query_list
|
296
|
-
|
295
|
+
has_q = false
|
296
|
+
q = str {has_q = media_query}
|
297
|
+
|
298
|
+
return unless has_q
|
299
|
+
queries = [q.strip]
|
297
300
|
|
298
301
|
ss
|
299
302
|
while tok(/,/)
|
300
|
-
ss; expr!(:media_query); ss
|
303
|
+
ss; queries << str {expr!(:media_query)}.strip; ss
|
301
304
|
end
|
302
305
|
|
303
|
-
|
306
|
+
queries
|
304
307
|
end
|
305
308
|
|
306
309
|
def media_query
|
@@ -437,7 +440,7 @@ module Sass
|
|
437
440
|
end
|
438
441
|
|
439
442
|
def selector_sequence
|
440
|
-
if sel = tok(STATIC_SELECTOR)
|
443
|
+
if sel = tok(STATIC_SELECTOR, true)
|
441
444
|
return [sel]
|
442
445
|
end
|
443
446
|
|
@@ -681,7 +684,7 @@ MESSAGE
|
|
681
684
|
# we don't parse it at all, and instead return a plain old string
|
682
685
|
# containing the value.
|
683
686
|
# This results in a dramatic speed increase.
|
684
|
-
if val = tok(STATIC_VALUE)
|
687
|
+
if val = tok(STATIC_VALUE, true)
|
685
688
|
return space, Sass::Script::String.new(val.strip)
|
686
689
|
end
|
687
690
|
return space, sass_script(:parse)
|
@@ -770,7 +773,7 @@ MESSAGE
|
|
770
773
|
end
|
771
774
|
|
772
775
|
def interp_ident(start = IDENT)
|
773
|
-
return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP)
|
776
|
+
return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
|
774
777
|
res = [val]
|
775
778
|
while val = tok(NAME) || interpolation
|
776
779
|
res << val
|
@@ -944,9 +947,20 @@ MESSAGE
|
|
944
947
|
# This is important because `#tok` is called all the time.
|
945
948
|
NEWLINE = "\n"
|
946
949
|
|
947
|
-
def tok(rx)
|
950
|
+
def tok(rx, last_group_lookahead = false)
|
948
951
|
res = @scanner.scan(rx)
|
949
952
|
if res
|
953
|
+
# This fixes https://github.com/nex3/sass/issues/104, which affects
|
954
|
+
# Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
|
955
|
+
# positive lookahead operator in the Regexp (which matches without
|
956
|
+
# consuming the matched group), with a match that does consume the
|
957
|
+
# group, but then rewinds the scanner and removes the group from the
|
958
|
+
# end of the matched string. This fix makes the assumption that the
|
959
|
+
# matched group will always occur at the end of the match.
|
960
|
+
if last_group_lookahead && @scanner[-1]
|
961
|
+
@scanner.pos -= @scanner[-1].length
|
962
|
+
res.slice!(-@scanner[-1].length..-1)
|
963
|
+
end
|
950
964
|
@line += res.count(NEWLINE)
|
951
965
|
@expected = nil
|
952
966
|
if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
|
data/lib/sass/scss/rx.rb
CHANGED
@@ -112,7 +112,7 @@ module Sass
|
|
112
112
|
INTERP_START = /#\{/
|
113
113
|
MOZ_ANY = quote(":-moz-any(", Regexp::IGNORECASE)
|
114
114
|
|
115
|
-
IDENT_HYPHEN_INTERP = /-(
|
115
|
+
IDENT_HYPHEN_INTERP = /-(#\{)/
|
116
116
|
STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
|
117
117
|
STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
|
118
118
|
STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
|
@@ -120,9 +120,13 @@ module Sass
|
|
120
120
|
# We could use it for 1.9 only, but I don't want to introduce a cross-version
|
121
121
|
# behavior difference.
|
122
122
|
# In any case, almost all CSS idents will be matched by this.
|
123
|
-
|
124
|
-
|
125
|
-
|
123
|
+
#
|
124
|
+
# We explicitly avoid parsing newlines or values/selectors longer than
|
125
|
+
# about 50 characters. This mitigates the problem of exponential parsing
|
126
|
+
# time when a value has a long string of valid, parsable content followed
|
127
|
+
# by something invalid.
|
128
|
+
STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i
|
129
|
+
STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i
|
126
130
|
end
|
127
131
|
end
|
128
132
|
end
|
data/lib/sass/tree/media_node.rb
CHANGED
@@ -6,9 +6,9 @@ module Sass::Tree
|
|
6
6
|
#
|
7
7
|
# @see Sass::Tree
|
8
8
|
class MediaNode < DirectiveNode
|
9
|
-
# The media query (e.g. `print` or `screen`).
|
9
|
+
# The media query. A list of comma-separated queries (e.g. `print` or `screen`).
|
10
10
|
#
|
11
|
-
# @return [String]
|
11
|
+
# @return [Array<String>]
|
12
12
|
attr_accessor :query
|
13
13
|
|
14
14
|
# @see RuleNode#tabs
|
@@ -17,7 +17,7 @@ module Sass::Tree
|
|
17
17
|
# @see RuleNode#group_end
|
18
18
|
attr_accessor :group_end
|
19
19
|
|
20
|
-
# @param query [String] See \{#query}
|
20
|
+
# @param query [Array<String>] See \{#query}
|
21
21
|
def initialize(query)
|
22
22
|
@query = query
|
23
23
|
@tabs = 0
|
@@ -26,7 +26,7 @@ module Sass::Tree
|
|
26
26
|
|
27
27
|
# @see DirectiveNode#value
|
28
28
|
def value
|
29
|
-
"@media #{query}"
|
29
|
+
"@media #{query.join(', ')}"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -154,7 +154,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
154
154
|
end
|
155
155
|
|
156
156
|
def visit_media(node)
|
157
|
-
"#{tab_str}@media #{node.query}#{yield}"
|
157
|
+
"#{tab_str}@media #{node.query.join(', ')}#{yield}"
|
158
158
|
end
|
159
159
|
|
160
160
|
def visit_mixindef(node)
|
@@ -230,7 +230,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
230
230
|
def selector_to_sass(sel)
|
231
231
|
sel.map do |r|
|
232
232
|
if r.is_a?(String)
|
233
|
-
r.gsub(/(,[ \t]*)
|
233
|
+
r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
|
234
234
|
else
|
235
235
|
"\#{#{r.to_sass(@options)}}"
|
236
236
|
end
|
@@ -239,7 +239,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
239
239
|
|
240
240
|
def selector_to_scss(sel)
|
241
241
|
sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
|
242
|
-
join.gsub(/^[ \t]*/, tab_str)
|
242
|
+
join.gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
|
243
243
|
end
|
244
244
|
|
245
245
|
def semi
|
@@ -117,7 +117,9 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
|
|
117
117
|
|
118
118
|
media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
|
119
119
|
node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
|
120
|
-
media.each
|
120
|
+
media.each do |n|
|
121
|
+
n.query = node.query.map {|pq| n.query.map {|cq| "#{pq} and #{cq}"}}.flatten
|
122
|
+
end
|
121
123
|
(node.children.empty? ? [] : [node]) + media
|
122
124
|
end
|
123
125
|
|
@@ -137,9 +137,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
137
137
|
if path = node.css_import?
|
138
138
|
return Sass::Tree::DirectiveNode.new("@import url(#{path})")
|
139
139
|
end
|
140
|
+
file = node.imported_file
|
141
|
+
handle_import_loop!(node) if @environment.files_in_use.include?(file.options[:filename])
|
140
142
|
|
141
143
|
@environment.push_frame(:filename => node.filename, :line => node.line)
|
142
|
-
root =
|
144
|
+
root = file.to_tree
|
143
145
|
Sass::Tree::Visitors::CheckNesting.visit(root)
|
144
146
|
node.children = root.children.map {|c| visit(c)}.flatten
|
145
147
|
node
|
@@ -293,9 +295,7 @@ END
|
|
293
295
|
def handle_include_loop!(node)
|
294
296
|
msg = "An @include loop has been found:"
|
295
297
|
mixins = @environment.stack.map {|s| s[:mixin]}.compact
|
296
|
-
|
297
|
-
raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself")
|
298
|
-
end
|
298
|
+
raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
|
299
299
|
|
300
300
|
mixins << node.name
|
301
301
|
msg << "\n" << Sass::Util.enum_cons(mixins, 2).map do |m1, m2|
|
@@ -304,6 +304,18 @@ END
|
|
304
304
|
raise Sass::SyntaxError.new(msg)
|
305
305
|
end
|
306
306
|
|
307
|
+
def handle_import_loop!(node)
|
308
|
+
msg = "An @import loop has been found:"
|
309
|
+
files = @environment.stack.map {|s| s[:filename]}.compact
|
310
|
+
raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself") if files.size == 1
|
311
|
+
|
312
|
+
files << node.filename << node.imported_file.options[:filename]
|
313
|
+
msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
|
314
|
+
" #{m1} imports #{m2}"
|
315
|
+
end.join("\n")
|
316
|
+
raise Sass::SyntaxError.new(msg)
|
317
|
+
end
|
318
|
+
|
307
319
|
def check_for_loud_silent_comment(node)
|
308
320
|
return unless node.loud && node.silent
|
309
321
|
Sass::Util.sass_warn <<MESSAGE
|
@@ -180,7 +180,7 @@ SCSS
|
|
180
180
|
def test_multiline_properties
|
181
181
|
assert_scss_to_sass <<SASS, <<SCSS
|
182
182
|
foo bar
|
183
|
-
baz: bip
|
183
|
+
baz: bip bam boon
|
184
184
|
SASS
|
185
185
|
foo bar {
|
186
186
|
baz:
|
@@ -191,7 +191,7 @@ SCSS
|
|
191
191
|
|
192
192
|
assert_scss_to_scss <<OUT, <<IN
|
193
193
|
foo bar {
|
194
|
-
baz: bip
|
194
|
+
baz: bip bam boon; }
|
195
195
|
OUT
|
196
196
|
foo bar {
|
197
197
|
baz:
|
data/test/sass/engine_test.rb
CHANGED
@@ -470,6 +470,53 @@ MESSAGE
|
|
470
470
|
assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
|
471
471
|
end
|
472
472
|
|
473
|
+
def test_basic_import_loop_exception
|
474
|
+
import = filename_for_test
|
475
|
+
importer = MockImporter.new
|
476
|
+
importer.add_import(import, "@import '#{import}'")
|
477
|
+
|
478
|
+
engine = Sass::Engine.new("@import '#{import}'", :filename => import,
|
479
|
+
:load_paths => [importer])
|
480
|
+
|
481
|
+
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
|
482
|
+
An @import loop has been found: #{import} imports itself
|
483
|
+
ERR
|
484
|
+
end
|
485
|
+
|
486
|
+
def test_double_import_loop_exception
|
487
|
+
importer = MockImporter.new
|
488
|
+
importer.add_import("foo", "@import 'bar'")
|
489
|
+
importer.add_import("bar", "@import 'foo'")
|
490
|
+
|
491
|
+
engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
|
492
|
+
:load_paths => [importer])
|
493
|
+
|
494
|
+
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
|
495
|
+
An @import loop has been found:
|
496
|
+
#{filename_for_test} imports foo
|
497
|
+
foo imports bar
|
498
|
+
bar imports foo
|
499
|
+
ERR
|
500
|
+
end
|
501
|
+
|
502
|
+
def test_deep_import_loop_exception
|
503
|
+
importer = MockImporter.new
|
504
|
+
importer.add_import("foo", "@import 'bar'")
|
505
|
+
importer.add_import("bar", "@import 'baz'")
|
506
|
+
importer.add_import("baz", "@import 'foo'")
|
507
|
+
|
508
|
+
engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
|
509
|
+
:load_paths => [importer])
|
510
|
+
|
511
|
+
assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
|
512
|
+
An @import loop has been found:
|
513
|
+
#{filename_for_test} imports foo
|
514
|
+
foo imports bar
|
515
|
+
bar imports baz
|
516
|
+
baz imports foo
|
517
|
+
ERR
|
518
|
+
end
|
519
|
+
|
473
520
|
def test_exception_css_with_offset
|
474
521
|
opts = {:full_exception => true, :line => 362}
|
475
522
|
render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)
|
@@ -1979,6 +2026,19 @@ CSS
|
|
1979
2026
|
SASS
|
1980
2027
|
end
|
1981
2028
|
|
2029
|
+
def test_double_media_bubbling_with_commas
|
2030
|
+
assert_equal <<CSS, render(<<SASS)
|
2031
|
+
@media foo and baz, foo and bang, bar and baz, bar and bang {
|
2032
|
+
.foo {
|
2033
|
+
c: d; } }
|
2034
|
+
CSS
|
2035
|
+
@media foo, bar
|
2036
|
+
@media baz, bang
|
2037
|
+
.foo
|
2038
|
+
c: d
|
2039
|
+
SASS
|
2040
|
+
end
|
2041
|
+
|
1982
2042
|
def test_rule_media_rule_bubbling
|
1983
2043
|
assert_equal <<CSS, render(<<SASS)
|
1984
2044
|
@media bar {
|
@@ -2041,6 +2101,24 @@ CSS
|
|
2041
2101
|
|
2042
2102
|
# Regression tests
|
2043
2103
|
|
2104
|
+
def test_tricky_mixin_loop_exception
|
2105
|
+
render <<SASS
|
2106
|
+
@mixin foo($a)
|
2107
|
+
@if $a
|
2108
|
+
@include foo(false)
|
2109
|
+
@include foo(true)
|
2110
|
+
@else
|
2111
|
+
a: b
|
2112
|
+
|
2113
|
+
a
|
2114
|
+
@include foo(true)
|
2115
|
+
SASS
|
2116
|
+
assert(false, "Exception not raised")
|
2117
|
+
rescue Sass::SyntaxError => err
|
2118
|
+
assert_equal("An @include loop has been found: foo includes itself", err.message)
|
2119
|
+
assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
|
2120
|
+
end
|
2121
|
+
|
2044
2122
|
def test_interpolated_comment_in_mixin
|
2045
2123
|
assert_equal <<CSS, render(<<SASS)
|
2046
2124
|
/* color: red */
|
data/test/sass/scss/scss_test.rb
CHANGED
@@ -1270,4 +1270,29 @@ CSS
|
|
1270
1270
|
foo {color: darken(black, 10%)}
|
1271
1271
|
SCSS
|
1272
1272
|
end
|
1273
|
+
|
1274
|
+
# ref: https://github.com/nex3/sass/issues/104
|
1275
|
+
def test_no_buffer_overflow
|
1276
|
+
template = render <<SCSS
|
1277
|
+
.aaa {
|
1278
|
+
background-color: white;
|
1279
|
+
}
|
1280
|
+
.aaa .aaa .aaa {
|
1281
|
+
background-color: black;
|
1282
|
+
}
|
1283
|
+
.bbb {
|
1284
|
+
@extend .aaa;
|
1285
|
+
}
|
1286
|
+
.xxx {
|
1287
|
+
@extend .bbb;
|
1288
|
+
}
|
1289
|
+
.yyy {
|
1290
|
+
@extend .bbb;
|
1291
|
+
}
|
1292
|
+
.zzz {
|
1293
|
+
@extend .bbb;
|
1294
|
+
}
|
1295
|
+
SCSS
|
1296
|
+
Sass::SCSS::Parser.new(template, "test.scss").parse
|
1297
|
+
end
|
1273
1298
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 3
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 3.1.
|
9
|
+
- 12
|
10
|
+
version: 3.1.12
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nathan Weizenbaum
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2011-
|
20
|
+
date: 2011-12-16 00:00:00 -08:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|