useless-doc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +18 -0
  2. data/.rbenv-version +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +3 -0
  6. data/Rakefile +1 -0
  7. data/config.ru +4 -0
  8. data/lib/useless/doc/action.rb +50 -0
  9. data/lib/useless/doc/body.rb +58 -0
  10. data/lib/useless/doc/dsl.rb +208 -0
  11. data/lib/useless/doc/header.rb +24 -0
  12. data/lib/useless/doc/rack/application.rb +31 -0
  13. data/lib/useless/doc/rack/proxy.rb +58 -0
  14. data/lib/useless/doc/rack/stylesheet.rb +24 -0
  15. data/lib/useless/doc/rack/transform.rb +35 -0
  16. data/lib/useless/doc/rack/ui.rb +35 -0
  17. data/lib/useless/doc/request/parameter.rb +47 -0
  18. data/lib/useless/doc/request.rb +29 -0
  19. data/lib/useless/doc/resource.rb +29 -0
  20. data/lib/useless/doc/response/status.rb +27 -0
  21. data/lib/useless/doc/response.rb +29 -0
  22. data/lib/useless/doc/serialization/dump.rb +122 -0
  23. data/lib/useless/doc/serialization/load.rb +171 -0
  24. data/lib/useless/doc/sinatra.rb +48 -0
  25. data/lib/useless/doc/ui/godel/stylesheet.css +1 -0
  26. data/lib/useless/doc/ui/godel/template.mustache +199 -0
  27. data/lib/useless/doc/ui/godel.rb +92 -0
  28. data/lib/useless/doc/ui.rb +37 -0
  29. data/lib/useless/doc/version.rb +5 -0
  30. data/lib/useless/doc.rb +4 -0
  31. data/spec/documents/twonk.json +106 -0
  32. data/spec/spec_helper.rb +10 -0
  33. data/spec/useless/doc/dsl_spec.rb +71 -0
  34. data/spec/useless/doc/proxy_spec.rb +48 -0
  35. data/spec/useless/doc/serialization/dump_spec.rb +116 -0
  36. data/spec/useless/doc/serialization/load_spec.rb +99 -0
  37. data/spec/useless/doc/sinatra_spec.rb +64 -0
  38. data/useless-doc.gemspec +26 -0
  39. metadata +217 -0
