lutaml 0.10.13 → 0.10.14

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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +58 -18
  3. data/lib/lutaml/cli/interactive_shell/export_handler.rb +9 -7
  4. data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
  5. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +36 -21
  6. data/lib/lutaml/cli/interactive_shell/query_commands.rb +63 -47
  7. data/lib/lutaml/cli/interactive_shell.rb +43 -25
  8. data/lib/lutaml/qea/factory/association_builder.rb +114 -125
  9. data/lib/lutaml/qea/factory/class_transformer.rb +67 -43
  10. data/lib/lutaml/qea/factory/enum_transformer.rb +22 -23
  11. data/lib/lutaml/qea/factory/generalization_builder.rb +146 -116
  12. data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
  13. data/lib/lutaml/qea/lookup_indexes.rb +8 -1
  14. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
  15. data/lib/lutaml/uml_repository/index_builders/association_index.rb +49 -47
  16. data/lib/lutaml/uml_repository/index_builders/class_index.rb +30 -23
  17. data/lib/lutaml/uml_repository/queries/class_query.rb +74 -48
  18. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
  19. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +55 -35
  20. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
  21. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +27 -17
  22. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +92 -68
  23. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +32 -23
  24. data/lib/lutaml/version.rb +1 -1
  25. metadata +1 -1
@@ -11,147 +11,42 @@ module Lutaml
11
11
  def load_class_associations(object_id, object_guid)
12
12
  return [] if object_id.nil?
13
13
 
14
- associations = []
15
14
  normalized_owner_xmi_id = normalize_guid_to_xmi_format(object_guid,
16
15
  "EAID")
17
16
 
18
17
  assoc_connectors = database.connectors_for_object(object_id)
19
18
  .select { |c| ASSOC_TYPES.include?(c.connector_type) }
20
19
 
21
- assoc_connectors.each do |ea_connector|
22
- is_start = ea_connector.start_object_id == object_id
23
-
24
- owner_end_attribute_name = if is_start
25
- ea_connector.destrole
26
- else
27
- ea_connector.sourcerole
28
- end
29
-
30
- if owner_end_attribute_name.nil? || owner_end_attribute_name.empty?
31
- next
32
- end
33
-
34
- member_obj = if is_start
35
- find_object_by_id(ea_connector.end_object_id)
36
- else
37
- find_object_by_id(ea_connector.start_object_id)
38
- end
39
- next unless member_obj
40
-
41
- member_end_attribute_name = if is_start
42
- ea_connector.sourcerole
43
- else
44
- ea_connector.destrole
45
- end
46
- if member_end_attribute_name.nil? ||
47
- member_end_attribute_name.empty?
48
- member_end_attribute_name = member_obj.name
49
- end
50
-
51
- member_cardinality_str = if is_start
52
- ea_connector.destcard
53
- else
54
- ea_connector.sourcecard
55
- end
56
-
57
- associations << Lutaml::Uml::Association.new.tap do |assoc|
58
- assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
59
- "EAID")
60
- assoc.name = ea_connector.name unless ea_connector.name.nil? || ea_connector.name.empty?
61
-
62
- assoc.owner_end = find_object_by_id(object_id)&.name
63
- assoc.owner_end_xmi_id = normalized_owner_xmi_id
64
- assoc.owner_end_attribute_name = owner_end_attribute_name
65
-
66
- assoc.member_end = member_obj.name
67
- assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
68
- member_obj.ea_guid, "EAID"
69
- )
70
- assoc.member_end_attribute_name = member_end_attribute_name
71
-
72
- assoc.member_end_type = ea_connector.connector_type&.downcase
73
-
74
- if member_cardinality_str && !member_cardinality_str.empty?
75
- parsed = parse_cardinality(member_cardinality_str)
76
- if parsed[:min] || parsed[:max]
77
- assoc.member_end_cardinality = Lutaml::Uml::Cardinality.new
78
- .tap do |card|
79
- card.min = parsed[:min]
80
- card.max = parsed[:max]
81
- end
82
- end
83
- end
84
- end
20
+ assoc_connectors.filter_map do |ea_connector|
21
+ build_association(ea_connector, object_id, normalized_owner_xmi_id)
85
22
  end
86
-
87
- associations.compact
88
23
  end
89
24
 
90
25
  def load_association_attributes(object_id)
91
26
  return [] if object_id.nil?
92
27
 
