lutaml 0.10.14 → 0.10.15

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +23 -67
  3. data/lib/lutaml/cli/interactive_shell/export_handler.rb +16 -10
  4. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +11 -7
  5. data/lib/lutaml/cli/interactive_shell/query_commands.rb +11 -1
  6. data/lib/lutaml/cli/interactive_shell.rb +11 -3
  7. data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
  8. data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
  9. data/lib/lutaml/formatter/graphviz.rb +65 -35
  10. data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
  11. data/lib/lutaml/qea/factory/association_builder.rb +50 -22
  12. data/lib/lutaml/qea/factory/class_transformer.rb +24 -10
  13. data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
  14. data/lib/lutaml/qea/factory/enum_transformer.rb +20 -9
  15. data/lib/lutaml/qea/factory/generalization_builder.rb +12 -12
  16. data/lib/lutaml/qea/lookup_indexes.rb +23 -12
  17. data/lib/lutaml/uml/inheritance_walker.rb +11 -7
  18. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
  19. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
  20. data/lib/lutaml/uml_repository/index_builders/association_index.rb +20 -10
  21. data/lib/lutaml/uml_repository/index_builders/class_index.rb +5 -1
  22. data/lib/lutaml/uml_repository/queries/class_query.rb +15 -10
  23. data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
  24. data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
  25. data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
  26. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +17 -8
  27. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +19 -13
  28. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +68 -66
  29. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
  30. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +7 -2
  31. data/lib/lutaml/version.rb +1 -1
  32. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
  33. data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
  34. metadata +2 -9
  35. data/TODO.cleanups/01-resolve-production-todos.md +0 -65
  36. data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
  37. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
  38. data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
  39. data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
  40. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
  41. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69670b20b9f7eeb39ebc7fc3fa638b7f2934d077e18319af972881c3ef0522b5
4
- data.tar.gz: b77ad5274ea62236961489bfef7751d7f4ca98ddcc8e4daa5cff30469d00abd8
3
+ metadata.gz: cb62e96e62545c49b005e89cc972bc7dfb9510dbd9867831db9b6f4ab0bbcc40
4
+ data.tar.gz: 3975abff3b9a8e54714a76209d5917ed1bcf291baf9dcdf405c658d211934714
5
5
  SHA512:
6
- metadata.gz: d75318da9968bee531f257272a490b574c7541a264ce54283f35bea1cd4cc24314d9766ab8da589a326ea06edb7ac953b43c4c2db3579cc55dee46f0bee22f25
7
- data.tar.gz: 9ab593c061dfb0983ded7744859ddf3e7214dc8e64f5808dae4f54f779bab4a772b6c5af65eb5c69d1df887b2ae52837d69e872790656e9aa444cd0f91eb77e0
6
+ metadata.gz: a09b2b6074e7fb3fa04d2084b16c35ac5f9c115708ac8f50efaac08b5999cd21613053df9eebe487a714c361317e4411de2ecc8d2286b8100e3fe23102854265
7
+ data.tar.gz: 6d6c1dd06afac86c94ba1047087914b59b95d09e11308482f90bb3b99b4b65633eb443a565200842f07ce51064b14e2441a4406f408697a8fe55ef67ce33c9eb
data/.rubocop_todo.yml CHANGED
@@ -1,41 +1,18 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-12 14:40:16 UTC using RuboCop version 1.86.1.
3
+ # on 2026-05-13 02:18:49 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 6
10
- # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: EnforcedStyle, IndentationWidth.
12
- # SupportedStyles: with_first_argument, with_fixed_indentation
13
- Layout/ArgumentAlignment:
14
- Exclude:
15
- - 'lib/lutaml/qea/factory/association_builder.rb'
16
- - 'lib/lutaml/qea/factory/generalization_builder.rb'
17
- - 'lib/lutaml/uml_repository/index_builders/class_index.rb'
18
- - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
19
- - 'lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb'
20
-
21
- # Offense count: 158
9
+ # Offense count: 155
22
10
  # This cop supports safe autocorrection (--autocorrect).
23
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
24
12
  # URISchemes: http, https
25
13
  Layout/LineLength:
26
14
  Enabled: false
27
15
 
