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,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