93
- attributes = []
94
28
  assoc_connectors = database.connectors_for_object(object_id)
95
29
  .select { |c| ASSOC_TYPES.include?(c.connector_type) }
96
30
  obj = find_object_by_id(object_id)
97
31
  obj_pkg_name = find_package_name(obj&.package_id)
98
32
 
99
- assoc_connectors.each do |ea_connector|
33
+ assoc_connectors.filter_map do |ea_connector|
100
34
  if ea_connector.start_object_id == object_id
101
- next if ea_connector.destrole.nil? || ea_connector.destrole.empty?
102
-
103
- target_obj = find_object_by_id(ea_connector.end_object_id)
104
- next unless target_obj
105
-
106
- target_obj_pkg_name = find_package_name(target_obj.package_id)
107
-
108
- attributes << create_association_attribute(
109
- name: ea_connector.destrole,
110
- type: target_obj.name,
111
- type_xmi_id: target_obj.ea_guid,
112
- association_xmi_id: ea_connector.ea_guid,
113
- cardinality: ea_connector.destcard,
114
- definition: ea_connector.notes,
115
- gen_name: obj.name,
116
- name_ns: obj_pkg_name,
117
- type_ns: target_obj_pkg_name,
118
- is_src: false,
119
- )
35
+ build_end_attribute(ea_connector, obj, obj_pkg_name)
120
36
  elsif ea_connector.end_object_id == object_id
121
- next if ea_connector.sourcerole.nil? || ea_connector.sourcerole.empty?
122
-
123
- source_obj = find_object_by_id(ea_connector.start_object_id)
124
- next unless source_obj
125
-
126
- source_obj_pkg_name = find_package_name(source_obj.package_id)
127
-
128
- attributes << create_association_attribute(
129
- name: ea_connector.sourcerole,
130
- type: source_obj.name,
131
- type_xmi_id: source_obj.ea_guid,
132
- association_xmi_id: ea_connector.ea_guid,
133
- cardinality: ea_connector.sourcecard,
134
- definition: ea_connector.notes,
135
- gen_name: obj.name,
136
- name_ns: obj_pkg_name,
137
- type_ns: source_obj_pkg_name,
138
- )
37
+ build_start_attribute(ea_connector, obj, obj_pkg_name)
139
38
  end
140
39
  end
141
-
142
- attributes.compact
143
40
  end
144
41
 
