Skip to content

Commit 9a5c79e

Browse files
committed
Merge pull request #83 from jonathanpa/develop
Textile documentation generator
2 parents b1dbdee + 637e143 commit 9a5c79e

19 files changed

+600
-116
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ tmp
33
example/docs
44
example/public/docs
55
*.gem
6+
*.swp

example/spec/spec_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
end
3434

3535
RspecApiDocumentation.configure do |config|
36-
config.format = [:json, :combined_text]
36+
config.format = [:json, :combined_text, :html]
3737
config.curl_host = 'http://localhost:3000'
3838
config.api_name = "Example App API"
3939
end
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
Feature: Generate Textile documentation from test examples
2+
3+
Background:
4+
Given a file named "app.rb" with:
5+
"""
6+
require 'sinatra'
7+
8+
class App < Sinatra::Base
9+
get '/orders' do
10+
content_type :json
11+
12+
[200, [{ name: 'Order 1', amount: 9.99, description: nil },
13+
{ name: 'Order 2', amount: 100.0, description: 'A great order' }].to_json]
14+
end
15+
16+
get '/orders/:id' do
17+
content_type :json
18+
19+
[200, { order: { name: 'Order 1', amount: 100.0, description: 'A great order' } }.to_json]
20+
end
21+
22+
post '/orders' do
23+
201
24+
end
25+
26+
put '/orders/:id' do
27+
200
28+
end
29+
30+
delete '/orders/:id' do
31+
200
32+
end
33+
34+
get '/help' do
35+
[200, 'Welcome Henry !']
36+
end
37+
end
38+
"""
39+
And a file named "app_spec.rb" with:
40+
"""
41+
require "rspec_api_documentation"
42+
require "rspec_api_documentation/dsl"
43+
44+
RspecApiDocumentation.configure do |config|
45+
config.app = App
46+
config.api_name = "Example API"
47+
config.format = :textile
48+
end
49+
50+
resource 'Orders' do
51+
get '/orders' do
52+
53+
example_request 'Getting a list of orders' do
54+
status.should eq(200)
55+
response_body.should eq('[{"name":"Order 1","amount":9.99,"description":null},{"name":"Order 2","amount":100.0,"description":"A great order"}]')
56+
end
57+
end
58+
59+
get '/orders/:id' do
60+
let(:id) { 1 }
61+
62+
example_request 'Getting a specific order' do
63+
status.should eq(200)
64+
response_body.should == '{"order":{"name":"Order 1","amount":100.0,"description":"A great order"}}'
65+
end
66+
end
67+
68+
post '/orders' do
69+
parameter :name, 'Name of order', :required => true
70+
parameter :amount, 'Amount paid', :required => true
71+
parameter :description, 'Some comments on the order'
72+
73+
let(:name) { "Order 3" }
74+
let(:amount) { 33.0 }
75+
76+
example_request 'Creating an order' do
77+
status.should == 201
78+
end
79+
end
80+
81+
put '/orders/:id' do
82+
parameter :name, 'Name of order', :required => true
83+
parameter :amount, 'Amount paid', :required => true
84+
parameter :description, 'Some comments on the order'
85+
86+
let(:id) { 2 }
87+
let(:name) { "Updated name" }
88+
89+
example_request 'Updating an order' do
90+
status.should == 200
91+
end
92+
end
93+
94+
delete "/orders/:id" do
95+
let(:id) { 1 }
96+
97+
example_request "Deleting an order" do
98+
status.should == 200
99+
end
100+
end
101+
end
102+
103+
resource 'Help' do
104+
get '/help' do
105+
example_request 'Getting welcome message' do
106+
status.should eq(200)
107+
response_body.should == 'Welcome Henry !'
108+
end
109+
end
110+
111+
end
112+
"""
113+
When I run `rspec app_spec.rb --require ./app.rb --format RspecApiDocumentation::ApiFormatter`
114+
115+
Scenario: Output helpful progress to the console
116+
Then the output should contain:
117+
"""
118+
Generating API Docs
119+
Orders
120+
GET /orders
121+
* Getting a list of orders
122+
GET /orders/:id
123+
* Getting a specific order
124+
POST /orders
125+
* Creating an order
126+
PUT /orders/:id
127+
* Updating an order
128+
DELETE /orders/:id
129+
* Deleting an order
130+
Help
131+
GET /help
132+
* Getting welcome message
133+
"""
134+
And the output should contain "6 examples, 0 failures"
135+
And the exit status should be 0
136+
137+
Scenario: Index file should look like we expect
138+
Then the file "doc/api/index.textile" should contain exactly:
139+
"""
140+
h1. Example API
141+
142+
h2. Help
143+
144+
* "Getting welcome message":help/getting_welcome_message.textile
145+
146+
h2. Orders
147+
148+
* "Creating an order":orders/creating_an_order.textile
149+
* "Deleting an order":orders/deleting_an_order.textile
150+
* "Getting a list of orders":orders/getting_a_list_of_orders.textile
151+
* "Getting a specific order":orders/getting_a_specific_order.textile
152+
* "Updating an order":orders/updating_an_order.textile
153+
154+
155+
"""
156+
157+
Scenario: Example 'Creating an order' file should look like we expect
158+
Then the file "doc/api/orders/creating_an_order.textile" should contain exactly:
159+
"""
160+
h1. Orders API
161+
162+
h2. Creating an order
163+
164+
h3. POST /orders
165+
166+
167+
h3. Parameters
168+
169+
Name : name *- required -*
170+
Description : Name of order
171+
172+
Name : amount *- required -*
173+
Description : Amount paid
174+
175+
Name : description
176+
Description : Some comments on the order
177+
178+
h3. Request
179+
180+
h4. Headers
181+
182+
<pre>Host: example.org
183+
Content-Type: application/x-www-form-urlencoded
184+
Cookie: </pre>
185+
186+
h4. Route
187+
188+
<pre>POST /orders</pre>
189+
190+
191+
h4. Body
192+
193+
<pre>name=Order+3&amount=33.0</pre>
194+
195+
196+
h3. Response
197+
198+
h4. Headers
199+
200+
<pre>X-Frame-Options: sameorigin
201+
X-XSS-Protection: 1; mode=block
202+
Content-Type: text/html;charset=utf-8
203+
Content-Length: 0</pre>
204+
205+
h4. Status
206+
207+
<pre>201 Created</pre>
208+
209+
210+
211+
212+
"""
213+
214+
Scenario: Example 'Deleting an order' file should be created
215+
Then a file named "doc/api/orders/deleting_an_order.textile" should exist
216+
217+
Scenario: Example 'Getting a list of orders' file should be created
218+
Then a file named "doc/api/orders/getting_a_list_of_orders.textile" should exist
219+
220+
Scenario: Example 'Getting a specific order' file should be created
221+
Then a file named "doc/api/orders/getting_a_specific_order.textile" should exist
222+
223+
Scenario: Example 'Updating an order' file should be created
224+
Then a file named "doc/api/orders/updating_an_order.textile" should exist
225+
226+
Scenario: Example 'Getting welcome message' file should be created
227+
Then a file named "doc/api/help/getting_welcome_message.textile" should exist
228+
229+

