lutaml 0.10.13 → 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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +29 -33
  3. data/lib/lutaml/cli/interactive_shell/export_handler.rb +25 -17
  4. data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
  5. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +45 -26
  6. data/lib/lutaml/cli/interactive_shell/query_commands.rb +73 -47
  7. data/lib/lutaml/cli/interactive_shell.rb +53 -27
  8. data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
  9. data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
  10. data/lib/lutaml/formatter/graphviz.rb +65 -35
  11. data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
  12. data/lib/lutaml/qea/factory/association_builder.rb +144 -127
  13. data/lib/lutaml/qea/factory/class_transformer.rb +91 -53
  14. data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
  15. data/lib/lutaml/qea/factory/enum_transformer.rb +41 -31
  16. data/lib/lutaml/qea/factory/generalization_builder.rb +155 -125
  17. data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
  18. data/lib/lutaml/qea/lookup_indexes.rb +31 -13
  19. data/lib/lutaml/uml/inheritance_walker.rb +11 -7
  20. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
  21. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
  22. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
  23. data/lib/lutaml/uml_repository/index_builders/association_index.rb +60 -48
  24. data/lib/lutaml/uml_repository/index_builders/class_index.rb +35 -24
  25. data/lib/lutaml/uml_repository/queries/class_query.rb +79 -48
  26. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
  27. data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
  28. data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
  29. data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
  30. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +64 -35
  31. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
  32. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +36 -20
  33. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +131 -105
  34. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
  35. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +38 -24
  36. data/lib/lutaml/version.rb +1 -1
  37. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
  38. data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
  39. metadata +2 -9
  40. data/TODO.cleanups/01-resolve-production-todos.md +0 -65
  41. data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
  42. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
  43. data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
  44. data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
  45. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
  46. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +0 -57
@@ -15,34 +15,52 @@ module Lutaml
15
15
  # instead of array
16
16
  # @return [Hash] Group index
17
17
  def build_group_index(collection, method, single: false)
18
- if single
19
- collection.each_with_object({}) do |item, hash|
20
- key = item.public_send(method)
21
- hash[key] = item if key
22
- end
23
- else
24
- collection.each_with_object({}) do |item, hash|
25
- key = item.public_send(method)
26
- (hash[key] ||= []) << item if key
27
- end
18
+ collection.each_with_object({}) do |item, hash|
19
+ key = item.public_send(method)
20
+ next unless key
21
+
22
+ single ? (hash[key] = item) : ((hash[key] ||= []) << item)
28
23
  end
29
24
  end
30
25
 
31
26
  # Eagerly build all lazy lookup indexes before freezing
32
27
  def build_lookup_indexes
28
+ build_primary_indexes
29
+ build_secondary_indexes
30
+ end
31
+
32
+ def build_primary_indexes
33
+ build_object_indexes
34
+ build_feature_indexes
35
+ build_connector_indexes
36
+ build_diagram_indexes
37
+ end
38
+
39
+ def build_object_indexes
33
40
  @objects_by_guid = build_group_index(objects, :ea_guid, single: true)
41
+ @objects_by_package_id = build_group_index(objects, :package_id)
42
+ @packages_by_parent = build_group_index(packages, :parent_id)
43
+ end
44
+
45
+ def build_feature_indexes
34
46
  @attributes_by_object_id = build_group_index(attributes, :ea_object_id)
35
47
  @operations_by_object_id = build_group_index(operations, :ea_object_id)
36
48
  @operation_params_by_id = build_group_index(operation_params,
37
49
  :operationid)
50
+ end
51
+
52
+ def build_connector_indexes
38
53
  @connectors_by_start = build_group_index(connectors, :start_object_id)
39
54
  @connectors_by_end = build_group_index(connectors, :end_object_id)
40
- @packages_by_parent = build_group_index(packages, :parent_id)
41
- @objects_by_package_id = build_group_index(objects, :package_id)
55
+ end
56
+
57
+ def build_diagram_indexes
42
58
  @diagrams_by_package_id = build_group_index(diagrams, :package_id)