28
- # Offense count: 7
29
- # This cop supports safe autocorrection (--autocorrect).
30
- # Configuration parameters: AllowInHeredoc.
31
- Layout/TrailingWhitespace:
32
- Exclude:
33
- - 'lib/lutaml/qea/factory/generalization_builder.rb'
34
- - 'lib/lutaml/uml_repository/index_builders/class_index.rb'
35
- - 'lib/lutaml/uml_repository/queries/inheritance_query.rb'
36
- - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
37
- - 'lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb'
38
-
39
16
  # Offense count: 1
40
17
  Lint/BinaryOperatorWithIdenticalOperands:
41
18
  Exclude:
@@ -95,46 +72,48 @@ Lint/UnusedMethodArgument:
95
72
  Exclude:
96
73
  - 'lib/lutaml/cli/uml/export_command.rb'
97
74
 
98
- # Offense count: 2
75
+ # Offense count: 3
99
76
  Lint/UselessConstantScoping:
100
77
  Exclude:
101
78
  - 'lib/lutaml/cli/interactive_shell.rb'
79
+ - 'lib/lutaml/qea/factory/ea_to_uml_factory.rb'
102
80
 
103
- # Offense count: 33
81
+ # Offense count: 8
104
82
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
105
83
  Metrics/AbcSize:
106
- Enabled: false
84
+ Exclude:
85
+ - 'lib/lutaml/cli/interactive_shell/query_commands.rb'
86
+ - 'lib/lutaml/qea/factory/generalization_builder.rb'
87
+ - 'lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb'
88
+ - 'lib/lutaml/uml_repository/queries/search_query.rb'
89
+ - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
90
+ - 'lib/lutaml/xmi/liquid_drops/klass_drop.rb'
107
91
 
108
- # Offense count: 23
92
+ # Offense count: 4
109
93
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
110
94
  Metrics/CyclomaticComplexity:
111
- Enabled: false
95
+ Exclude:
96
+ - 'lib/lutaml/qea/factory/generalization_builder.rb'
97
+ - 'lib/lutaml/uml_repository/index_builders/association_index.rb'
98
+ - 'lib/lutaml/uml_repository/queries/search_query.rb'
99
+ - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
112
100
 
113
- # Offense count: 63
101
+ # Offense count: 48
114
102
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
115
103
  Metrics/MethodLength:
116
- Max: 21
104
+ Max: 18
117
105
 
118
- # Offense count: 2
106
+ # Offense count: 4
119
107
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
120
108
  Metrics/ParameterLists:
121
109
  Max: 7
122
110
 
123
- # Offense count: 12
111
+ # Offense count: 2
124
112
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
125
113
  Metrics/PerceivedComplexity:
126
114
  Exclude:
127
- - 'lib/lutaml/cli/interactive_shell/navigation_commands.rb'
128
- - 'lib/lutaml/converter/xmi_to_uml.rb'
129
- - 'lib/lutaml/qea/factory/association_builder.rb'
130
115
  - 'lib/lutaml/qea/factory/ea_to_uml_factory.rb'
131
- - 'lib/lutaml/qea/lookup_indexes.rb'
132
- - 'lib/lutaml/uml_repository/index_builders/association_index.rb'
133
- - 'lib/lutaml/uml_repository/repository/loader.rb'
134
- - 'lib/lutaml/uml_repository/static_site/data_transformer.rb'
135
116
  - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
136
- - 'lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb'
137
- - 'lib/lutaml/xmi/parsers/xmi_connector.rb'
138
117
 
139
118
  # Offense count: 3
140
119
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
@@ -154,11 +133,10 @@ Naming/PredicateMethod:
154
133
  - 'lib/lutaml/qea/factory/document_builder.rb'
155
134
  - 'spec/lutaml/xmi/liquid_drops/nil_safety_spec.rb'
156
135
 
157
- # Offense count: 3
136
+ # Offense count: 2
158
137
  # Configuration parameters: MinSize.
159
138
  Performance/CollectionLiteralInLoop:
160
139
  Exclude:
161
- - 'lib/lutaml/xmi/parsers/xmi_connector.rb'
162
140
  - 'spec/lutaml/qea/infrastructure/table_reader_spec.rb'
163
141
  - 'spec/lutaml/uml_repository/index_builder_spec.rb'