145
- def create_association_attribute( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
42
+ def create_association_attribute( # rubocop:disable Metrics/ParameterLists
146
43
  name:, type:, type_xmi_id:,
147
44
  association_xmi_id:, cardinality:, definition:,
148
45
  gen_name:, name_ns:, type_ns:, is_src: true
149
46
  )
150
47
  Lutaml::Uml::GeneralAttribute.new.tap do |attr|
151
- attr.name = name
152
- attr.type = type
153
- attr.gen_name = gen_name
154
- attr.definition = definition
48
+ assign_assoc_attr_basic(attr, name, type, gen_name, definition,
49
+ name_ns, type_ns)
155
50
  attr.xmi_id = normalize_guid_to_xmi_format(type_xmi_id, "EAID")
156
51
  attr.association = normalize_guid_to_xmi_format(
157
52
  association_xmi_id, "EAID"
@@ -160,23 +55,117 @@ module Lutaml
160
55
  attr.id = normalize_guid_to_xmi_src_dst_format(
161
56
  association_xmi_id, "EAID", is_src
162
57
  )
163
- attr.name_ns = name_ns
164
- attr.type_ns = type_ns
165
-
166
- if cardinality && !cardinality.empty?
167
- parsed = parse_cardinality(cardinality)
168
- if parsed[:min] || parsed[:max]
169
- attr.cardinality = Lutaml::Uml::Cardinality.new.tap do |card|
170
- card.min = parsed[:min]
171
- card.max = parsed[:max]
172
- end
173
- end
174
- end
58
+ attr.cardinality = build_cardinality(cardinality)
175
59
  end
176
60
  end
177
61
 
178
62
  private
179
63
 
64
+ def assign_assoc_attr_basic(attr, name, type, gen_name,
65
+ definition, name_ns, type_ns)
66
+ attr.name = name
67
+ attr.type = type
68
+ attr.gen_name = gen_name
69
+ attr.definition = definition
70
+ attr.name_ns = name_ns
71
+ attr.type_ns = type_ns
72
+ end
73
+
74
+ def build_association(ea_connector, object_id, normalized_owner_xmi_id)
75
+ is_start = ea_connector.start_object_id == object_id
76
+ owner_role = is_start ? ea_connector.destrole : ea_connector.sourcerole
77
+ return nil if owner_role.nil? || owner_role.empty?
78
+
79
+ member_obj = resolve_member_object(ea_connector, is_start)
80
+ return nil unless member_obj
81
+
82
+ member_role = resolve_member_role(ea_connector, is_start, member_obj)
83
+ cardinality_str = is_start ? ea_connector.destcard : ea_connector.sourcecard
84
+
85
+ Lutaml::Uml::Association.new.tap do |assoc|
86
+ assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
87
+ "EAID")
88
+ assoc.name = ea_connector.name unless ea_connector.name.nil? || ea_connector.name.empty?
89
+ assign_association_ends(assoc, object_id, normalized_owner_xmi_id,
90
+ owner_role, member_obj, member_role)
91
+ assoc.member_end_type = ea_connector.connector_type&.downcase
92
+ assoc.member_end_cardinality = build_cardinality(cardinality_str)
93
+ end
94
+ end
95
+
96
+ def assign_association_ends(assoc, object_id, owner_xmi_id,
97
+ owner_role, member_obj, member_role)
98
+ assoc.owner_end = find_object_by_id(object_id)&.name
99
+ assoc.owner_end_xmi_id = owner_xmi_id
100
+ assoc.owner_end_attribute_name = owner_role
101
+ assoc.member_end = member_obj.name
102
+ assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
103
+ member_obj.ea_guid, "EAID"
104
+ )
105
+ assoc.member_end_attribute_name = member_role
106
+ end
107
+
108
+ def resolve_member_object(ea_connector, is_start)
109
+ peer_id = is_start ? ea_connector.end_object_id : ea_connector.start_object_id
110
+ find_object_by_id(peer_id)
111
+ end
112
+
113
+ def resolve_member_role(ea_connector, is_start, member_obj)
114
+ role = is_start ? ea_connector.sourcerole : ea_connector.destrole
115
+ role.nil? || role.empty? ? member_obj.name : role
116
+ end
117
+
118
+ def build_end_attribute(ea_connector, obj, obj_pkg_name)
119
+ return nil if ea_connector.destrole.nil? || ea_connector.destrole.empty?
120
+
121
+ target_obj = find_object_by_id(ea_connector.end_object_id)
122
+ return nil unless target_obj
123
+
124
+ create_association_attribute(
125
+ name: ea_connector.destrole,
126
+ type: target_obj.name,
127
+ type_xmi_id: target_obj.ea_guid,
128
+ association_xmi_id: ea_connector.ea_guid,
129
+ cardinality: ea_connector.destcard,
130
+ definition: ea_connector.notes,
131
+ gen_name: obj.name,
132
+ name_ns: obj_pkg_name,
133
+ type_ns: find_package_name(target_obj.package_id),
134
+ is_src: false,
135
+ )
136
+ end
137
+
138
+ def build_start_attribute(ea_connector, obj, obj_pkg_name)
139
+ return nil if ea_connector.sourcerole.nil? || ea_connector.sourcerole.empty?
140
+
141
+ source_obj = find_object_by_id(ea_connector.start_object_id)
142
+ return nil unless source_obj
143
+
144
+ create_association_attribute(
145
+ name: ea_connector.sourcerole,
146
+ type: source_obj.name,
147
+ type_xmi_id: source_obj.ea_guid,
148
+ association_xmi_id: ea_connector.ea_guid,
149
+ cardinality: ea_connector.sourcecard,
150
+ definition: ea_connector.notes,
151
+ gen_name: obj.name,
152
+ name_ns: obj_pkg_name,
153
+ type_ns: find_package_name(source_obj.package_id),
154
+ )
155
+ end
156
+
157
+ def build_cardinality(cardinality_str)
158
+ return nil unless cardinality_str && !cardinality_str.empty?
159
+
160
+ parsed = parse_cardinality(cardinality_str)
161
+ return nil unless parsed[:min] || parsed[:max]
162
+
163
+ Lutaml::Uml::Cardinality.new.tap do |card|
164
+ card.min = parsed[:min]
165
+ card.max = parsed[:max]
166
+ end
167
+ end
168
+
180
169
  def find_package_name(package_id)
