apress-documentation 0.4.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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +28 -0
  3. data/.gitignore +10 -0
  4. data/Appraisals +30 -0
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +4 -0
  7. data/README.md +101 -0
  8. data/Rakefile +6 -0
  9. data/app/assets/javascripts/package/documentation.js +18 -0
  10. data/app/assets/javascripts/shared/dependency_switcher.js +10 -0
  11. data/app/assets/javascripts/swagger_binder.js +19 -0
  12. data/app/assets/javascripts/swagger_ui.js +24 -0
  13. data/app/assets/javascripts/templates/document.hamlbars +25 -0
  14. data/app/assets/stylesheets/document/base.scss +112 -0
  15. data/app/assets/stylesheets/document/document.scss +19 -0
  16. data/app/assets/stylesheets/document/layout.scss +9 -0
  17. data/app/assets/stylesheets/document/sidebar.scss +19 -0
  18. data/app/assets/stylesheets/document/swagger.scss +3 -0
  19. data/app/assets/stylesheets/document/switch.scss +46 -0
  20. data/app/assets/stylesheets/document/variables.scss +26 -0
  21. data/app/assets/stylesheets/package/documentation.css +9 -0
  22. data/app/assets/stylesheets/package/swagger_print.css +4 -0
  23. data/app/assets/stylesheets/package/swagger_screen.css +4 -0
  24. data/app/controllers/apress/documentation/documents_controller.rb +14 -0
  25. data/app/controllers/apress/documentation/swagger_controller.rb +22 -0
  26. data/app/controllers/apress/documentation/swagger_ui_controller.rb +11 -0
  27. data/app/controllers/concerns/apress/documentation/preload_docs.rb +20 -0
  28. data/app/helpers/apress/documentation/documents_helper.rb +14 -0
  29. data/app/presenters/apress/documentation/dependency_presenter.rb +75 -0
  30. data/app/services/apress/documentation/swagger_json_builder.rb +22 -0
  31. data/app/views/apress/documentation/documents/_document.html.haml +32 -0
  32. data/app/views/apress/documentation/documents/_swagger.html.haml +10 -0
  33. data/app/views/apress/documentation/documents/show.html.haml +13 -0
  34. data/app/views/apress/documentation/presenters/dependency_presenter/_dependencies.html.haml +21 -0
  35. data/app/views/apress/documentation/presenters/dependency_presenter/_links.html.haml +17 -0
  36. data/app/views/apress/documentation/swagger_ui/show.html.haml +26 -0
  37. data/app/views/layouts/apress/documentation/_menu.html.haml +6 -0
  38. data/app/views/layouts/apress/documentation/_menu_item.html.haml +7 -0
  39. data/app/views/layouts/apress/documentation/_sidebar.html.haml +2 -0
  40. data/app/views/layouts/documentation.html.haml +17 -0
  41. data/apress-documentation.gemspec +35 -0
  42. data/config/routes.rb +16 -0
  43. data/dip.yml +48 -0
  44. data/docker-compose.development.yml +18 -0
  45. data/docker-compose.drone.yml +7 -0
  46. data/docker-compose.yml +10 -0
  47. data/lib/apress/documentation.rb +48 -0
  48. data/lib/apress/documentation/dsl/compilers/base_compiler.rb +32 -0
  49. data/lib/apress/documentation/dsl/compilers/document_compiler.rb +111 -0
  50. data/lib/apress/documentation/dsl/compilers/mixins/dependable.rb +31 -0
  51. data/lib/apress/documentation/dsl/compilers/mixins/publicity.rb +34 -0
  52. data/lib/apress/documentation/dsl/compilers/swagger_compiler.rb +25 -0
  53. data/lib/apress/documentation/dsl/document.rb +14 -0
  54. data/lib/apress/documentation/dsl/modules.rb +40 -0
  55. data/lib/apress/documentation/dsl/swagger_document.rb +14 -0
  56. data/lib/apress/documentation/dsl/utils/swagger_bind_point_extractor.rb +37 -0
  57. data/lib/apress/documentation/engine.rb +16 -0
  58. data/lib/apress/documentation/extensions/rgl/adjacency.rb +18 -0
  59. data/lib/apress/documentation/storage/base_storage.rb +88 -0
  60. data/lib/apress/documentation/storage/dependency_graph.rb +96 -0
  61. data/lib/apress/documentation/storage/document.rb +52 -0
  62. data/lib/apress/documentation/storage/modules.rb +83 -0
  63. data/lib/apress/documentation/storage/swagger_document.rb +62 -0
  64. data/lib/apress/documentation/swagger/schema.rb +39 -0
  65. data/lib/apress/documentation/version.rb +5 -0
  66. data/spec/app/controllers/documents_controller_spec.rb +42 -0
  67. data/spec/app/controllers/swagger_controller_spec.rb +46 -0
  68. data/spec/app/controllers/swagger_ui_controller_spec.rb +11 -0
  69. data/spec/app/services/swagger_json_builder_spec.rb +41 -0
  70. data/spec/apress/documentation_spec.rb +342 -0
  71. data/spec/helpers/apress/documentation/documents_helper_spec.rb +17 -0
  72. data/spec/internal/app/docs/swagger/root.rb +7 -0
  73. data/spec/internal/config/database.yml +7 -0
  74. data/spec/internal/config/environments/test.rb +1 -0
  75. data/spec/internal/config/hosts.rb +1 -0
  76. data/spec/internal/config/routes.rb +3 -0
  77. data/spec/internal/lib/stub_docs/module.rb +3 -0
  78. data/spec/internal/lib/stub_docs/module/document/child_document.rb +7 -0
  79. data/spec/internal/log/.gitignore +1 -0
  80. data/spec/presenters/apress/documentation/dependency_presenter_spec.rb +139 -0
  81. data/spec/spec_helper.rb +27 -0
  82. metadata +335 -0
