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,14 @@
1
+ require_relative 'compilers/document_compiler'
2
+
3
+ module Apress
4
+ module Documentation
5
+ module Dsl
6
+ module Document
7
+ # Public: Подключает DSL в класс данных документа
8
+ def compile(fields = {}, &block)
9
+ Apress::Documentation::Dsl::Compilers::DocumentCompiler.new(self).compile(fields, &block)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ module Apress
2
+ module Documentation
3
+ module Dsl
4
+ module Modules
5
+ # Protected: Точка входа для построения DS
6
+ # используется через делегацию в модуле Apress::Documentation
7
+ #
8
+ #
9
+ # module_slug - Symbol - слаг модуля
10
+ # fields - Hash(optional, default - {}) - поля для установки в короткой записи
11
+ # (например, Apress::Documentation.build(:slug, title: 'name'))
12
+ # &block - Proc(optional) - вызовы DSL методов
13
+ #
14
+ # Examples
15
+ #
16
+ # Apress::Documentation.build(:module) do
17
+ # name 'some module'
18
+ # description 'tests'
19
+ # end
20
+ #
21
+ # Apress::Documentation.build(:module) do
22
+ # document(:some, title: 'Some doc') do
23
+ # description 'Тут вставить описание'
24
+ # publicity 'Публичное'
25
+ # end
26
+ # end
27
+ #
28
+ def build(module_slug, fields = {}, &block)
29
+ module_slug = module_slug.to_s
30
+ document = self[module_slug]
31
+ document ||= Apress::Documentation::Storage::Document.new(module_slug)
32
+ Apress::Documentation::Storage::DependencyGraph.instance.add_document(document)
33
+ self << document
34
+
35
+ document.compile(fields, &block)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'compilers/swagger_compiler'
2
+
3
+ module Apress
4
+ module Documentation
5
+ module Dsl
6
+ module SwaggerDocument
7
+ # Public: Подключает DSL в класс данных swagger-описания
8
+ def compile(fields = {}, &block)
9
+ Apress::Documentation::Dsl::Compilers::SwaggerCompiler.new(self).compile(fields, &block)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ module Apress
2
+ module Documentation
3
+ module Dsl
4
+ module Utils
5
+ # Private: "Распознает" идентификатор html-tag'а в SwaggerUI по переданному блоку Swagger::Blocks,
6
+ # в который будет вставлена дополнительная информация из SwaggerDocument.
7
+ #
8
+ # Идея: Выполнить блок DSL swagger_path из Swagger::Blocks без вызовов реальных методов.
9
+ #
10
+ # Алгоритм:
11
+ # - Выполняем переданный блок от swagger_path, пропуская неизвестные методы
12
+ # - как только нашли первый вызов "key :operationId, value", запоминаем value
13
+ # - тоже самое для key :tags, [value]
14
+ # - если после выполнения блока оба значения заданы (@tag, @operation_id) возвращаем результат
15
+ class SwaggerBindPointExtractor
16
+ def extract(&block)
17
+ instance_eval(&block)
18
+ "#{@tag}_#{@operation_id}" if @tag && @operation_id
19
+ end
20
+
21
+ def method_missing(name, *args, &block)
22
+ if block_given?
23
+ instance_eval(&block)
24
+ elsif name.to_s == 'key'
25
+ case args[0]
26
+ when :operationId
27
+ @operation_id ||= args[1]
28
+ when :tags
29
+ @tag ||= args[1].try(:first)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module Apress
2
+ module Documentation
3
+ class Engine < Rails::Engine
4
+ config.autoload_paths += [
5
+ config.root.join('app', 'controllers', 'concerns'),
6
+ config.root.join('app', 'presenters')
7
+ ]
8
+
9
+ config.documentation = {path_scope: nil, routes_constraints: {domain: :current}}
10
+
11
+ initializer "apress-documentation", before: :load_init_rb do |app|
12
+ RGL::DirectedAdjacencyGraph.include Apress::Documentation::Extensions::RGL::Adjacency
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Apress
2
+ module Documentation
3
+ module Extensions
4
+ module RGL
5
+ module Adjacency
6
+ # Private: Расширение графа для замены вершины на новую
7
+ def replace_vertex(old_v, new_v)
8
+ @vertices_dict[new_v] = @vertices_dict.delete(old_v)
9
+
10
+ @vertices_dict.each_value do |list|
11
+ list.add(new_v) if list.delete?(old_v)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,88 @@
1
+ module Apress
2
+ module Documentation
3
+ module Storage
4
+ # Private: AbstractClass, Базовый класс хранилища
5
+ #
6
+ # описывает методы аттрибутов для сериализации в json формата:
7
+ # {
8
+ # {
9
+ # "attr_0": send(:attr_0),
10
+ # "attr_1": send(:attr_1),
11
+ # ....
12
+ # }
13
+ # }
14
+ class BaseStorage
15
+ # Public: Составной слаг, используется как URL
16
+ attr_reader :slug
17
+
18
+ def self.json_attr_names
19
+ @json_attr_names ||= []
20
+ end
21
+
22
+ # Public: Задает аттрибуты для сериализации в json
23
+ def self.json_attr(*method_names)
24
+ json_attr_names.concat(method_names.map(&:to_s))
25
+
26
+ attr_accessor(*method_names)
27
+ end
28
+
29
+ # Public: Сериализует объект в JSON
30
+ def as_json(options = {})
31
+ self.class.json_attr_names.each_with_object({}) do |attr_name, json|
32
+ value = send(attr_name)
33
+
34
+ json[attr_name] = value if value
35
+ end
36
+ end
37
+
38
+ # Public: задает аттрибуты на основе хеша
39
+ def assign(options = {})
40
+ options.each do |key, value|
41
+ unless self.class.json_attr_names.include?(key.to_s)
42
+ raise "Undefined attribute #{key}, allowed attributes are #{self.class.json_attr_names}"
43
+ end
44
+
45
+ send("#{key}=", value)
46
+ end
47
+ end
48
+
49
+ def eql?(other)
50
+ slug == other.to_s
51
+ end
52
+ alias_method :==, :eql?
53
+
54
+ def to_s
55
+ slug.to_s
56
+ end
57
+
58
+ def hash
59
+ slug.hash
60
+ end
61
+
62
+ def inspect
63
+ "<#{self.class} slug = #{slug}>"
64
+ end
65
+
66
+ # Public: находит зависимости для текущего документа
67
+ #
68
+ # Arguments:
69
+ # reverse - флаг для отпеределения какой тип зависимостей нужно вернуть
70
+ # возможные значения
71
+ # - false (default) - документы, от которых зависит текущий документ (AKA зависимости self)
72
+ # - true - документы, которые зависят от текущего (AKA потребители self)
73
+ #
74
+ # Returns Array of Pairs - [[doc1, doc2], [doc1, doc2]]
75
+ def dependencies(reverse: false)
76
+ @dependencies ||= Hash.new do |hash, key|
77
+ hash[key] = Storage::DependencyGraph.instance.dependencies(
78
+ self,
79
+ reverse: key
80
+ )
81
+ end
82
+
83
+ @dependencies[reverse]
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,96 @@
1
+ require 'singleton'
2
+
3
+ module Apress
4
+ module Documentation
5
+ module Storage
6
+ # Private: Основное хранилище всех зависимостей между документами
7
+ # Инкапсулирует орентированный граф, вершины которого документы (Document или SwaggerDocument)
8
+ class DependencyGraph
9
+ include Singleton
10
+
11
+ # Public: добавление документа
12
+ #
13
+ # Arguments:
14
+ # document - (String or Document) - документ или его слаг(если документ еще не был создан)
15
+ #
16
+ # Note:
17
+ # Данный метод позволяет ""лениво"" создавать документы в графе,
18
+ # подменяя слаг документа, вставленного в граф до создания самого документа, на сам документ
19
+ # Returns nothing
20
+ def add_document(document)
21
+ if graph.has_vertex?(document)
22
+ graph.replace_vertex(document, document)
23
+ else
24
+ graph.add_vertex(document)
25
+ end
26
+ end
27
+
28
+ # Public: добавление связи между документами, создает ребро в графе, если оно не было создано
29
+ #
30
+ # Arguments:
31
+ # document_from - (Document) - документ от который зависит
32
+ # document_to - (String or Document) - документ или его слаг(если документ еще не был создан)
33
+ #
34
+ # Returns nothing
35
+ def add_dependency(document_from, document_to)
36
+ graph.add_edge(document_from, document_to) unless graph.has_edge?(document_from, document_to)
37
+ end
38
+
39
+ # Public: находит все зависимости для заданного документа
40
+ #
41
+ # Arguments:
42
+ # contract - (Document) - документ для которого определяем зависимости
43
+ # reverse - (boolean) - флаг, различает тип определяемых зависимостей
44
+ # возможные значения
45
+ # - false (default) - документы, от которых зависит текущий документ (AKA зависимости contract)
46
+ # - true - документы, которые зависят от текущего (AKA потребители contract)
47
+ #
48
+ # Returns Array of Pairs - [[doc_from, doc_to], [doc_from_other, doc_to_other]]
49
+ def dependencies(contract, reverse:)
50
+ dep = []
51
+
52
+ condition =
53
+ if reverse
54
+ lambda { |doc, _, to| doc == to }
55
+ else
56
+ lambda { |doc, from, _| doc == from }
57
+ end
58
+
59
+ graph.each_edge do |from, to|
60
+ next unless condition.call(contract, from, to)
61
+
62
+ dep << (reverse ? [to, from] : [from, to])
63
+ end
64
+
65
+ dep
66
+ end
67
+
68
+ # Public: валидирует зависимости
69
+ #
70
+ # Throws RuntimeError если найдена вершина неверного типа
71
+ #
72
+ # Returns nothing
73
+ def validate!
74
+ graph.each_vertex do |v|
75
+ unless v.is_a?(Apress::Documentation::Storage::BaseStorage)
76
+ raise "Несуществующий документ - #{v}, объявлен в - #{dependencies(v, reverse: true).map(&:last)}"
77
+ end
78
+ end
79
+ end
80
+
81
+ # Public: очищает все текущие зависисмости
82
+ #
83
+ # Returns nothing
84
+ def reset!
85
+ @graph = RGL::DirectedAdjacencyGraph.new
86
+ end
87
+
88
+ private
89
+
90
+ def graph
91
+ @graph ||= RGL::DirectedAdjacencyGraph.new
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'base_storage'
2
+ require_relative '../dsl/document'
3
+
4
+ module Apress
5
+ module Documentation
6
+ module Storage
7
+ # Protected
8
+ #
9
+ # Внутренний класс системы документации
10
+ # Описывает отдельный документ
11
+ class Document < BaseStorage
12
+ include Apress::Documentation::Dsl::Document
13
+ # Public: Заголовок документа
14
+ json_attr :title
15
+ # Public: Описание документа
16
+ json_attr :description
17
+ # Public: Бизнесс описание - заполняется менаджером
18
+ json_attr :business_desc
19
+ # Public: Наличие тестов, ссылка на задачу с тестами
20
+ json_attr :tests
21
+ # Public: Публичность описываемого функционала - (Защищенный, Публичный)
22
+ json_attr :publicity
23
+
24
+ def initialize(slug)
25
+ @slug = slug
26
+ end
27
+
28
+ # Public: проверка, необходимо ли для данного документа отображать SwaggerUI
29
+ def swagger?
30
+ !swagger_documents.empty?
31
+ end
32
+
33
+ # Public: Хранит дочерние документы
34
+ def documents
35
+ @documents ||= {}
36
+ end
37
+
38
+ # Public: Хранит объекты SwaggerDocument для отображения на одной старнице через SwaggerUI
39
+ def swagger_documents
40
+ @swagger_documents ||= {}
41
+ end
42
+
43
+ # Public: находит документ верхнего уровня - модуль
44
+ #
45
+ # Returns Document
46
+ def current_module
47
+ Apress::Documentation::Storage::Modules.instance[slug.to_s.split('/').first]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,83 @@
1
+ require 'singleton'
2
+ require_relative '../dsl/modules'
3
+
4
+ module Apress
5
+ module Documentation
6
+ module Storage
7
+ # Protected
8
+ #
9
+ # Класс хранения документов верхнего уровня (модулей)
10
+ class Modules
11
+ include Apress::Documentation::Dsl::Modules
12
+ include Singleton
13
+
14
+ # Public
15
+ #
16
+ # Хеш модулей
17
+ def data
18
+ @data ||= {}
19
+ end
20
+
21
+ # Public
22
+ #
23
+ # Добавление модуля
24
+ #
25
+ # Arguments
26
+ # document - Document
27
+ #
28
+ # Example usage:
29
+ # Apress::Documentation::Modules.instance << document
30
+ def <<(document)
31
+ data[document.slug.to_s] = document
32
+ end
33
+
34
+ # Public
35
+ #
36
+ # Поиск модуля
37
+ #
38
+ # Arguments
39
+ # slug - String (или любой совместимый объект) - слаг документа
40
+ #
41
+ # Example usage:
42
+ # Apress::Documentation::Modules.instance[slug]
43
+ #
44
+ # Returns Document
45
+ def [](slug)
46
+ data[slug.to_s]
47
+ end
48
+
49
+ # Public
50
+ #
51
+ # Получение документа по его URL
52
+ #
53
+ # Arguments
54
+ # path - String - строка разделенная '/' (пример "/module/document/some_function")
55
+ #
56
+ # Example usage:
57
+ # Apress::Documentation.fetch_document('module_name/document/test')
58
+ def fetch_document(path)
59
+ keys = path.split('/')
60
+ doc = data[keys.shift]
61
+ return unless doc
62
+
63
+ keys.each do |key|
64
+ doc = doc.documents[key] || (doc.respond_to?(:swagger_documents) && doc.swagger_documents[key])
65
+ break unless doc
66
+ end
67
+
68
+ doc
69
+ end
70
+
71
+ # Public
72
+ #
73
+ # Удаление всех документов, используется для тестирования
74
+ #
75
+ # Example usage:
76
+ # Apress::Documentation.reset!
77
+ def reset!
78
+ @data = {}
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end