181
170
  return nil if package_id.nil?
182
171
 
@@ -17,56 +17,80 @@ module Lutaml
17
17
  class ClassTransformer < BaseTransformer
18
18
  def transform(ea_object)
19
19
  return nil if ea_object.nil?
20
-
21
- is_class_type = ea_object.uml_class? || ea_object.interface?
22
- is_proxy = ea_object.object_type == "ProxyConnector"
23
- is_text_class = ea_object.object_type == "Text"
24
- return nil unless is_class_type || is_proxy || is_text_class
20
+ return nil unless transformable?(ea_object)
25
21
 
26
22
  Lutaml::Uml::Class.new.tap do |klass|
27
- klass.name = ea_object.name
28
- klass.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid,
29
- "EAID")
30
- klass.is_abstract = ea_object.abstract?
31
- klass.type = "Class"
32
- klass.visibility = map_visibility(ea_object.visibility)
33
-
34
- stereotypes = build_stereotypes(ea_object)
35
- klass.stereotype = stereotypes unless stereotypes.empty?
36
-
37
- klass.definition = normalize_line_endings(ea_object.note) unless
38
- ea_object.note.nil? || ea_object.note.empty?
39
-
40
- gen_builder = GeneralizationBuilder.new(database)
41
- assoc_builder = AssociationBuilder.new(database)
42
-
43
- attrs = load_attributes(ea_object.ea_object_id)
44
- assoc_attrs = gen_builder.convert_to_top_element_attributes(
45
- assoc_builder.load_association_attributes(ea_object.ea_object_id),
46
- )
47
- klass.attributes = attrs + assoc_attrs
48
-
49
- klass.operations = load_operations(ea_object.ea_object_id)
50
- klass.constraints = load_constraints(ea_object.ea_object_id)
51
- klass.tagged_values = load_tagged_values(ea_object.ea_guid)
52
- klass.tagged_values.concat(
53
- load_object_properties(ea_object.ea_object_id),
54
- )
55
-
56
- klass.generalization = gen_builder.load_generalization(
57
- ea_object.ea_object_id,
58
- )
59
- klass.association_generalization = gen_builder
60
- .load_association_generalizations(ea_object.ea_object_id)
61
-
62
- klass.associations = assoc_builder.load_class_associations(
63
- ea_object.ea_object_id, ea_object.ea_guid
64
- )
23
+ assign_basic_properties(klass, ea_object)
24
+ assign_features(klass, ea_object)
25
+ assign_relationships(klass, ea_object)
65
26
  end
66
27
  end
67
28
 
68
29
  private
69
30
 
31
+ def transformable?(ea_object)
32
+ ea_object.uml_class? || ea_object.interface? ||
33
+ ea_object.object_type == "ProxyConnector" ||
34
+ ea_object.object_type == "Text"
35
+ end
36
+
37
+ def assign_basic_properties(klass, ea_object)
38
+ klass.name = ea_object.name
39
+ klass.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid, "EAID")
40
+ klass.is_abstract = ea_object.abstract?
41
+ klass.type = "Class"
42
+ klass.visibility = map_visibility(ea_object.visibility)
43
+ assign_stereotypes(klass, ea_object)
44
+ assign_definition(klass, ea_object)
45
+ end
46
+
47
+ def assign_stereotypes(klass, ea_object)
48
+ stereotypes = build_stereotypes(ea_object)
49
+ klass.stereotype = stereotypes unless stereotypes.empty?
50
+ end
51
+
52
+ def assign_definition(klass, ea_object)
53
+ return if ea_object.note.nil? || ea_object.note.empty?
54
+
55
+ klass.definition = normalize_line_endings(ea_object.note)
56
+ end
57
+
58
+ def assign_features(klass, ea_object)
59
+ klass.attributes = load_all_attributes(ea_object)
60
+ klass.operations = load_operations(ea_object.ea_object_id)
61
+ klass.constraints = load_constraints(ea_object.ea_object_id)
62
+ klass.tagged_values = load_tagged_values(ea_object.ea_guid)
63
+ klass.tagged_values.concat(
64
+ load_object_properties(ea_object.ea_object_id),
65
+ )
66
+ end
67
+
68
+ def assign_relationships(klass, ea_object)
69
+ gen_builder = GeneralizationBuilder.new(database)
70
+ assoc_builder = AssociationBuilder.new(database)
71
+
72
+ klass.generalization = gen_builder.load_generalization(
73
+ ea_object.ea_object_id,
74
+ )
75
+ klass.association_generalization = gen_builder
76
+ .load_association_generalizations(ea_object.ea_object_id)
77
+
78
+ klass.associations = assoc_builder.load_class_associations(
79
+ ea_object.ea_object_id, ea_object.ea_guid
80
+ )
81
+ end
82
+
83
+ def load_all_attributes(ea_object)
84
+ gen_builder = GeneralizationBuilder.new(database)
85
+ assoc_builder = AssociationBuilder.new(database)
86
+
87
+ attrs = load_attributes(ea_object.ea_object_id)
88
+ assoc_attrs = gen_builder.convert_to_top_element_attributes(
89
+ assoc_builder.load_association_attributes(ea_object.ea_object_id),
90
+ )
91
+ attrs + assoc_attrs
92
+ end
93
+
70
94
  def build_stereotypes(ea_object)