@@ -0,0 +1,62 @@
1
+ require_relative 'base_storage'
2
+ require_relative '../dsl/swagger_document'
3
+
4
+ module Apress
5
+ module Documentation
6
+ module Storage
7
+ # Protected
8
+ #
9
+ # Описывает дополнительные данные для swagger_path в SwaggerUI
10
+ #
11
+ # Алгоритм добавления данных в SwaggerUI:
12
+ # - записывакем нужные экземпляры этого класса в объекте Document(метод swagger_documents)
13
+ # - сериализуем данные из swagger_documents во вьюхе в js-переменную (метод as_json в BaseStorage)
14
+ # - вешаем событие на добавление данных из новосозданной переменной в HTML-таг с id == bind_id
15
+ # - после отрисовки SwaggerUI, вызываем триггер события, которое добавляет дополнительные данные
16
+ class SwaggerDocument < BaseStorage
17
+ include Apress::Documentation::Dsl::SwaggerDocument
18
+ # Public: Ссылка на документ(Document) в котором записан данный SwaggerDocument
19
+ attr_reader :document
20
+ # Public: tag и openperation_id для SwaggerUI
21
+ attr_reader :tag, :operation_id
22
+ alias_method :title, :operation_id
23
+ # Public: Бизнесс описание - заполняется менаджером
24
+ json_attr :business_desc
25
+ # Public: Наличие тестов, ссылка на задачу с тестами
26
+ json_attr :tests
27
+ # Public: Публичность описываемого функционала - (Закрытый, Защищенный, Публичный)
28
+ json_attr :publicity
29
+
30
+ def initialize(document, html_id)
31
+ @document = document
32
+ @tag, @operation_id = html_id.split('_')
33
+ @slug = document.slug + '/' + html_id
34
+ end
35
+
36
+ def swagger_class
37
+ return @swagger_class if defined?(@swagger_class)
38
+ @swagger_class = Class.new(Apress::Documentation::Swagger::Schema)
39
+ @swagger_class.document_slug = document.slug
40
+ @swagger_class
41
+ end
42
+
43
+ def as_json(options = {})
44
+ json = super(options)
45
+
46
+ json[:slug] = slug
47
+
48
+ if view = options[:view]
49
+ json[:depends_on] = Apress::Documentation::DependencyPresenter.new(view, self).render_deps
50
+ json[:consumers] = Apress::Documentation::DependencyPresenter.new(view, self).render_deps(reverse: true)
51
+ end
52
+
53
+ json
54
+ end
55
+
56
+ def current_module
57
+ Apress::Documentation::Storage::Modules.instance[document.slug.to_s.split('/').first]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ module Apress
2
+ module Documentation
3
+ module Swagger
4
+ class Schema
5
+ include ::Swagger::Blocks
6
+
7
+ class << self
8
+ attr_accessor :resource, :document_slug, :schema_block
9
+ end
10
+
11
+ def self.schema_name(name)
12
+ "#{self.name}::#{name.to_s.camelize}".to_sym
13
+ end
14
+
15
+ def self.swagger_classes
16
+ @swagger_classes ||= []
17
+ end
18
+
19
+ def self.inherited(child)
20
+ swagger_classes << child
21
+ end
22
+
23
+ module Extensions
24
+ def swagger_path(*args, &block)
25
+ self.resource = true
26
+ super
27
+ end
28
+
29
+ def swagger_schema(*args, &block)
30
+ self.schema_block = block
31
+ super
32
+ end
33
+ end
34
+
35
+ singleton_class.prepend Extensions
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Apress
2
+ module Documentation
3
+ VERSION = "0.4.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ def form_params(p)
4
+ if Rails::VERSION::MAJOR > 4
5
+ {params: p}
6
+ else
7
+ p
8
+ end
9
+ end
10
+
11
+ describe Apress::Documentation::DocumentsController, type: :controller do
12
+ after do
13
+ Apress::Documentation.data.clear
14
+ end
15
+
16
+ describe '#show' do
17
+ before do
18
+ Apress::Documentation.build(:docs) do
19
+ document(:doc1, title: 'test')
20
+ end
21
+ end
22
+
23
+ context 'without params' do
24
+ it 'response with 200' do
25
+ get :show
26
+
27
+ expect(assigns(:document)).to be_nil
28
+ expect(response).to have_http_status(:ok)
29
+ end
30
+ end
31
+
32
+ context 'with path' do
33
+ it 'response with 200' do
34
+ get :show, form_params(path: 'docs/doc1')
35
+
36
+ expect(assigns(:document).title).to eq 'test'
37
+
38
+ expect(response).to have_http_status(:ok)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ def form_params(p)
4
+ if Rails::VERSION::MAJOR > 4
5
+ {params: p}
6
+ else
7
+ p
8
+ end
9
+ end
10
+
11
+ describe Apress::Documentation::SwaggerController, type: :controller do
12
+ describe '#show' do
13
+ it 'response with 200' do
14
+ get :show
15
+
16
+ expect(response).to have_http_status(:ok)
17
+ end
18
+
19
+ context 'with params' do
20
+ it 'response ok' do
21
+ get :show, form_params(module: 'somemodule')
22
+
23
+ expect(response).to have_http_status(:ok)
24
+ end
25
+ end
26
+
27
+ context 'with cache' do
28
+ around do |example|
29
+ begin
30
+ Rails.application.config.action_controller.perform_caching = true
31
+ example.run
32
+ ensure
33
+ Rails.application.config.action_controller.perform_caching = false
34
+ end
35
+ end
36
+
37
+ it 'caches documentation json' do
38
+ spy = Apress::Documentation::SwaggerJsonBuilder.new(nil)
39
+ allow(Apress::Documentation::SwaggerJsonBuilder).to receive(:new).and_return(spy)
40
+ expect(spy).to receive(:call).once.and_call_original
41
+ get :show
42
+ get :show
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apress::Documentation::SwaggerUiController, type: :controller do
4
+ describe '#show' do
5
+ it 'response with 200' do
6
+ get :show
7
+
8
+ expect(response).to have_http_status(:ok)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Apress::Documentation::SwaggerJsonBuilder, type: :service do
4
+ describe '#call' do
5
+ let(:service) { described_class.new(slug) }
6
+ before do
7
+ klass = Class.new(Apress::Documentation::Swagger::Schema) do
8
+ swagger_path 'api/test' do
9
+ operation :get
10
+ end
11
+ end
12
+ klass.document_slug = slug
13
+
14
+ Class.new(Apress::Documentation::Swagger::Schema) do
15
+ swagger_path 'api/test2' do
16
+ operation :get
17
+ end
18
+ end
19
+ end
20
+
21
+ context 'when slug is present' do
22
+ let(:slug) { 'test' }
23
+
24
+ it 'filters paths' do
25
+ data = service.call[:paths]
26
+ expect(data).to include :"api/test"
27
+ expect(data).not_to include :"api/test2"
28
+ end
29
+ end
30
+
31
+ context 'without slug' do
32
+ let(:slug) { nil }
33
+
34
+ it 'returns all data' do
35
+ data = service.call[:paths]
36
+ expect(data).to include :"api/test"
37
+ expect(data).to include :"api/test2"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,342 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Apress::Documentation do
4
+ context 'simple build call' do
5
+ before do
6
+ Apress::Documentation.build(:doc) do
7
+ title 'name'
8
+ description 'description'
9
+ publicity :public
10
+ tests 'tests'
11
+ end
12
+ end
13
+
14
+ it 'create document' do
15
+ expect(Apress::Documentation.data.size).to eq 1
16
+ doc = Apress::Documentation.data['doc']
17
+ expect(doc.title).to eq 'name'
18
+ expect(doc.description).to eq 'description'
19
+ expect(doc.publicity).to eq 'Публичный'
20
+ expect(doc.tests).to eq 'tests'
21
+ end
22
+ end
23
+
24
+ context 'without block call' do
25
+ before do
26
+ Apress::Documentation.build(
27
+ :doc,
28
+ title: 'some',
29
+ description: 'test'
30
+ )
31
+ end
32
+
33
+ it 'create document' do
34
+ expect(Apress::Documentation.data.size).to eq 1
35
+ doc = Apress::Documentation.data['doc']
36
+ expect(doc.title).to eq 'some'
37
+ expect(doc.description).to eq 'test'
38
+ end
39
+ end
40
+
41
+ context 'when documents is nesting' do
42
+ before do
43
+ Apress::Documentation.build(:doc) do
44
+ document(:doc1) do
45
+ title 'cool document'
46
+
47
+ document(:doc2) do
48
+ title 'cool document 2'
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ it 'create all documents' do
55
+ expect(Apress::Documentation.data.size).to eq 1
56
+ doc = Apress::Documentation.data['doc'].documents['doc1']
57
+ expect(doc.title).to eq 'cool document'
58
+ expect(doc.documents['doc2'].title).to eq 'cool document 2'
59
+ end
60
+ end
61
+
62
+ context 'when multiple documents in one block call' do
63
+ before do
64
+ Apress::Documentation.build(:doc) do
65
+ document(:doc1) do
66
+ title 'cool document'
67
+ end
68
+
69
+ document(:doc2) do
70
+ title 'cool document 2'
71
+ end
72
+ end
73
+ end
74
+
75
+ it 'create all documents' do
76
+ doc = Apress::Documentation.data['doc']
77
+ expect(doc.documents.size).to eq 2
78
+ expect(doc.documents['doc1'].title).to eq 'cool document'
79
+ expect(doc.documents['doc2'].title).to eq 'cool document 2'
80
+ end
81
+ end
82
+
83
+ context 'when documents rewretes in next block' do
84
+ before do
85
+ Apress::Documentation.build(:doc) do
86
+ document(:doc1) do
87
+ title 'cool document'
88
+ end
89
+
90
+ document(:doc1) do
91
+ title 'cool document 2'
92
+ end
93
+ end
94
+ end
95
+
96
+ it 'rewrites it' do
97
+ doc = Apress::Documentation.data['doc']
98
+ expect(doc.documents.size).to eq 1
99
+ expect(doc.documents['doc1'].title).to eq 'cool document 2'
100
+ end
101
+ end
102
+
103
+ context 'when document is swagger' do
104
+ before do
105
+ Apress::Documentation.build(:doc) do
106
+ document(:doc1) do
107
+ title 'cool document'
108
+
109
+ swagger_bind('some_point') do
110
+ business_desc 'cool document 2'
111
+
112
+ swagger_path('api/docs') do
113
+ operation :get
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ it 'returns proper json' do
121
+ doc = Apress::Documentation.data['doc'].documents['doc1']
122
+ expect(doc.title).to eq 'cool document'
123
+ expect(doc.swagger_documents.size).to eq 1
124
+ expect(doc.swagger_documents.as_json).to eq(
125
+ "some_point" => {"business_desc" => "cool document 2", slug: "doc/doc1/some_point"}
126
+ )
127
+ end
128
+
129
+ context 'when swagger document is rewritten' do
130
+ before do
131
+ Apress::Documentation.build(:doc) do
132
+ document(:doc1) do
133
+ title 'cool document'
134
+
135
+ swagger_bind('some_point') do
136
+ tests 'somewhere'
137
+
138
+ swagger_path('api/docs') do
139
+ operation :get
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ it 'returns proper json' do
147
+ doc = Apress::Documentation.data['doc'].documents['doc1']
148
+ expect(doc.title).to eq 'cool document'
149
+ expect(doc.swagger_documents.size).to eq 1
150
+ expect(doc.swagger_documents.as_json).to eq(
151
+ "some_point" => {
152
+ "business_desc" => "cool document 2",
153
+ "tests" => "somewhere",
154
+ slug: "doc/doc1/some_point"
155
+ }
156
+ )
157
+ end
158
+ end
159
+
160
+ context 'when bind point is not defined' do
161
+ before do
162
+ Apress::Documentation.build(:swagger_auto) do
163
+ document(:swagger1) do
164
+ title 'swagger document'
165
+
166
+ swagger_bind do
167
+ tests 'here'
168
+
169
+ swagger_path('api/tests') do
170
+ operation :get do
171
+ key :operationId, 'testIndex'
172
+ key :tags, ['tests']
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ it 'returns proper json' do
181
+ doc = Apress::Documentation.data['swagger_auto'].documents['swagger1']
182
+ expect(doc.title).to eq 'swagger document'
183
+ expect(doc.swagger_documents.size).to eq 1
184
+ expect(doc.swagger_documents.as_json).to eq(
185
+ "tests_testIndex" => {"tests" => "here", slug: "swagger_auto/swagger1/tests_testIndex"}
186
+ )
187
+ end
188
+ end
189
+
190
+ context 'when paseed field is unknow' do
191
+ it 'raises RuntimeError' do
192
+ expect do
193
+ Apress::Documentation.build(:module, unexpected_field: 'test')
194
+ end.to raise_error RuntimeError
195
+ end
196
+ end
197
+ end
198
+
199
+ context 'publicity' do
200
+ context 'when argumens is valid' do
201
+ it 'set proper value to document' do
202
+ Apress::Documentation.build(:module) do
203
+ document(:doc1) do
204
+ publicity :public
205
+ end
206
+ end
207
+
208
+ doc = Apress::Documentation.data['module'].documents['doc1']
209
+
210
+ expect(doc.publicity).to eq 'Публичный'
211
+ end
212
+ end
213
+
214
+ context 'when argument is invalid' do
215
+ it 'raises error' do
216
+ expect do
217
+ Apress::Documentation.build(:module) do
218
+ document(:doc1) do
219
+ publicity :test
220
+ end
221
+ end
222
+ end.to raise_error("Неизвестный уровень доступа - test, объявлен в документе module/doc1")
223
+ end
224
+ end
225
+ end
226
+
227
+ context 'dependencies' do
228
+ context 'for document' do
229
+ context 'when refered document exists' do
230
+ before do
231
+ Apress::Documentation.build(:module) do
232
+ document(:doc) do
233
+ depends_on('module/doc2')
234
+ end
235
+
236
+ document(:doc2)
237
+ end
238
+ end
239
+
240
+ it 'build dependency' do
241
+ doc = Apress::Documentation.data['module'].documents['doc']
242
+ doc2 = Apress::Documentation.data['module'].documents['doc2']
243
+ expect(doc.dependencies).to include [doc, doc2]
244
+ end
245
+ end
246
+
247
+ context 'when refered document does exists' do
248
+ before do
249
+ Apress::Documentation.build(:module) do
250
+ document(:doc) do
251
+ depends_on('module/doc2')
252
+ end
253
+ end
254
+ end
255
+
256
+ it 'is not valid' do
257
+ doc = Apress::Documentation.data['module'].documents['doc']
258
+ expect { Apress::Documentation.validate_dependencies! }.
259
+ to raise_error("Несуществующий документ - module/doc2, объявлен в - [#{doc.inspect}]")
260
+ end
261
+ end
262
+ end
263
+
264
+ context 'for swagger_document' do
265
+ context 'when refered document exists' do
266
+ before do
267
+ Apress::Documentation.build(:module) do
268
+ document(:doc) do
269
+ depends_on('module/doc2')
270
+ end
271
+
272
+ document(:doc2)
273
+ end
274
+ end
275
+
276
+ it 'build dependency' do
277
+ doc = Apress::Documentation.data['module'].documents['doc']
278
+ doc2 = Apress::Documentation.data['module'].documents['doc2']
279
+ expect(doc.dependencies).to include [doc, doc2]
280
+ end
281
+ end
282
+
283
+ context 'when refered document does exists' do
284
+ before do
285
+ Apress::Documentation.build(:module) do
286
+ document(:doc) do
287
+ depends_on('module/doc2')
288
+ end
289
+ end
290
+ end
291
+
292
+ it 'is not valid' do
293
+ doc = Apress::Documentation.data['module'].documents['doc']
294
+ expect { Apress::Documentation.validate_dependencies! }.
295
+ to raise_error("Несуществующий документ - module/doc2, объявлен в - [#{doc.inspect}]")
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ describe '#fetch_document' do
302
+ before do
303
+ Apress::Documentation.build(:module) do
304
+ document(:doc1) do
305
+ document(:doc2) do
306
+ document(:doc3) do
307
+ title 'test'
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+ it 'fetches document by path' do
315
+ expect(Apress::Documentation.fetch_document('module/doc1/doc2/doc3').title).to eq 'test'
316
+ end
317
+ end
318
+
319
+ describe '#add_load_path' do
320
+ it 'loads all docs in folder on callback run' do
321
+ Apress::Documentation.add_load_path(Rails.root.join('lib/stub_docs'))
322
+
323
+ ActiveSupport.run_load_hooks(:documentation)
324
+
325
+ module_document = Apress::Documentation.data['test_load_module']
326
+ expect(module_document.description).to eq 'Cool module'
327
+ expect(module_document.documents['document'].documents['child'].description).to eq 'Cool document'
328
+ end
329
+ end
330
+
331
+ context 'config' do
332
+ it 'has default path scope' do
333
+ expect(subject.fetch(:path_scope)).to be_nil
334
+ end
335
+
336
+ it 'applies changes' do
337
+ expect { subject[:path_scope] = :cosmos }
338
+ .to change { subject.fetch(:path_scope) }
339
+ .from(nil).to(:cosmos)
340
+ end
341
+ end
342
+ end