164
142
 
@@ -327,12 +305,6 @@ Style/EvalWithLocation:
327
305
  Exclude:
328
306
  - 'spec/lutaml/cli/uml/search_command_spec.rb'
329
307
 
330
- # Offense count: 2
331
- # This cop supports safe autocorrection (--autocorrect).
332
- Style/MultilineIfModifier:
333
- Exclude:
334
- - 'lib/lutaml/uml_repository/index_builders/class_index.rb'
335
-
336
308
  # Offense count: 1
337
309
  # This cop supports unsafe autocorrection (--autocorrect-all).
338
310
  Style/PartitionInsteadOfDoubleSelect:
@@ -345,19 +317,3 @@ Style/SafeNavigationChainLength:
345
317
  Exclude:
346
318
  - 'lib/lutaml/qea/validation/attribute_validator.rb'
347
319
  - 'lib/lutaml/qea/validation/operation_validator.rb'
348
-
349
- # Offense count: 8
350
- # This cop supports safe autocorrection (--autocorrect).
351
- # Configuration parameters: EnforcedStyle.
352
- # SupportedStyles: single_quotes, double_quotes
353
- Style/StringLiteralsInInterpolation:
354
- Exclude:
355
- - 'lib/lutaml/cli/interactive_shell/help_display.rb'
356
-
357
- # Offense count: 1
358
- # This cop supports safe autocorrection (--autocorrect).
359
- # Configuration parameters: EnforcedStyle, AllowSafeAssignment.
360
- # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
361
- Style/TernaryParentheses:
362
- Exclude:
363
- - 'lib/lutaml/qea/factory/association_builder.rb'
@@ -13,19 +13,17 @@ module Lutaml
13
13
  }.freeze
14
14
 
15
15
  def cmd_export(args)
16
- if last_results.nil? || last_results.empty?
17
- puts OutputFormatter.warning("No results to export")
18
- return
19
- end
16
+ return warn_no_results if last_results.nil? || last_results.empty?
17
+ return warn_export_usage unless valid_export_args?(args)
20
18
 
21
- if args.size < 3 || args[0] != "last"
22
- puts OutputFormatter.warning("Usage: export last FORMAT FILE")
23
- return
24
- end
19
+ dispatch_export(args[1].downcase, args[2])
20
+ end
25
21
 
26
- format = args[1].downcase
27
- file_path = args[2]
22
+ def valid_export_args?(args)
23
+ args.size >= 3 && args[0] == "last"
24
+ end
28
25
 
26
+ def dispatch_export(format, file_path)
29
27
  exporter = EXPORT_FORMATS[format]
30
28
  if exporter
31
29
  public_send(exporter, file_path)
@@ -34,6 +32,14 @@ module Lutaml
34
32
  end
35
33
  end
36
34
 
35
+ def warn_no_results
36
+ puts OutputFormatter.warning("No results to export")
37
+ end
38
+
39
+ def warn_export_usage
40
+ puts OutputFormatter.warning("Usage: export last FORMAT FILE")
41
+ end
42
+
37
43
  def export_csv(file_path)
38
44
  require "csv"
39
45
 
@@ -95,14 +95,18 @@ module Lutaml
95
95
  return path if path.start_with?("ModelRoot")
96
96
  return current_path if path == "."
97
97
  return "ModelRoot" if path == "/"
98
+ return resolve_parent_path(path) if path.start_with?("../")
99
+ return resolve_child_path(path) if path.start_with?("./")
98
100
 
99
- if path.start_with?("../")
100
- resolve_parent_path(path)
101
- elsif path.start_with?("./")
102
- "#{current_path}::#{path[2..]}"
103
- else
104
- current_path == "ModelRoot" ? path : "#{current_path}::#{path}"
105
- end
101
+ resolve_simple_path(path)
102
+ end
103
+
104
+ def resolve_child_path(path)
105
+ "#{current_path}::#{path[2..]}"
106
+ end
107
+
108
+ def resolve_simple_path(path)
109
+ current_path == "ModelRoot" ? path : "#{current_path}::#{path}"
106
110
  end
107
111
 
108
112
  private
@@ -172,8 +172,14 @@ module Lutaml
172
172
  puts ""