43
59
  @diagram_objects_by_id = build_group_index(diagram_objects, :diagram_id)
44
60
  @diagram_links_by_id = build_group_index(diagram_links, :diagramid)
45
- # Also build hash indexes for find_* methods
61
+ end
62
+
63
+ def build_secondary_indexes
46
64
  @packages_by_id = build_group_index(packages, :package_id, single: true)
47
65
  @connectors_by_id = build_group_index(connectors, :connector_id,
48
66
  single: true)
@@ -36,13 +36,7 @@ module Lutaml
36
36
  ancestors = []
37
37
  @visited.clear
38
38
  collect_ancestors(klass, ancestors)
39
- ancestors.reverse_each.with_index(1) do |ancestor, level|
40
- break if @visited.include?(ancestor.xmi_id)
41
-
42
- @visited.add(ancestor.xmi_id)
43
- yield(ancestor, level) if block_given?
44
- end
45
- ancestors.reverse_each.with_index(1)
39
+ yield_ancestors(ancestors)
46
40
  end
47
41
 
48
42
  # Get the direct supertype (immediate parent) of a class.
@@ -65,6 +59,16 @@ module Lutaml
65
59
 
66
60
  private
67
61
 
62
+ def yield_ancestors(ancestors)
63
+ ancestors.reverse_each.with_index(1) do |ancestor, level|
64
+ break if @visited.include?(ancestor.xmi_id)
65
+
66
+ @visited.add(ancestor.xmi_id)
67
+ yield(ancestor, level) if block_given?
68
+ end
69
+ ancestors.reverse_each.with_index(1)
70
+ end
71
+
68
72
  # Collect ancestors recursively into the result array.
69
73
  # Uses a trail set for cycle detection (avoids Set mutation issues across calls).
70
74
  def collect_ancestors(klass, result, trail = [])
@@ -39,7 +39,7 @@ module Lutaml
39
39
 
40
40
  ---
41
41
 
