dictum 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +117 -28
- data/lib/dictum.rb +11 -2
- data/lib/dictum/html_helpers.rb +85 -0
- data/lib/dictum/html_writer.rb +108 -0
- data/lib/dictum/markdown_writer.rb +1 -1
- data/lib/dictum/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e544707e273d694375827f0032d4ee924dff02d9
|
4
|
+
data.tar.gz: df5bea103157a87699791ff558464f3bf4693f4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdfbb27c664eb351c72eab2617c6bc5cca57be8e8cb9aa72cc17c42bbecc0738fb187a7830ecf9d513520a408cd544b8e3ecfcb35ca694effb22ec237dc49556
|
7
|
+
data.tar.gz: 50187a697fd04d757580bb00ecbfe4cafc60fe8a6f6559b1ab9c22e2b52816a9097126e86e0607d2ee4f1f1968f6e203799ac123af5aacb3ccf4dd40cc200ee0
|
data/README.md
CHANGED
@@ -23,23 +23,24 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
$ gem install dictum
|
25
25
|
|
26
|
-
##
|
26
|
+
## Basic usage
|
27
27
|
|
28
28
|
First you need to set a configuration file inside /config/initializers/dictum.rb
|
29
29
|
|
30
30
|
```ruby
|
31
|
+
# /config/initializers/dictum.rb
|
31
32
|
Dictum.configure do |config|
|
32
33
|
config.output_path = Rails.root.join('docs')
|
33
34
|
config.root_path = Rails.root
|
34
35
|
config.output_filename = 'Documentation'
|
35
|
-
|
36
|
-
# test_suite = :rspec
|
36
|
+
config.output_format = :markdown
|
37
37
|
end
|
38
38
|
```
|
39
39
|
|
40
40
|
Then you can use Dictum in the most verbose and fully customizable way like this in your tests:
|
41
41
|
|
42
42
|
```ruby
|
43
|
+
# spec/controllers/my_resource_controller_spec.rb
|
43
44
|
require 'rails_helper'
|
44
45
|
|
45
46
|
describe V1::MyResourceController do
|
@@ -72,9 +73,62 @@ describe V1::MyResourceController do
|
|
72
73
|
end
|
73
74
|
end
|
74
75
|
```
|
75
|
-
|
76
|
+
|
77
|
+
Then execute:
|
78
|
+
|
79
|
+
$ bundle exec rake dictum:document
|
80
|
+
|
81
|
+
And voilà, Dictum will create a document like this in '/docs/Documentation':
|
82
|
+
|
83
|
+
# Index
|
84
|
+
- MyResource
|
85
|
+
|
86
|
+
# MyResource
|
87
|
+
This is MyResource description.
|
88
|
+
|
89
|
+
## POST /api/v1/my_resource
|
90
|
+
|
91
|
+
### Description:
|
92
|
+
Some description of the endpoint.
|
93
|
+
|
94
|
+
### Request headers:
|
95
|
+
```json
|
96
|
+
{
|
97
|
+
"AUTHORIZATION" : "user_token",
|
98
|
+
"Content-Type" : "application/json",
|
99
|
+
"Accept" : "application/json"
|
100
|
+
}
|
101
|
+
```
|
102
|
+
|
103
|
+
### Path parameters:
|
104
|
+
```json
|
105
|
+
{ "id": 1, "page": 1 }
|
106
|
+
```
|
107
|
+
|
108
|
+
### Body parameters:
|
109
|
+
```json
|
110
|
+
{ "some": "parameter" }
|
111
|
+
```
|
112
|
+
|
113
|
+
### Response headers:
|
114
|
+
```json
|
115
|
+
{ "some_header": "some_header_value" }
|
116
|
+
```
|
117
|
+
|
118
|
+
### Response status:
|
119
|
+
200
|
120
|
+
|
121
|
+
### Response body:
|
122
|
+
```json
|
123
|
+
"no_content"
|
124
|
+
```
|
125
|
+
|
126
|
+
# Advanced usage
|
127
|
+
|
128
|
+
If you pay attention to the basic usage, you will notice that much code is needed if your API has a lot of endpoints, this is not DRY and adds unnecesary boilerplate. Luckily you can work around it using some Rspec tricks:
|
76
129
|
|
77
130
|
```ruby
|
131
|
+
# spec/controllers/my_resource_controller_spec.rb
|
78
132
|
require 'rails_helper'
|
79
133
|
|
80
134
|
describe V1::MyResourceController do
|
@@ -112,39 +166,74 @@ describe V1::MyResourceController do
|
|
112
166
|
end
|
113
167
|
```
|
114
168
|
|
115
|
-
|
169
|
+
This is much better, but it is not DRYed enough because you would have to repeat the after(:each) declaration on every controller spec you have, so you can still improve it a bit more:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
# spec/rails_helper.rb
|
173
|
+
RSpec.configure do |config|
|
174
|
+
config.after(:each) do |test|
|
175
|
+
if test.metadata[:dictum]
|
176
|
+
Dictum.endpoint(
|
177
|
+
# All the parameters that you want
|
178
|
+
)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
116
184
|
|
117
|
-
|
185
|
+
```ruby
|
186
|
+
# spec/controllers/my_resource_controller_spec.rb
|
187
|
+
require 'rails_helper'
|
118
188
|
|
119
|
-
|
189
|
+
describe V1::MyResourceController do
|
190
|
+
Dictum.resource(
|
191
|
+
name: 'MyResource',
|
192
|
+
description: 'This is MyResource description.'
|
193
|
+
)
|
120
194
|
|
121
|
-
|
122
|
-
|
195
|
+
describe '#some_method' do
|
196
|
+
context 'some context for my resource' do
|
197
|
+
it 'returns status ok', dictum: true, dictum_description: 'Some description of the endpoint.' do
|
198
|
+
get :index
|
199
|
+
expect(response_status).to eq(200)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
123
205
|
|
124
|
-
|
125
|
-
This is MyResource description.
|
206
|
+
# Dynamic HTML documentation
|
126
207
|
|
127
|
-
|
208
|
+
So far so good, but your team needs to read the documentation everytime you update it, and sending the documentation file to them doesn't seem too practical. Instead you can use the HTML version of Dictum and generate static views with the content. Here is a very basic example of what you can do to generate the views and routes dynamycally:
|
128
209
|
|
129
|
-
|
130
|
-
|
210
|
+
```ruby
|
211
|
+
# /config/initializers/dictum.rb
|
212
|
+
Dictum.configure do |config|
|
213
|
+
config.output_path = Rails.root.join('app', 'views')
|
214
|
+
config.root_path = Rails.root
|
215
|
+
config.output_filename = 'docs'
|
216
|
+
config.output_format = :html
|
217
|
+
end
|
218
|
+
```
|
131
219
|
|
132
|
-
|
133
|
-
```json
|
134
|
-
{
|
135
|
-
"AUTHORIZATION" : "user_token",
|
136
|
-
"Content-Type" : "application/json",
|
137
|
-
"Accept" : "application/json"
|
138
|
-
}
|
139
|
-
```
|
220
|
+
Here we are telling Dictum to generate the HTML documentation in 'app/views/docs', where it will put one HTML file per resource you have defined. Now that we have the files, lets create the routes for them:
|
140
221
|
|
141
|
-
|
142
|
-
|
222
|
+
```ruby
|
223
|
+
# config/routes.rb
|
224
|
+
YourApp::Application.routes.draw do
|
225
|
+
#
|
226
|
+
# Other routes defined
|
227
|
+
#
|
228
|
+
|
229
|
+
doc_paths = Dir["#{Rails.root.join('app', 'views', 'docs')}/*"].each do |path|
|
230
|
+
resource = path.split('/').last.gsub('.html', '')
|
231
|
+
get "/docs/#{resource}", to: "docs##{resource}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
```
|
143
235
|
|
144
|
-
|
145
|
-
```json
|
146
|
-
"no_content"
|
147
|
-
```
|
236
|
+
Of course you will need to have a controller for this, in this case one named 'docs_controller.rb'. And finally go to 'http://localhost:3000/docs/index.html'
|
148
237
|
|
149
238
|
## Contributing
|
150
239
|
|
data/lib/dictum.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'dictum/version'
|
2
2
|
require 'dictum/documenter'
|
3
3
|
require 'dictum/markdown_writer'
|
4
|
+
require 'dictum/html_writer'
|
5
|
+
require 'dictum/html_helpers'
|
4
6
|
|
5
7
|
module Dictum
|
6
8
|
load 'tasks/dictum.rake' if defined?(Rails)
|
@@ -57,17 +59,24 @@ module Dictum
|
|
57
59
|
def self.document
|
58
60
|
Dir.mkdir(@config[:output_path]) unless Dir.exist?(@config[:output_path])
|
59
61
|
Documenter.instance.reset_resources
|
62
|
+
|
60
63
|
system "bundle exec rspec #{@config[:root_path]}" if @config[:test_suite] == :rspec
|
64
|
+
|
61
65
|
save_to_file
|
62
66
|
end
|
63
67
|
|
64
68
|
def self.save_to_file
|
65
69
|
writer = nil
|
70
|
+
output_filename = "#{@config[:output_path]}/#{@config[:output_filename]}"
|
71
|
+
tempfile_path = Documenter.instance.tempfile_path
|
72
|
+
|
66
73
|
case @config[:output_format]
|
67
74
|
when :markdown
|
68
|
-
writer = MarkdownWriter.new(
|
69
|
-
|
75
|
+
writer = MarkdownWriter.new(output_filename, tempfile_path)
|
76
|
+
when :html
|
77
|
+
writer = HtmlWriter.new(output_filename, tempfile_path, 'Dictum')
|
70
78
|
end
|
79
|
+
|
71
80
|
writer.write
|
72
81
|
end
|
73
82
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Dictum
|
2
|
+
class HtmlHelpers
|
3
|
+
BOOTSTRAP_JS = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js'.freeze
|
4
|
+
BOOTSTRAP_CSS = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css'.freeze
|
5
|
+
JQUERY = 'https://code.jquery.com/jquery-1.12.2.min.js'.freeze
|
6
|
+
PRETTIFY = 'https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js'.freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def html_header(title)
|
10
|
+
"<!DOCTYPE html>\n<html>\n<head>\n<title>#{title}</title>\n#{external_css(BOOTSTRAP_CSS)}"\
|
11
|
+
"\n<style>\n#{page_css}\n</style>\n</head>\n<body>\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def html_footer
|
15
|
+
"#{script(JQUERY)}\n#{script(BOOTSTRAP_JS)}\n#{script(PRETTIFY)}\n</body>\n</html>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def page_css
|
19
|
+
''
|
20
|
+
end
|
21
|
+
|
22
|
+
def container
|
23
|
+
"<div class='container-fluid'>\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
def container_end
|
27
|
+
'</div>'
|
28
|
+
end
|
29
|
+
|
30
|
+
def row
|
31
|
+
"<div class='row'>\n<div class='col-md-8 col-md-offset-2'>\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
def row_end
|
35
|
+
"</div>\n</div>"
|
36
|
+
end
|
37
|
+
|
38
|
+
def script(text)
|
39
|
+
"<script src='#{text}'></script>"
|
40
|
+
end
|
41
|
+
|
42
|
+
def external_css(text)
|
43
|
+
"<link rel='stylesheet' href='#{text}'>"
|
44
|
+
end
|
45
|
+
|
46
|
+
def unordered_list(elements)
|
47
|
+
answer = "<ul>\n"
|
48
|
+
elements.each do |element|
|
49
|
+
answer += "<li><a href='#{element.downcase}.html'>#{element}</a></li>\n"
|
50
|
+
end
|
51
|
+
answer += '</ul>'
|
52
|
+
end
|
53
|
+
|
54
|
+
def title(text, html_class = nil)
|
55
|
+
"<h1 class='#{html_class}'>#{text}</h1>"
|
56
|
+
end
|
57
|
+
|
58
|
+
def subtitle(text, html_class = nil)
|
59
|
+
"<h3 class='#{html_class}'>#{text}</h3>"
|
60
|
+
end
|
61
|
+
|
62
|
+
def paragraph(text, html_class = nil)
|
63
|
+
"<p class='#{html_class}'>#{text}</p>"
|
64
|
+
end
|
65
|
+
|
66
|
+
def button(text, glyphicon = nil)
|
67
|
+
"<a href='index.html'><button type='button' class='btn btn-primary back'" \
|
68
|
+
"aria-label='Left Align'><span class='glyphicon #{glyphicon}' aria-hidden='true'>" \
|
69
|
+
"</span>#{text}</button></a>"
|
70
|
+
end
|
71
|
+
|
72
|
+
def code_block(title, json)
|
73
|
+
"#{sub_subtitle(title)}\n#{code(json)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def sub_subtitle(text, html_class = nil)
|
77
|
+
"<h4 class='#{html_class}'>#{text}</h4>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def code(json)
|
81
|
+
"<pre class='prettyprint'>#{json}</pre>"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require_relative 'html_helpers'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Dictum
|
5
|
+
class HtmlWriter
|
6
|
+
attr_reader :temp_path, :temp_json, :output_dir, :output_file, :output_title
|
7
|
+
|
8
|
+
def initialize(output_dir, temp_path, output_title)
|
9
|
+
@output_dir = output_dir
|
10
|
+
@temp_path = temp_path
|
11
|
+
@temp_json = JSON.parse(File.read(temp_path))
|
12
|
+
@output_title = output_title
|
13
|
+
end
|
14
|
+
|
15
|
+
def write
|
16
|
+
Dir.mkdir(output_dir) unless Dir.exist?(output_dir)
|
17
|
+
write_index
|
18
|
+
write_pages
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def write_index_header(file)
|
24
|
+
return unless file
|
25
|
+
file.puts(HtmlHelpers.html_header(output_title))
|
26
|
+
file.puts(HtmlHelpers.container)
|
27
|
+
file.puts(HtmlHelpers.row)
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_index_footer(file)
|
31
|
+
return unless file
|
32
|
+
file.puts(HtmlHelpers.row_end)
|
33
|
+
file.puts(HtmlHelpers.container_end)
|
34
|
+
file.puts(HtmlHelpers.html_footer)
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_index
|
38
|
+
index = File.open("#{output_dir}/index.html", 'w+')
|
39
|
+
write_index_header(index)
|
40
|
+
index.puts("<div class='jumbotron'>\n#{HtmlHelpers.title('Index', 'title')}\n</div>")
|
41
|
+
index.puts(HtmlHelpers.unordered_list(temp_json.keys))
|
42
|
+
write_index_footer(index)
|
43
|
+
index.close
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_pages_header(resource_name, text, file)
|
47
|
+
write_index_header(file)
|
48
|
+
file.puts(HtmlHelpers.title(resource_name, 'title'))
|
49
|
+
file.puts(HtmlHelpers.paragraph(text))
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_pages_footer(file)
|
53
|
+
return unless file
|
54
|
+
file.puts(HtmlHelpers.row_end)
|
55
|
+
file.puts(HtmlHelpers.row)
|
56
|
+
file.puts(HtmlHelpers.button('Back', 'glyphicon-menu-left'))
|
57
|
+
write_index_footer(file)
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_pages
|
61
|
+
temp_json.each do |resource_name, information|
|
62
|
+
file = File.open("#{output_dir}/#{resource_name.downcase}.html", 'w+')
|
63
|
+
write_pages_header(resource_name, information['description'], file)
|
64
|
+
|
65
|
+
write_endpoints(information['endpoints'], file)
|
66
|
+
|
67
|
+
write_pages_footer(file)
|
68
|
+
file.close
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_endpoints(endpoints, file)
|
73
|
+
endpoints.each do |endpoint|
|
74
|
+
file.puts HtmlHelpers.subtitle("#{endpoint['http_verb']} #{endpoint['endpoint']}")
|
75
|
+
file.puts HtmlHelpers.paragraph(endpoint['description'])
|
76
|
+
write_request_parameters(endpoint, file)
|
77
|
+
write_response(endpoint, file)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def write_request_parameters(endpoint, file)
|
82
|
+
write_codeblock('Request headers', JSON.pretty_generate(endpoint['request_headers']), file)
|
83
|
+
write_codeblock('Request path parameters',
|
84
|
+
JSON.pretty_generate(endpoint['request_path_parameters']), file)
|
85
|
+
write_codeblock('Request body parameters',
|
86
|
+
JSON.pretty_generate(endpoint['request_body_parameters']), file)
|
87
|
+
end
|
88
|
+
|
89
|
+
def write_response(endpoint, file)
|
90
|
+
write_codeblock('Status', endpoint['response_status'], file)
|
91
|
+
write_codeblock(
|
92
|
+
'Response headers',
|
93
|
+
JSON.pretty_generate(endpoint['response_headers']),
|
94
|
+
file
|
95
|
+
) if endpoint['response_headers']
|
96
|
+
|
97
|
+
if endpoint['response_body']
|
98
|
+
param = (endpoint['response_body'] == 'no_content') ? {} : endpoint['response_body']
|
99
|
+
write_codeblock('Response body', JSON.pretty_generate(param), file)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def write_codeblock(text, json, file)
|
104
|
+
return unless text && json && file
|
105
|
+
file.puts HtmlHelpers.code_block(text, json)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -5,7 +5,7 @@ module Dictum
|
|
5
5
|
attr_reader :temp_path, :temp_json, :output_path, :output_file
|
6
6
|
|
7
7
|
def initialize(output_path, temp_path)
|
8
|
-
@output_path = output_path
|
8
|
+
@output_path = "#{output_path}.md"
|
9
9
|
File.delete(output_path) if File.exist?(output_path)
|
10
10
|
@temp_path = temp_path
|
11
11
|
@temp_json = JSON.parse(File.read(temp_path))
|
data/lib/dictum/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dictum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alejandro Bezdjian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,8 @@ files:
|
|
108
108
|
- dictum.gemspec
|
109
109
|
- lib/dictum.rb
|
110
110
|
- lib/dictum/documenter.rb
|
111
|
+
- lib/dictum/html_helpers.rb
|
112
|
+
- lib/dictum/html_writer.rb
|
111
113
|
- lib/dictum/markdown_writer.rb
|
112
114
|
- lib/dictum/version.rb
|
113
115
|
- lib/tasks/dictum.rake
|
@@ -131,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
133
|
version: '0'
|
132
134
|
requirements: []
|
133
135
|
rubyforge_project:
|
134
|
-
rubygems_version: 2.5.1
|
136
|
+
rubygems_version: 2.4.5.1
|
135
137
|
signing_key:
|
136
138
|
specification_version: 4
|
137
139
|
summary: Document your APIs.
|