useless-doc 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/useless/doc/core/api.rb +8 -1
- data/lib/useless/doc/dsl.rb +93 -8
- data/lib/useless/doc/rack/retriever.rb +26 -2
- data/lib/useless/doc/sinatra.rb +66 -32
- data/lib/useless/doc/version.rb +1 -1
- data/lib/useless/doc.rb +5 -0
- data/spec/useless/doc/dsl_spec.rb +45 -15
- data/spec/useless/doc/rack/retriever_spec.rb +29 -2
- data/spec/useless/doc/sinatra_spec.rb +65 -14
- metadata +2 -2
data/lib/useless/doc/core/api.rb
CHANGED
@@ -4,21 +4,28 @@ module Useless
|
|
4
4
|
|
5
5
|
# Documentation for an entire API.
|
6
6
|
#
|
7
|
+
# @!attribute [r] url
|
8
|
+
# @return [String] a the URL of the API.
|
9
|
+
#
|
7
10
|
# @!attribute [r] description
|
8
11
|
# @return [String] a description of the API.
|
9
12
|
#
|
13
|
+
# @!attribute [r] timestamp
|
14
|
+
# @return [Time] the time that this API doc was last updated.
|
15
|
+
#
|
10
16
|
# @!attribute [r] resources
|
11
17
|
# @return [Array<Resource>] the resources included in the API.
|
12
18
|
#
|
13
19
|
class API
|
14
20
|
|
15
|
-
attr_accessor :url, :description, :resources
|
21
|
+
attr_accessor :url, :description, :timestamp, :resources
|
16
22
|
|
17
23
|
# @param [Hash] attrs corresponds to the class's instance attributes.
|
18
24
|
#
|
19
25
|
def initialize(attrs = {})
|
20
26
|
@url = attrs[:url]
|
21
27
|
@description = attrs[:description]
|
28
|
+
@timestamp = attrs[:timestamp]
|
22
29
|
@resources = attrs[:resources]
|
23
30
|
end
|
24
31
|
end
|
data/lib/useless/doc/dsl.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'useless/doc/core/api'
|
1
2
|
require 'useless/doc/core/body'
|
2
3
|
require 'useless/doc/core/header'
|
3
4
|
require 'useless/doc/core/request'
|
@@ -7,14 +8,14 @@ require 'useless/doc/core/response'
|
|
7
8
|
module Useless
|
8
9
|
module Doc
|
9
10
|
|
10
|
-
# A simple DSL for building
|
11
|
+
# A simple DSL for building API documentation.
|
11
12
|
#
|
12
13
|
# @example
|
13
|
-
# Useless::Doc
|
14
|
-
#
|
15
|
-
# description 'The entire collection of resources.'
|
14
|
+
# Useless::Doc.api 'resource.useless.io' do
|
15
|
+
# description 'A source of resources'
|
16
16
|
#
|
17
|
-
# get '
|
17
|
+
# get '/resources' do
|
18
|
+
# description 'Returns a full listing of the resources.'
|
18
19
|
# authentication_required false
|
19
20
|
# parameter 'page', 'The page of resources to be returned.'
|
20
21
|
# header 'X-Twiddle', 'The twiddle threshold.'
|
@@ -42,7 +43,10 @@ module Useless
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def self.included(base)
|
45
|
-
base.
|
46
|
+
base.instance_eval do
|
47
|
+
extend ClassMethods
|
48
|
+
attr_reader :attributes
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def initialize(attributes = {})
|
@@ -60,6 +64,89 @@ module Useless
|
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
67
|
+
class API
|
68
|
+
include DSL::Member
|
69
|
+
|
70
|
+
def initialize(attributes = {})
|
71
|
+
@resource_dsls = []
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_attributes
|
76
|
+
{ resources: [] }
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate
|
80
|
+
@attributes[:resources] = @resource_dsls.map do |resource_dsl|
|
81
|
+
resource_dsl.generate
|
82
|
+
end
|
83
|
+
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
def url(url)
|
88
|
+
@attributes[:url] = url
|
89
|
+
end
|
90
|
+
|
91
|
+
def description(description)
|
92
|
+
@attributes[:description] = description
|
93
|
+
end
|
94
|
+
|
95
|
+
def timestamp(timestamp)
|
96
|
+
if timestamp.is_a?(String)
|
97
|
+
timestamp = Time.parse(timestamp)
|
98
|
+
end
|
99
|
+
|
100
|
+
@attributes[:timestamp] = timestamp
|
101
|
+
end
|
102
|
+
|
103
|
+
def get(path, &block)
|
104
|
+
resource(path).get(&block)
|
105
|
+
end
|
106
|
+
|
107
|
+
def head(path, &block)
|
108
|
+
resource(path).head(&block)
|
109
|
+
end
|
110
|
+
|
111
|
+
def post(path, &block)
|
112
|
+
resource(path).post(&block)
|
113
|
+
end
|
114
|
+
|
115
|
+
def put(path, &block)
|
116
|
+
resource(path).put(&block)
|
117
|
+
end
|
118
|
+
|
119
|
+
def patch(path, &block)
|
120
|
+
resource(path).patch(&block)
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete(path, &block)
|
124
|
+
resource(path).delete(&block)
|
125
|
+
end
|
126
|
+
|
127
|
+
def trace(path, &block)
|
128
|
+
resource(path).trace(&block)
|
129
|
+
end
|
130
|
+
|
131
|
+
def connect(path, &block)
|
132
|
+
resource(path).connect(&block)
|
133
|
+
end
|
134
|
+
|
135
|
+
def resource(path, &block)
|
136
|
+
resource_dsl = @resource_dsls.find do |resource|
|
137
|
+
resource.attributes[:path] == path
|
138
|
+
end
|
139
|
+
|
140
|
+
unless resource_dsl
|
141
|
+
resource_dsl = Resource.new(path: path)
|
142
|
+
@resource_dsls << resource_dsl
|
143
|
+
end
|
144
|
+
|
145
|
+
resource_dsl.instance_eval(&block) if block_given?
|
146
|
+
resource_dsl
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
63
150
|
class Resource
|
64
151
|
include DSL::Member
|
65
152
|
|
@@ -107,8 +194,6 @@ module Useless
|
|
107
194
|
method(Doc::Core::Request::Method::CONNECT, description, &block)
|
108
195
|
end
|
109
196
|
|
110
|
-
private
|
111
|
-
|
112
197
|
def method(type, description, &block)
|
113
198
|
attributes = { method: type, description: description }
|
114
199
|
@attributes[:requests] << Request.build(attributes, &block)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'time'
|
1
2
|
require 'typhoeus'
|
2
3
|
|
3
4
|
module Useless
|
@@ -25,9 +26,32 @@ module Useless
|
|
25
26
|
end
|
26
27
|
|
27
28
|
module Standard
|
29
|
+
NotModified = 304
|
30
|
+
|
31
|
+
@cache = {}
|
32
|
+
|
28
33
|
def self.retrieve(url)
|
29
|
-
|
30
|
-
|
34
|
+
headers = { 'Accept' => 'application/json' }
|
35
|
+
|
36
|
+
if @cache[url]
|
37
|
+
headers['If-Modified-Since'] = @cache[url].timestamp.httpdate()
|
38
|
+
end
|
39
|
+
|
40
|
+
response = Typhoeus.options url, headers: headers
|
41
|
+
|
42
|
+
unless response.response_code == NotModified
|
43
|
+
@cache[url] = CacheItem.new(response.response_body, Time.now)
|
44
|
+
end
|
45
|
+
|
46
|
+
@cache[url].response_body
|
47
|
+
end
|
48
|
+
|
49
|
+
class CacheItem
|
50
|
+
attr_accessor :response_body, :timestamp
|
51
|
+
|
52
|
+
def initialize(response_body, timestamp)
|
53
|
+
@response_body, @timestamp = response_body, timestamp
|
54
|
+
end
|
31
55
|
end
|
32
56
|
end
|
33
57
|
|
data/lib/useless/doc/sinatra.rb
CHANGED
@@ -5,40 +5,74 @@ require 'useless/doc/serialization/dump'
|
|
5
5
|
|
6
6
|
module Useless
|
7
7
|
module Doc
|
8
|
+
|
9
|
+
# Provides access to the +Doc::DSL+ via the +.doc+ method. The JSON of the
|
10
|
+
# API doc that is built will be served via an OPTIONS request to the root.
|
11
|
+
# Resource documentation is similarly served via an OPTIONS request to the
|
12
|
+
# corresponding path.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# class ResourceApp < Sinatra::Base
|
16
|
+
# register Useless::Doc::Sinatra
|
17
|
+
#
|
18
|
+
# doc 'resources.useless.io' do
|
19
|
+
# description 'A place with resources'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# doc.get '/some-resources' do
|
23
|
+
# description 'Get all of these resources'
|
24
|
+
#
|
25
|
+
# request do
|
26
|
+
# parameter 'page', 'The page of resources to return.'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# response do
|
30
|
+
# body do
|
31
|
+
# attribute 'name', 'The name of the resource.'
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# ...
|
37
|
+
#
|
38
|
+
# end
|
39
|
+
#
|
8
40
|
module Sinatra
|
41
|
+
def doc=(doc)
|
42
|
+
@doc = doc
|
43
|
+
end
|
44
|
+
|
45
|
+
def doc(url = nil, &block)
|
46
|
+
@dsl ||= Useless::Doc::DSL::API.new(url: url)
|
47
|
+
@dsl.instance_eval(&block) if block_given?
|
48
|
+
@dsl
|
49
|
+
end
|
50
|
+
|
51
|
+
def generated_doc
|
52
|
+
@doc ||= @dsl.generate if @dsl
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.registered(app)
|
56
|
+
app.options '/' do
|
57
|
+
if api = self.class.generated_doc
|
58
|
+
last_modified api.timestamp
|
59
|
+
Useless::Doc::Serialization::Dump.api(api)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
app.options '/*' do
|
64
|
+
if api = self.class.generated_doc
|
65
|
+
resource = api.resources.find do |resource|
|
66
|
+
resource.path == "/#{params[:splat].first}"
|
67
|
+
end
|
68
|
+
|
69
|
+
if resource
|
70
|
+
last_modified api.timestamp
|
71
|
+
return Useless::Doc::Serialization::Dump.resource(resource)
|
72
|
+
end
|
73
|
+
end
|
9
74
|
|
10
|
-
|
11
|
-
# created will then be served via an OPTIONS request to the specified
|
12
|
-
# +path+.
|
13
|
-
#
|
14
|
-
# @param [String] path the path of the resource to be documented.
|
15
|
-
#
|
16
|
-
# @param block is passed to the DSL to build the resource.
|
17
|
-
#
|
18
|
-
# @example
|
19
|
-
# class ResourceApp < Sinatra::Base
|
20
|
-
# register Useless::Doc::Sinatra
|
21
|
-
#
|
22
|
-
# doc '/some-resources' do
|
23
|
-
# get 'Get all of these resources' do
|
24
|
-
# request do
|
25
|
-
# parameter 'page', 'The page of resources to return.'
|
26
|
-
# end
|
27
|
-
#
|
28
|
-
# response do
|
29
|
-
# body do
|
30
|
-
# attribute 'name', 'The name of the resource.'
|
31
|
-
# end
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
def doc(path, &block)
|
38
|
-
resource = Useless::Doc::DSL::Resource.build path: path, &block
|
39
|
-
|
40
|
-
options(path) do
|
41
|
-
Doc::Serialization::Dump.resource(resource)
|
75
|
+
pass
|
42
76
|
end
|
43
77
|
end
|
44
78
|
end
|
data/lib/useless/doc/version.rb
CHANGED
data/lib/useless/doc.rb
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
2
|
|
3
|
+
require 'time'
|
3
4
|
require 'rack/test'
|
5
|
+
require 'useless/doc'
|
4
6
|
require 'useless/doc/dsl'
|
5
7
|
|
6
8
|
describe Useless::Doc::DSL do
|
7
9
|
describe '.build' do
|
8
|
-
it 'should provide a terse DSL for building
|
9
|
-
|
10
|
-
|
11
|
-
description 'The entire collection of widgets'
|
10
|
+
it 'should provide a terse DSL for building API documentation (through Useless::Doc.api)' do
|
11
|
+
api = Useless::Doc.api 'widget.useless.io' do
|
12
|
+
description 'The canonical source of worldwide widgets.'
|
12
13
|
|
13
|
-
|
14
|
+
resource('/widgets').description 'The whole kit and kaboodle.'
|
15
|
+
|
16
|
+
get '/widgets' do
|
17
|
+
description 'List the entire collection of widgets.'
|
14
18
|
authentication_required false
|
19
|
+
|
15
20
|
parameter 'page', 'The page of widgets that you\'d like',
|
16
21
|
type: :query, required: false, default: 1
|
17
22
|
header 'X-Twiddle', 'The twiddle threshold.'
|
@@ -30,7 +35,7 @@ describe Useless::Doc::DSL do
|
|
30
35
|
end
|
31
36
|
end
|
32
37
|
|
33
|
-
post do
|
38
|
+
post '/widgets' do
|
34
39
|
authentication_required true
|
35
40
|
description 'Creates a new widget'
|
36
41
|
|
@@ -44,17 +49,42 @@ describe Useless::Doc::DSL do
|
|
44
49
|
response 201, 'The widget was successfully created'
|
45
50
|
response 422, 'The widget couldn\'t be created because name was missing'
|
46
51
|
end
|
52
|
+
|
53
|
+
delete '/widgets/:id' do
|
54
|
+
response 404, 'The widget could not be found.'
|
55
|
+
response 200, 'The widgets was deleted successfully'
|
56
|
+
end
|
57
|
+
|
58
|
+
resource '/widgets/:id' do
|
59
|
+
put do
|
60
|
+
description 'Resource-oriented specification.'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
timestamp '2013-01-12 12:44 AM'
|
47
65
|
end
|
48
66
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
api.url.should == 'widget.useless.io'
|
68
|
+
api.description.should == 'The canonical source of worldwide widgets.'
|
69
|
+
api.timestamp.should == Time.parse('2013-01-12 12:44 AM')
|
70
|
+
|
71
|
+
collection = api.resources.find { |resource| resource.path == '/widgets' }
|
72
|
+
collection.description.should == 'The whole kit and kaboodle.'
|
73
|
+
collection.requests[0].method.should == 'GET'
|
74
|
+
collection.requests[0].headers[0].description.should == 'The twiddle threshold.'
|
75
|
+
collection.requests[0].responses[1].code.should == 200
|
76
|
+
collection.requests[1].method.should == 'POST'
|
77
|
+
collection.requests[1].description.should == 'Creates a new widget'
|
78
|
+
collection.requests[1].body.content_type.should == 'application/json'
|
79
|
+
collection.requests[1].body.attributes[0].key.should == 'name'
|
80
|
+
collection.requests[1].responses[1].description.should == 'The widget couldn\'t be created because name was missing'
|
81
|
+
|
82
|
+
instance = api.resources.find { |resource| resource.path == '/widgets/:id' }
|
83
|
+
instance.requests[0].method.should == 'DELETE'
|
84
|
+
instance.requests[0].responses[0].code.should == 404
|
85
|
+
instance.requests[0].responses[1].code.should == 200
|
86
|
+
instance.requests[1].method.should == 'PUT'
|
87
|
+
instance.requests[1].description.should == 'Resource-oriented specification.'
|
58
88
|
end
|
59
89
|
end
|
60
90
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../../spec_helper'
|
2
2
|
|
3
|
+
require 'time'
|
3
4
|
require 'rack/test'
|
4
5
|
require 'useless/doc/rack/retriever'
|
5
6
|
|
@@ -42,8 +43,34 @@ describe Useless::Doc::Rack::Retriever do
|
|
42
43
|
end
|
43
44
|
|
44
45
|
describe Useless::Doc::Rack::Retriever::Standard do
|
45
|
-
|
46
|
-
Useless::Doc::Rack::Retriever::Standard.
|
46
|
+
before(:each) do
|
47
|
+
Useless::Doc::Rack::Retriever::Standard.instance_variable_set(:@cache, {})
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should make a normal request if the cache is empty.' do
|
51
|
+
Typhoeus.should_receive(:options).
|
52
|
+
with('http://some-api.granmal.com/some/resource', headers: { 'Accept' => 'application/json' }).
|
53
|
+
and_return(mock(:response, response_code: 200, response_body: '{ "some": "json" }'))
|
54
|
+
|
55
|
+
response = Useless::Doc::Rack::Retriever::Standard.retrieve('http://some-api.granmal.com/some/resource')
|
56
|
+
response.should == '{ "some": "json" }'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should make a request with a cache control header if there is a cache hit.' do
|
60
|
+
now = Time.now
|
61
|
+
Time.should_receive(:now).once.and_return(now)
|
62
|
+
|
63
|
+
Typhoeus.should_receive(:options).once.
|
64
|
+
with('http://some-api.granmal.com/some/resource', headers: { 'Accept' => 'application/json' }).
|
65
|
+
and_return(mock(:response, response_code: 200, response_body: '{ "some": "json" }'))
|
66
|
+
|
67
|
+
Typhoeus.should_receive(:options).once.
|
68
|
+
with('http://some-api.granmal.com/some/resource', headers: { 'Accept' => 'application/json', 'If-Modified-Since' => now.httpdate}).
|
69
|
+
and_return(mock(:response, response_code: 304, response_body: ''))
|
70
|
+
|
71
|
+
Useless::Doc::Rack::Retriever::Standard.retrieve('http://some-api.granmal.com/some/resource')
|
72
|
+
response = Useless::Doc::Rack::Retriever::Standard.retrieve('http://some-api.granmal.com/some/resource')
|
73
|
+
response.should == '{ "some": "json" }'
|
47
74
|
end
|
48
75
|
end
|
49
76
|
|
@@ -9,23 +9,20 @@ describe Useless::Doc::Sinatra do
|
|
9
9
|
class DocApp < Sinatra::Base
|
10
10
|
register Useless::Doc::Sinatra
|
11
11
|
|
12
|
-
doc '
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
12
|
+
doc 'resource.useless.io' do
|
13
|
+
description 'A resource repository'
|
14
|
+
timestamp '2013-01-01 12:00 PM'
|
15
|
+
end
|
16
|
+
|
17
|
+
doc.get '/some-resources' do
|
18
|
+
description 'Get all of these resources'
|
22
19
|
|
23
|
-
|
20
|
+
parameter 'since', 'Only resources after this date.'
|
21
|
+
|
22
|
+
response 200, 'The responses were fetched correctly.' do
|
24
23
|
body do
|
25
24
|
attribute 'name', 'The name of the resource.'
|
26
25
|
end
|
27
|
-
|
28
|
-
response 201, 'The resource was successfully created.'
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
@@ -33,9 +30,33 @@ describe Useless::Doc::Sinatra do
|
|
33
30
|
[]
|
34
31
|
end
|
35
32
|
|
33
|
+
doc.post '/some-resources' do
|
34
|
+
description 'Make a new resource'
|
35
|
+
|
36
|
+
body do
|
37
|
+
attribute 'name', 'The name of the resource.'
|
38
|
+
end
|
39
|
+
|
40
|
+
response 201, 'The resource was successfully created.'
|
41
|
+
end
|
42
|
+
|
36
43
|
post '/some-resources' do
|
37
44
|
201
|
38
45
|
end
|
46
|
+
|
47
|
+
doc.put '/some-resources/:id' do
|
48
|
+
description 'Update a resource'
|
49
|
+
|
50
|
+
body do
|
51
|
+
attribute 'name', 'The name of the resource.'
|
52
|
+
end
|
53
|
+
|
54
|
+
response 200, 'The resource was successfully updated.'
|
55
|
+
end
|
56
|
+
|
57
|
+
put '/some-resources/:id' do
|
58
|
+
200
|
59
|
+
end
|
39
60
|
end
|
40
61
|
|
41
62
|
include Rack::Test::Methods
|
@@ -44,7 +65,17 @@ describe Useless::Doc::Sinatra do
|
|
44
65
|
DocApp
|
45
66
|
end
|
46
67
|
|
47
|
-
it 'should serve documentation for the
|
68
|
+
it 'should serve documentation for the whole API from root.' do
|
69
|
+
options 'http://some-api.granmal.com/'
|
70
|
+
api = Useless::Doc::Serialization::Load.api(last_response.body)
|
71
|
+
|
72
|
+
paths = api.resources.map { |resource| resource.path }
|
73
|
+
paths.length.should == 2
|
74
|
+
paths.should include '/some-resources'
|
75
|
+
paths.should include '/some-resources/:id'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should serve documentation for the specified resource.' do
|
48
79
|
options 'http://some-api.granmal.com/some-resources'
|
49
80
|
resource = Useless::Doc::Serialization::Load.resource(last_response.body)
|
50
81
|
|
@@ -57,4 +88,24 @@ describe Useless::Doc::Sinatra do
|
|
57
88
|
post.responses[0].code.should == 201
|
58
89
|
end
|
59
90
|
|
91
|
+
it 'should return a 404 if the specified resource doesn\'t exist' do
|
92
|
+
options 'http://some-api.granmal.com/some-nonexistant-resources'
|
93
|
+
last_response.should be_not_found
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should return a 304 if the doc has not been modified since specified time' do
|
97
|
+
header 'If-Modified-Since', Time.parse('2013-01-02 12:00 PM').httpdate
|
98
|
+
options 'http://some-api.granmal.com/'
|
99
|
+
last_response.status.should == 304
|
100
|
+
options 'http://some-api.granmal.com/some-resources'
|
101
|
+
last_response.status.should == 304
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should not return a 304 if the doc has been modified since the specified time' do
|
105
|
+
header 'If-Modified-Since', Time.parse('2012-12-31 12:00 PM').httpdate
|
106
|
+
options 'http://some-api.granmal.com/'
|
107
|
+
last_response.status.should_not == 304
|
108
|
+
options 'http://some-api.granmal.com/some-resources'
|
109
|
+
last_response.status.should_not == 304
|
110
|
+
end
|
60
111
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: useless-doc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: oj
|