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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d6c5ff3c6e60436b079a7686e5ac902c0667dd16
4
- data.tar.gz: 1e01d5c17f6c352d49f3b701dc6f574ad5bbad81
3
+ metadata.gz: e544707e273d694375827f0032d4ee924dff02d9
4
+ data.tar.gz: df5bea103157a87699791ff558464f3bf4693f4d
5
5
  SHA512:
6
- metadata.gz: 5d10b7d5b8a4f91682e0a40424bae391b1c12269e6b4b6080d90cb73ebe9e8ec62a26529b2e002df6b8f189d2fa5317f76db87d67fdbb1176d578cd4842512c5
7
- data.tar.gz: af6203c9ea677dca51c9fbf1fe14b7dc451d36ef3c745a51f795ce7d209c603a1df1f0198dfa150b24254734956e7858720486e5350b672197b20aa301a84c0b
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
- ## Usage
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
- # config.output_format = :markdown
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
- Most parameters are not mandatory, and as you can see, writing all of these for each endpoint can add a lot of unnecessary boilerplate. But since you know everything the method receives you can customize the usage anyway you like, for example:
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
- Then execute:
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
- $ bundle exec dictum
185
+ ```ruby
186
+ # spec/controllers/my_resource_controller_spec.rb
187
+ require 'rails_helper'
118
188
 
119
- Both ways Dictum will create a document like this:
189
+ describe V1::MyResourceController do
190
+ Dictum.resource(
191
+ name: 'MyResource',
192
+ description: 'This is MyResource description.'
193
+ )
120
194
 
121
- # Index
122
- - MyResource
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
- # MyResource
125
- This is MyResource description.
206
+ # Dynamic HTML documentation
126
207
 
127
- ## GET /api/v1/my_resource
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
- ### Description:
130
- Some description of the endpoint.
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
- ### Request headers:
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
- ### Response status:
142
- 200
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
- ### Response body:
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
 
@@ -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("#{@config[:output_path]}/#{@config[:output_filename]}.md",
69
- Documenter.instance.tempfile_path)
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))
@@ -1,3 +1,3 @@
1
1
  module Dictum
2
- VERSION = '0.0.2'.freeze
2
+ VERSION = '0.0.3'.freeze
3
3
  end
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.2
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-15 00:00:00.000000000 Z
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.