42
- [Back to Package](#{@link_resolver.package_link(pkg_path)}) | [Back to Index](../index.md)
42
+ #{build_navigation_links(pkg_path)}
43
43
  MARKDOWN
44
44
  end
45
45
 
@@ -67,21 +67,8 @@ module Lutaml
67
67
  return "" if parent.nil? && children.empty?
68
68
 
69
69
  content = "## Inheritance\n\n"
70
-
71
- if parent
72
- parent_qname = @link_resolver.qualified_name(parent)
73
- content += "**Extends**: [#{parent.name}](#{@link_resolver.class_link(parent_qname)})\n\n"
74
- end
75
-
76
- if children.any?
77
- content += "**Extended by**:\n\n"
78
- children.each do |child|
79
- child_qname = @link_resolver.qualified_name(child)
80
- content += "- [#{child.name}](#{@link_resolver.class_link(child_qname)})\n"
81
- end
82
- content += "\n"
83
- end
84
-
70
+ content += build_parent_link(parent) if parent
71
+ content += build_children_links(children) if children.any?
85
72
  content
86
73
  rescue StandardError
87
74
  ""
@@ -138,15 +125,7 @@ module Lutaml
138
125
  end
139
126
 
140
127
  def format_association_row(association, klass)
141
- source_end = association.member_end&.first
142
- target_end = association.member_end&.last
143
-
144
- end_obj = if source_end&.type&.xmi_id == klass.xmi_id
145
- target_end
146
- else
147
- source_end
148
- end
149
-
128
+ end_obj = resolve_target_end(association, klass)
150
129
  return "" unless end_obj&.type
151
130
 
152
131
  target_qname = @link_resolver.qualified_name(end_obj.type)
@@ -172,6 +151,35 @@ module Lutaml
172
151
 
173
152
  "#{content}\n"
174
153
  end
154
+
155
+ def build_navigation_links(pkg_path)
156
+ "[Back to Package](#{@link_resolver.package_link(pkg_path)}) | [Back to Index](../index.md)"
157
+ end
158
+
159
+ def build_parent_link(parent)
160
+ parent_qname = @link_resolver.qualified_name(parent)
161
+ "**Extends**: [#{parent.name}](#{@link_resolver.class_link(parent_qname)})\n\n"
162
+ end
163
+
164
+ def build_children_links(children)
165
+ content = "**Extended by**:\n\n"
166
+ children.each do |child|
167
+ child_qname = @link_resolver.qualified_name(child)
168
+ content += "- [#{child.name}](#{@link_resolver.class_link(child_qname)})\n"
169
+ end
170
+ "#{content}\n"
171
+ end
172
+
173
+ def resolve_target_end(association, klass)
174
+ source_end = association.member_end&.first
175
+ target_end = association.member_end&.last
176
+
177
+ if source_end&.type&.xmi_id == klass.xmi_id
178
+ target_end
179
+ else
180
+ source_end
181
+ end
182
+ end
175
183
  end
176
184
  end
177
185
  end
@@ -54,16 +54,24 @@ module Lutaml
54
54
  indent = " " * depth
55
55
  path = node[:path]
56
56
  link = @link_resolver.package_link(path)
57
- result = "#{indent}- [#{node[:name]}](#{link})"
58
- result += " (#{node[:classes_count]} classes)" if node[:classes_count].positive?
59
- result += "\n"
60
-
61
- if node[:children]&.any?
62
- node[:children].each do |child|
63
- result += build_tree_node(child, depth + 1)
64
- end
65
- end
57
+ result = format_tree_line(indent, node[:name], link,
58
+ node[:classes_count])
59
+
60
+ append_child_nodes(result, node[:children], depth)
61
+ end
66
62
 
63
+ def format_tree_line(indent, name, link, classes_count)
64
+ line = "#{indent}- [#{name}](#{link})"
65
+ line += " (#{classes_count} classes)" if classes_count&.positive?
66
+ "#{line}\n"
67
+ end
68
+
69
+ def append_child_nodes(result, children, depth)
70
+ return result unless children&.any?
71
+
72
+ children.each do |child|
73
+ result << build_tree_node(child, depth + 1)
74
+ end
67
75
  result
68
76
  end
69
77
  end
@@ -54,32 +54,39 @@ module Lutaml
54
54
  )
55
55
 
56
56
  builder = Markdown::PackagePageBuilder.new(repository, link_resolver)
57
- packages.each do |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)
62
- end
57
+ packages.each { |pkg| write_package_page(builder, pkg) }
63
58
  end
64
59
 
65
- def generate_class_pages
66
- classes = if options[:package]
67
- repository.classes_in_package(
68
- options[:package],
69
- recursive: options.fetch(:recursive, true),
70
- )
71
- else
72
- indexes&.dig(:classes)&.values || []
73
- end
60
+ def write_package_page(builder, package)
61
+ path = link_resolver.package_path(package)
62
+ content = builder.build(package, path)
63
+ filename = link_resolver.sanitize_filename("#{path}.md")
64
+ File.write(File.join(output_dir, "packages", filename), content)
65
+ end
74
66
 
67
+ def generate_class_pages
68
+ classes = collect_export_classes
75
69
  builder = Markdown::ClassPageBuilder.new(repository, link_resolver)
76
- classes.each do |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)
70
+ classes.each { |klass| write_class_page(builder, klass) }
71
+ end
72
+
73
+ def collect_export_classes
74
+ if options[:package]
75
+ repository.classes_in_package(
76
+ options[:package],
77
+ recursive: options.fetch(:recursive, true),
78
+ )
79
+ else
80
+ indexes&.dig(:classes)&.values || []
81
81
  end
82
82
  end
