betterdocs 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.vscode/settings.json +6 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/betterdocs.gemspec +5 -5
- data/lib/betterdocs.rb +1 -0
- data/lib/betterdocs/dsl/json_params/param.rb +2 -0
- data/lib/betterdocs/dsl/result/property.rb +2 -0
- data/lib/betterdocs/generator/swagger.rb +405 -0
- data/lib/betterdocs/generator/swagger_static/index.html +66 -0
- data/lib/betterdocs/global.rb +44 -8
- data/lib/betterdocs/tasks/doc.rake +25 -18
- data/lib/betterdocs/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7324105b58e4926bdbcec0e372efda88e523c1c31fb4f0181e9d4cebc94b135c
|
4
|
+
data.tar.gz: 6926a2ea9814cae5e763113badf138104f2fd9ae250c57e9cc8a16fb6eb18d43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccaab04fbb9dfc351d79b9b722d1430c1a8afc5007f3f8bfd68d2d7d1c06ab4d26cf843e745750d1eb805b3d83122fb6b48597a8c1a3bc127fee5bc95e43443e
|
7
|
+
data.tar.gz: 67968de292f5c9e0d17b0d2669d72ca75f1e6d709ea219de4f36beab3b99e14452dfa626ff4cb0fe6222419ba99e19f3be4f4765242dbd8d9f0622839d150cf9
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ GemHadar do
|
|
12
12
|
test_dir 'spec'
|
13
13
|
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc',
|
14
14
|
'.ruby-version', '.AppleDouble', 'tags', '.DS_Store', '.utilsrc',
|
15
|
-
'.bundle', '.byebug_history', 'errors.lst', '.yardoc'
|
15
|
+
'.bundle', '.byebug_history', 'errors.lst', '.yardoc', '.history'
|
16
16
|
readme 'README.md'
|
17
17
|
title "#{name.camelize}"
|
18
18
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
data/betterdocs.gemspec
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: betterdocs 0.
|
2
|
+
# stub: betterdocs 0.8.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "betterdocs".freeze
|
6
|
-
s.version = "0.
|
6
|
+
s.version = "0.8.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
10
10
|
s.authors = ["betterplace Developers".freeze]
|
11
|
-
s.date = "2021-
|
11
|
+
s.date = "2021-07-07"
|
12
12
|
s.description = "This library provides tools to generate API documention for a web site's REST-ful JSON API.".freeze
|
13
13
|
s.email = "developers@betterplace.org".freeze
|
14
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/version.rb".freeze]
|
15
|
-
s.files = [".codeclimate.yml".freeze, ".gitignore".freeze, ".rspec".freeze, ".semaphore/semaphore.yml".freeze, ".tool-versions".freeze, "COPYING".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "betterdocs.gemspec".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/generator/markdown/templates/README.md.erb".freeze, "lib/betterdocs/generator/markdown/templates/section.md.erb".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/tasks/doc.rake".freeze, "lib/betterdocs/version.rb".freeze, "spec/assets/app/assets/images/logos/logo.png".freeze, "spec/assets/app/controllers/api/foos_controller.rb".freeze, "spec/assets/app/views/api_v4/documentation/assets/CHANGELOG.md".freeze, "spec/assets/config/betterdocs.yml".freeze, "spec/betterdocs/dsl/controller/action/param_spec.rb".freeze, "spec/betterdocs/dsl/controller/action/response_spec.rb".freeze, "spec/betterdocs/dsl/json_type_mapper_spec.rb".freeze, "spec/betterdocs/dsl/result/collection_property_spec.rb".freeze, "spec/betterdocs/dsl/result/link_spec.rb".freeze, "spec/betterdocs/dsl/result/property_spec.rb".freeze, "spec/betterdocs/generator/markdown_spec.rb".freeze, "spec/betterdocs/global_spec.rb".freeze, "spec/betterdocs/json_params_representer_spec.rb".freeze, "spec/betterdocs/result_representer_spec.rb".freeze, "spec/betterdocs/sanitizer_spec.rb".freeze, "spec/controller_dsl_spec.rb".freeze, "spec/result_representer_dsl_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
14
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/generator/swagger.rb".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/version.rb".freeze]
|
15
|
+
s.files = [".codeclimate.yml".freeze, ".gitignore".freeze, ".rspec".freeze, ".semaphore/semaphore.yml".freeze, ".tool-versions".freeze, ".vscode/settings.json".freeze, "COPYING".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "betterdocs.gemspec".freeze, "lib/betterdocs.rb".freeze, "lib/betterdocs/controller_collector.rb".freeze, "lib/betterdocs/dsl.rb".freeze, "lib/betterdocs/dsl/common.rb".freeze, "lib/betterdocs/dsl/controller.rb".freeze, "lib/betterdocs/dsl/controller/action.rb".freeze, "lib/betterdocs/dsl/controller/action/param.rb".freeze, "lib/betterdocs/dsl/controller/action/response.rb".freeze, "lib/betterdocs/dsl/controller/controller.rb".freeze, "lib/betterdocs/dsl/controller/controller_base.rb".freeze, "lib/betterdocs/dsl/json_params.rb".freeze, "lib/betterdocs/dsl/json_params/param.rb".freeze, "lib/betterdocs/dsl/json_type_mapper.rb".freeze, "lib/betterdocs/dsl/naming.rb".freeze, "lib/betterdocs/dsl/representer.rb".freeze, "lib/betterdocs/dsl/result.rb".freeze, "lib/betterdocs/dsl/result/collection_property.rb".freeze, "lib/betterdocs/dsl/result/link.rb".freeze, "lib/betterdocs/dsl/result/property.rb".freeze, "lib/betterdocs/generator/config_shortcuts.rb".freeze, "lib/betterdocs/generator/markdown.rb".freeze, "lib/betterdocs/generator/markdown/templates/README.md.erb".freeze, "lib/betterdocs/generator/markdown/templates/section.md.erb".freeze, "lib/betterdocs/generator/swagger.rb".freeze, "lib/betterdocs/generator/swagger_static/index.html".freeze, "lib/betterdocs/global.rb".freeze, "lib/betterdocs/json_params_representer.rb".freeze, "lib/betterdocs/json_params_representer_collector.rb".freeze, "lib/betterdocs/json_time_with_zone.rb".freeze, "lib/betterdocs/mix_into_controller.rb".freeze, "lib/betterdocs/railtie.rb".freeze, "lib/betterdocs/rake_tasks.rb".freeze, "lib/betterdocs/representer.rb".freeze, "lib/betterdocs/result_representer.rb".freeze, "lib/betterdocs/result_representer_collector.rb".freeze, "lib/betterdocs/sanitizer.rb".freeze, "lib/betterdocs/section.rb".freeze, "lib/betterdocs/tasks/doc.rake".freeze, "lib/betterdocs/version.rb".freeze, "spec/assets/app/assets/images/logos/logo.png".freeze, "spec/assets/app/controllers/api/foos_controller.rb".freeze, "spec/assets/app/views/api_v4/documentation/assets/CHANGELOG.md".freeze, "spec/assets/config/betterdocs.yml".freeze, "spec/betterdocs/dsl/controller/action/param_spec.rb".freeze, "spec/betterdocs/dsl/controller/action/response_spec.rb".freeze, "spec/betterdocs/dsl/json_type_mapper_spec.rb".freeze, "spec/betterdocs/dsl/result/collection_property_spec.rb".freeze, "spec/betterdocs/dsl/result/link_spec.rb".freeze, "spec/betterdocs/dsl/result/property_spec.rb".freeze, "spec/betterdocs/generator/markdown_spec.rb".freeze, "spec/betterdocs/global_spec.rb".freeze, "spec/betterdocs/json_params_representer_spec.rb".freeze, "spec/betterdocs/result_representer_spec.rb".freeze, "spec/betterdocs/sanitizer_spec.rb".freeze, "spec/controller_dsl_spec.rb".freeze, "spec/result_representer_dsl_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
16
16
|
s.homepage = "http://github.com/betterplace/betterdocs".freeze
|
17
17
|
s.rdoc_options = ["--title".freeze, "Betterdocs".freeze, "--main".freeze, "README.md".freeze]
|
18
18
|
s.rubygems_version = "3.2.15".freeze
|
data/lib/betterdocs.rb
CHANGED
@@ -26,6 +26,7 @@ require 'betterdocs/section'
|
|
26
26
|
require 'betterdocs/mix_into_controller'
|
27
27
|
require 'betterdocs/generator/config_shortcuts'
|
28
28
|
require 'betterdocs/generator/markdown'
|
29
|
+
require 'betterdocs/generator/swagger'
|
29
30
|
require 'betterdocs/rake_tasks'
|
30
31
|
require 'betterdocs/json_time_with_zone'
|
31
32
|
defined? Rails and require 'betterdocs/railtie'
|
@@ -0,0 +1,405 @@
|
|
1
|
+
require 'infobar'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'term/ansicolor'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Betterdocs
|
7
|
+
module Generator
|
8
|
+
class Swagger
|
9
|
+
include ::Betterdocs::Generator::ConfigShortcuts
|
10
|
+
include Term::ANSIColor
|
11
|
+
include FileUtils
|
12
|
+
|
13
|
+
def initialize(only: nil)
|
14
|
+
only and @only = Regexp.new(only)
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
if dir = config.swagger_output_directory.full?
|
19
|
+
generate_to dir
|
20
|
+
else
|
21
|
+
raise 'Specify an output_directory in your configuration!'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_to(dirname)
|
26
|
+
map = { openapi: '3.0.2', paths: {}, components: { schemas: {} } }
|
27
|
+
add_error_envelope_schema(map[:components][:schemas])
|
28
|
+
configure_for_creation
|
29
|
+
prepare_dir(dirname)
|
30
|
+
add_global_info(map)
|
31
|
+
create_sections(map)
|
32
|
+
create_swagger(map, dirname)
|
33
|
+
create_assets
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def configure_for_creation
|
38
|
+
infobar.puts color(40, "Setting asset_host to #{Betterdocs::Global.asset_host.inspect}.")
|
39
|
+
Betterdocs.rails.configuration.action_controller.asset_host = Betterdocs::Global.asset_host
|
40
|
+
options = {
|
41
|
+
host: Betterdocs::Global.api_host,
|
42
|
+
protocol: Betterdocs::Global.api_protocol,
|
43
|
+
}
|
44
|
+
infobar.puts color(40, "Setting default_url_options to #{options.inspect}.")
|
45
|
+
Betterdocs.rails.application.routes.default_url_options = options
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_global_info(map)
|
50
|
+
slugs = get_request_url_slugs(sections.values.first.first.request)
|
51
|
+
map[:info] = { title: project_name, version: slugs[:ver] }
|
52
|
+
map[:servers] =
|
53
|
+
[{ url: "#{slugs[:protocol]}://#{[slugs[:host], slugs[:lang], "api_#{slugs[:ver]}"].join('/')}" }]
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_sections(map)
|
57
|
+
Infobar(total: sections.size)
|
58
|
+
sections.values.each do |section|
|
59
|
+
infobar.progress(
|
60
|
+
message: " Section #{section.name.to_s.inspect} %c/%t in %te ETA %e @%E ",
|
61
|
+
force: true,
|
62
|
+
)
|
63
|
+
@only =~ section.name or next if @only
|
64
|
+
|
65
|
+
map = get_section_data(map, section)
|
66
|
+
end
|
67
|
+
infobar.finish message: ' %t sections created in %te, completed @%E '
|
68
|
+
infobar.newline
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_assets
|
73
|
+
Infobar(total: config.swagger_assets.size)
|
74
|
+
config.each_swagger_asset do |src, dst|
|
75
|
+
infobar.progress(
|
76
|
+
message: " Asset #{File.basename(src).inspect} %c/%t in %te ETA %e @%E ",
|
77
|
+
force: true,
|
78
|
+
)
|
79
|
+
mkdir_p File.dirname(dst)
|
80
|
+
cp Betterdocs.rails.root.join(src), dst
|
81
|
+
end
|
82
|
+
infobar.finish message: ' %t assets created in %te, completed @%E '
|
83
|
+
infobar.newline
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_swagger(map, dirname)
|
88
|
+
name = 'swagger.json'
|
89
|
+
cd dirname do
|
90
|
+
infobar.puts color(40, 'Creating swagger.')
|
91
|
+
json = JSON.pretty_generate(map)
|
92
|
+
render_to(name, json)
|
93
|
+
end
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def fail_while_rendering(exception)
|
98
|
+
message = color(
|
99
|
+
231,
|
100
|
+
on_color(124, " *** ERROR #{exception.class}: #{exception.message.inspect} ***")
|
101
|
+
)
|
102
|
+
infobar.puts message
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_param(hash, name, param_in, required = false, type = nil, description = '', schema = nil)
|
107
|
+
hash[:parameters] ||= []
|
108
|
+
p = { in: param_in, required: required, description: description, name: name }
|
109
|
+
p[:schema] = { type: type } unless type.nil?
|
110
|
+
p[:schema] = schema unless schema.nil?
|
111
|
+
hash[:parameters].push(p)
|
112
|
+
hash
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_default_list_params(hash)
|
116
|
+
p = add_param(hash, 'page', 'query', false, 'integer')
|
117
|
+
add_param(p, 'per_page', 'query', false, 'integer')
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_list_response_wrapper_schema
|
121
|
+
{
|
122
|
+
type: 'object',
|
123
|
+
properties: {
|
124
|
+
total_entries: { type: 'integer' },
|
125
|
+
offset: { type: 'integer' },
|
126
|
+
total_pages: { type: 'integer' },
|
127
|
+
current_page: { type: 'integer' },
|
128
|
+
per_page: { type: 'integer' },
|
129
|
+
},
|
130
|
+
required: %w[total_entries offset current_page per_page total_pages data],
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_param_value_type(value)
|
135
|
+
case value
|
136
|
+
when String then 'string'
|
137
|
+
when Integer then 'integer'
|
138
|
+
when TrueClass, FalseClass then 'boolean'
|
139
|
+
when Float then 'number'
|
140
|
+
when Array then 'array'
|
141
|
+
else 'object'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def get_path_param_slug_type(slug)
|
146
|
+
return 'integer' if /\A[0-9]+\z/.match(slug)
|
147
|
+
|
148
|
+
'string'
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_error_envelope_schema(definitions)
|
152
|
+
definitions['ErrorEnvelope'] = {
|
153
|
+
type: 'object',
|
154
|
+
properties: {
|
155
|
+
name: { type: 'string' },
|
156
|
+
status: { type: 'string' },
|
157
|
+
status_code: { type: 'integer' },
|
158
|
+
reason: { type: 'string' },
|
159
|
+
backtrace: { type: 'array', items: { type: 'string' } },
|
160
|
+
message: { type: 'string' },
|
161
|
+
errors: { type: 'object' },
|
162
|
+
links: { type: 'array', items: { type: 'string' } },
|
163
|
+
},
|
164
|
+
required: %w[name status status_code reason backtrace message links],
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_error_envelope_schema_ref
|
169
|
+
{ schema: get_schema_ref('ErrorEnvelope') }
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_request_url_slugs(request)
|
173
|
+
slugs = %r{
|
174
|
+
(?<method>GET|POST|PUT|DELETE|OPTIONS)\s+
|
175
|
+
(?<protocol>https?)://
|
176
|
+
(?<host>[a-z.]+)/
|
177
|
+
(?<lang>[a-z]{2})/api_
|
178
|
+
(?<ver>v[0-9]+)/
|
179
|
+
(?<path>[\w/]+\.json?)
|
180
|
+
}x.match(request).named_captures.transform_keys(&:to_sym)
|
181
|
+
slugs[:params] = []
|
182
|
+
split = slugs[:path].split('/')
|
183
|
+
path_cmp = split.map.with_index do |cmp, i|
|
184
|
+
unless i.even?
|
185
|
+
suffix = cmp['.json'] || ''
|
186
|
+
name = "#{split[i - 1]}_id"
|
187
|
+
cmp = "{#{name}}#{suffix}"
|
188
|
+
param = { name: name, type: get_path_param_slug_type(cmp.gsub('.json', '')) }
|
189
|
+
slugs[:params].push(param)
|
190
|
+
end
|
191
|
+
cmp
|
192
|
+
end
|
193
|
+
slugs[:method] = slugs[:method].downcase
|
194
|
+
slugs[:path] = "/#{path_cmp.join('/')}"
|
195
|
+
slugs
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_request_definition(params)
|
199
|
+
schema = { type: 'object', properties: {}, required: [] }
|
200
|
+
params.each do |param|
|
201
|
+
schema[:properties][param.name] = get_schema(param.types, nil, param.description)
|
202
|
+
schema[:required].push(param.name) if param.required == true || param.required == 'yes'
|
203
|
+
end
|
204
|
+
schema
|
205
|
+
end
|
206
|
+
|
207
|
+
def get_name(title)
|
208
|
+
/([\w\s]+)/.match(title)[1]
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_type(types)
|
212
|
+
type = types
|
213
|
+
nullable = false
|
214
|
+
if types.instance_of? Array
|
215
|
+
case types.length
|
216
|
+
when 0
|
217
|
+
type = nil
|
218
|
+
when 1
|
219
|
+
type = types[0]
|
220
|
+
else
|
221
|
+
types.each do |t|
|
222
|
+
if t == 'null'
|
223
|
+
nullable = true
|
224
|
+
else
|
225
|
+
type = t
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
unless %w[integer number string boolean array object].include?(type)
|
231
|
+
puts "Warning: invalid type #{type || 'nil'}"
|
232
|
+
end
|
233
|
+
[type, nullable]
|
234
|
+
end
|
235
|
+
|
236
|
+
def get_deprecated_from_description(description)
|
237
|
+
description.include?('DEPRECATED')
|
238
|
+
end
|
239
|
+
|
240
|
+
def get_schema(types, sub_cls, description)
|
241
|
+
type, nullable = get_type(types)
|
242
|
+
deprecated = get_deprecated_from_description(description)
|
243
|
+
res = { description: description, type: type, nullable: nullable, deprecated: deprecated }
|
244
|
+
case type
|
245
|
+
when 'array'
|
246
|
+
items = { type: 'string' }
|
247
|
+
items = get_schema_ref(sub_cls) if sub_cls
|
248
|
+
res[:items] = items
|
249
|
+
when 'object'
|
250
|
+
res = get_schema_ref(sub_cls) if sub_cls
|
251
|
+
res[:description] = description
|
252
|
+
res[:deprecated] = deprecated
|
253
|
+
end
|
254
|
+
res[:nullable] = true if nullable
|
255
|
+
res
|
256
|
+
end
|
257
|
+
|
258
|
+
def get_dto_name(representer)
|
259
|
+
representer&.name&.demodulize
|
260
|
+
end
|
261
|
+
|
262
|
+
def add_links_definition(definitions, cls, value)
|
263
|
+
definition = initialise_definition(definitions, cls)
|
264
|
+
definition[:properties][:links] ||= {
|
265
|
+
type: 'array',
|
266
|
+
nullable: false,
|
267
|
+
items: {
|
268
|
+
type: 'object',
|
269
|
+
properties: {
|
270
|
+
rel: { type: 'string', enum: [] },
|
271
|
+
href: { type: 'string' },
|
272
|
+
},
|
273
|
+
required: %w[rel href],
|
274
|
+
},
|
275
|
+
}
|
276
|
+
enum = definition[:properties][:links][:items][:properties][:rel][:enum]
|
277
|
+
enum.push(value) unless enum.include?(value)
|
278
|
+
end
|
279
|
+
|
280
|
+
def initialise_definition(definitions, cls)
|
281
|
+
definitions[cls] ||= { type: 'object', properties: {} }
|
282
|
+
end
|
283
|
+
|
284
|
+
def add_response_schema(definitions, response)
|
285
|
+
return nil unless response.full?
|
286
|
+
|
287
|
+
subs = {}
|
288
|
+
resp_cls = get_dto_name(response.representer)
|
289
|
+
(response.full?(:properties) || []).each do |p|
|
290
|
+
subs[p.full_name] = p.sub_representer? if p.sub_representer?
|
291
|
+
sub_cls = get_dto_name(subs[p.nesting_name])
|
292
|
+
cls = sub_cls || resp_cls
|
293
|
+
definition = initialise_definition(definitions, cls)
|
294
|
+
definition[:properties][p.public_name] = get_schema(p.types, get_dto_name(p.sub_representer?), p.description)
|
295
|
+
end
|
296
|
+
(response.full?(:links) || []).each do |l|
|
297
|
+
sub_cls = get_dto_name(subs[l.nesting_name])
|
298
|
+
add_links_definition(definitions, sub_cls || resp_cls, l.public_name)
|
299
|
+
end
|
300
|
+
get_schema_ref(resp_cls)
|
301
|
+
end
|
302
|
+
|
303
|
+
def get_schema_ref(name)
|
304
|
+
{
|
305
|
+
"$ref": "#/components/schemas/#{name}",
|
306
|
+
}
|
307
|
+
end
|
308
|
+
|
309
|
+
def add_required_to_definitions(definitions)
|
310
|
+
definitions.each do |def_key, d|
|
311
|
+
req = d[:properties].select { |_k, v| v.key?(:nullable) }.keys
|
312
|
+
definitions[def_key][:required] = req unless req.empty?
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def add_body(definitions, params, action, name)
|
317
|
+
if action.json_params.full?
|
318
|
+
payload_name = "#{name.titleize.gsub(/\s/, '')}Request"
|
319
|
+
definitions[payload_name] = get_request_definition(action.json_params.values)
|
320
|
+
params[:requestBody] = wrap_content_object({ schema: get_schema_ref(payload_name) }, "#{name} Request")
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def add_path_params(params, from_path)
|
325
|
+
from_path.each do |param|
|
326
|
+
add_param(params, param[:name], 'path', true, param[:type], param[:name])
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def add_query_params(params, from_query, is_a_list)
|
331
|
+
from_query.each do |param|
|
332
|
+
add_param(params, param.name, 'query', param.required, get_param_value_type(param.value),
|
333
|
+
param.description)
|
334
|
+
end
|
335
|
+
add_default_list_params(params) if is_a_list
|
336
|
+
end
|
337
|
+
|
338
|
+
def wrap_content_object(object, description)
|
339
|
+
{ content: { "application/json": object }, description: description }
|
340
|
+
end
|
341
|
+
|
342
|
+
def add_example(obj, name, example)
|
343
|
+
obj[:examples] ||= {}
|
344
|
+
obj[:examples][name] = { value: example }
|
345
|
+
end
|
346
|
+
|
347
|
+
def get_section_data(map, section)
|
348
|
+
section.each do |action|
|
349
|
+
name = get_name(action.title)
|
350
|
+
p = { description: action.description, summary: name }
|
351
|
+
is_a_list = action.response.data.instance_of?(ApiTools::ResultSet)
|
352
|
+
slugs = get_request_url_slugs(action.request)
|
353
|
+
add_path_params(p, slugs[:params])
|
354
|
+
add_query_params(p, action.params.values, is_a_list)
|
355
|
+
add_body(map[:components][:schemas], p, action, name)
|
356
|
+
item = add_response_schema(map[:components][:schemas], action.response)
|
357
|
+
schema = item
|
358
|
+
if is_a_list
|
359
|
+
list = get_list_response_wrapper_schema
|
360
|
+
list[:properties][:data] = { type: 'array', items: item }
|
361
|
+
schema = list
|
362
|
+
end
|
363
|
+
ok = { schema: schema }
|
364
|
+
unless action.response.nil?
|
365
|
+
add_example(ok, 'example', JSON.parse(JSON.pretty_generate(action.response, quirks_mode: true)))
|
366
|
+
end
|
367
|
+
p[:responses] =
|
368
|
+
{ "200": wrap_content_object(ok, 'OK'),
|
369
|
+
default: wrap_content_object(get_error_envelope_schema_ref, 'Error') }
|
370
|
+
wrapped = {}
|
371
|
+
wrapped = map[slugs[:path]] if map.key?(slugs[:path])
|
372
|
+
wrapped[slugs[:method]] = p
|
373
|
+
map[:paths][slugs[:path]] = wrapped
|
374
|
+
end
|
375
|
+
add_required_to_definitions(map[:components][:schemas])
|
376
|
+
map
|
377
|
+
end
|
378
|
+
|
379
|
+
def render_to(filename, content)
|
380
|
+
File.open(filename, 'w') do |output|
|
381
|
+
output.write(content)
|
382
|
+
end
|
383
|
+
self
|
384
|
+
rescue StandardError => e
|
385
|
+
fail_while_rendering(e)
|
386
|
+
end
|
387
|
+
|
388
|
+
def prepare_dir(dirname)
|
389
|
+
dirname.present? or raise ArgumentError,
|
390
|
+
"#{dirname.inspect} should be an explicite output dirname"
|
391
|
+
begin
|
392
|
+
stat = File.stat(dirname)
|
393
|
+
if stat.directory?
|
394
|
+
rm_rf Dir[dirname.to_s + '/**/*']
|
395
|
+
else
|
396
|
+
raise ArgumentError, "#{dirname.inspect} is not a directory"
|
397
|
+
end
|
398
|
+
rescue Errno::ENOENT
|
399
|
+
end
|
400
|
+
mkdir_p dirname.to_s
|
401
|
+
self
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<!-- HTML for static distribution bundle build -->
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<html lang="en">
|
4
|
+
<head>
|
5
|
+
<meta charset="UTF-8" />
|
6
|
+
<title>Swagger UI</title>
|
7
|
+
|
8
|
+
<link
|
9
|
+
rel="stylesheet"
|
10
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.8.1/swagger-ui.css"
|
11
|
+
type="text/css"
|
12
|
+
charset="UTF-8"
|
13
|
+
/>
|
14
|
+
<style>
|
15
|
+
html {
|
16
|
+
box-sizing: border-box;
|
17
|
+
overflow: -moz-scrollbars-vertical;
|
18
|
+
overflow-y: scroll;
|
19
|
+
}
|
20
|
+
|
21
|
+
*,
|
22
|
+
*:before,
|
23
|
+
*:after {
|
24
|
+
box-sizing: inherit;
|
25
|
+
}
|
26
|
+
|
27
|
+
body {
|
28
|
+
margin: 0;
|
29
|
+
background: #fafafa;
|
30
|
+
}
|
31
|
+
</style>
|
32
|
+
</head>
|
33
|
+
|
34
|
+
<body>
|
35
|
+
<div id="swagger-ui"></div>
|
36
|
+
|
37
|
+
<script
|
38
|
+
src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.8.1/swagger-ui-bundle.js"
|
39
|
+
charset="UTF-8"
|
40
|
+
></script>
|
41
|
+
<script
|
42
|
+
src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.8.1/swagger-ui-standalone-preset.js"
|
43
|
+
charset="UTF-8"
|
44
|
+
></script>
|
45
|
+
<script>
|
46
|
+
window.onload = function () {
|
47
|
+
fetch("./swagger.json")
|
48
|
+
.then(resp => resp.json())
|
49
|
+
.then(spec => {
|
50
|
+
const ui = SwaggerUIBundle({
|
51
|
+
spec: spec,
|
52
|
+
dom_id: "#swagger-ui",
|
53
|
+
deepLinking: true,
|
54
|
+
presets: [
|
55
|
+
SwaggerUIBundle.presets.apis,
|
56
|
+
SwaggerUIStandalonePreset,
|
57
|
+
],
|
58
|
+
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
59
|
+
layout: "BaseLayout",
|
60
|
+
});
|
61
|
+
window.ui = ui;
|
62
|
+
});
|
63
|
+
};
|
64
|
+
</script>
|
65
|
+
</body>
|
66
|
+
</html>
|
data/lib/betterdocs/global.rb
CHANGED
@@ -27,7 +27,7 @@ module Betterdocs
|
|
27
27
|
platform_host host
|
28
28
|
__send__("#{prefix}_host", host)
|
29
29
|
else
|
30
|
-
[
|
30
|
+
[__send__("#{prefix}_protocol"), __send__("#{prefix}_host")].join('://')
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -41,7 +41,7 @@ module Betterdocs
|
|
41
41
|
|
42
42
|
dsl_accessor :api_protocol do platform_protocol end # Protocol the API understands
|
43
43
|
|
44
|
-
dsl_accessor :api_host
|
44
|
+
dsl_accessor :api_host do platform_host end # Hostname of the API (eventuallly with port number)
|
45
45
|
|
46
46
|
def api_url(url = nil)
|
47
47
|
handle_url(:api, url)
|
@@ -55,7 +55,7 @@ module Betterdocs
|
|
55
55
|
handle_url(:asset, url)
|
56
56
|
end
|
57
57
|
|
58
|
-
dsl_accessor :api_default_format,
|
58
|
+
dsl_accessor :api_default_format, 'json'
|
59
59
|
|
60
60
|
def api_base_url
|
61
61
|
"#{api_protocol}://#{api_host}/#{api_prefix}"
|
@@ -65,9 +65,11 @@ module Betterdocs
|
|
65
65
|
{ protocol: api_protocol, host: api_host, format: api_default_format }
|
66
66
|
end
|
67
67
|
|
68
|
-
dsl_accessor :templates_directory
|
68
|
+
dsl_accessor :templates_directory # Template directory, where customised templates live if any exist
|
69
69
|
|
70
|
-
dsl_accessor :output_directory,
|
70
|
+
dsl_accessor :output_directory, 'api_docs' # Output directory, where the api docs are created
|
71
|
+
|
72
|
+
dsl_accessor :swagger_output_directory, 'swagger_docs' # Output directory, where the swagger docs are created
|
71
73
|
|
72
74
|
dsl_accessor :publish_git # URL to the git repo to which the docs are pushed
|
73
75
|
|
@@ -89,6 +91,17 @@ module Betterdocs
|
|
89
91
|
@assets
|
90
92
|
end
|
91
93
|
|
94
|
+
def swagger_assets=(hash)
|
95
|
+
@swagger_assets&.clear
|
96
|
+
swagger_assets(hash)
|
97
|
+
end
|
98
|
+
|
99
|
+
def swagger_assets(hash = nil)
|
100
|
+
@swagger_assets ||= {}
|
101
|
+
hash&.each { |path, to| swagger_asset path, to: to }
|
102
|
+
@swagger_assets
|
103
|
+
end
|
104
|
+
|
92
105
|
# Defines an asset for the file at +path+. If +to+ was given it will be
|
93
106
|
# copied to this path (it includes the basename) below
|
94
107
|
# +templates_directory+ in the output, otherwise it will be copied
|
@@ -100,14 +113,25 @@ module Betterdocs
|
|
100
113
|
elsif to == :root
|
101
114
|
@assets[path.to_s] = to
|
102
115
|
else
|
103
|
-
raise ArgumentError,
|
116
|
+
raise ArgumentError, 'keyword argument to needs to be a string or :root'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def swagger_asset(path, to: :root)
|
121
|
+
@swagger_assets ||= {}
|
122
|
+
if destination = to.ask_and_send(:to_str)
|
123
|
+
@swagger_assets[path.to_s] = destination
|
124
|
+
elsif to == :root
|
125
|
+
@swagger_assets[path.to_s] = to
|
126
|
+
else
|
127
|
+
raise ArgumentError, 'keyword argument to needs to be a string or :root'
|
104
128
|
end
|
105
129
|
end
|
106
130
|
|
107
131
|
# Maps the assets original source path to its destination path in the
|
108
132
|
# output by yielding to every asset's source/destination pair.
|
109
133
|
def each_asset
|
110
|
-
|
134
|
+
assets.each do |(path, destination)|
|
111
135
|
path = path.to_s
|
112
136
|
if destination == :root
|
113
137
|
yield path, File.join(output_directory.to_s, File.basename(path))
|
@@ -117,6 +141,17 @@ module Betterdocs
|
|
117
141
|
end
|
118
142
|
end
|
119
143
|
|
144
|
+
def each_swagger_asset
|
145
|
+
swagger_assets.each do |(path, destination)|
|
146
|
+
path = path.to_s
|
147
|
+
if destination == :root
|
148
|
+
yield path, File.join(swagger_output_directory.to_s, File.basename(path))
|
149
|
+
else
|
150
|
+
yield path, File.join(swagger_output_directory.to_s, destination.to_str)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
120
155
|
def configuration_file
|
121
156
|
cc.betterdocs
|
122
157
|
rescue ComplexConfig::ConfigurationFileMissing
|
@@ -192,7 +227,8 @@ module Betterdocs
|
|
192
227
|
|
193
228
|
def url_for(options = {})
|
194
229
|
Betterdocs.rails.application.routes.url_for(
|
195
|
-
options | Betterdocs::Global.config.api_url_options
|
230
|
+
options | Betterdocs::Global.config.api_url_options
|
231
|
+
)
|
196
232
|
end
|
197
233
|
end
|
198
234
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
namespace :doc do
|
2
|
-
desc
|
3
|
-
task :
|
2
|
+
desc 'Create the API documentation'
|
3
|
+
task api: :'doc:api:sandbox' do
|
4
4
|
Betterdocs::Global.config do |config|
|
5
5
|
Betterdocs::Generator::Markdown.new(only: ENV['ONLY']).generate
|
6
6
|
cd config.output_directory do
|
@@ -15,39 +15,46 @@ namespace :doc do
|
|
15
15
|
|
16
16
|
namespace :api do
|
17
17
|
desc 'Let database transactions run in a sandboxed environment'
|
18
|
-
task :
|
19
|
-
|
18
|
+
task sandbox: %i[doc:set_betterdocs_env environment] do
|
20
19
|
ActiveRecord::Base.connection.begin_db_transaction
|
21
20
|
at_exit do
|
22
|
-
|
21
|
+
ActiveRecord::Base.connection.rollback_db_transaction
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
|
-
desc
|
27
|
-
task :
|
25
|
+
desc 'Create the API swagger documentation'
|
26
|
+
task swagger: :'doc:api:sandbox' do
|
28
27
|
Betterdocs::Global.config do |config|
|
29
|
-
|
28
|
+
Betterdocs::Generator::Swagger.new(only: ENV['ONLY']).generate
|
29
|
+
cd config.swagger_output_directory do
|
30
|
+
File.open('.gitignore', 'w') { |ignore| ignore.puts config.ignore }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Push the newly created API documentation to the remote git repo'
|
36
|
+
task push: :api do
|
37
|
+
Betterdocs::Global.config do |config|
|
38
|
+
config.publish_git or raise 'Configure a git repo as publish_git to publish to'
|
30
39
|
cd config.output_directory do
|
31
|
-
File.directory?('.git') or sh
|
32
|
-
sh
|
40
|
+
File.directory?('.git') or sh 'git init'
|
41
|
+
sh 'git remote rm publish_git || true'
|
33
42
|
sh "git remote add publish_git #{config.publish_git}"
|
34
|
-
sh
|
43
|
+
sh 'git add -A'
|
35
44
|
sh 'git commit -m "Add some more changes to API documentation" || true'
|
36
45
|
sh 'git push -f publish_git master'
|
37
46
|
end
|
38
47
|
end
|
39
48
|
end
|
40
49
|
|
41
|
-
desc
|
42
|
-
task :
|
50
|
+
desc 'Publish the newly created API documentation'
|
51
|
+
task publish: [:push]
|
43
52
|
|
44
|
-
desc
|
45
|
-
task :
|
53
|
+
desc 'Publish and view the newly created API documentation'
|
54
|
+
task view: :publish do
|
46
55
|
Betterdocs::Global.config do |config|
|
47
56
|
url = config.publish_git
|
48
|
-
if url !~ /\Ahttps?:/
|
49
|
-
url.sub!(/.*?([^@]*):/, 'http://\1/')
|
50
|
-
end
|
57
|
+
url.sub!(/.*?([^@]*):/, 'http://\1/') if url !~ /\Ahttps?:/
|
51
58
|
sh "open #{url.inspect}"
|
52
59
|
end
|
53
60
|
end
|
data/lib/betterdocs/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: betterdocs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- betterplace Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gem_hadar
|
@@ -190,6 +190,7 @@ extra_rdoc_files:
|
|
190
190
|
- lib/betterdocs/dsl/result/property.rb
|
191
191
|
- lib/betterdocs/generator/config_shortcuts.rb
|
192
192
|
- lib/betterdocs/generator/markdown.rb
|
193
|
+
- lib/betterdocs/generator/swagger.rb
|
193
194
|
- lib/betterdocs/global.rb
|
194
195
|
- lib/betterdocs/json_params_representer.rb
|
195
196
|
- lib/betterdocs/json_params_representer_collector.rb
|
@@ -209,6 +210,7 @@ files:
|
|
209
210
|
- ".rspec"
|
210
211
|
- ".semaphore/semaphore.yml"
|
211
212
|
- ".tool-versions"
|
213
|
+
- ".vscode/settings.json"
|
212
214
|
- COPYING
|
213
215
|
- Gemfile
|
214
216
|
- LICENSE
|
@@ -239,6 +241,8 @@ files:
|
|
239
241
|
- lib/betterdocs/generator/markdown.rb
|
240
242
|
- lib/betterdocs/generator/markdown/templates/README.md.erb
|
241
243
|
- lib/betterdocs/generator/markdown/templates/section.md.erb
|
244
|
+
- lib/betterdocs/generator/swagger.rb
|
245
|
+
- lib/betterdocs/generator/swagger_static/index.html
|
242
246
|
- lib/betterdocs/global.rb
|
243
247
|
- lib/betterdocs/json_params_representer.rb
|
244
248
|
- lib/betterdocs/json_params_representer_collector.rb
|