betterdocs 0.2.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +5 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/COPYING +202 -0
- data/Gemfile +8 -0
- data/LICENSE +202 -0
- data/README.md +124 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/betterdocs.gemspec +48 -0
- data/lib/betterdocs.rb +27 -0
- data/lib/betterdocs/controller_collector.rb +50 -0
- data/lib/betterdocs/dsl.rb +9 -0
- data/lib/betterdocs/dsl/common.rb +26 -0
- data/lib/betterdocs/dsl/controller.rb +9 -0
- data/lib/betterdocs/dsl/controller/action.rb +126 -0
- data/lib/betterdocs/dsl/controller/action/param.rb +25 -0
- data/lib/betterdocs/dsl/controller/action/response.rb +47 -0
- data/lib/betterdocs/dsl/controller/controller.rb +31 -0
- data/lib/betterdocs/dsl/controller/controller_base.rb +21 -0
- data/lib/betterdocs/dsl/json_params.rb +8 -0
- data/lib/betterdocs/dsl/json_params/param.rb +31 -0
- data/lib/betterdocs/dsl/json_type_mapper.rb +27 -0
- data/lib/betterdocs/dsl/naming.rb +32 -0
- data/lib/betterdocs/dsl/representer.rb +29 -0
- data/lib/betterdocs/dsl/result.rb +10 -0
- data/lib/betterdocs/dsl/result/collection_property.rb +9 -0
- data/lib/betterdocs/dsl/result/link.rb +37 -0
- data/lib/betterdocs/dsl/result/property.rb +53 -0
- data/lib/betterdocs/generator/config_shortcuts.rb +28 -0
- data/lib/betterdocs/generator/markdown.rb +151 -0
- data/lib/betterdocs/generator/markdown/templates/README.md.erb +9 -0
- data/lib/betterdocs/generator/markdown/templates/section.md.erb +132 -0
- data/lib/betterdocs/global.rb +143 -0
- data/lib/betterdocs/json_params_representer.rb +37 -0
- data/lib/betterdocs/json_params_representer_collector.rb +48 -0
- data/lib/betterdocs/mix_into_controller.rb +19 -0
- data/lib/betterdocs/rake_tasks.rb +5 -0
- data/lib/betterdocs/representer.rb +42 -0
- data/lib/betterdocs/result_representer.rb +68 -0
- data/lib/betterdocs/result_representer_collector.rb +82 -0
- data/lib/betterdocs/section.rb +6 -0
- data/lib/betterdocs/tasks/doc.rake +55 -0
- data/lib/betterdocs/version.rb +8 -0
- data/spec/controller_dsl_spec.rb +143 -0
- data/spec/generator/markdown_spec.rb +5 -0
- data/spec/json_params_representer_spec.rb +79 -0
- data/spec/json_type_mapper_spec.rb +33 -0
- data/spec/result_representer_dsl_spec.rb +183 -0
- data/spec/result_representer_spec.rb +182 -0
- data/spec/spec_helper.rb +19 -0
- metadata +234 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
<%- for action in section -%>
|
2
|
+
|
3
|
+
# <%= action.title %>
|
4
|
+
|
5
|
+
```Rebol
|
6
|
+
<%= action.request %>
|
7
|
+
```
|
8
|
+
|
9
|
+
<%= action.description %>
|
10
|
+
|
11
|
+
## URL Parameters
|
12
|
+
|
13
|
+
<table>
|
14
|
+
<tr>
|
15
|
+
<th>Parameter</th>
|
16
|
+
<th>Example</th>
|
17
|
+
<th>Required</th>
|
18
|
+
<th>Description</th>
|
19
|
+
</tr>
|
20
|
+
<%- for param in action.params.values -%>
|
21
|
+
<tr>
|
22
|
+
<th align="left"><%= param.name %></th>
|
23
|
+
<td><code><%= param.value %></code></td>
|
24
|
+
<td><%= param.required ? 'yes' : 'no' %></td>
|
25
|
+
<td><%= param.description %></td>
|
26
|
+
</tr>
|
27
|
+
<%- end -%>
|
28
|
+
</table>
|
29
|
+
|
30
|
+
<%- if json_params = action.json_params.full? -%>
|
31
|
+
## JSON Parameters
|
32
|
+
|
33
|
+
JSON parameters have to be provided in the body of the request with the
|
34
|
+
Content-Type header set to "application/json". The parameters are part of a
|
35
|
+
flat JSON document without any nesting. Some parameters are required, others
|
36
|
+
are optional.
|
37
|
+
|
38
|
+
### Example
|
39
|
+
|
40
|
+
```json
|
41
|
+
<%= action.json_params_example_json %>
|
42
|
+
```
|
43
|
+
|
44
|
+
### Supported Parameters
|
45
|
+
|
46
|
+
<table>
|
47
|
+
<tr>
|
48
|
+
<th>Parameter</th>
|
49
|
+
<th>Example</th>
|
50
|
+
<th>Types</th>
|
51
|
+
<th>Required</th>
|
52
|
+
<th>Description</th>
|
53
|
+
</tr>
|
54
|
+
<%- for param in action.json_params.values -%>
|
55
|
+
<tr>
|
56
|
+
<th align="left"><%= param.name %></th>
|
57
|
+
<td><code><%= param.value %></code></td>
|
58
|
+
<td><%= param.types * ?| -%></td>
|
59
|
+
<td><%= param.required ? 'yes' : 'no' %></td>
|
60
|
+
<td><%= param.description %></td>
|
61
|
+
</tr>
|
62
|
+
<%- end -%>
|
63
|
+
</table>
|
64
|
+
<%- end -%>
|
65
|
+
|
66
|
+
## Response Attributes
|
67
|
+
|
68
|
+
<%- if properties = action.response.full?(:properties) -%>
|
69
|
+
<%- for (representer, properties) in properties.group_by(&:nesting_name) -%>
|
70
|
+
<%- if properties.first.nesting_name.blank? -%>
|
71
|
+
### Root Attributes
|
72
|
+
<%- else -%>
|
73
|
+
### <a id="<%= nn = properties.first.nesting_name %>" href="#<%= "#{nn}-ref" %>">↑Nested Attributes: <%= nn %></a>
|
74
|
+
<%- end -%>
|
75
|
+
|
76
|
+
<table>
|
77
|
+
<tr>
|
78
|
+
<th>Attribute</th>
|
79
|
+
<th>Types</th>
|
80
|
+
<th>Example</th>
|
81
|
+
<th>Description</th>
|
82
|
+
</tr>
|
83
|
+
<%- for property in properties -%>
|
84
|
+
<tr>
|
85
|
+
<%- if r = property.sub_representer? and r.docs.properties.full? -%>
|
86
|
+
<th align="left" style="white-space: nowrap">
|
87
|
+
<a id="<%= fn = property.full_name; "#{fn}-ref" %>" href="#<%= fn %>">
|
88
|
+
↓<%= property.full_name %>
|
89
|
+
</a>
|
90
|
+
</th>
|
91
|
+
<%- else -%>
|
92
|
+
<th align="left"><%= property.full_name %></th>
|
93
|
+
<%- end -%>
|
94
|
+
<td><%= property.types * ' | ' %></td>
|
95
|
+
<td><%= property.example %></td>
|
96
|
+
<td><%= property.description %></td>
|
97
|
+
</tr>
|
98
|
+
<%- end -%>
|
99
|
+
</table>
|
100
|
+
<%- end -%>
|
101
|
+
<%- else -%>
|
102
|
+
<th colspan="4">No response example defined</th>
|
103
|
+
<%- end -%>
|
104
|
+
</table>
|
105
|
+
|
106
|
+
## Response Links
|
107
|
+
|
108
|
+
<table>
|
109
|
+
<tr>
|
110
|
+
<th>Linkname</th>
|
111
|
+
<th>Description</th>
|
112
|
+
</tr>
|
113
|
+
|
114
|
+
<%- if links = action.response.full?(:links) -%>
|
115
|
+
<%- for link in links.group_by(&:nesting_name).values.reduce(&:concat) -%>
|
116
|
+
<tr>
|
117
|
+
<th align="left"><%= link.full_name %></th>
|
118
|
+
<td><%= link.description %></td>
|
119
|
+
</tr>
|
120
|
+
<%- end -%>
|
121
|
+
<%- else -%>
|
122
|
+
<th colspan="2">No response example defined</th>
|
123
|
+
<%- end -%>
|
124
|
+
</table>
|
125
|
+
|
126
|
+
## Response Example
|
127
|
+
|
128
|
+
```json
|
129
|
+
<%= JSON.pretty_generate(action.response) %>
|
130
|
+
```
|
131
|
+
|
132
|
+
<%- end -%>
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Betterdocs
|
2
|
+
module Global
|
3
|
+
class << self
|
4
|
+
extend Tins::DSLAccessor
|
5
|
+
|
6
|
+
dsl_accessor :project_name, 'Project' # Name of the project
|
7
|
+
|
8
|
+
dsl_accessor :api_prefix, 'api' # Prefix that denotes the api namespace in URLs
|
9
|
+
|
10
|
+
dsl_accessor :api_controllers do
|
11
|
+
Dir[Rails.root.join("app/controllers/#{api_prefix}/**/*_controller.rb")]
|
12
|
+
end
|
13
|
+
|
14
|
+
dsl_accessor :platform_protocol, 'http' # Not used atm
|
15
|
+
|
16
|
+
dsl_accessor :platform_host, 'localhost:3000' # Actually host with port, but rails seems to be confused about the concept
|
17
|
+
|
18
|
+
dsl_accessor :api_protocol do platform_protocol end # Protocol the API understands
|
19
|
+
|
20
|
+
dsl_accessor :api_host do platform_host end # Hostname of the API (eventuallly with port number)
|
21
|
+
|
22
|
+
dsl_accessor :asset_protocol do platform_protocol end # Reserved
|
23
|
+
|
24
|
+
dsl_accessor :asset_host do platform_host end # Rails asset host
|
25
|
+
|
26
|
+
dsl_accessor :api_default_format, 'json'
|
27
|
+
|
28
|
+
def api_base_url
|
29
|
+
"#{api_protocol}://#{api_host}/#{api_prefix}"
|
30
|
+
end
|
31
|
+
|
32
|
+
dsl_accessor :api_url_options do
|
33
|
+
{ protocol: api_protocol, host: api_host, format: api_default_format }
|
34
|
+
end
|
35
|
+
|
36
|
+
dsl_accessor :templates_directory # Template directory, where customised templates live if any exist
|
37
|
+
|
38
|
+
dsl_accessor :output_directory, 'api_docs' # Output directory, where the api docs are created
|
39
|
+
|
40
|
+
dsl_accessor :publish_git # URL to the git repo to which the docs are pushed
|
41
|
+
|
42
|
+
dsl_accessor :ignore do [] end # All lines of the .gitignore file as an array
|
43
|
+
|
44
|
+
def assets
|
45
|
+
@assets ||= {}
|
46
|
+
end
|
47
|
+
private :assets
|
48
|
+
|
49
|
+
# Defines an asset for the file at +path+. If +to+ was given it will be
|
50
|
+
# copied to this path (it includes the basename) below
|
51
|
+
# +templates_directory+ in the output, otherwise it will be copied
|
52
|
+
# directly to +templates_directory+.
|
53
|
+
def asset(path, to: :root)
|
54
|
+
if destination = to.ask_and_send(:to_str)
|
55
|
+
assets[path.to_s] = destination
|
56
|
+
elsif to == :root
|
57
|
+
assets[path.to_s] = to
|
58
|
+
else
|
59
|
+
raise ArgumentError, "keyword argument to needs to be a string or :root"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Maps the assets original source path to its destination path in the
|
64
|
+
# output by yielding to every asset's source/destination pair.
|
65
|
+
def each_asset
|
66
|
+
for path in assets.keys
|
67
|
+
path = path.to_s
|
68
|
+
if destination = assets[path]
|
69
|
+
if destination == :root && output_directory
|
70
|
+
yield path, File.join(output_directory.to_s, File.basename(path))
|
71
|
+
else
|
72
|
+
yield path, File.join(output_directory.to_s, destination.to_str)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def configure(&block)
|
79
|
+
instance_eval(&block)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def config
|
84
|
+
if block_given?
|
85
|
+
yield self
|
86
|
+
else
|
87
|
+
self
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def all_docs
|
92
|
+
Dir[api_prefix.to_s + '/**/*_controller.rb'].each_with_object([]) do |cf, all|
|
93
|
+
controller_name = cf.sub(/\.rb$/, '').camelcase
|
94
|
+
controller =
|
95
|
+
begin
|
96
|
+
controller_name.constantize
|
97
|
+
rescue NameError => e
|
98
|
+
STDERR.puts "Skipping #{cf.inspect}, #{e.class}: #{e}"
|
99
|
+
next
|
100
|
+
end
|
101
|
+
if docs = controller.ask_and_send(:docs)
|
102
|
+
all << docs
|
103
|
+
else
|
104
|
+
STDERR.puts "Skipping #{cf.inspect}, #{controller_name.inspect} doesn't respond to :docs method"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def actions
|
110
|
+
all_docs.reduce([]) { |a, d| a.concat(d.actions) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def sections
|
114
|
+
@sections and return @sections
|
115
|
+
Dir.chdir Rails.root.join('app/controllers') do
|
116
|
+
actions.each_with_object(@sections = {}) do |action, sections|
|
117
|
+
sections[action.section] ||= Section.new(action.section)
|
118
|
+
sections[action.section] << action
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@sections.freeze
|
122
|
+
end
|
123
|
+
|
124
|
+
def sections_clear
|
125
|
+
@sections = nil
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def section(name)
|
130
|
+
sections[name] if sections.key?(name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def url_helpers
|
134
|
+
Betterdocs.rails.application.routes.url_helpers
|
135
|
+
end
|
136
|
+
|
137
|
+
def url_for(options = {})
|
138
|
+
Betterdocs.rails.application.routes.url_for(
|
139
|
+
options | Betterdocs::Global.config.api_url_options)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Betterdocs::JsonParamsRepresenter
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
include Betterdocs::Representer
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def build_result_object
|
7
|
+
ActionController::Parameters.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def hashify(object)
|
11
|
+
super do |result|
|
12
|
+
assign_params result, object
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def docs
|
17
|
+
@docs ||= Betterdocs::JsonParamsRepresenterCollector.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def assign_params(result, object)
|
21
|
+
for param in params
|
22
|
+
param.assign(result, object)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
private :assign_params
|
26
|
+
|
27
|
+
def params
|
28
|
+
@params ||= Set.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def param(name, **options, &block)
|
32
|
+
d = doc(:param, name, **options, &block) and
|
33
|
+
params << d
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Betterdocs
|
2
|
+
class JsonParamsRepresenterCollector
|
3
|
+
def initialize
|
4
|
+
@params = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :params
|
8
|
+
|
9
|
+
def param(param_name)
|
10
|
+
param_name = param_name.to_sym
|
11
|
+
@params[param_name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_element(representer, type, name, **options, &block)
|
15
|
+
element = build_element(representer, type, name, options, &block)
|
16
|
+
element.add_to_collector(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def representer
|
20
|
+
@params.values.find { |v|
|
21
|
+
v.representer and break v.representer
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
result = "*** #{representer} ***\n"
|
27
|
+
if params = @params.values.full?
|
28
|
+
result << "\nProperties:"
|
29
|
+
params.each_with_object(result) do |param, r|
|
30
|
+
r << "\n#{param.full_name}: (#{param.types * '|'}): #{param.description}\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_element(representer, type, *args, &block)
|
39
|
+
begin
|
40
|
+
element = Dsl::JsonParams.const_get(type.to_s.camelcase)
|
41
|
+
rescue NameError => e
|
42
|
+
raise ArgumentError, "unknown documentation element type #{type.inspect}"
|
43
|
+
end
|
44
|
+
element.new(representer, *args, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Betterdocs
|
2
|
+
module MixIntoController
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def method_added(name)
|
7
|
+
docs.configure_current_element(name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def doc(type, &block)
|
11
|
+
docs.add_element(self, type, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def docs
|
15
|
+
@docs ||= ControllerCollector.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
|
3
|
+
module Betterdocs::Representer
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def as_json(*)
|
7
|
+
singleton_class.ancestors.find do |c|
|
8
|
+
c != singleton_class && c < Betterdocs::Representer
|
9
|
+
end.hashify(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_json(*a)
|
13
|
+
JSON::generate(as_json, *a)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def apply(object)
|
18
|
+
object.extend self
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_result_object
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
|
25
|
+
def hashify(object, &block)
|
26
|
+
apply(object)
|
27
|
+
result = build_result_object
|
28
|
+
instance_exec(result, &block)
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
def doc(type, name, **options, &block)
|
33
|
+
docs.add_element(self, type, name, options, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def object_name(*) end
|
37
|
+
|
38
|
+
def docs
|
39
|
+
raise NotImplementedError, 'has to be implemented in including module'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'betterdocs/representer'
|
3
|
+
|
4
|
+
module Betterdocs::ResultRepresenter
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include Betterdocs::Dsl::Common
|
7
|
+
include Betterdocs::Representer
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def hashify(object)
|
11
|
+
super do |result|
|
12
|
+
assign_properties result, object
|
13
|
+
assign_links result, object
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def doc(type, name, **options, &block)
|
17
|
+
docs.add_element(self, type, name, options, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def docs
|
21
|
+
@docs ||= Betterdocs::ResultRepresenterCollector.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def assign_links(result, object)
|
25
|
+
result['links'] = []
|
26
|
+
for link in links
|
27
|
+
link.assign(result, object)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
private :assign_links
|
31
|
+
|
32
|
+
def assign_properties(result, object)
|
33
|
+
for property in properties
|
34
|
+
property.assign(result, object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
private :assign_properties
|
38
|
+
|
39
|
+
def properties
|
40
|
+
@properties ||= Set.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def property(name, **options, &block)
|
44
|
+
d = doc(:property, name, **options, &block) and
|
45
|
+
properties << d
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def collection(name, **options, &block)
|
50
|
+
d = doc(:collection_property, name, **options, &block) and
|
51
|
+
properties << d
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def links
|
56
|
+
@links ||= Set.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def link(name, **options, &block)
|
60
|
+
d = doc(:link, name, **options, &block) and links << d
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def api_url_for(options = {})
|
65
|
+
Betterdocs::Global.url_for(options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|