83
+
84
+ def write_class_page(builder, klass)
85
+ qname = link_resolver.qualified_name(klass)
86
+ content = builder.build(klass, qname)
87
+ filename = link_resolver.sanitize_filename("#{qname}.md")
88
+ File.write(File.join(output_dir, "classes", filename), content)
89
+ end
83
90
  end
84
91
  end
85
92
  end
@@ -3,28 +3,35 @@
3
3
  module Lutaml
4
4
  module UmlRepository
5
5
  class IndexBuilder
6
- def build_association_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
7
- # Collect document-level associations (XMI format)
6
+ def build_association_index
7
+ index_document_associations
8
+ index_class_level_associations
9
+ end
10
+
11
+ def index_document_associations
8
12
  @document.associations&.each do |assoc|
9
13
  next unless assoc.xmi_id
10
14
 
11
15
  @associations[assoc.xmi_id] = assoc
12
16
  end
17
+ end
13
18
 
14
- # Collect class-level associations (QEA/EA format)
15
- # Note: This requires qualified_names index to be built first
19
+ def index_class_level_associations
16
20
  @qualified_names.each_value do |klass|
17
- next unless (klass.is_a?(Lutaml::Uml::Class) || klass.is_a?(Lutaml::Uml::DataType)) && klass.associations
21
+ next unless klassifiable?(klass) && klass.associations
18
22
 
19
23
  klass.associations.each do |assoc|
20
24
  next unless assoc.xmi_id
21
25
 
22
- # Avoid duplicates - only add if not already present
23
26
  @associations[assoc.xmi_id] ||= assoc
24
27
  end
25
28
  end
26
29
  end
27
30
 
31
+ def klassifiable?(klass)
32
+ klass.is_a?(Lutaml::Uml::Class) || klass.is_a?(Lutaml::Uml::DataType)
33
+ end
34
+
28
35
  # Build the inheritance graph index
29
36
  #
30
37
  # Creates a hash mapping parent qualified names to arrays of
@@ -49,70 +56,68 @@ module Lutaml
49
56
  #
50
57
  # @param classes [Array<Lutaml::Uml::Class>] Classes to process
51
58
  # @param package_path [String] Package path for these classes
