useless-doc 0.1.0

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.
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>