apress-documentation 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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