rack-accept 0.3 → 0.4.1
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/.gemspec +2 -2
- data/CHANGES +6 -0
- data/README +8 -10
- data/doc/usage.markdown +13 -13
- data/lib/rack/accept.rb +1 -1
- data/lib/rack/accept/charset.rb +1 -1
- data/lib/rack/accept/encoding.rb +1 -1
- data/lib/rack/accept/header.rb +44 -18
- data/lib/rack/accept/language.rb +1 -1
- data/lib/rack/accept/media_type.rb +11 -2
- data/test/charset_test.rb +8 -0
- data/test/encoding_test.rb +7 -0
- data/test/header_test.rb +16 -0
- data/test/language_test.rb +8 -0
- data/test/media_type_test.rb +13 -0
- data/test/request_test.rb +27 -58
- metadata +4 -3
data/.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'rack-accept'
|
3
|
-
s.version = '0.
|
4
|
-
s.date = '2010-04-
|
3
|
+
s.version = '0.4.1'
|
4
|
+
s.date = '2010-04-05'
|
5
5
|
|
6
6
|
s.summary = 'HTTP Accept* for Ruby/Rack'
|
7
7
|
s.description = 'HTTP Accept, Accept-Charset, Accept-Encoding, and Accept-Language for Ruby/Rack'
|
data/CHANGES
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## 0.4 / April 5, 2010
|
2
|
+
|
3
|
+
* Added support for media type queries with multiple range parameters
|
4
|
+
* Changed Rack::Accept::Header#sort method to return only single values
|
5
|
+
and moved previous functionality to Rack::Accept::Header#sort_with_qvalues
|
6
|
+
|
1
7
|
## 0.3 / April 3, 2010
|
2
8
|
|
3
9
|
* Enhanced Rack middleware component to be able to automatically send a 406
|
data/README
CHANGED
@@ -56,27 +56,25 @@ middleware pipeline and access the Rack::Accept::Request object in the
|
|
56
56
|
|
57
57
|
Rack::Accept can also construct automatic 406 responses if you set up the types
|
58
58
|
of media, character sets, encoding, or languages your server is able to serve
|
59
|
-
ahead of time.
|
59
|
+
ahead of time. If you pass a configuration block to your `use` statement it will
|
60
|
+
yield the Rack::Accept::Context object that is used for that invocation.
|
60
61
|
|
61
62
|
require 'rack/accept'
|
62
63
|
|
63
|
-
use(Rack::Accept) do |
|
64
|
+
use(Rack::Accept) do |context|
|
64
65
|
# We only ever serve content in English or Japanese from this site, so if
|
65
66
|
# the user doesn't accept either of these we will respond with a 406.
|
66
|
-
|
67
|
-
# Note: +accept+ is an instance of Rack::Accept::Context.
|
68
|
-
accept.languages = %w< en jp >
|
67
|
+
context.languages = %w< en jp >
|
69
68
|
end
|
70
69
|
|
71
70
|
app = ...
|
72
71
|
|
73
72
|
run app
|
74
73
|
|
75
|
-
Note: You should
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
necessary.
|
74
|
+
Note: You should think carefully before using Rack::Accept in this way.
|
75
|
+
Many user agents are careless about the types of Accept headers they send, and
|
76
|
+
depend on apps not being too picky. Instead of automatically sending a 406, you
|
77
|
+
should probably only send one when absolutely necessary.
|
80
78
|
|
81
79
|
Additionally, Rack::Accept may be used outside of a Rack context to provide
|
82
80
|
any Ruby app the ability to construct and interpret Accept headers.
|
data/doc/usage.markdown
CHANGED
@@ -3,7 +3,7 @@ Usage
|
|
3
3
|
|
4
4
|
Rack::Accept implements the Rack middleware interface and may be used with any
|
5
5
|
Rack-based application. Simply insert the Rack::Accept module in your Rack
|
6
|
-
middleware pipeline and access the [
|
6
|
+
middleware pipeline and access the [Request][req] object in the
|
7
7
|
"rack-accept.request" environment key, as in the following example:
|
8
8
|
|
9
9
|
require 'rack/accept'
|
@@ -27,29 +27,28 @@ middleware pipeline and access the [Rack::Accept::Request][req] object in the
|
|
27
27
|
|
28
28
|
run app
|
29
29
|
|
30
|
-
Rack::Accept can also construct
|
31
|
-
|
32
|
-
|
30
|
+
Rack::Accept can also construct automatic [406][406] responses if you set up
|
31
|
+
the types of media, character sets, encoding, or languages your server is able
|
32
|
+
to serve ahead of time. If you pass a configuration block to your `use`
|
33
|
+
statement it will yield the [Context][ctx] object that is used for that
|
34
|
+
invocation.
|
33
35
|
|
34
36
|
require 'rack/accept'
|
35
37
|
|
36
|
-
use(Rack::Accept) do |
|
38
|
+
use(Rack::Accept) do |context|
|
37
39
|
# We only ever serve content in English or Japanese from this site, so if
|
38
40
|
# the user doesn't accept either of these we will respond with a 406.
|
39
|
-
|
40
|
-
# Note: +accept+ is an instance of Rack::Accept::Context.
|
41
|
-
accept.languages = %w< en jp >
|
41
|
+
context.languages = %w< en jp >
|
42
42
|
end
|
43
43
|
|
44
44
|
app = ...
|
45
45
|
|
46
46
|
run app
|
47
47
|
|
48
|
-
__Note:__ You should
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
necessary.
|
48
|
+
__Note:__ You should think carefully before using Rack::Accept in this way.
|
49
|
+
Many user agents are careless about the types of Accept headers they send, and
|
50
|
+
depend on apps not being too picky. Instead of automatically sending a 406, you
|
51
|
+
should probably only send one when absolutely necessary.
|
53
52
|
|
54
53
|
Additionally, Rack::Accept may be used outside of a Rack context to provide
|
55
54
|
any Ruby app the ability to construct and interpret Accept headers.
|
@@ -71,4 +70,5 @@ don't have to worry about these kinds of details.
|
|
71
70
|
|
72
71
|
[req]: api/classes/Rack/Accept/Request.html
|
73
72
|
[406]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.7
|
73
|
+
[ctx]: api/classes/Rack/Accept/Context.html
|
74
74
|
[sec14-3]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
|
data/lib/rack/accept.rb
CHANGED
data/lib/rack/accept/charset.rb
CHANGED
data/lib/rack/accept/encoding.rb
CHANGED
data/lib/rack/accept/header.rb
CHANGED
@@ -16,7 +16,7 @@ module Rack::Accept
|
|
16
16
|
m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
|
17
17
|
|
18
18
|
if m
|
19
|
-
qvalues[m[1]] = (m[2] || 1).to_f
|
19
|
+
qvalues[m[1]] = normalize_qvalue((m[2] || 1).to_f)
|
20
20
|
else
|
21
21
|
raise "Invalid header value: #{part.inspect}"
|
22
22
|
end
|
@@ -40,11 +40,29 @@ module Rack::Accept
|
|
40
40
|
# subtype, and 3) the media type parameters. An empty array is returned if
|
41
41
|
# no match can be made.
|
42
42
|
def parse_media_type(media_type)
|
43
|
-
m = media_type.to_s.match(/^([a-z*]+)\/([a-z*-]+)(?:;([a-z0-9
|
43
|
+
m = media_type.to_s.match(/^([a-z*]+)\/([a-z*-]+)(?:;([a-z0-9=;]+))?$/)
|
44
44
|
m ? [m[1], m[2], m[3] || ''] : []
|
45
45
|
end
|
46
46
|
module_function :parse_media_type
|
47
47
|
|
48
|
+
# Parses a string of media type range parameters into a hash of parameters
|
49
|
+
# to their respective values.
|
50
|
+
def parse_range_params(params)
|
51
|
+
params.split(';').inject({}) do |m, p|
|
52
|
+
k, v = p.split('=', 2)
|
53
|
+
m[k] = v if v
|
54
|
+
m
|
55
|
+
end
|
56
|
+
end
|
57
|
+
module_function :parse_range_params
|
58
|
+
|
59
|
+
# Converts 1.0 and 0.0 qvalues to 1 and 0 respectively. Used to maintain
|
60
|
+
# consistency across qvalue methods.
|
61
|
+
def normalize_qvalue(q)
|
62
|
+
(q == 1 || q == 0) && q.is_a?(Float) ? q.to_i : q
|
63
|
+
end
|
64
|
+
module_function :normalize_qvalue
|
65
|
+
|
48
66
|
module PublicInstanceMethods
|
49
67
|
# A table of all values of this header to their respective quality
|
50
68
|
# factors (qvalues).
|
@@ -87,29 +105,37 @@ module Rack::Accept
|
|
87
105
|
# containing two objects: 1) the value's qvalue and 2) the original
|
88
106
|
# value.
|
89
107
|
#
|
90
|
-
# It is important to note that
|
91
|
-
# the original values is preserved so long as the
|
92
|
-
#
|
108
|
+
# It is important to note that this sort is a "stable sort". In other
|
109
|
+
# words, the order of the original values is preserved so long as the
|
110
|
+
# qvalue for each is the same. This expectation can be useful when
|
93
111
|
# trying to determine which of a variety of options has the highest
|
94
112
|
# qvalue. If the user prefers using one option over another (for any
|
95
113
|
# number of reasons), he should put it first in +values+. He may then
|
96
114
|
# use the first result with confidence that it is both most acceptable
|
97
|
-
# to the
|
98
|
-
def
|
99
|
-
|
115
|
+
# to the client and most convenient for him as well.
|
116
|
+
def sort_with_qvalues(values, keep_unacceptables=true)
|
117
|
+
qvalues = {}
|
118
|
+
values.each do |v|
|
119
|
+
q = qvalue(v)
|
120
|
+
if q != 0 || keep_unacceptables
|
121
|
+
qvalues[q] ||= []
|
122
|
+
qvalues[q] << v
|
123
|
+
end
|
124
|
+
end
|
125
|
+
order = qvalues.keys.sort.reverse
|
126
|
+
order.inject([]) {|m, q| m.concat(qvalues[q].map {|v| [q, v] }) }
|
100
127
|
end
|
101
128
|
|
102
|
-
#
|
103
|
-
#
|
104
|
-
# exactly how the
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
129
|
+
# Sorts the given +values+ according to the qvalue of each while
|
130
|
+
# preserving the original order. See #sort_with_qvalues for more
|
131
|
+
# information on exactly how the sort is performed.
|
132
|
+
def sort(values, keep_unacceptables=false)
|
133
|
+
sort_with_qvalues(values, keep_unacceptables).map {|q, v| v }
|
134
|
+
end
|
135
|
+
|
136
|
+
# A shortcut for retrieving the first result of #sort.
|
109
137
|
def best_of(values, keep_unacceptables=false)
|
110
|
-
|
111
|
-
s.reject! {|q, v| q == 0 } unless keep_unacceptables
|
112
|
-
s.first && s.first[1]
|
138
|
+
sort(values, keep_unacceptables).first
|
113
139
|
end
|
114
140
|
|
115
141
|
# Returns a string representation of this header.
|
data/lib/rack/accept/language.rb
CHANGED
@@ -20,7 +20,7 @@ module Rack::Accept
|
|
20
20
|
return 1 if @qvalues.empty?
|
21
21
|
m = matches(media_type)
|
22
22
|
return 0 if m.empty?
|
23
|
-
@qvalues[m.first]
|
23
|
+
normalize_qvalue(@qvalues[m.first])
|
24
24
|
end
|
25
25
|
|
26
26
|
# Returns an array of media types from this header that match the given
|
@@ -32,7 +32,7 @@ module Rack::Accept
|
|
32
32
|
true
|
33
33
|
else
|
34
34
|
t, s, p = parse_media_type(v)
|
35
|
-
t == type && (s ==
|
35
|
+
t == type && (s == '*' || s == subtype) && (p == '' || params_match?(params, p))
|
36
36
|
end
|
37
37
|
}.sort_by {|v|
|
38
38
|
# Most specific gets precedence.
|
@@ -40,5 +40,14 @@ module Rack::Accept
|
|
40
40
|
}.reverse
|
41
41
|
end
|
42
42
|
|
43
|
+
private
|
44
|
+
|
45
|
+
# Returns true if all parameters and values in +match+ are also present in
|
46
|
+
# +params+.
|
47
|
+
def params_match?(params, match)
|
48
|
+
return true if params == match
|
49
|
+
parsed = parse_range_params(params)
|
50
|
+
parsed == parsed.merge(parse_range_params(match))
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
data/test/charset_test.rb
CHANGED
@@ -34,4 +34,12 @@ class CharsetTest < Test::Unit::TestCase
|
|
34
34
|
assert_equal(%w{*}, c.matches('unicode-1-1'))
|
35
35
|
end
|
36
36
|
|
37
|
+
def test_best_of
|
38
|
+
c = C.new('iso-8859-5, unicode-1-1;q=0.8')
|
39
|
+
assert_equal('iso-8859-5', c.best_of(%w< iso-8859-5 unicode-1-1 >))
|
40
|
+
assert_equal('iso-8859-5', c.best_of(%w< iso-8859-5 utf-8 >))
|
41
|
+
assert_equal('iso-8859-1', c.best_of(%w< iso-8859-1 utf-8 >))
|
42
|
+
assert_equal(nil, c.best_of(%w< utf-8 >))
|
43
|
+
end
|
44
|
+
|
37
45
|
end
|
data/test/encoding_test.rb
CHANGED
@@ -21,4 +21,11 @@ class EncodingTest < Test::Unit::TestCase
|
|
21
21
|
assert_equal(%w{*}, e.matches('compress'))
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_best_of
|
25
|
+
e = E.new('gzip, compress')
|
26
|
+
assert_equal('gzip', e.best_of(%w< gzip compress >))
|
27
|
+
assert_equal('identity', e.best_of(%w< identity compress >))
|
28
|
+
assert_equal(nil, e.best_of(%w< zip >))
|
29
|
+
end
|
30
|
+
|
24
31
|
end
|
data/test/header_test.rb
CHANGED
@@ -43,7 +43,23 @@ class HeaderTest < Test::Unit::TestCase
|
|
43
43
|
assert_equal(['text', '*', ''], H.parse_media_type('text/*'))
|
44
44
|
assert_equal(['text', 'html', ''], H.parse_media_type('text/html'))
|
45
45
|
assert_equal(['text', 'html', 'level=1'], H.parse_media_type('text/html;level=1'))
|
46
|
+
assert_equal(['text', 'html', 'level=1;answer=42'], H.parse_media_type('text/html;level=1;answer=42'))
|
46
47
|
assert_equal(['text', 'x-dvi', ''], H.parse_media_type('text/x-dvi'))
|
47
48
|
end
|
48
49
|
|
50
|
+
def test_parse_range_params
|
51
|
+
assert_equal({}, H.parse_range_params(''))
|
52
|
+
assert_equal({}, H.parse_range_params('a'))
|
53
|
+
assert_equal({'a' => 'a'}, H.parse_range_params('a=a'))
|
54
|
+
assert_equal({'a' => 'a', 'b' => 'b'}, H.parse_range_params('a=a;b=b'))
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_normalize_qvalue
|
58
|
+
assert_equal(1, H.normalize_qvalue(1.0))
|
59
|
+
assert_equal(0, H.normalize_qvalue(0.0))
|
60
|
+
assert_equal(1, H.normalize_qvalue(1))
|
61
|
+
assert_equal(0, H.normalize_qvalue(0))
|
62
|
+
assert_equal(0.5, H.normalize_qvalue(0.5))
|
63
|
+
end
|
64
|
+
|
49
65
|
end
|
data/test/language_test.rb
CHANGED
@@ -26,4 +26,12 @@ class LanguageTest < Test::Unit::TestCase
|
|
26
26
|
assert_equal(%w{en-gb en}, l.matches('en-gb'))
|
27
27
|
end
|
28
28
|
|
29
|
+
def test_best_of
|
30
|
+
l = L.new('en;q=0.5, en-gb')
|
31
|
+
assert_equal('en-gb', l.best_of(%w< en en-gb >))
|
32
|
+
assert_equal('en', l.best_of(%w< en da >))
|
33
|
+
assert_equal('en-us', l.best_of(%w< en-us en-au >))
|
34
|
+
assert_equal(nil, l.best_of(%w< da >))
|
35
|
+
end
|
36
|
+
|
29
37
|
end
|
data/test/media_type_test.rb
CHANGED
@@ -24,6 +24,19 @@ class MediaTypeTest < Test::Unit::TestCase
|
|
24
24
|
assert_equal(%w{text/* */*}, m.matches('text/plain'))
|
25
25
|
assert_equal(%w{text/html text/* */*}, m.matches('text/html'))
|
26
26
|
assert_equal(%w{text/html;level=1 text/html text/* */*}, m.matches('text/html;level=1'))
|
27
|
+
assert_equal(%w{text/html;level=1 text/html text/* */*}, m.matches('text/html;level=1;answer=42'))
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_best_of
|
31
|
+
m = M.new('text/*;q=0.5, text/html')
|
32
|
+
assert_equal('text/html', m.best_of(%w< text/plain text/html >))
|
33
|
+
assert_equal('text/plain', m.best_of(%w< text/plain image/png >))
|
34
|
+
assert_equal('text/plain', m.best_of(%w< text/plain text/javascript >))
|
35
|
+
assert_equal(nil, m.best_of(%w< image/png >))
|
36
|
+
|
37
|
+
m = M.new('text/*')
|
38
|
+
assert_equal('text/html', m.best_of(%w< text/html text/xml >))
|
39
|
+
assert_equal('text/xml', m.best_of(%w< text/xml text/html >))
|
27
40
|
end
|
28
41
|
|
29
42
|
end
|
data/test/request_test.rb
CHANGED
@@ -5,70 +5,47 @@ class RequestTest < Test::Unit::TestCase
|
|
5
5
|
R = Rack::Accept::Request
|
6
6
|
|
7
7
|
def test_media_type
|
8
|
-
|
9
|
-
assert(
|
10
|
-
assert(
|
11
|
-
assert(!
|
12
|
-
assert(!
|
8
|
+
r = R.new('HTTP_ACCEPT' => 'text/*;q=0, text/html')
|
9
|
+
assert(r.media_type?('text/html'))
|
10
|
+
assert(r.media_type?('text/html;level=1'))
|
11
|
+
assert(!r.media_type?('text/plain'))
|
12
|
+
assert(!r.media_type?('image/png'))
|
13
13
|
|
14
14
|
request = R.new('HTTP_ACCEPT' => '*/*')
|
15
15
|
assert(request.media_type?('image/png'))
|
16
16
|
end
|
17
17
|
|
18
|
-
def test_best_media_type
|
19
|
-
request = R.new('HTTP_ACCEPT' => 'text/*;q=0.5, text/html')
|
20
|
-
assert_equal('text/html', request.best_media_type(%w< text/plain text/html >))
|
21
|
-
assert_equal('text/plain', request.best_media_type(%w< text/plain image/png >))
|
22
|
-
assert_equal('text/plain', request.best_media_type(%w< text/plain text/javascript >))
|
23
|
-
assert_equal(nil, request.best_media_type(%w< image/png >))
|
24
|
-
end
|
25
|
-
|
26
18
|
def test_charset
|
27
|
-
|
28
|
-
assert(
|
29
|
-
assert(
|
30
|
-
assert(
|
31
|
-
assert(!
|
19
|
+
r = R.new('HTTP_ACCEPT_CHARSET' => 'iso-8859-5, unicode-1-1;q=0.8')
|
20
|
+
assert(r.charset?('iso-8859-5'))
|
21
|
+
assert(r.charset?('unicode-1-1'))
|
22
|
+
assert(r.charset?('iso-8859-1'))
|
23
|
+
assert(!r.charset?('utf-8'))
|
32
24
|
|
33
|
-
|
34
|
-
assert(!
|
35
|
-
end
|
36
|
-
|
37
|
-
def test_best_charset
|
38
|
-
request = R.new('HTTP_ACCEPT_CHARSET' => 'iso-8859-5, unicode-1-1;q=0.8')
|
39
|
-
assert_equal('iso-8859-5', request.best_charset(%w< iso-8859-5 unicode-1-1 >))
|
40
|
-
assert_equal('iso-8859-5', request.best_charset(%w< iso-8859-5 utf-8 >))
|
41
|
-
assert_equal('iso-8859-1', request.best_charset(%w< iso-8859-1 utf-8 >))
|
42
|
-
assert_equal(nil, request.best_charset(%w< utf-8 >))
|
25
|
+
r = R.new('HTTP_ACCEPT_CHARSET' => 'iso-8859-1;q=0')
|
26
|
+
assert(!r.charset?('iso-8859-1'))
|
43
27
|
end
|
44
28
|
|
45
29
|
def test_encoding
|
46
|
-
|
47
|
-
assert(
|
48
|
-
assert(!
|
30
|
+
r = R.new('HTTP_ACCEPT_ENCODING' => '')
|
31
|
+
assert(r.encoding?('identity'))
|
32
|
+
assert(!r.encoding?('gzip'))
|
49
33
|
|
50
|
-
|
51
|
-
assert(
|
52
|
-
assert(
|
53
|
-
assert(!
|
34
|
+
r = R.new('HTTP_ACCEPT_ENCODING' => 'gzip')
|
35
|
+
assert(r.encoding?('identity'))
|
36
|
+
assert(r.encoding?('gzip'))
|
37
|
+
assert(!r.encoding?('compress'))
|
54
38
|
|
55
|
-
|
56
|
-
assert(
|
57
|
-
assert(
|
58
|
-
assert(!
|
39
|
+
r = R.new('HTTP_ACCEPT_ENCODING' => 'gzip;q=0, *')
|
40
|
+
assert(r.encoding?('compress'))
|
41
|
+
assert(r.encoding?('identity'))
|
42
|
+
assert(!r.encoding?('gzip'))
|
59
43
|
|
60
|
-
|
61
|
-
assert(!
|
44
|
+
r = R.new('HTTP_ACCEPT_ENCODING' => 'identity;q=0')
|
45
|
+
assert(!r.encoding?('identity'))
|
62
46
|
|
63
|
-
|
64
|
-
assert(!
|
65
|
-
end
|
66
|
-
|
67
|
-
def test_best_encoding
|
68
|
-
request = R.new('HTTP_ACCEPT_ENCODING' => 'gzip, compress')
|
69
|
-
assert_equal('gzip', request.best_encoding(%w< gzip compress >))
|
70
|
-
assert_equal('identity', request.best_encoding(%w< identity compress >))
|
71
|
-
assert_equal(nil, request.best_encoding(%w< zip >))
|
47
|
+
r = R.new('HTTP_ACCEPT_ENCODING' => '*;q=0')
|
48
|
+
assert(!r.encoding?('identity'))
|
72
49
|
end
|
73
50
|
|
74
51
|
def test_language
|
@@ -82,12 +59,4 @@ class RequestTest < Test::Unit::TestCase
|
|
82
59
|
assert(!request.language?('da'))
|
83
60
|
end
|
84
61
|
|
85
|
-
def test_best_language
|
86
|
-
request = R.new('HTTP_ACCEPT_LANGUAGE' => 'en;q=0.5, en-gb')
|
87
|
-
assert_equal('en-gb', request.best_language(%w< en en-gb >))
|
88
|
-
assert_equal('en', request.best_language(%w< en da >))
|
89
|
-
assert_equal('en-us', request.best_language(%w< en-us en-au >))
|
90
|
-
assert_equal(nil, request.best_language(%w< da >))
|
91
|
-
end
|
92
|
-
|
93
62
|
end
|
metadata
CHANGED
@@ -4,8 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
|
7
|
+
- 4
|
8
|
+
- 1
|
9
|
+
version: 0.4.1
|
9
10
|
platform: ruby
|
10
11
|
authors:
|
11
12
|
- Michael J. I. Jackson
|
@@ -13,7 +14,7 @@ autorequire:
|
|
13
14
|
bindir: bin
|
14
15
|
cert_chain: []
|
15
16
|
|
16
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-05 00:00:00 -06:00
|
17
18
|
default_executable:
|
18
19
|
dependencies:
|
19
20
|
- !ruby/object:Gem::Dependency
|