52
- def process_generalizations(classes, package_path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
59
+ def process_generalizations(classes, package_path)
53
60
  return unless classes
54
61
 
55
62
  classes.each do |klass|
56
63
  next unless klass.name
57
64
 
58
65
  child_qname = "#{package_path}::#{klass.name}"
66
+ index_generalization_edge(child_qname, klass, package_path)
67
+ index_inheritance_assoc_edges(child_qname, klass, package_path)
68
+ end
69
+ end
59
70
 
60
- # Handle generalization attribute
61
- if klass.generalization
62
- parent_name = extract_parent_name(klass.generalization)
63
- if parent_name
64
- parent_qname = resolve_qualified_name(parent_name, package_path)
65
- if parent_qname && child_qname != parent_qname
66
- @inheritance_graph[parent_qname] ||= []
67
- @inheritance_graph[parent_qname] << child_qname
68
- end
69
- end
70
- end
71
-
72
- # Handle inheritance associations
73
- next unless klass.associations
71
+ def index_generalization_edge(child_qname, klass, package_path)
72
+ return unless klass.generalization
74
73
 
75
- klass.associations.each do |assoc|
76
- next unless assoc.member_end_type
77
- next unless assoc.member_end_type == "inheritance"
74
+ parent_name = extract_parent_name(klass.generalization)
75
+ return unless parent_name
78
76
 
79
- parent_name = assoc.member_end
80
- next unless parent_name
77
+ parent_qname = resolve_qualified_name(parent_name, package_path)
78
+ return unless parent_qname && child_qname != parent_qname
81
79
 
82
- parent_name = parent_name.name if parent_name.is_a?(Lutaml::Uml::Generalization)
83
- next unless parent_name.is_a?(String) && !parent_name.empty?
80
+ (@inheritance_graph[parent_qname] ||= []) << child_qname
81
+ end
84
82
 
85
- parent_qname = resolve_qualified_name(parent_name, package_path)
86
- next unless parent_qname
87
- next if child_qname == parent_qname
83
+ def index_inheritance_assoc_edges(child_qname, klass, package_path)
84
+ return unless klass.associations
88
85
 
89
- @inheritance_graph[parent_qname] ||= []
90
- @inheritance_graph[parent_qname] << child_qname
91
- end
86
+ klass.associations
87
+ .select { |assoc| assoc.member_end_type == "inheritance" }
88
+ .each do |assoc|
89
+ index_inheritance_edge(child_qname, assoc,
90
+ package_path)
92
91
  end
93
92
  end
94
93
 
94
+ def index_inheritance_edge(child_qname, assoc, package_path)
95
+ parent_name = resolve_parent_name_from_assoc(assoc)
96
+ return unless parent_name
97
+
98
+ parent_qname = resolve_qualified_name(parent_name, package_path)
99
+ return unless parent_qname && child_qname != parent_qname
100
+
101
+ (@inheritance_graph[parent_qname] ||= []) << child_qname
102
+ end
103
+
104
+ def resolve_parent_name_from_assoc(assoc)
105
+ parent_name = assoc.member_end
106
+ return nil unless parent_name
107
+
108
+ parent_name = parent_name.name if parent_name.is_a?(Lutaml::Uml::Generalization)
109
+ parent_name.is_a?(String) && !parent_name.empty? ? parent_name : nil
110
+ end
111
+
95
112
  # Extract parent name from generalization object
96
113
  #
97
114
  # @param generalization [Lutaml::Uml::Generalization]
98
115
  # Generalization object
99
116
  # @return [String, nil] Parent class name
100
- def extract_parent_name(generalization) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
117
+ def extract_parent_name(generalization)
101
118
  return nil unless generalization
102
119
 
103
- # Check for general attribute (could be a string or object)
104
- if generalization.general
105
- parent = generalization.general
106
- return parent.name if parent
107
- return parent.to_s if parent
108
- end
109
-
110
- # Check for name attribute directly
111
- if generalization.name
112
- return generalization.name
113
- end
114
-
115
- nil
120
+ name_from_general(generalization) || generalization.name
116
121
  end
117
122
 
118
123
  # Resolve a class name to its qualified name
@@ -124,6 +129,13 @@ module Lutaml
124
129
  # @param name [String] Class name to resolve
125
130
  # @param current_package_path [String] Current package context
126
131
  # @return [String, nil] Resolved qualified name
132
+ def name_from_general(generalization)
133
+ parent = generalization.general
134
+ return nil unless parent
135
+
136
+ parent.respond_to?(:name) ? parent.name : parent.to_s
137
+ end
138
+
127
139
  def resolve_qualified_name(name, current_package_path)
128
140
  # If name contains "::", it might already be qualified
129
141
  return name if @qualified_names.key?(name)
@@ -8,8 +8,12 @@ module Lutaml
8
8
  # Creates a hash mapping qualified names to Class/DataType/Enum objects:
9
9
  # "ModelRoot::i-UR::urf::Building" => Class{}
10
10
  # @api public
11
- def build_qualified_name_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
12
- # Index top-level classes, data_types, and enums from document
11
+ def build_qualified_name_index
12
+ index_document_classifiers
13
+ index_package_classifiers
14
+ end
15
+
16
+ def index_document_classifiers
13
17
  if @document.classes
14
18
  index_classifiers(@document.classes,
15
19
  ROOT_PACKAGE_NAME)
@@ -19,8 +23,9 @@ module Lutaml
19
23
  ROOT_PACKAGE_NAME)
20
24
  end
21
25
  index_classifiers(@document.enums, ROOT_PACKAGE_NAME) if @document.enums
26
+ end
22
27
 
