sass 3.1.11 → 3.1.12
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.
- 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
|