ghost_writer 0.0.1 → 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.
- 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
|
+
[](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
|