173
173
  puts "Name: #{cls.name}"
174
174
 
175
- return unless cls.is_a?(Lutaml::Uml::Classifier) && cls.attributes && !cls.attributes.empty?
175
+ display_class_attributes(cls) if has_displayable_attributes?(cls)
176
+ end
177
+
178
+ def has_displayable_attributes?(cls)
179
+ cls.is_a?(Lutaml::Uml::Classifier) && cls.attributes && !cls.attributes.empty?
180
+ end
176
181
 
182
+ def display_class_attributes(cls)
177
183
  puts ""
178
184
  puts OutputFormatter.colorize("Attributes:", :yellow)
179
185
  cls.attributes.each do |attr|
@@ -188,6 +194,10 @@ module Lutaml
188
194
  puts "Name: #{pkg.name}"
189
195
  puts ""
190
196
 
197
+ display_package_classes(path)
198
+ end
199
+
200
+ def display_package_classes(path)
191
201
  classes = repository.classes_in_package(path)
192
202
  puts OutputFormatter.colorize("Classes (#{classes.size}):", :yellow)
193
203
  classes.each do |cls|
@@ -88,14 +88,22 @@ module Lutaml
88
88
  deduplicate_history(input)
89
89
  execute_command(input.strip)
90
90
  rescue Interrupt
91
- puts "\nUse 'exit' or 'quit' to exit the shell"
91
+ handle_interrupt
92
92
  rescue StandardError => e
93
- puts OutputFormatter.error("Error: #{e.message}")
94
- puts e.backtrace.first(3).join("\n") if ENV["DEBUG"]
93
+ handle_error(e)
95
94
  end
96
95
  end
97
96
  end
98
97
 
98
+ def handle_interrupt
99
+ puts "\nUse 'exit' or 'quit' to exit the shell"
100
+ end
101
+
102
+ def handle_error(error)
103
+ puts OutputFormatter.error("Error: #{error.message}")
104
+ puts error.backtrace.first(3).join("\n") if ENV["DEBUG"]
105
+ end
106
+
99
107
  def deduplicate_history(input)
100
108
  if Readline::HISTORY.length > 1 && Readline::HISTORY[-2] == input
101
109
  Readline::HISTORY.pop
@@ -275,13 +275,21 @@ module Lutaml
275
275
 
276
276
  # Determine class type (class, interface, enumeration)
277
277
  def determine_class_type(klass)
278
- return :enumeration if klass.class.name&.include?("Enum")
279
- return :interface if klass.is_a?(Lutaml::Uml::TopElement) &&
280
- Array(klass.stereotype).any? { |s| s&.downcase == "interface" }
278
+ return :enumeration if enumeration?(klass)
279
+ return :interface if interface?(klass)
281
280
 
282
281
  :class
283
282
  end
284
283
 
284
+ def enumeration?(klass)
285
+ klass.class.name&.include?("Enum")
286
+ end
287
+
288
+ def interface?(klass)
289
+ klass.is_a?(Lutaml::Uml::TopElement) &&
290
+ Array(klass.stereotype).any? { |s| s&.downcase == "interface" }
291
+ end
292
+
285
293
  # Find package path for a package object
286
294
  def find_package_path(repository, package)
287
295
  repository.indexes[:package_paths].each do |path, pkg|
@@ -148,12 +148,17 @@ module Lutaml
148
148
  # Lazy-built hash index for O(1) diagram lookups by package
149
149
  # @return [Hash] Mapping of package_id => [diagrams]
150
150
  def diagram_lookup
151
- @diagram_lookup ||= begin
152
- idx = Hash.new { |h, k| h[k] = [] }
153
- diagrams = @xmi_root_model.extension&.diagrams&.diagram || []
154
- diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
155
- idx
156
- end
151
+ @diagram_lookup ||= build_diagram_index
152
+ end
153
+
154
+ def build_diagram_index
155
+ idx = Hash.new { |h, k| h[k] = [] }
156
+ xmi_diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
157
+ idx
158
+ end
159
+
160
+ def xmi_diagrams
161
+ @xmi_root_model.extension&.diagrams&.diagram || []
157
162
  end
158
163
 
159
164
  def create_uml_class_attributes(klass) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
@@ -227,50 +227,65 @@ module Lutaml
227
227
  field_table
