lutaml 0.10.4 → 0.10.6
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.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +218 -94
- data/TODO.cleanups/01-resolve-production-todos.md +65 -0
- data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
- data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
- data/TODO.cleanups/05-replace-marshal-load.md +37 -0
- data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
- data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
- data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
- data/TODO.cleanups/10-split-large-files.md +47 -0
- data/bin/console +0 -1
- data/exe/lutaml +1 -0
- data/lib/lutaml/cli/element_identifier.rb +3 -6
- data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
- data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
- data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
- data/lib/lutaml/cli/interactive_shell.rb +116 -802
- data/lib/lutaml/cli/uml/build_command.rb +5 -5
- data/lib/lutaml/cli/uml/verify_command.rb +0 -1
- data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
- data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
- data/lib/lutaml/formatter/graphviz.rb +1 -2
- data/lib/lutaml/qea/database.rb +1 -47
- data/lib/lutaml/qea/factory/association_builder.rb +188 -0
- data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
- data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
- data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
- data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
- data/lib/lutaml/qea/lookup_indexes.rb +54 -0
- data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
- data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
- data/lib/lutaml/uml/has_members.rb +0 -1
- data/lib/lutaml/uml/inheritance_walker.rb +92 -0
- data/lib/lutaml/uml/model_helpers.rb +129 -0
- data/lib/lutaml/uml/node/attribute.rb +3 -1
- data/lib/lutaml/uml/node/class_node.rb +3 -3
- data/lib/lutaml/uml/operation.rb +2 -0
- data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
- data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
- data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
- data/lib/lutaml/uml_repository/index_builder.rb +3 -271
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
- data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
- data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
- data/lib/lutaml/uml_repository/package_loader.rb +37 -17
- data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
- data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
- data/lib/lutaml/uml_repository/repository.rb +7 -57
- data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
- data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
- data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
- data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
- data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
- data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
- data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
- data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
- data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
- data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
- data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
- data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
- data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
- data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
- data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
- data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
- data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
- data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
- data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
- data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
- data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
- data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
- data/lib/lutaml/xmi/parsers/xml.rb +7 -120
- data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
- data/lib/lutaml.rb +0 -1
- metadata +48 -21
- data/lib/lutaml/cli/commands/base_command.rb +0 -118
- data/lib/lutaml/command_line.rb +0 -272
- data/lib/lutaml/sysml/allocate.rb +0 -9
- data/lib/lutaml/sysml/allocated.rb +0 -9
- data/lib/lutaml/sysml/binding_connector.rb +0 -9
- data/lib/lutaml/sysml/block.rb +0 -32
- data/lib/lutaml/sysml/constraint_block.rb +0 -14
- data/lib/lutaml/sysml/copy.rb +0 -8
- data/lib/lutaml/sysml/derive_requirement.rb +0 -9
- data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
- data/lib/lutaml/sysml/refine.rb +0 -9
- data/lib/lutaml/sysml/requirement.rb +0 -44
- data/lib/lutaml/sysml/requirement_related.rb +0 -9
- data/lib/lutaml/sysml/satisfy.rb +0 -9
- data/lib/lutaml/sysml/test_case.rb +0 -25
- data/lib/lutaml/sysml/trace.rb +0 -9
- data/lib/lutaml/sysml/verify.rb +0 -8
- data/lib/lutaml/sysml/xmi_file.rb +0 -486
- data/lib/lutaml/sysml.rb +0 -11
|
@@ -2,33 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require_relative "base_exporter"
|
|
5
|
+
require_relative "../../uml/model_helpers"
|
|
6
|
+
require_relative "markdown/link_resolver"
|
|
7
|
+
require_relative "markdown/formatting"
|
|
8
|
+
require_relative "markdown/index_page_builder"
|
|
9
|
+
require_relative "markdown/package_page_builder"
|
|
10
|
+
require_relative "markdown/class_page_builder"
|
|
5
11
|
|
|
6
12
|
module Lutaml
|
|
7
13
|
module UmlRepository
|
|
8
14
|
module Exporters
|
|
9
|
-
# Export UML repository to Markdown documentation.
|
|
10
|
-
#
|
|
11
|
-
# Generates a complete documentation site in Markdown format with:
|
|
12
|
-
# - Index page with package tree
|
|
13
|
-
# - Package pages with class listings
|
|
14
|
-
# - Class pages with attributes and associations
|
|
15
|
-
# - Diagram references
|
|
16
|
-
#
|
|
17
|
-
# @example Basic export
|
|
18
|
-
# exporter = MarkdownExporter.new(repository)
|
|
19
|
-
# exporter.export("docs/")
|
|
20
|
-
#
|
|
21
|
-
# @example Export specific package
|
|
22
|
-
# exporter.export("docs/", package: "ModelRoot::i-UR::urf")
|
|
23
15
|
class MarkdownExporter < BaseExporter
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# @param options [Hash] Export options
|
|
28
|
-
# @option options [String] :package Filter by package path
|
|
29
|
-
# @option options [Boolean] :recursive (true) Include nested packages
|
|
30
|
-
# @option options [String] :title ("UML Model Documentation") Site title
|
|
31
|
-
# @return [void]
|
|
16
|
+
include Lutaml::Uml::ModelHelpers
|
|
17
|
+
include Markdown::Formatting
|
|
18
|
+
|
|
32
19
|
def export(output_path, options = {})
|
|
33
20
|
@output_dir = output_path
|
|
34
21
|
@options = options
|
|
@@ -43,93 +30,22 @@ module Lutaml
|
|
|
43
30
|
|
|
44
31
|
attr_reader :output_dir, :options
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
def link_resolver
|
|
34
|
+
@link_resolver ||= Markdown::LinkResolver.new(indexes)
|
|
35
|
+
end
|
|
36
|
+
|
|
49
37
|
def create_directory_structure
|
|
50
38
|
FileUtils.mkdir_p(output_dir)
|
|
51
39
|
FileUtils.mkdir_p(File.join(output_dir, "packages"))
|
|
52
40
|
FileUtils.mkdir_p(File.join(output_dir, "classes"))
|
|
53
41
|
end
|
|
54
42
|
|
|
55
|
-
# Generate the index page.
|
|
56
|
-
#
|
|
57
|
-
# @return [void]
|
|
58
43
|
def generate_index_page
|
|
59
|
-
content =
|
|
44
|
+
content = Markdown::IndexPageBuilder.new(repository, options,
|
|
45
|
+
link_resolver).build
|
|
60
46
|
File.write(File.join(output_dir, "index.md"), content)
|
|
61
47
|
end
|
|
62
48
|
|
|
63
|
-
# Build index page content.
|
|
64
|
-
#
|
|
65
|
-
# @return [String] The index page content
|
|
66
|
-
def build_index_content
|
|
67
|
-
title = options.fetch(:title, "UML Model Documentation")
|
|
68
|
-
stats = repository.statistics
|
|
69
|
-
|
|
70
|
-
<<~MARKDOWN
|
|
71
|
-
# #{title}
|
|
72
|
-
|
|
73
|
-
## Overview
|
|
74
|
-
|
|
75
|
-
This documentation provides comprehensive information about the UML model.
|
|
76
|
-
|
|
77
|
-
## Statistics
|
|
78
|
-
|
|
79
|
-
- **Total Packages**: #{stats&.dig(:total_packages) || 0}
|
|
80
|
-
- **Total Classes**: #{stats&.dig(:total_classes) || 0}
|
|
81
|
-
- **Total Associations**: #{stats&.dig(:total_associations) || 0}
|
|
82
|
-
- **Total Diagrams**: #{stats&.dig(:total_diagrams) || 0}
|
|
83
|
-
|
|
84
|
-
## Package Structure
|
|
85
|
-
|
|
86
|
-
#{build_package_tree_markdown}
|
|
87
|
-
|
|
88
|
-
## Navigation
|
|
89
|
-
|
|
90
|
-
- [Packages](packages/)
|
|
91
|
-
- [Classes](classes/)
|
|
92
|
-
MARKDOWN
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Build package tree in Markdown format.
|
|
96
|
-
#
|
|
97
|
-
# @return [String] The package tree markdown
|
|
98
|
-
def build_package_tree_markdown
|
|
99
|
-
root_path = options[:package] || "ModelRoot"
|
|
100
|
-
tree = repository.package_tree(root_path)
|
|
101
|
-
return "No packages found." unless tree
|
|
102
|
-
|
|
103
|
-
build_tree_node(tree, 0)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Build a tree node recursively.
|
|
107
|
-
#
|
|
108
|
-
# @param node [Hash] The tree node
|
|
109
|
-
# @param depth [Integer] Current depth
|
|
110
|
-
# @return [String] Markdown representation
|
|
111
|
-
def build_tree_node(node, depth) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
112
|
-
indent = " " * depth
|
|
113
|
-
path = node[:path]
|
|
114
|
-
link = package_link(path)
|
|
115
|
-
result = "#{indent}- [#{node[:name]}](#{link})"
|
|
116
|
-
if node[:classes_count].positive?
|
|
117
|
-
result += " (#{node[:classes_count]} classes)"
|
|
118
|
-
end
|
|
119
|
-
result += "\n"
|
|
120
|
-
|
|
121
|
-
if node[:children]&.any?
|
|
122
|
-
node[:children].each do |child|
|
|
123
|
-
result += build_tree_node(child, depth + 1)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
result
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Generate package pages.
|
|
131
|
-
#
|
|
132
|
-
# @return [void]
|
|
133
49
|
def generate_package_pages
|
|
134
50
|
root_path = options[:package] || "ModelRoot"
|
|
135
51
|
packages = repository.list_packages(
|
|
@@ -137,148 +53,16 @@ module Lutaml
|
|
|
137
53
|
recursive: options.fetch(:recursive, true),
|
|
138
54
|
)
|
|
139
55
|
|
|
56
|
+
builder = Markdown::PackagePageBuilder.new(repository, link_resolver)
|
|
140
57
|
packages.each do |package|
|
|
141
|
-
|
|
58
|
+
path = link_resolver.package_path(package)
|
|
59
|
+
content = builder.build(package, path)
|
|
60
|
+
filename = link_resolver.sanitize_filename("#{path}.md")
|
|
61
|
+
File.write(File.join(output_dir, "packages", filename), content)
|
|
142
62
|
end
|
|
143
63
|
end
|
|
144
64
|
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
# @param package [Lutaml::Uml::Package, Lutaml::Uml::Document]
|
|
148
|
-
# The package object
|
|
149
|
-
# @return [void]
|
|
150
|
-
def generate_package_page(package)
|
|
151
|
-
path = package_path(package)
|
|
152
|
-
content = build_package_content(package, path)
|
|
153
|
-
filename = sanitize_filename("#{path}.md")
|
|
154
|
-
File.write(File.join(output_dir, "packages", filename), content)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Build package page content.
|
|
158
|
-
#
|
|
159
|
-
# @param package [Object] The package object
|
|
160
|
-
# @param path [String] The package path
|
|
161
|
-
# @return [String] The package page content
|
|
162
|
-
def build_package_content(package, path)
|
|
163
|
-
classes = repository.classes_in_package(path, recursive: false)
|
|
164
|
-
sub_packages = package.packages || []
|
|
165
|
-
|
|
166
|
-
<<~MARKDOWN
|
|
167
|
-
# Package: #{package.name}
|
|
168
|
-
|
|
169
|
-
**Qualified Path**: `#{path}`
|
|
170
|
-
|
|
171
|
-
## Description
|
|
172
|
-
|
|
173
|
-
#{package.definition || 'No description available.'}
|
|
174
|
-
|
|
175
|
-
## Statistics
|
|
176
|
-
|
|
177
|
-
- **Direct Classes**: #{classes.size}
|
|
178
|
-
- **Sub-packages**: #{sub_packages.size}
|
|
179
|
-
|
|
180
|
-
#{build_sub_packages_section(sub_packages)}
|
|
181
|
-
|
|
182
|
-
#{build_classes_section(classes)}
|
|
183
|
-
|
|
184
|
-
#{build_diagrams_section(path)}
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
[Back to Index](../index.md)
|
|
189
|
-
MARKDOWN
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Build sub-packages section.
|
|
193
|
-
#
|
|
194
|
-
# @param packages [Array] Array of package objects
|
|
195
|
-
# @return [String] Markdown content
|
|
196
|
-
def build_sub_packages_section(packages)
|
|
197
|
-
return "" if packages.empty?
|
|
198
|
-
|
|
199
|
-
content = "## Sub-packages\n\n"
|
|
200
|
-
packages.each do |pkg|
|
|
201
|
-
pkg_path = package_path(pkg)
|
|
202
|
-
link = package_link(pkg_path)
|
|
203
|
-
content += "- [#{pkg.name}](#{link})\n"
|
|
204
|
-
end
|
|
205
|
-
"#{content}\n"
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Build classes section.
|
|
209
|
-
#
|
|
210
|
-
# @param classes [Array] Array of class objects
|
|
211
|
-
# @return [String] Markdown content
|
|
212
|
-
def build_classes_section(classes)
|
|
213
|
-
return "## Classes\n\nNo classes in this package.\n" if classes.empty?
|
|
214
|
-
|
|
215
|
-
content = "## Classes\n\n"
|
|
216
|
-
content += "| Name | Type | Stereotypes | Attributes | " \
|
|
217
|
-
"Associations |\n"
|
|
218
|
-
content += "|------|------|-------------|------------|-" \
|
|
219
|
-
"-------------|\n"
|
|
220
|
-
|
|
221
|
-
classes.sort_by(&:name).each do |klass|
|
|
222
|
-
content += format_class_table_row(klass)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
"#{content}\n"
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# Format a class as a table row.
|
|
229
|
-
#
|
|
230
|
-
# @param klass [Object] The class object
|
|
231
|
-
# @return [String] Markdown table row
|
|
232
|
-
def format_class_table_row(klass)
|
|
233
|
-
qname = qualified_name(klass)
|
|
234
|
-
link = class_link(qname)
|
|
235
|
-
type = klass.class.name.split("::").last
|
|
236
|
-
stereotypes = format_stereotypes(klass.stereotype)
|
|
237
|
-
attrs_count = klass.attributes&.size || 0
|
|
238
|
-
assocs_count = count_associations(klass)
|
|
239
|
-
|
|
240
|
-
"| [#{klass.name}](#{link}) | #{type} | #{stereotypes} | " \
|
|
241
|
-
"#{attrs_count} | #{assocs_count} |\n"
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Format stereotypes (can be string or array).
|
|
245
|
-
#
|
|
246
|
-
# @param stereotype [String, Array, nil] The stereotype(s)
|
|
247
|
-
# @return [String] Formatted stereotype string
|
|
248
|
-
def format_stereotypes(stereotype)
|
|
249
|
-
return "" unless stereotype
|
|
250
|
-
|
|
251
|
-
case stereotype
|
|
252
|
-
when Array
|
|
253
|
-
stereotype.join(", ")
|
|
254
|
-
when String
|
|
255
|
-
stereotype
|
|
256
|
-
else
|
|
257
|
-
""
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Build diagrams section.
|
|
262
|
-
#
|
|
263
|
-
# @param package_path [String] The package path
|
|
264
|
-
# @return [String] Markdown content
|
|
265
|
-
def build_diagrams_section(package_path)
|
|
266
|
-
diagrams = repository.diagrams_in_package(package_path)
|
|
267
|
-
return "" if diagrams.empty?
|
|
268
|
-
|
|
269
|
-
content = "## Diagrams\n\n"
|
|
270
|
-
diagrams.each do |diagram|
|
|
271
|
-
content += "- **#{diagram.name}** (#{diagram.diagram_type})\n"
|
|
272
|
-
end
|
|
273
|
-
"#{content}\n"
|
|
274
|
-
rescue StandardError
|
|
275
|
-
""
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
# Generate class pages.
|
|
279
|
-
#
|
|
280
|
-
# @return [void]
|
|
281
|
-
def generate_class_pages # rubocop:disable Metrics/MethodLength
|
|
65
|
+
def generate_class_pages
|
|
282
66
|
classes = if options[:package]
|
|
283
67
|
repository.classes_in_package(
|
|
284
68
|
options[:package],
|
|
@@ -288,310 +72,14 @@ module Lutaml
|
|
|
288
72
|
indexes&.dig(:classes)&.values || []
|
|
289
73
|
end
|
|
290
74
|
|
|
75
|
+
builder = Markdown::ClassPageBuilder.new(repository, link_resolver)
|
|
291
76
|
classes.each do |klass|
|
|
292
|
-
|
|
77
|
+
qname = link_resolver.qualified_name(klass)
|
|
78
|
+
content = builder.build(klass, qname)
|
|
79
|
+
filename = link_resolver.sanitize_filename("#{qname}.md")
|
|
80
|
+
File.write(File.join(output_dir, "classes", filename), content)
|
|
293
81
|
end
|
|
294
82
|
end
|
|
295
|
-
|
|
296
|
-
# Generate a single class page.
|
|
297
|
-
#
|
|
298
|
-
# @param klass [Object] The class object
|
|
299
|
-
# @return [void]
|
|
300
|
-
def generate_class_page(klass)
|
|
301
|
-
qname = qualified_name(klass)
|
|
302
|
-
content = build_class_content(klass, qname)
|
|
303
|
-
filename = sanitize_filename("#{qname}.md")
|
|
304
|
-
File.write(File.join(output_dir, "classes", filename), content)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# Build class page content.
|
|
308
|
-
#
|
|
309
|
-
# @param klass [Object] The class object
|
|
310
|
-
# @param qname [String] The qualified name
|
|
311
|
-
# @return [String] The class page content
|
|
312
|
-
def build_class_content(klass, qname) # rubocop:disable Metrics/AbcSize
|
|
313
|
-
type = klass.class.name.split("::").last
|
|
314
|
-
pkg_path = extract_package_path(qname)
|
|
315
|
-
|
|
316
|
-
<<~MARKDOWN
|
|
317
|
-
# #{type}: #{klass.name}
|
|
318
|
-
|
|
319
|
-
**Qualified Name**: `#{qname}`
|
|
320
|
-
|
|
321
|
-
**Package**: [#{pkg_path}](#{package_link(pkg_path)})
|
|
322
|
-
|
|
323
|
-
#{build_stereotypes_section(klass)}
|
|
324
|
-
|
|
325
|
-
#{build_definition_section(klass)}
|
|
326
|
-
|
|
327
|
-
#{build_inheritance_section(klass)}
|
|
328
|
-
|
|
329
|
-
#{build_attributes_section(klass)}
|
|
330
|
-
|
|
331
|
-
#{build_operations_section(klass)}
|
|
332
|
-
|
|
333
|
-
#{build_associations_section(klass)}
|
|
334
|
-
|
|
335
|
-
#{build_enum_literals_section(klass)}
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
[Back to Package](#{package_link(pkg_path)}) | [Back to Index](../index.md)
|
|
340
|
-
MARKDOWN
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
# Build stereotypes section.
|
|
344
|
-
#
|
|
345
|
-
# @param klass [Object] The class object
|
|
346
|
-
# @return [String] Markdown content
|
|
347
|
-
def build_stereotypes_section(klass)
|
|
348
|
-
stereotypes_array = normalize_stereotypes(klass.stereotype)
|
|
349
|
-
return "" if stereotypes_array.empty?
|
|
350
|
-
|
|
351
|
-
"**Stereotypes**: #{stereotypes_array.map do |s|
|
|
352
|
-
"`#{s}`"
|
|
353
|
-
end.join(', ')}\n\n"
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
# Normalize stereotypes to array format.
|
|
357
|
-
#
|
|
358
|
-
# @param stereotype [String, Array, nil] The stereotype(s)
|
|
359
|
-
# @return [Array] Array of stereotypes
|
|
360
|
-
def normalize_stereotypes(stereotype)
|
|
361
|
-
return [] unless stereotype
|
|
362
|
-
|
|
363
|
-
case stereotype
|
|
364
|
-
when Array
|
|
365
|
-
stereotype
|
|
366
|
-
when String
|
|
367
|
-
[stereotype]
|
|
368
|
-
else
|
|
369
|
-
[]
|
|
370
|
-
end
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
# Build definition section.
|
|
374
|
-
#
|
|
375
|
-
# @param klass [Object] The class object
|
|
376
|
-
# @return [String] Markdown content
|
|
377
|
-
def build_definition_section(klass)
|
|
378
|
-
return "" unless klass.respond_to?(:definition) && klass.definition
|
|
379
|
-
|
|
380
|
-
"## Description\n\n#{klass.definition}\n\n"
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
# Build inheritance section.
|
|
384
|
-
#
|
|
385
|
-
# @param klass [Object] The class object
|
|
386
|
-
# @return [String] Markdown content
|
|
387
|
-
def build_inheritance_section(klass) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
388
|
-
parent = repository.supertype_of(klass)
|
|
389
|
-
children = repository.subtypes_of(klass)
|
|
390
|
-
|
|
391
|
-
return "" if parent.nil? && children.empty?
|
|
392
|
-
|
|
393
|
-
content = "## Inheritance\n\n"
|
|
394
|
-
|
|
395
|
-
if parent
|
|
396
|
-
parent_qname = qualified_name(parent)
|
|
397
|
-
content += "**Extends**: [#{parent.name}]" \
|
|
398
|
-
"(#{class_link(parent_qname)})\n\n"
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
if children.any?
|
|
402
|
-
content += "**Extended by**:\n\n"
|
|
403
|
-
children.each do |child|
|
|
404
|
-
child_qname = qualified_name(child)
|
|
405
|
-
content += "- [#{child.name}](#{class_link(child_qname)})\n"
|
|
406
|
-
end
|
|
407
|
-
content += "\n"
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
content
|
|
411
|
-
rescue StandardError
|
|
412
|
-
""
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
# Build attributes section.
|
|
416
|
-
#
|
|
417
|
-
# @param klass [Object] The class object
|
|
418
|
-
# @return [String] Markdown content
|
|
419
|
-
def build_attributes_section(klass) # rubocop:disable Metrics/MethodLength
|
|
420
|
-
return "" unless klass.attributes&.any?
|
|
421
|
-
|
|
422
|
-
content = "## Attributes\n\n"
|
|
423
|
-
content += "| Name | Type | Visibility | Cardinality |\n"
|
|
424
|
-
content += "|------|------|------------|-------------|\n"
|
|
425
|
-
|
|
426
|
-
klass.attributes.each do |attr|
|
|
427
|
-
visibility = attr.visibility || ""
|
|
428
|
-
cardinality = format_cardinality(attr.cardinality)
|
|
429
|
-
content += "| #{attr.name} | `#{attr.type}` | #{visibility} | " \
|
|
430
|
-
"#{cardinality} |\n"
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
"#{content}\n"
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
# Build operations section.
|
|
437
|
-
#
|
|
438
|
-
# @param klass [Object] The class object
|
|
439
|
-
# @return [String] Markdown content
|
|
440
|
-
def build_operations_section(klass) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
441
|
-
unless klass.respond_to?(:operations) && klass.operations&.any?
|
|
442
|
-
return ""
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
content = "## Operations\n\n"
|
|
446
|
-
content += "| Name | Return Type | Visibility |\n"
|
|
447
|
-
content += "|------|-------------|------------|\n"
|
|
448
|
-
|
|
449
|
-
klass.operations.each do |op|
|
|
450
|
-
visibility = op.visibility || ""
|
|
451
|
-
return_type = op.return_type || "void"
|
|
452
|
-
content += "| #{op.name} | `#{return_type}` | #{visibility} |\n"
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
"#{content}\n"
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
# Build associations section.
|
|
459
|
-
#
|
|
460
|
-
# @param klass [Object] The class object
|
|
461
|
-
# @return [String] Markdown content
|
|
462
|
-
def build_associations_section(klass) # rubocop:disable Metrics/MethodLength
|
|
463
|
-
associations = repository.associations_of(klass)
|
|
464
|
-
return "" if associations.empty?
|
|
465
|
-
|
|
466
|
-
content = "## Associations\n\n"
|
|
467
|
-
content += "| Name | Target Class | Cardinality | Navigable |\n"
|
|
468
|
-
content += "|------|--------------|-------------|-----------|\n"
|
|
469
|
-
|
|
470
|
-
associations.each do |assoc|
|
|
471
|
-
content += format_association_row(assoc, klass)
|
|
472
|
-
end
|
|
473
|
-
|
|
474
|
-
"#{content}\n"
|
|
475
|
-
rescue StandardError
|
|
476
|
-
""
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
# Format association as table row.
|
|
480
|
-
#
|
|
481
|
-
# @param association [Object] The association object
|
|
482
|
-
# @param klass [Object] The source class
|
|
483
|
-
# @return [String] Markdown table row
|
|
484
|
-
def format_association_row(association, klass) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
485
|
-
source_end = association.member_end&.first
|
|
486
|
-
target_end = association.member_end&.last
|
|
487
|
-
|
|
488
|
-
# Determine which end is the target
|
|
489
|
-
end_obj = if source_end&.type&.xmi_id == klass.xmi_id
|
|
490
|
-
target_end
|
|
491
|
-
else
|
|
492
|
-
source_end
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
return "" unless end_obj&.type
|
|
496
|
-
|
|
497
|
-
target_qname = qualified_name(end_obj.type)
|
|
498
|
-
name = association.name || end_obj.name || ""
|
|
499
|
-
cardinality = format_cardinality(end_obj.cardinality)
|
|
500
|
-
navigable = end_obj.navigable? ? "Yes" : "No"
|
|
501
|
-
|
|
502
|
-
"| #{name} | [#{end_obj.type.name}](#{class_link(target_qname)}) | " \
|
|
503
|
-
"#{cardinality} | #{navigable} |\n"
|
|
504
|
-
end
|
|
505
|
-
|
|
506
|
-
# Build enum literals section.
|
|
507
|
-
#
|
|
508
|
-
# @param klass [Object] The class object
|
|
509
|
-
# @return [String] Markdown content
|
|
510
|
-
def build_enum_literals_section(klass)
|
|
511
|
-
unless klass.is_a?(Lutaml::Uml::Enum) && klass.owned_literal&.any?
|
|
512
|
-
return ""
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
content = "## Literals\n\n"
|
|
516
|
-
klass.owned_literal.each do |literal|
|
|
517
|
-
content += "- `#{literal.name}`"
|
|
518
|
-
content += ": #{literal.definition}" if literal.definition
|
|
519
|
-
content += "\n"
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
"#{content}\n"
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
# Format cardinality.
|
|
526
|
-
#
|
|
527
|
-
# @param cardinality [Lutaml::Uml::Cardinality, nil] The cardinality
|
|
528
|
-
# @return [String] Formatted cardinality
|
|
529
|
-
def format_cardinality(cardinality)
|
|
530
|
-
return "" unless cardinality
|
|
531
|
-
|
|
532
|
-
min = cardinality.min || "0"
|
|
533
|
-
max = cardinality.max || "*"
|
|
534
|
-
"#{min}..#{max}"
|
|
535
|
-
end
|
|
536
|
-
|
|
537
|
-
# Get package path.
|
|
538
|
-
#
|
|
539
|
-
# @param package [Object] The package object
|
|
540
|
-
# @return [String] The package path
|
|
541
|
-
def package_path(package)
|
|
542
|
-
indexes&.dig(:package_to_path, package.xmi_id) || package.name
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
# Get qualified name.
|
|
546
|
-
#
|
|
547
|
-
# @param klass [Object] The class object
|
|
548
|
-
# @return [String] The qualified name
|
|
549
|
-
def qualified_name(klass)
|
|
550
|
-
indexes&.dig(:class_to_qname, klass.xmi_id) || klass.name
|
|
551
|
-
end
|
|
552
|
-
|
|
553
|
-
# Extract package path from qualified name.
|
|
554
|
-
#
|
|
555
|
-
# @param qname [String] The qualified name
|
|
556
|
-
# @return [String] The package path
|
|
557
|
-
def extract_package_path(qname)
|
|
558
|
-
parts = qname.split("::")
|
|
559
|
-
parts.size > 1 ? parts[0..-2].join("::") : "ModelRoot"
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
# Count associations involving a class.
|
|
563
|
-
#
|
|
564
|
-
# @param klass [Object] The class object
|
|
565
|
-
# @return [Integer] Count of associations
|
|
566
|
-
def count_associations(klass)
|
|
567
|
-
repository.associations_of(klass).size
|
|
568
|
-
rescue StandardError
|
|
569
|
-
0
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
# Generate package link.
|
|
573
|
-
#
|
|
574
|
-
# @param path [String] The package path
|
|
575
|
-
# @return [String] Relative link
|
|
576
|
-
def package_link(path)
|
|
577
|
-
"../packages/#{sanitize_filename(path)}.md"
|
|
578
|
-
end
|
|
579
|
-
|
|
580
|
-
# Generate class link.
|
|
581
|
-
#
|
|
582
|
-
# @param qname [String] The qualified name
|
|
583
|
-
# @return [String] Relative link
|
|
584
|
-
def class_link(qname)
|
|
585
|
-
"../classes/#{sanitize_filename(qname)}.md"
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
# Sanitize filename for filesystem compatibility.
|
|
589
|
-
#
|
|
590
|
-
# @param name [String] The filename
|
|
591
|
-
# @return [String] Sanitized filename
|
|
592
|
-
def sanitize_filename(name)
|
|
593
|
-
name.gsub("::", "_").gsub(/[^a-zA-Z0-9_\-.]/, "_")
|
|
594
|
-
end
|
|
595
83
|
end
|
|
596
84
|
end
|
|
597
85
|
end
|