@@ -0,0 +1,29 @@
1
+ module Useless
2
+ module Doc
3
+
4
+ # Documentation for an API resource.
5
+ #
6
+ # @!attribute [r] path
7
+ # @return [String] the path segment of the resource URL.
8
+ #
9
+ # @!attribute [r] description
10
+ # @return [String] a description of the resource.
11
+ #
12
+ # @!attribute [r] actions
13
+ # @return [Array<Action>] documentation for the available actions for the
14
+ # resource.
15
+ #
16
+ class Resource
17
+
18
+ attr_reader :path, :description, :actions
19
+
20
+ # @param [Hash] attrs corresponds to the class's instance attributes.
21
+ #
22
+ def initialize(attrs = {})
23
+ @path = attrs[:path]
24
+ @description = attrs[:description]
25
+ @actions = attrs[:actions]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ module Useless
2
+ module Doc
3
+ class Response
4
+
5
+ # Documentation for a response status for an API action.
6
+ #
7
+ # @!attribute [r] code
8
+ # @return [Integer] the HTTP status code of the response.
9
+ #
10
+ # @!attribute [r] description
11
+ # @return [String] a description of the rationale for
12
+ # the response.
13
+ #
14
+ class Status
15
+
16
+ attr_reader :code, :description
17
+
18
+ # @param [Hash] attrs corresponds to the class's instance attributes.
19
+ #
20
+ def initialize(attrs = {})
21
+ @code = attrs[:code]
22
+ @description = attrs[:description]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module Useless
2
+ module Doc
3
+
4
+ # Documentation for an HTTP response.
5
+ #
6
+ # @!attribute [r] statuses
7
+ # @return [Array<Response::Status] documentation for the possible
8
+ # statuses of the response.
9
+ #
10
+ # @!attribute [r] headers
11
+ # @return [Array<Header>] documentation for the headers of the
12
+ # response.
13
+ #
14
+ # @!attribute [r] body
15
+ # @return [Body] documentation for the body of the response.
16
+ #
17
+ class Response
18
+ attr_accessor :statuses, :headers, :body
19
+
20
+ # @param [Hash] attrs corresponds to the class's instance attributes.
21
+ #
22
+ def initialize(attrs = {})
23
+ @statuses = attrs[:statuses]
24
+ @headers = attrs[:headers]
25
+ @body = attrs[:body]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,122 @@
1
+ require 'oj'
2
+
3
+ module Useless
4
+ module Doc
5
+ module Serialization
6
+ module Dump
7
+
8
+ # Converts a hash to a JSON representation.
9
+ #
10
+ # @param [Hash, String] hash the hash to be converted.
11
+ #
12
+ # @raise [ArgumentError] if json is not a Hash, String or IO.
13
+ #
14
+ # @return [String] a JSON representation corresponding to the
15
+ # specified hash.
16
+ #
17
+ def self.hash_to_json(hash)
18
+ hash.is_a?(String) ? hash : Oj.dump(hash)
19
+ end
20
+
21
+ # Converts +Doc::Resource+ instance to a JSON representation.
22
+ #
23
+ # @param [Doc::Resource] resource the resource to be converted to JSON.
24
+ #
25
+ # @return [String] a JSON representation of the specified resource.
26
+ #
27
+ def self.resource(resource)
28
+ if resource
29
+ hash_to_json \
30
+ 'path' => resource.path,
31
+ 'description' => resource.description,
32
+ 'actions' => resource.actions.map { |action| action(action) }
33
+ end
34
+ end
35
+
36
+ # @api private
37
+ def self.action(action)
38
+ if action
39
+ hash_to_json \
40
+ 'description' => action.description,
41
+ 'method' => action.method,
42
+ 'authentication_required' => action.authentication_required,
43
+ 'request' => request(action.request),
44
+ 'response' => response(action.response)
45
+ end
46
+ end
47
+
48
+ # @api private
49
+ def self.request(request)
50
+ if request
51
+ hash_to_json \
52
+ 'parameters' => request.parameters.map { |parameter| request_parameter(parameter) },
53
+ 'headers' => request.headers.map { |header| header(header) },
54
+ 'body' => body(request.body)
55
+ end
56
+ end
57
+
58
+ # @api private
59
+ def self.response(response)
60
+ if response
61
+ hash_to_json \
62
+ 'statuses' => response.statuses.map { |status| response_status(status) },
63
+ 'headers' => response.headers.map { |header| header(header) },
64
+ 'body' => body(response.body)
65
+ end
66
+ end
67
+
68
+ # @api private
69
+ def self.request_parameter(parameter)
70
+ if parameter
71
+ hash_to_json \
72
+ 'type' => parameter.type,
73
+ 'key' => parameter.key,
74
+ 'required' => parameter.required,
75
+ 'default' => parameter.default,
76
+ 'description' => parameter.description
77
+ end
78
+ end
79
+
80
+ # @api private
81
+ def self.header(header)
82
+ if header
83
+ hash_to_json \
84
+ 'key' => header.key,
85
+ 'description' => header.description
86
+ end
87
+ end
88
+
89
+ # @api private
90
+ def self.response_status(status)
91
+ if status
92
+ hash_to_json \
93
+ 'code' => status.code,
94
+ 'description' => status.description
95
+ end
96
+ end
97
+
98
+ # @api private
99
+ def self.body(body)
100
+ if body
101
+ hash_to_json \
102
+ 'content_type' => body.content_type,
103
+ 'attributes' => body.attributes.map { |attribute| body_attribute(attribute) }
104
+ end
105
+ end
106
+
107
+ # @api private
108
+ def self.body_attribute(attribute)
109
+ if attribute
110
+ hash_to_json \
111
+ 'key' => attribute.key,
112
+ 'type' => attribute.type,
113
+ 'required' => attribute.required,
114
+ 'default' => attribute.default,
115
+ 'description' => attribute.description
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,171 @@
1
+ require 'oj'
2
+
3
+ require 'useless/doc/action'
4
+ require 'useless/doc/body'
5
+ require 'useless/doc/header'
6
+ require 'useless/doc/request'
7
+ require 'useless/doc/request/parameter'
8
+ require 'useless/doc/resource'
9
+ require 'useless/doc/response'
10
+ require 'useless/doc/response/status'
11
+
12
+ module Useless
13
+ module Doc
14
+ module Serialization
15
+ module Load
16
+
17
+ # Converts a JSON representation to a hash.
18
+ #
19
+ # @param [String, Hash] json the JSON representation to be converted.
20
+ #
21
+ # @raise [ArgumentError] if json is not a Hash, String or IO.
22
+ #
23
+ # @return [Hash] a hash corresponding to the specified JSON representation.
24
+ #
25
+ def self.json_to_hash(json)
26
+ json.is_a?(Hash) ? json : Oj.load(json)
27
+ end
28
+
29
+ # Converts a JSON represntation to an instance of +Doc::Resource+
30
+ #
31
+ # @param [String, Hash] json the JSON representation to be converted to
32
+ # a resource.
33
+ #
34
+ # @return [Doc::Resource] the resource corresponding to the specified
35
+ # JSON.
36
+ #
37
+ def self.resource(json)
38
+ hash = json_to_hash json
39
+
40
+ actions = (hash['actions'] || []).map do |json|
41
+ action(json)
42
+ end
43
+
44
+ Useless::Doc::Resource.new \
45
+ path: hash['path'],
46
+ description: hash['description'],
47
+ actions: actions
48
+ end
49
+
50
+ # @api private
51
+ def self.action(json)
52
+ hash = json_to_hash json
53
+
54
+ if hash['request']
55
+ request = request hash['request']
56
+ end
57
+
58
+ if hash['response']
59
+ response = response hash['response']
60
+ end
61
+
62
+ Useless::Doc::Action.new \
63
+ description: hash['description'],
64
+ method: hash['method'],
65
+ authentication_required: hash['authentication_required'],
66
+ request: request,
67
+ response: response
68
+ end
69
+
70
+ # @api private
71
+ def self.request(json)
72
+ hash = json_to_hash json
73
+
74
+ parameters = (hash['parameters'] || []).map do |json|
75
+ request_parameter json
76
+ end
77
+
78
+ headers = (hash['headers'] || []).map do |json|
79
+ header json
80
+ end
81
+
82
+ if hash['body']
83
+ body = body hash['body']
84
+ end
85
+
86
+ Useless::Doc::Request.new \
87
+ parameters: parameters,
88
+ headers: headers,
89
+ body: body
90
+ end
91
+
92
+ # @api private
93
+ def self.response(json)
94
+ hash = json_to_hash json
95
+
96
+ statuses = (hash['statuses'] || []).map do |json|
97
+ response_statuses json
98
+ end
99
+
100
+ headers = (hash['headers'] || []).map do |json|
101
+ header json
102
+ end
103
+
104
+ if hash['body']
105
+ body = body hash['body']
106
+ end
107
+
108
+ Useless::Doc::Response.new \
109
+ statuses: statuses,
110
+ headers: headers,
111
+ body: body
112
+ end
113
+
114
+ # @api private
115
+ def self.request_parameter(json)
116
+ hash = json_to_hash json
117
+
118
+ Useless::Doc::Request::Parameter.new \
119
+ type: hash['type'],
120
+ key: hash['key'],
121
+ required: hash['required'],
122
+ default: hash['default'],
123
+ description: hash['description']
124
+ end
125
+
126
+ # @api private
127
+ def self.header(json)
128
+ hash = json_to_hash json
129
+
130
+ Useless::Doc::Header.new \
131
+ key: hash['key'],
132
+ description: hash['description']
133
+ end
134
+
135
+ # @api private
136
+ def self.body(json)
137
+ hash = json_to_hash json
138
+
139
+ attributes = (hash['attributes'] || []).map do |json|
140
+ body_attribute json
141
+ end
142
+
143
+ Useless::Doc::Body.new \
144
+ content_type: hash['content_type'],
145
+ attributes: attributes
146
+ end
147
+
148
+ # @api private
149
+ def self.body_attribute(json)
150
+ hash = json_to_hash json
151
+
152
+ Useless::Doc::Body::Attribute.new \
153
+ key: hash['key'],
154
+ type: hash['type'],
155
+ required: hash['required'],
156
+ default: hash['default'],
157
+ description: hash['description']
158
+ end
159
+
160
+ # @api private
161
+ def self.response_statuses(json)
162
+ hash = json_to_hash json
163
+
164
+ Useless::Doc::Response::Status.new \
165
+ code: hash['code'],
166
+ description: hash['description']
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,48 @@
1
+ require 'sinatra'
2
+
3
+ require 'useless/doc/dsl'
4
+ require 'useless/doc/serialization/dump'
5
+
6
+ module Useless
7
+ module Doc
8
+ module Sinatra
9
+
10
+ # Provides access to the +Doc::DSL+. The JSON of the resource that is
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)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ Sinatra.register Useless::Doc::Sinatra
@@ -0,0 +1 @@
1
+ body { color: #333; }
@@ -0,0 +1,199 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{path}} | doc.useless.io</title>
5
+ <link href="/doc.css" media="screen" rel="stylesheet" type="text/css" />
6
+ </head>
7
+ <body>
8
+
9
+ <h1>{{path}}</h1>
10
+
11
+ <p>{{description}}</p>
12
+
13
+ {{#actions}}
14
+ <section>
15
+ <h2>{{method}}</h2>
16
+
17
+ <p>{{description}}</p>
18
+
19
+ <ul>
20
+ <li>{{authentication_requirement}}</li>
21
+ </ul>
22
+
23
+ {{#request}}
24
+ <section>
25
+ <h3>Request</h3>
26
+
27
+ {{#parameters?}}
28
+ <section>
29
+ <h4>Parameters</h4>
30
+ <table>
31
+ <th>
32
+ <tr>
33
+ <td>Name</td>
34
+ <td>Type</td>
35
+ <td>Required</td>
36
+ <td>Default</td>
37
+ <td>Description</td>
38
+ <tr>
39
+ </th>
40
+ <tbody>
41
+ {{#parameters}}
42
+ <tr>
43
+ <td>{{key}}</td>
44
+ <td>{{type}}</td>
45
+ <td>{{required}}</td>
46
+ <td>{{default}}</td>
47
+ <td>{{description}}</td>
48
+ </tr>
49
+ {{/parameters}}
50
+ </tbody>
51
+ </table>
52
+ </section>
53
+ {{/parameters?}}
54
+
55
+ {{#headers?}}
56
+ <section>
57
+ <h4>Headers</h4>
58
+ <table>
59
+ <th>
60
+ <tr>
61
+ <td>Name</td>
62
+ <td>Description</td>
63
+ <tr>
64
+ </th>
65
+ <tbody>
66
+ {{#headers}}
67
+ <tr>
68
+ <td>{{key}}</td>
69
+ <td>{{description}}</td>
70
+ </tr>
71
+ {{/headers}}
72
+ </tbody>
73
+ </table>
74
+ </section>
75
+ {{/headers?}}
76
+
77
+ {{#body}}
78
+ <section>
79
+ <h4>Body</h4>
80
+
81
+ <ul>
82
+ <li>{{content_type}}</li>
83
+ </ul>
84
+
85
+ <table>
86
+ <th>
87
+ <tr>
88
+ <td>Name</td>
89
+ <td>Type</td>
90
+ <td>Required</td>
91
+ <td>Default</td>
92
+ <td>Description</td>
93
+ <tr>
94
+ </th>
95
+ <tbody>
96
+ {{#attributes}}
97
+ <tr>
98
+ <td>{{key}}</td>
99
+ <td>{{type}}</td>
100
+ <td>{{required}}</td>
101
+ <td>{{default}}</td>
102
+ <td>{{description}}</td>
103
+ </tr>
104
+ {{/attributes}}
105
+ </tbody>
106
+ </table>
107
+ </section>
108
+ {{/body}}
109
+
110
+ </section>
111
+ {{/request}}
112
+
113
+ {{#response}}
114
+ <section>
115
+ <h3>Response</h3>
116
+
117
+ {{#statuses?}}
118
+ <section>
119
+ <h4>Statuses</h4>
120
+ <table>
121
+ <th>
122
+ <tr>
123
+ <td>Code</td>
124
+ <td>Description</td>
125
+ <tr>
126
+ </th>
127
+ <tbody>
128
+ {{#statuses}}
129
+ <tr>
130
+ <td>{{code}}</td>
131
+ <td>{{description}}</td>
132
+ </tr>
133
+ {{/statuses}}
134
+ </tbody>
135
+ </table>
136
+ </section>
137
+ {{/statuses?}}
138
+
139
+ {{#headers?}}
140
+ <section>
141
+ <h4>Headers</h4>
142
+ <table>
143
+ <th>
144
+ <tr>
145
+ <td>Name</td>
146
+ <td>Description</td>
147
+ <tr>
148
+ </th>
149
+ <tbody>
150
+ {{#headers}}
151
+ <tr>
152
+ <td>{{key}}</td>
153
+ <td>{{description}}</td>
154
+ </tr>
155
+ {{/headers}}
156
+ </tbody>
157
+ </table>
158
+ </section>
159
+ {{/headers?}}
160
+
161
+ {{#body}}
162
+ <section>
163
+ <h4>Body</h4>
164
+
165
+ <ul>
166
+ <li>{{content_type}}</li>
167
+ </ul>
168
+
169
+ <table>
170
+ <th>
171
+ <tr>
172
+ <td>Name</td>
173
+ <td>Type</td>
174
+ <td>Required</td>
175
+ <td>Default</td>
176
+ <td>Description</td>
177
+ <tr>
178
+ </th>
179
+ <tbody>
180
+ {{#attributes}}
181
+ <tr>
182
+ <td>{{key}}</td>
183
+ <td>{{type}}</td>
184
+ <td>{{required}}</td>
185
+ <td>{{default}}</td>
186
+ <td>{{description}}</td>
187
+ </tr>
188
+ {{/attributes}}
189
+ </tbody>
190
+ </table>
191
+ </section>
192
+ {{/body}}
193
+
194
+ </section>
195
+ {{/response}}
196
+ </section>
197
+ {{/actions}}
198
+
199
+ </body>