228
228
  end
229
229
 
230
- def format_class(node, hide_members) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
230
+ def format_class(node, hide_members) # rubocop:disable Metrics/MethodLength
231
231
  name = ["<B>#{node.name}</B>"]
232
232
  name.unshift("«#{node.keyword}»") if node.keyword
233
- name_html = <<~HEREDOC
234
- <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
235
- #{name.map { |n| %(<TR><TD ALIGN="CENTER">#{n}</TD></TR>) }.join('\n')}
236
- </TABLE>
237
- HEREDOC
233
+ name_html = build_name_table(name)
238
234
 
239
235
  field_table = format_member_rows(node.attributes, hide_members)
240
236
  method_table = if node.operations&.any?
241
237
  format_member_rows(node.operations, hide_members)
242
238
  end
243
- table_body = [name_html, field_table, method_table].map do |type|
244
- next if type.nil?
239
+ table_body = build_table_body(name_html, field_table, method_table)
240
+
241
+ <<~HEREDOC.chomp
242
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="10">
243
+ #{table_body}
244
+ </TABLE>
245
+ HEREDOC
246
+ end
247
+
248
+ def build_name_table(name_parts)
249
+ <<~HEREDOC
250
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
251
+ #{name_parts.map { |n| %(<TR><TD ALIGN="CENTER">#{n}</TD></TR>) }.join('\n')}
252
+ </TABLE>
253
+ HEREDOC
254
+ end
245
255
 
256
+ def build_table_body(name_html, field_table, method_table)
257
+ [name_html, field_table, method_table].compact.filter_map do |type|
246
258
  <<~TEXT
247
259
  <TR>
248
260
  <TD>#{type}</TD>
249
261
  </TR>
250
262
  TEXT
251
- end
252
-
253
- <<~HEREDOC.chomp
254
- <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="10">
255
- #{table_body.compact.join("\n")}
256
- </TABLE>
257
- HEREDOC
263
+ end.join("\n")
258
264
  end
259
265
 
260
- def format_document(node) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
266
+ def format_document(node)
261
267
  @fontname = node.fontname || DEFAULT_CLASS_FONT
262
268
  @node["fontname"] = "#{@fontname}-bold"
263
269
 
270
+ hide_members, hide_other_classes = extract_fidelity_options(node)
271
+ classes = format_all_classes(node, hide_members)
272
+ associations = build_associations(node, hide_other_classes)
273
+
274
+ build_digraph(classes, associations)
275
+ end
276
+
277
+ def extract_fidelity_options(node)
264
278
  if node.fidelity
265
- hide_members = node.fidelity.hideMembers
266
- hide_other_classes = node.fidelity.hideOtherClasses
279
+ [node.fidelity.hideMembers, node.fidelity.hideOtherClasses]
280
+ else
281
+ [nil, nil]
267
282
  end
268
- classes = (node.classes +
269
- node.enums +
270
- node.data_types +
271
- node.primitives).map do |class_node|
272
- graph_node_name = generate_graph_name(class_node.name)
283
+ end
273
284
 
285
+ def format_all_classes(node, hide_members)
286
+ all_classes = node.classes + node.enums + node.data_types + node.primitives
287
+ all_classes.map do |class_node|
288
+ graph_node_name = generate_graph_name(class_node.name)
274
289
  <<~HEREDOC
275
290
  #{graph_node_name} [
276
291
  shape="plain"
@@ -278,25 +293,36 @@ module Lutaml
278
293
  label=<#{format_class(class_node, hide_members)}>]
279
294
  HEREDOC
280
295
  end.join("\n")
281
- associations = node.classes.filter_map(&:associations).flatten +
282
- node.associations
296
+ end
297
+
298
+ def build_associations(node, hide_other_classes)
299
+ associations = collect_all_associations(node)
283
300
  if node.groups
284
301
  associations = sort_by_document_grouping(node.groups,
285
302
  associations)
286
303
  end
304
+
287
305
  classes_names = node.classes.map(&:name)