lib/rspec_api_documentation.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,27 @@ module RspecApiDocumentation
2828
module Writers
2929
extend ActiveSupport::Autoload
3030

31+
autoload :GeneralMarkupWriter
3132
autoload :HtmlWriter
33+
autoload :TextileWriter
3234
autoload :JsonWriter
3335
autoload :JsonIodocsWriter
3436
autoload :IndexWriter
3537
autoload :CombinedTextWriter
3638
autoload :CombinedJsonWriter
3739
end
3840

41+
module Views
42+
extend ActiveSupport::Autoload
43+
44+
autoload :MarkupIndex
45+
autoload :MarkupExample
46+
autoload :HtmlIndex
47+
autoload :HtmlExample
48+
autoload :TextileIndex
49+
autoload :TextileExample
50+
end
51+
3952
def self.configuration
4053
@configuration ||= Configuration.new
4154
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module RspecApiDocumentation
2+
module Views
3+
class HtmlExample < MarkupExample
4+
EXTENSION = 'html'
5+
6+
def initialize(example, configuration)
7+
super
8+
self.template_name = "rspec_api_documentation/html_example"
9+
end
10+
11+
def extension
12+
EXTENSION
13+
end
14+
end
15+
end
16+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module RspecApiDocumentation
2+
module Views
3+
class HtmlIndex < MarkupIndex
4+
def initialize(index, configuration)
5+
super
6+
self.template_name = "rspec_api_documentation/html_index"
7+
end
8+
9+
def examples
10+
@index.examples.map { |example| HtmlExample.new(example, @configuration) }
11+
end
12+
end
13+
end
14+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
require 'mustache'
2+
3+
module RspecApiDocumentation
4+
module Views
5+
class MarkupExample < Mustache
6+
def initialize(example, configuration)
7+
@example = example
8+
@host = configuration.curl_host
9+
self.template_path = configuration.template_path
10+
end
11+
12+
def method_missing(method, *args, &block)
13+
@example.send(method, *args, &block)
14+
end
15+
16+
def respond_to?(method, include_private = false)
17+
super || @example.respond_to?(method, include_private)
18+
end
19+
20+
def dirname
21+
resource_name.downcase.gsub(/\s+/, '_')
22+
end
23+
24+
def filename
25+
basename = description.downcase.gsub(/\s+/, '_').gsub(/[^a-z_]/, '')
26+
basename = Digest::MD5.new.update(description).to_s if basename.blank?
27+
"#{basename}.#{extension}"
28+
end
29+
30+
def requests
31+
super.map do |hash|
32+
hash[:request_headers_text] = format_hash(hash[:request_headers])
33+
hash[:request_query_parameters_text] = format_hash(hash[:request_query_parameters])
34+
hash[:response_headers_text] = format_hash(hash[:response_headers])
35+
if @host
36+
hash[:curl] = hash[:curl].output(@host) if hash[:curl].is_a? RspecApiDocumentation::Curl
37+
else
38+
hash[:curl] = nil
39+
end
40+
hash
41+
end
42+
end
43+
44+
def extension
45+
raise 'Parent class. This method should not be called.'
46+
end
47+
48+
private
49+
50+
def format_hash(hash = {})
51+
return nil unless hash.present?
52+
hash.collect do |k, v|
53+
"#{k}: #{v}"
54+
end.join("\n")
55+
end
56+
end
57+
end
58+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'mustache'
2+
3+
module RspecApiDocumentation
4+
module Views
5+
class MarkupIndex < Mustache
6+
def initialize(index, configuration)
7+
@index = index
8+
@configuration = configuration
9+
self.template_path = configuration.template_path
10+
end
11+
12+
def api_name
13+
@configuration.api_name
14+
end
15+
16+
def sections
17+
RspecApiDocumentation::Writers::IndexWriter.sections(examples, @configuration)
18+
end
19+
end
20+
end
21+
end

0 commit comments

Comments
 (0)