23
- # Traverse packages and index their classifiers
28
+ def index_package_classifiers
24
29
  traverse_packages(@document.packages,
25
30
  parent_path: ROOT_PACKAGE_NAME) do |package, path|
26
31
  index_classifiers(package.classes, path) if package.classes
@@ -35,13 +40,18 @@ module Lutaml
35
40
  # "featureType" => [Class{}, Class{}],
36
41
  # "dataType" => [Class{}]
37
42
  # @api public
38
- def build_stereotype_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
39
- # Process top-level classes
43
+ def build_stereotype_index
44
+ index_document_stereotypes
45
+ index_package_stereotypes
46
+ end
47
+
48
+ def index_document_stereotypes
40
49
  index_by_stereotype(@document.classes) if @document.classes
41
50
  index_by_stereotype(@document.data_types) if @document.data_types
42
51
  index_by_stereotype(@document.enums) if @document.enums
52
+ end
43
53
 
44
- # Process classes in packages
54
+ def index_package_stereotypes
45
55
  traverse_packages(@document.packages) do |package, _path|
46
56
  index_by_stereotype(package.classes) if package.classes
47
57
  index_by_stereotype(package.data_types) if package.data_types
@@ -53,42 +63,43 @@ module Lutaml
53
63
  #
54
64
  # @param classifiers [Array] Array of classifier objects
55
65
  # @param package_path [String] Package path for these classifiers
56
- def index_classifiers(classifiers, package_path) # rubocop:disable Metrics/MethodLength
66
+ def index_classifiers(classifiers, package_path)
57
67
  return unless classifiers
58
68
 
59
69
  classifiers.each do |classifier|
60
70
  next unless classifier.name
61
71
 
62
- qualified_name = "#{package_path}::#{classifier.name}"
63
- @qualified_names[qualified_name] = classifier
64
- if classifier.xmi_id
65
- @class_to_qname[classifier.xmi_id] =
66
- qualified_name
67
- end
68
- @classes[classifier.xmi_id] = classifier if classifier.xmi_id
69
- @simple_name_to_qnames[classifier.name] ||= []
70
- @simple_name_to_qnames[classifier.name] << qualified_name
71
- (@package_to_classes[package_path] ||= []) << classifier
72
+ index_classifier(classifier, package_path)
72
73
  end
73
74
  end
74
75
 
76
+ def index_classifier(classifier, package_path)
77
+ qualified_name = "#{package_path}::#{classifier.name}"
78
+ @qualified_names[qualified_name] = classifier
79
+ @class_to_qname[classifier.xmi_id] = qualified_name if classifier.xmi_id
80
+ @classes[classifier.xmi_id] = classifier if classifier.xmi_id
81
+ (@simple_name_to_qnames[classifier.name] ||= []) << qualified_name
82
+ (@package_to_classes[package_path] ||= []) << classifier
83
+ end
84
+
75
85
  # Index classifiers by their stereotypes
76
86
  #
77
87
  # @param classifiers [Array] Array of classifier objects
78
- def index_by_stereotype(classifiers) # rubocop:disable Metrics/CyclomaticComplexity
88
+ def index_by_stereotype(classifiers)
79
89
  return unless classifiers
80
90
 
81
91
  classifiers.each do |classifier|
82
- next unless classifier.stereotype && !classifier.stereotype.empty?
92
+ next unless has_stereotype?(classifier)
83
93
 
84
- # Handle both String and Array stereotypes
85
- stereotypes = Array(classifier.stereotype)
86
- stereotypes.each do |stereotype|
87
- @stereotypes[stereotype] ||= []
88
- @stereotypes[stereotype] << classifier
94
+ Array(classifier.stereotype).each do |stereotype|
95
+ (@stereotypes[stereotype] ||= []) << classifier
89
96
  end
90
97
  end
91
98
  end
99
+
100
+ def has_stereotype?(classifier)
101
+ classifier.stereotype && !classifier.stereotype.empty?
102
+ end
92
103
  end
93
104
  end
94
105
  end