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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +10 -0
  4. data/.rubocop_todo.yml +218 -94
  5. data/TODO.cleanups/01-resolve-production-todos.md +65 -0
  6. data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
  7. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
  8. data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
  9. data/TODO.cleanups/05-replace-marshal-load.md +37 -0
  10. data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
  11. data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
  12. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
  13. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
  14. data/TODO.cleanups/10-split-large-files.md +47 -0
  15. data/bin/console +0 -1
  16. data/exe/lutaml +1 -0
  17. data/lib/lutaml/cli/element_identifier.rb +3 -6
  18. data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
  19. data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
  20. data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
  21. data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
  22. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
  23. data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
  24. data/lib/lutaml/cli/interactive_shell.rb +116 -802
  25. data/lib/lutaml/cli/uml/build_command.rb +5 -5
  26. data/lib/lutaml/cli/uml/verify_command.rb +0 -1
  27. data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
  28. data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
  29. data/lib/lutaml/formatter/graphviz.rb +1 -2
  30. data/lib/lutaml/qea/database.rb +1 -47
  31. data/lib/lutaml/qea/factory/association_builder.rb +188 -0
  32. data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
  33. data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
  34. data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
  35. data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
  36. data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
  37. data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
  38. data/lib/lutaml/qea/lookup_indexes.rb +54 -0
  39. data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
  40. data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
  41. data/lib/lutaml/uml/has_members.rb +0 -1
  42. data/lib/lutaml/uml/inheritance_walker.rb +92 -0
  43. data/lib/lutaml/uml/model_helpers.rb +129 -0
  44. data/lib/lutaml/uml/node/attribute.rb +3 -1
  45. data/lib/lutaml/uml/node/class_node.rb +3 -3
  46. data/lib/lutaml/uml/operation.rb +2 -0
  47. data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
  48. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
  49. data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
  50. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
  51. data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
  52. data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
  53. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
  54. data/lib/lutaml/uml_repository/index_builder.rb +3 -271
  55. data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
  56. data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
  57. data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
  58. data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
  59. data/lib/lutaml/uml_repository/package_loader.rb +37 -17
  60. data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
  61. data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
  62. data/lib/lutaml/uml_repository/repository.rb +7 -57
  63. data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
  64. data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
  65. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
  66. data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
  67. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
  68. data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
  69. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
  70. data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
  71. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
  72. data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
  73. data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
  74. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
  75. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
  76. data/lib/lutaml/version.rb +1 -1
  77. data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
  78. data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
  79. data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
  80. data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
  81. data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
  82. data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
  83. data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
  84. data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
  85. data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
  86. data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
  87. data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
  88. data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
  89. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
  90. data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
  91. data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
  92. data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
  93. data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
  94. data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
  95. data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
  96. data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
  97. data/lib/lutaml/xmi/parsers/xml.rb +7 -120
  98. data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
  99. data/lib/lutaml.rb +0 -1
  100. metadata +48 -21
  101. data/lib/lutaml/cli/commands/base_command.rb +0 -118
  102. data/lib/lutaml/command_line.rb +0 -272
  103. data/lib/lutaml/sysml/allocate.rb +0 -9
  104. data/lib/lutaml/sysml/allocated.rb +0 -9
  105. data/lib/lutaml/sysml/binding_connector.rb +0 -9
  106. data/lib/lutaml/sysml/block.rb +0 -32
  107. data/lib/lutaml/sysml/constraint_block.rb +0 -14
  108. data/lib/lutaml/sysml/copy.rb +0 -8
  109. data/lib/lutaml/sysml/derive_requirement.rb +0 -9
  110. data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
  111. data/lib/lutaml/sysml/refine.rb +0 -9
  112. data/lib/lutaml/sysml/requirement.rb +0 -44
  113. data/lib/lutaml/sysml/requirement_related.rb +0 -9
  114. data/lib/lutaml/sysml/satisfy.rb +0 -9
  115. data/lib/lutaml/sysml/test_case.rb +0 -25
  116. data/lib/lutaml/sysml/trace.rb +0 -9
  117. data/lib/lutaml/sysml/verify.rb +0 -8
  118. data/lib/lutaml/sysml/xmi_file.rb +0 -486
  119. 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
- # Export repository to Markdown documentation.
25
- #
26
- # @param output_path [String] Path to the output directory
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
- # Create the directory structure for documentation.
47
- #
48
- # @return [void]
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 = build_index_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
- generate_package_page(package)
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
- # Generate a single package page.
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
- generate_class_page(klass)
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