ghost_writer 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/README.md +26 -0
- data/lib/ghost_writer.rb +41 -49
- data/lib/ghost_writer/document.rb +71 -0
- data/lib/ghost_writer/document_index.rb +30 -0
- data/lib/ghost_writer/format/markdown.rb +27 -0
- data/lib/ghost_writer/version.rb +1 -1
- data/output_examples/anonymous_controller/index.markdown +24 -0
- data/output_examples/anonymous_controller/show.markdown +27 -0
- data/output_examples/document_index.markdown +4 -0
- data/spec/lib/ghost_writer_spec.rb +34 -4
- metadata +11 -4
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# GhostWriter
|
2
|
+
[![Build Status](https://travis-ci.org/joker1007/ghost_writer.png)](https://travis-ci.org/joker1007/ghost_writer)
|
2
3
|
|
3
4
|
Generate API examples from params and response of controller specs
|
4
5
|
|
@@ -20,6 +21,18 @@ Or install it yourself as:
|
|
20
21
|
|
21
22
|
Write controller spec:
|
22
23
|
```ruby
|
24
|
+
# spec_helper
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.include GhostWriter
|
27
|
+
config.after(:suite) do
|
28
|
+
GhostWriter.generate_api_doc
|
29
|
+
end
|
30
|
+
|
31
|
+
GhostWriter.output_dir = "api_docs" # Optional (default is "api_examples")
|
32
|
+
GhostWriter.github_base_url = "https://github.com/joker1007/ghost_writer/tree/master/output_examples" # Optional
|
33
|
+
end
|
34
|
+
|
35
|
+
# posts_controller_spec
|
23
36
|
require 'spec_helper'
|
24
37
|
|
25
38
|
describe PostsController do
|
@@ -28,6 +41,11 @@ describe PostsController do
|
|
28
41
|
get :index
|
29
42
|
response.should be_success
|
30
43
|
end
|
44
|
+
|
45
|
+
it "should be success", generate_api_doc: "index_error" do # if metadata value is string, use it as filename
|
46
|
+
get :index
|
47
|
+
response.status.should eq 404
|
48
|
+
end
|
31
49
|
end
|
32
50
|
end
|
33
51
|
```
|
@@ -40,6 +58,14 @@ GENERATE_API_DOC=1 bundle exec rspec spec
|
|
40
58
|
|
41
59
|
If you don't set environment variable, this gem doesn't generate docs.
|
42
60
|
|
61
|
+
## Output Example
|
62
|
+
Please look at [output_examples](https://github.com/joker1007/ghost_writer/tree/master/output_examples)
|
63
|
+
|
64
|
+
## Config
|
65
|
+
If output_dir is set, generate docs at `[Rails.root]/doc/[output_dir]`
|
66
|
+
|
67
|
+
If github_base_url is set, link index is based on the url, like output_examples
|
68
|
+
|
43
69
|
## TODO
|
44
70
|
- support more output formats (now markdown only)
|
45
71
|
|
data/lib/ghost_writer.rb
CHANGED
@@ -1,44 +1,58 @@
|
|
1
1
|
require "ghost_writer/version"
|
2
|
+
require "ghost_writer/document"
|
3
|
+
require "ghost_writer/document_index"
|
2
4
|
require "active_support/concern"
|
3
5
|
|
4
6
|
module GhostWriter
|
5
7
|
extend ActiveSupport::Concern
|
6
8
|
|
7
|
-
|
9
|
+
module Format
|
10
|
+
autoload "Markdown", "ghost_writer/format/markdown"
|
11
|
+
end
|
12
|
+
|
13
|
+
DOCUMENT_INDEX_FILENAME = "document_index.markdown"
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :output_dir, :github_base_url
|
8
17
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
FileUtils.mkdir_p(doc_dir)
|
18
|
+
def documents
|
19
|
+
@documents ||= []
|
20
|
+
@documents
|
13
21
|
end
|
14
22
|
|
15
|
-
|
23
|
+
def generate_api_doc
|
24
|
+
if ENV["GENERATE_API_DOC"]
|
25
|
+
unless File.exist?(output_path)
|
26
|
+
FileUtils.mkdir_p(output_path)
|
27
|
+
end
|
28
|
+
document_index = GhostWriter::DocumentIndex.new(output_path + DOCUMENT_INDEX_FILENAME, documents)
|
29
|
+
document_index.write_file
|
30
|
+
@documents.each(&:write_file)
|
31
|
+
@documents.clear
|
32
|
+
end
|
33
|
+
end
|
16
34
|
|
17
|
-
|
18
|
-
|
19
|
-
doc.puts quote("#{request.env["REQUEST_METHOD"]} #{request.env["PATH_INFO"]}")
|
20
|
-
doc.puts ""
|
21
|
-
doc.puts headword("request params:", 2)
|
22
|
-
doc.puts quote(controller.params.reject {|key, val| key == "controller" || key == "action"}.inspect, :ruby)
|
23
|
-
doc.puts ""
|
24
|
-
doc.puts headword("status code:", 2)
|
25
|
-
doc.puts quote(response.status.inspect)
|
26
|
-
doc.puts ""
|
27
|
-
doc.puts headword("response:", 2)
|
28
|
-
if controller.params[:format] && controller.params[:format].to_sym == :json
|
29
|
-
puts_json_data(doc)
|
30
|
-
else
|
31
|
-
doc.puts quote(response.body)
|
35
|
+
def output_path
|
36
|
+
output_dir ? Rails.root + "doc" + output_dir : Rails.root + "doc" + "api_examples"
|
32
37
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
end
|
39
|
+
|
40
|
+
def collect_example
|
41
|
+
document = GhostWriter::Document.new(File.join(doc_dir, "#{doc_name}.markdown"), {
|
42
|
+
title: "#{described_class} #{doc_name.titleize}",
|
43
|
+
description: example.full_description.dup,
|
44
|
+
location: example.location.dup,
|
45
|
+
url_example: "#{request.env["REQUEST_METHOD"]} #{request.env["PATH_INFO"]}",
|
46
|
+
param_example: controller.params.reject {|key, val| key == "controller" || key == "action"},
|
47
|
+
status_example: response.status.inspect,
|
48
|
+
response_example: response.body,
|
49
|
+
})
|
50
|
+
GhostWriter.documents << document
|
37
51
|
end
|
38
52
|
|
39
53
|
private
|
40
54
|
def doc_dir
|
41
|
-
|
55
|
+
GhostWriter.output_path + described_class.to_s.underscore
|
42
56
|
end
|
43
57
|
|
44
58
|
def doc_name
|
@@ -49,32 +63,10 @@ module GhostWriter
|
|
49
63
|
end
|
50
64
|
end
|
51
65
|
|
52
|
-
# TODO: outputのフォーマットを選択可能に
|
53
|
-
def headword(text, level = 1)
|
54
|
-
"#{'#'*level} #{text}\n"
|
55
|
-
end
|
56
|
-
|
57
|
-
def paragraph(text)
|
58
|
-
text + "\n\n"
|
59
|
-
end
|
60
|
-
|
61
|
-
def quote(text, quote_format = nil)
|
62
|
-
"```#{quote_format}\n#{text}\n```"
|
63
|
-
end
|
64
|
-
|
65
|
-
def puts_json_data(doc)
|
66
|
-
data = ActiveSupport::JSON.decode(response.body)
|
67
|
-
if data.is_a?(Array) || data.is_a?(Hash)
|
68
|
-
doc.puts quote(JSON.pretty_generate(data), :javascript)
|
69
|
-
else
|
70
|
-
doc.puts quote(data)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
66
|
included do
|
75
67
|
after do
|
76
68
|
if example.metadata[:type] == :controller && example.metadata[:generate_api_doc]
|
77
|
-
|
69
|
+
collect_example if ENV["GENERATE_API_DOC"]
|
78
70
|
end
|
79
71
|
end
|
80
72
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class GhostWriter::Document
|
2
|
+
attr_reader :title, :description, :location, :url_example, :param_example, :status_example, :response_example, :output, :relative_path
|
3
|
+
|
4
|
+
def initialize(output, attrs)
|
5
|
+
extend(GhostWriter::Format::Markdown)
|
6
|
+
@output = output
|
7
|
+
@relative_path = Pathname.new(output).relative_path_from(GhostWriter.output_path)
|
8
|
+
@title = attrs[:title]
|
9
|
+
@description = attrs[:description]
|
10
|
+
@location = attrs[:location]
|
11
|
+
@url_example = attrs[:url_example]
|
12
|
+
@param_example = attrs[:param_example]
|
13
|
+
@status_example = attrs[:status_example]
|
14
|
+
@response_example = attrs[:response_example]
|
15
|
+
end
|
16
|
+
|
17
|
+
def write_file
|
18
|
+
unless File.exist?(File.dirname(output))
|
19
|
+
FileUtils.mkdir_p(File.dirname(output))
|
20
|
+
end
|
21
|
+
doc = File.open(output, "w")
|
22
|
+
|
23
|
+
doc.write paragraph(<<EOP)
|
24
|
+
#{headword(title, 1)}
|
25
|
+
EOP
|
26
|
+
|
27
|
+
doc.write paragraph(<<EOP)
|
28
|
+
#{headword("access path:", 2)}
|
29
|
+
#{quote(url_example)}
|
30
|
+
EOP
|
31
|
+
|
32
|
+
doc.write paragraph(<<EOP)
|
33
|
+
#{headword("request params:", 2)}
|
34
|
+
#{quote(param_example.inspect, :ruby)}
|
35
|
+
EOP
|
36
|
+
|
37
|
+
doc.write paragraph(<<EOP)
|
38
|
+
#{headword("status code:", 2)}
|
39
|
+
#{quote(status_example)}
|
40
|
+
EOP
|
41
|
+
|
42
|
+
doc.write paragraph(<<EOP)
|
43
|
+
#{headword("response:", 2)}
|
44
|
+
#{quote_response(response_example)}
|
45
|
+
EOP
|
46
|
+
|
47
|
+
doc.write paragraph(<<EOP)
|
48
|
+
"Generated by \"#{description}\" at #{location}"
|
49
|
+
EOP
|
50
|
+
|
51
|
+
doc.close
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def quote_response(body)
|
56
|
+
if param_example[:format] && param_example[:format].to_sym == :json
|
57
|
+
quote(arrange_json(response_example), :javascript)
|
58
|
+
else
|
59
|
+
quote(response_example, param_example[:format])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def arrange_json(body)
|
64
|
+
data = ActiveSupport::JSON.decode(body)
|
65
|
+
if data.is_a?(Array) || data.is_a?(Hash)
|
66
|
+
JSON.pretty_generate(data)
|
67
|
+
else
|
68
|
+
data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class GhostWriter::DocumentIndex
|
2
|
+
attr_reader :output, :documents
|
3
|
+
|
4
|
+
def initialize(output, documents)
|
5
|
+
extend(GhostWriter::Format::Markdown)
|
6
|
+
@output = output
|
7
|
+
@documents = documents
|
8
|
+
end
|
9
|
+
|
10
|
+
def write_file
|
11
|
+
if GhostWriter.github_base_url
|
12
|
+
base_url = GhostWriter.github_base_url + "/"
|
13
|
+
else
|
14
|
+
base_url = ""
|
15
|
+
end
|
16
|
+
|
17
|
+
document_list = documents.map do |document|
|
18
|
+
list(
|
19
|
+
link(document.description, base_url + "#{document.relative_path}")
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
index_file = File.open(output, "w")
|
24
|
+
index_file.write paragraph(<<EOP)
|
25
|
+
#{headword("API Examples")}
|
26
|
+
#{document_list.join("\n")}
|
27
|
+
EOP
|
28
|
+
index_file.close
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GhostWriter
|
2
|
+
module Format
|
3
|
+
module Markdown
|
4
|
+
private
|
5
|
+
# TODO: outputのフォーマットを選択可能に
|
6
|
+
def headword(text, level = 1)
|
7
|
+
"#{'#'*level} #{text}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def paragraph(text)
|
11
|
+
text + "\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote(text, quote_format = nil)
|
15
|
+
"```#{quote_format}\n#{text}\n```"
|
16
|
+
end
|
17
|
+
|
18
|
+
def list(text, level = 1)
|
19
|
+
"#{" " * (level - 1)}- #{text}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def link(text, url)
|
23
|
+
"[#{text}](#{url})"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/ghost_writer/version.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
# AnonymousController Index
|
2
|
+
|
3
|
+
## access path:
|
4
|
+
```
|
5
|
+
GET /anonymous
|
6
|
+
```
|
7
|
+
|
8
|
+
## request params:
|
9
|
+
```ruby
|
10
|
+
{"param1"=>"value"}
|
11
|
+
```
|
12
|
+
|
13
|
+
## status code:
|
14
|
+
```
|
15
|
+
200
|
16
|
+
```
|
17
|
+
|
18
|
+
## response:
|
19
|
+
```
|
20
|
+
[{"id":1,"name":"name"},{"id":2,"name":"name"}]
|
21
|
+
```
|
22
|
+
|
23
|
+
"Generated by "GET /anonymous returns Resources array" at ./spec/lib/ghost_writer_spec.rb:38"
|
24
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# AnonymousController Show
|
2
|
+
|
3
|
+
## access path:
|
4
|
+
```
|
5
|
+
GET /anonymous/1.json
|
6
|
+
```
|
7
|
+
|
8
|
+
## request params:
|
9
|
+
```ruby
|
10
|
+
{"param1"=>"value", "params2"=>["value1", "value2"], "id"=>"1", "format"=>"json"}
|
11
|
+
```
|
12
|
+
|
13
|
+
## status code:
|
14
|
+
```
|
15
|
+
200
|
16
|
+
```
|
17
|
+
|
18
|
+
## response:
|
19
|
+
```javascript
|
20
|
+
{
|
21
|
+
"id": 1,
|
22
|
+
"name": "name"
|
23
|
+
}
|
24
|
+
```
|
25
|
+
|
26
|
+
"Generated by "GET /anonymous/1.json returns a Resource json" at ./spec/lib/ghost_writer_spec.rb:49"
|
27
|
+
|
@@ -0,0 +1,4 @@
|
|
1
|
+
# API Examples
|
2
|
+
- [GET /anonymous returns Resources array](https://github.com/joker1007/ghost_writer/tree/master/output_examples/anonymous_controller/index.markdown)
|
3
|
+
- [GET /anonymous/1.json returns a Resource json](https://github.com/joker1007/ghost_writer/tree/master/output_examples/anonymous_controller/show.markdown)
|
4
|
+
|
@@ -28,12 +28,27 @@ describe GhostWriter do
|
|
28
28
|
]
|
29
29
|
render json: collection.as_json
|
30
30
|
end
|
31
|
+
|
32
|
+
def show
|
33
|
+
render json: {id: 1, name: "name"}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "GET /anonymous" do
|
38
|
+
it "returns Resources array", generate_api_doc: true do
|
39
|
+
begin
|
40
|
+
get :index, param1: "value"
|
41
|
+
response.should be_success
|
42
|
+
rescue Exception => e
|
43
|
+
p e
|
44
|
+
end
|
45
|
+
end
|
31
46
|
end
|
32
47
|
|
33
|
-
describe "GET
|
34
|
-
it "
|
48
|
+
describe "GET /anonymous/1.json" do
|
49
|
+
it "returns a Resource json", generate_api_doc: true do
|
35
50
|
begin
|
36
|
-
get :
|
51
|
+
get :show, id: 1, format: :json, param1: "value", params2: ["value1", "value2"]
|
37
52
|
response.should be_success
|
38
53
|
rescue Exception => e
|
39
54
|
p e
|
@@ -59,17 +74,30 @@ describe GhostWriter do
|
|
59
74
|
|
60
75
|
it "generate api doc file" do
|
61
76
|
group.run(NullObject.new)
|
77
|
+
GhostWriter.generate_api_doc
|
62
78
|
File.exist?(Rails.root + "doc" + "api_examples" + "anonymous_controller" + "index.markdown").should be_true
|
79
|
+
File.read(Rails.root + "doc" + "api_examples" + "anonymous_controller" + "index.markdown").should =~ /# AnonymousController Index/
|
80
|
+
end
|
81
|
+
|
82
|
+
context "Given github_base_url" do
|
83
|
+
let(:github_base_url) { "https://github.com/joker1007/ghost_writer/tree/master/output_examples" }
|
84
|
+
it "create index file written github links" do
|
85
|
+
GhostWriter.github_base_url = github_base_url
|
86
|
+
group.run(NullObject.new)
|
87
|
+
GhostWriter.generate_api_doc
|
88
|
+
File.read(Rails.root + "doc" + "api_examples" + GhostWriter::DOCUMENT_INDEX_FILENAME).should =~ /#{github_base_url}/
|
89
|
+
end
|
63
90
|
end
|
64
91
|
end
|
65
92
|
|
66
|
-
context 'ENV["GENERATE_API_DOC"] is
|
93
|
+
context 'ENV["GENERATE_API_DOC"] is false' do
|
67
94
|
before do
|
68
95
|
ENV["GENERATE_API_DOC"] = nil
|
69
96
|
end
|
70
97
|
|
71
98
|
it "does not generate api doc file" do
|
72
99
|
group.run(NullObject.new)
|
100
|
+
GhostWriter.generate_api_doc
|
73
101
|
File.exist?(Rails.root + "doc" + "api_examples" + "anonymous_controller" + "index.markdown").should be_false
|
74
102
|
end
|
75
103
|
end
|
@@ -91,7 +119,9 @@ describe GhostWriter do
|
|
91
119
|
it "generate api doc file" do
|
92
120
|
ENV["GENERATE_API_DOC"] = "1"
|
93
121
|
group.run(NullObject.new)
|
122
|
+
GhostWriter.generate_api_doc
|
94
123
|
File.exist?(Rails.root + "doc" + output_dir + "anonymous_controller" + "index.markdown").should be_true
|
124
|
+
File.read(Rails.root + "doc" + output_dir + "anonymous_controller" + "index.markdown").should =~ /# AnonymousController Index/
|
95
125
|
end
|
96
126
|
end
|
97
127
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ghost_writer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
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-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -99,13 +99,20 @@ extensions: []
|
|
99
99
|
extra_rdoc_files: []
|
100
100
|
files:
|
101
101
|
- .gitignore
|
102
|
+
- .travis.yml
|
102
103
|
- Gemfile
|
103
104
|
- LICENSE.txt
|
104
105
|
- README.md
|
105
106
|
- Rakefile
|
106
107
|
- ghost_writer.gemspec
|
107
108
|
- lib/ghost_writer.rb
|
109
|
+
- lib/ghost_writer/document.rb
|
110
|
+
- lib/ghost_writer/document_index.rb
|
111
|
+
- lib/ghost_writer/format/markdown.rb
|
108
112
|
- lib/ghost_writer/version.rb
|
113
|
+
- output_examples/anonymous_controller/index.markdown
|
114
|
+
- output_examples/anonymous_controller/show.markdown
|
115
|
+
- output_examples/document_index.markdown
|
109
116
|
- spec/lib/ghost_writer_spec.rb
|
110
117
|
- spec/rails_app/.gitignore
|
111
118
|
- spec/rails_app/README.rdoc
|
@@ -160,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
167
|
version: '0'
|
161
168
|
segments:
|
162
169
|
- 0
|
163
|
-
hash:
|
170
|
+
hash: 2108698769650766932
|
164
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
172
|
none: false
|
166
173
|
requirements:
|
@@ -169,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
176
|
version: '0'
|
170
177
|
segments:
|
171
178
|
- 0
|
172
|
-
hash:
|
179
|
+
hash: 2108698769650766932
|
173
180
|
requirements: []
|
174
181
|
rubyforge_project:
|
175
182
|
rubygems_version: 1.8.23
|