288
- associations = associations.map do |assoc_node|
289
- if hide_other_classes &&
290
- !classes_names.include?(assoc_node.member_end)
291
- next
292
- end
306
+ format_filtered_associations(associations, classes_names,
307
+ hide_other_classes)
308
+ end
309
+
310
+ def collect_all_associations(node)
311
+ node.classes.filter_map(&:associations).flatten + node.associations
312
+ end
313
+
314
+ def format_filtered_associations(associations, classes_names,
315
+ hide_other_classes)
316
+ associations.filter_map do |assoc_node|
317
+ next if hide_other_classes && !classes_names.include?(assoc_node.member_end)
293
318
 
294
319
  format_relationship(assoc_node)
295
320
  end.join("\n")
321
+ end
296
322
 
297
- classes = classes.lines.map { |line| " #{line}" }.join.chomp
298
- associations = associations
299
- .lines.map { |line| " #{line}" }.join.chomp
323
+ def build_digraph(classes, associations)
324
+ indented_classes = indent_lines(classes)
325
+ indented_assocs = indent_lines(associations)
300
326
 
301
327
  <<~HEREDOC
302
328
  digraph G {
@@ -304,13 +330,17 @@ module Lutaml
304
330
  edge [#{@edge}]
305
331
  node [#{@node}]
306
332
 
307
- #{classes}
333
+ #{indented_classes}
308
334
 
309
- #{associations}
335
+ #{indented_assocs}
310
336
  }
311
337
  HEREDOC
312
338
  end
313
339
 
340
+ def indent_lines(text)
341
+ text.lines.map { |line| " #{line}" }.join.chomp
342
+ end
343
+
314
344
  protected
315
345
 
316
346
  def sort_by_document_grouping(groups, associations) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
@@ -55,7 +55,7 @@ module Lutaml
55
55
  # @param file_path [String] Path to the model file
56
56
  # @return [Lutaml::Uml::Document] Parsed UML document
57
57
  # @raise [ParseError] if parsing fails
58
- def parse(file_path) # rubocop:disable Metrics/MethodLength
58
+ def parse(file_path)
59
59
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
60
60
  @stats_mutex.synchronize { @parse_stats[:total_parses] += 1 }
61
61
  parse_succeeded = false
@@ -65,37 +65,35 @@ module Lutaml
65
65
  validate_file!(file_path) if should_validate_input?
66
66
  clear_errors_and_warnings
67
67
 
68
- begin
69
- # Pre-parsing hook
70
- before_parse(file_path)
71
-
72
- # Core parsing (implemented by subclasses)
73
- document = parse_internal(file_path)
68
+ parse_succeeded, result = execute_parse(file_path)
69
+ parse_handled = !parse_succeeded
70
+ result
71
+ ensure
72
+ record_parse_stats(parse_succeeded, parse_handled, start_time)
73
+ end
74
+ end
74
75
 
75
- # Post-parsing processing
76
- document = after_parse(document, file_path)
76
+ def execute_parse(file_path)
77
+ before_parse(file_path)
78
+ document = parse_internal(file_path)
79
+ document = after_parse(document, file_path)
80
+ validate_output!(document) if should_validate_output?
77
81
 
78
- # Validate output if requested
79
- validate_output!(document) if should_validate_output?
82
+ @stats_mutex.synchronize { @parse_stats[:successful_parses] += 1 }
83
+ [true, document]
84
+ rescue StandardError => e
85
+ [false, handle_parsing_error(e, file_path)]
86
+ end
80
87
 
81
- parse_succeeded = true
82
- @stats_mutex.synchronize { @parse_stats[:successful_parses] += 1 }
83
- document
84
- rescue StandardError => e
85
- parse_handled = true
86
- @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
87
- handle_parsing_error(e, file_path)
88
- end
89
- ensure
90
- unless parse_succeeded || parse_handled
91
- @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
92
- end
93
- duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
94
- @last_duration = duration
95
- @stats_mutex.synchronize do
96
- @parse_stats[:total_duration] += duration
97
- @parse_stats[:durations] << duration
98
- end
88
+ def record_parse_stats(succeeded, handled, start_time)
89
+ unless succeeded || handled
90
+ @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
91
+ end
92
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
93
+ @last_duration = duration
94
+ @stats_mutex.synchronize do
95
+ @parse_stats[:total_duration] += duration
96
+ @parse_stats[:durations] << duration
99
97
  end
100
98
  end
101
99