71
95
  stereotypes = []
72
96
  if ea_object.stereotype && !ea_object.stereotype.empty?
@@ -13,53 +13,52 @@ module Lutaml
13
13
  # Transform EA object to UML enum
14
14
  # @param ea_object [EaObject] EA object model
15
15
  # @return [Lutaml::Uml::Enum] UML enum
16
- def transform(ea_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
16
+ def transform(ea_object)
17
17
  return nil if ea_object.nil?
18
- return nil unless ea_object.enumeration? ||
19
- (ea_object.stereotype && ea_object.stereotype.downcase == "enumeration")
18
+ return nil unless enum?(ea_object)
20
19
 
21
20
  Lutaml::Uml::Enum.new.tap do |enum|
22
- # Map basic properties
23
21
  enum.name = ea_object.name
24
22
  enum.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid,
25
23
  "EAID")
26
24
  enum.visibility = map_visibility(ea_object.visibility)
27
-
28
- # Map stereotype
29
- if ea_object.stereotype && !ea_object.stereotype.empty?
30
- enum.stereotype = [ea_object.stereotype]
31
- end
32
-
33
- # Map definition/notes
25
+ enum.stereotype = [ea_object.stereotype] if valid_stereotype?(ea_object)
34
26
  enum.definition = ea_object.note unless
35
27
  ea_object.note.nil? || ea_object.note.empty?
36
-
37
- # Load enum values (stored as attributes in EA)
38
28
  enum.values = load_enum_values(ea_object.ea_object_id)
39
-
40
- # Load and transform tagged values
41
29
  enum.tagged_values = load_tagged_values(ea_object.ea_guid)
42
30
  end
43
31
  end
44
32
 
45
33
  private
46
34
 
35
+ def enum?(ea_object)
36
+ ea_object.enumeration? ||
37
+ (ea_object.stereotype && ea_object.stereotype.downcase == "enumeration")
38
+ end
39
+
40
+ def valid_stereotype?(ea_object)
41
+ ea_object.stereotype && !ea_object.stereotype.empty?
42
+ end
43
+
47
44
  # Load enum values (literals) from attributes
48
45
  # @param object_id [Integer] Object ID
49
46
  # @return [Array<Lutaml::Uml::Value>] Enum values
50
- def load_enum_values(object_id) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
47
+ def load_enum_values(object_id)
51
48
  return [] if object_id.nil?
52
49
 
53
50
  ea_attrs = database.attributes_for_object(object_id)
54
51
  .sort_by { |a| a.pos || 0 }
55
52
 
56
- ea_attrs.filter_map do |ea_attr|
57
- Lutaml::Uml::Value.new.tap do |value|
58
- value.name = ea_attr.name
59
- value.id = normalize_guid_to_xmi_format(ea_attr.ea_guid, "EAID")
60
- value.definition = ea_attr.notes unless
61
- ea_attr.notes.nil? || ea_attr.notes.empty?
62
- end
53
+ ea_attrs.filter_map { |ea_attr| build_enum_value(ea_attr) }
54
+ end
55
+
56
+ def build_enum_value(ea_attr)
57
+ Lutaml::Uml::Value.new.tap do |value|
58
+ value.name = ea_attr.name
59
+ value.id = normalize_guid_to_xmi_format(ea_attr.ea_guid, "EAID")
60
+ value.definition = ea_attr.notes unless
61
+ ea_attr.notes.nil? || ea_attr.notes.empty?
63
62
  end
64
63
  end
65
64