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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +29 -33
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +25 -17
- data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +45 -26
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +73 -47
- data/lib/lutaml/cli/interactive_shell.rb +53 -27
- data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
- data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
- data/lib/lutaml/formatter/graphviz.rb +65 -35
- data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
- data/lib/lutaml/qea/factory/association_builder.rb +144 -127
- data/lib/lutaml/qea/factory/class_transformer.rb +91 -53
- data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
- data/lib/lutaml/qea/factory/enum_transformer.rb +41 -31
- data/lib/lutaml/qea/factory/generalization_builder.rb +155 -125
- data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
- data/lib/lutaml/qea/lookup_indexes.rb +31 -13
- data/lib/lutaml/uml/inheritance_walker.rb +11 -7
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +60 -48
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +35 -24
- data/lib/lutaml/uml_repository/queries/class_query.rb +79 -48
- data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
- data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
- data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
- data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +64 -35
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +36 -20
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +131 -105
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +38 -24
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
- metadata +2 -9
- data/TODO.cleanups/01-resolve-production-todos.md +0 -65
- data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
- data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
- data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
- data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +0 -57
|
@@ -66,7 +66,7 @@ module Lutaml
|
|
|
66
66
|
# @example Recursive query
|
|
67
67
|
# classes = query.in_package("ModelRoot::i-UR", recursive: true)
|
|
68
68
|
# # Returns classes in i-UR and all nested packages
|
|
69
|
-
def in_package(package_path_string, recursive: false)
|
|
69
|
+
def in_package(package_path_string, recursive: false)
|
|
70
70
|
return [] if package_path_string.nil? || package_path_string.empty?
|
|
71
71
|
|
|
72
72
|
pkg_to_classes = indexes[:package_to_classes]
|
|
@@ -81,69 +81,100 @@ module Lutaml
|
|
|
81
81
|
private
|
|
82
82
|
|
|
83
83
|
# O(1) indexed lookup for in_package
|
|
84
|
-
def in_package_indexed(package_path_string, pkg_to_classes, recursive:)
|
|
84
|
+
def in_package_indexed(package_path_string, pkg_to_classes, recursive:)
|
|
85
85
|
is_absolute = package_path_string.start_with?("::")
|
|
86
86
|
search_segs = package_path_string.split("::").reject(&:empty?)
|
|
87
87
|
|
|
88
88
|
results = []
|
|
89
89
|
pkg_to_classes.each do |path, classes|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
path == package_path_string ||
|
|
94
|
-
path.start_with?("#{package_path_string}::")
|
|
95
|
-
else
|
|
96
|
-
path == package_path_string
|
|
97
|
-
end
|
|
98
|
-
elsif recursive
|
|
99
|
-
# Relative: match when path ends with search segments
|
|
100
|
-
(0..(path_segs.size - search_segs.size)).any? do |i|
|
|
101
|
-
path_segs[i, search_segs.size] == search_segs
|
|
102
|
-
end
|
|
103
|
-
else
|
|
104
|
-
path_segs.size >= search_segs.size &&
|
|
105
|
-
path_segs[-search_segs.size..] == search_segs
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
results.concat(classes) if matched
|
|
90
|
+
results.concat(classes) if indexed_path_matches?(
|
|
91
|
+
path, package_path_string, is_absolute, search_segs, recursive
|
|
92
|
+
)
|
|
109
93
|
end
|
|
110
94
|
results
|
|
111
95
|
end
|
|
112
96
|
|
|
97
|
+
def indexed_path_matches?(path, package_path_string, is_absolute,
|
|
98
|
+
search_segs, recursive)
|
|
99
|
+
if is_absolute
|
|
100
|
+
indexed_absolute_match?(path, package_path_string, recursive)
|
|
101
|
+
else
|
|
102
|
+
indexed_relative_match?(path.split("::"), search_segs, recursive)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def indexed_absolute_match?(path, package_path_string, recursive)
|
|
107
|
+
if recursive
|
|
108
|
+
path == package_path_string ||
|
|
109
|
+
path.start_with?("#{package_path_string}::")
|
|
110
|
+
else
|
|
111
|
+
path == package_path_string
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def indexed_relative_match?(path_segs, search_segs, recursive)
|
|
116
|
+
if recursive
|
|
117
|
+
segments_overlap?(path_segs, search_segs)
|
|
118
|
+
else
|
|
119
|
+
segments_end_with?(path_segs, search_segs)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
113
123
|
# Fallback: original O(n) scan
|
|
114
124
|
def in_package_scan(package_path_string, recursive:)
|
|
115
125
|
package_path = Lutaml::Uml::PackagePath.new(package_path_string)
|
|
116
|
-
results = []
|
|
117
126
|
is_absolute = package_path.absolute?
|
|
118
127
|
|
|
119
|
-
indexes[:qualified_names].
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
matched = if is_absolute
|
|
123
|
-
if recursive
|
|
124
|
-
qname.package_path.starts_with?(package_path)
|
|
125
|
-
else
|
|
126
|
-
qname.package_path == package_path
|
|
127
|
-
end
|
|
128
|
-
else
|
|
129
|
-
class_pkg_segs = qname.package_path.segments
|
|
130
|
-
search_segs = package_path.segments
|
|
131
|
-
|
|
132
|
-
if recursive
|
|
133
|
-
(0..(class_pkg_segs.size - search_segs.size))
|
|
134
|
-
.any? do |i|
|
|
135
|
-
class_pkg_segs[i, search_segs.size] == search_segs
|
|
136
|
-
end
|
|
137
|
-
else
|
|
138
|
-
class_pkg_segs.size >= search_segs.size &&
|
|
139
|
-
class_pkg_segs[-search_segs.size..] == search_segs
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
results << klass if matched
|
|
128
|
+
indexes[:qualified_names].each_value.select do |klass|
|
|
129
|
+
scan_matches_package?(klass, package_path, is_absolute, recursive)
|
|
144
130
|
end
|
|
131
|
+
end
|
|
145
132
|
|
|
146
|
-
|
|
133
|
+
def scan_matches_package?(klass, package_path, is_absolute, recursive)
|
|
134
|
+
qname = resolve_qname_for(klass)
|
|
135
|
+
return false unless qname
|
|
136
|
+
|
|
137
|
+
if is_absolute
|
|
138
|
+
match_absolute_path?(qname, package_path, recursive)
|
|
139
|
+
else
|
|
140
|
+
match_relative_path?(qname, package_path, recursive)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def resolve_qname_for(klass)
|
|
145
|
+
indexes[:qualified_names].find { |_, v| v == klass }&.first
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def match_absolute_path?(qname, package_path, recursive)
|
|
149
|
+
qname = Lutaml::Uml::QualifiedName.new(qname)
|
|
150
|
+
if recursive
|
|
151
|
+
qname.package_path.starts_with?(package_path)
|
|
152
|
+
else
|
|
153
|
+
qname.package_path == package_path
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def match_relative_path?(qname_string, package_path, recursive)
|
|
158
|
+
qname = Lutaml::Uml::QualifiedName.new(qname_string)
|
|
159
|
+
class_pkg_segs = qname.package_path.segments
|
|
160
|
+
search_segs = package_path.segments
|
|
161
|
+
|
|
162
|
+
if recursive
|
|
163
|
+
segments_overlap?(class_pkg_segs, search_segs)
|
|
164
|
+
else
|
|
165
|
+
segments_end_with?(class_pkg_segs, search_segs)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def segments_overlap?(class_segs, search_segs)
|
|
170
|
+
(0..(class_segs.size - search_segs.size)).any? do |i|
|
|
171
|
+
class_segs[i, search_segs.size] == search_segs
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def segments_end_with?(class_segs, search_segs)
|
|
176
|
+
class_segs.size >= search_segs.size &&
|
|
177
|
+
class_segs[-search_segs.size..] == search_segs
|
|
147
178
|
end
|
|
148
179
|
end
|
|
149
180
|
end
|
|
@@ -33,27 +33,15 @@ module Lutaml
|
|
|
33
33
|
# parent = query.supertype("ModelRoot::Child")
|
|
34
34
|
# # Or
|
|
35
35
|
# parent = query.supertype(child_class)
|
|
36
|
-
def supertype(class_or_qname)
|
|
36
|
+
def supertype(class_or_qname)
|
|
37
37
|
klass = resolve_class(class_or_qname)
|
|
38
|
-
return nil unless klass
|
|
39
|
-
return nil unless klass.is_a?(Lutaml::Uml::Class)
|
|
40
|
-
return nil unless klass.generalization
|
|
38
|
+
return nil unless valid_supertype_target?(klass)
|
|
41
39
|
|
|
42
40
|
parent_name = extract_parent_name(klass.generalization)
|
|
43
41
|
return nil unless parent_name
|
|
44
|
-
# avoid self-references
|
|
45
42
|
return nil if parent_name == klass.name
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
qname_string = resolve_qname(class_or_qname)
|
|
49
|
-
return nil unless qname_string
|
|
50
|
-
|
|
51
|
-
qname = Lutaml::Uml::QualifiedName.new(qname_string)
|
|
52
|
-
package_path = qname.package_path.to_s
|
|
53
|
-
|
|
54
|
-
# Try to resolve parent qualified name
|
|
55
|
-
parent_qname = resolve_parent_qualified_name(parent_name,
|
|
56
|
-
package_path)
|
|
44
|
+
parent_qname = resolve_parent_qname(class_or_qname, parent_name)
|
|
57
45
|
return nil unless parent_qname
|
|
58
46
|
|
|
59
47
|
indexes[:qualified_names][parent_qname]
|
|
@@ -200,14 +188,8 @@ module Lutaml
|
|
|
200
188
|
# qualified name, or xmi_id
|
|
201
189
|
# @return [Boolean] true if circular inheritance detected
|
|
202
190
|
def has_circular_inheritance?(class_or_id, visited: Set.new)
|
|
203
|
-
qname =
|
|
204
|
-
indexes[:qualified_names].key?(class_or_id)
|
|
205
|
-
class_or_id
|
|
206
|
-
else
|
|
207
|
-
resolve_qname(class_or_id)
|
|
208
|
-
end
|
|
191
|
+
qname = resolve_to_qname(class_or_id)
|
|
209
192
|
return false unless qname
|
|
210
|
-
|
|
211
193
|
return true if visited.include?(qname)
|
|
212
194
|
|
|
213
195
|
visited.add(qname)
|
|
@@ -219,6 +201,21 @@ module Lutaml
|
|
|
219
201
|
|
|
220
202
|
private
|
|
221
203
|
|
|
204
|
+
def valid_supertype_target?(klass)
|
|
205
|
+
return false unless klass
|
|
206
|
+
return false unless klass.is_a?(Lutaml::Uml::Class)
|
|
207
|
+
|
|
208
|
+
klass.generalization ? true : false
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def resolve_parent_qname(class_or_qname, parent_name)
|
|
212
|
+
qname_string = resolve_qname(class_or_qname)
|
|
213
|
+
return nil unless qname_string
|
|
214
|
+
|
|
215
|
+
qname = Lutaml::Uml::QualifiedName.new(qname_string)
|
|
216
|
+
resolve_parent_qualified_name(parent_name, qname.package_path.to_s)
|
|
217
|
+
end
|
|
218
|
+
|
|
222
219
|
# Resolve a class by xmi_id or qualified name
|
|
223
220
|
#
|
|
224
221
|
# @param class_or_id [String] Qualified name or xmi_id
|
|
@@ -257,22 +254,24 @@ module Lutaml
|
|
|
257
254
|
# @param max_depth [Integer, nil] Maximum depth to traverse
|
|
258
255
|
# @param current_depth [Integer] Current depth
|
|
259
256
|
# @return [Array] Array of descendant class objects
|
|
260
|
-
def collect_descendants(qname_string, max_depth, current_depth)
|
|
257
|
+
def collect_descendants(qname_string, max_depth, current_depth)
|
|
261
258
|
return [] if max_depth && current_depth >= max_depth
|
|
262
259
|
|
|
263
260
|
children = direct_subtypes(qname_string)
|
|
264
261
|
result = children.dup
|
|
262
|
+
collect_child_descendants(children, max_depth, current_depth, result)
|
|
263
|
+
result
|
|
264
|
+
end
|
|
265
265
|
|
|
266
|
+
def collect_child_descendants(children, max_depth, current_depth,
|
|
267
|
+
result)
|
|
266
268
|
children.each do |child|
|
|
267
269
|
child_qname = resolve_qname(child)
|
|
268
270
|
next unless child_qname
|
|
269
271
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
result.concat(grandchildren)
|
|
272
|
+
result.concat(collect_descendants(child_qname, max_depth,
|
|
273
|
+
current_depth + 1))
|
|
273
274
|
end
|
|
274
|
-
|
|
275
|
-
result
|
|
276
275
|
end
|
|
277
276
|
|
|
278
277
|
# Extract parent name from generalization object
|
|
@@ -285,13 +284,24 @@ module Lutaml
|
|
|
285
284
|
return nil unless generalization.is_a?(Lutaml::Uml::Generalization)
|
|
286
285
|
|
|
287
286
|
parent = generalization.general
|
|
288
|
-
if parent
|
|
289
|
-
|
|
287
|
+
return extract_name_from_parent(parent) if parent
|
|
288
|
+
|
|
289
|
+
generalization.name if generalization.name
|
|
290
|
+
end
|
|
290
291
|
|
|
291
|
-
|
|
292
|
+
def resolve_to_qname(class_or_id)
|
|
293
|
+
if class_or_id.is_a?(String) &&
|
|
294
|
+
indexes[:qualified_names].key?(class_or_id)
|
|
295
|
+
class_or_id
|
|
296
|
+
else
|
|
297
|
+
resolve_qname(class_or_id)
|
|
292
298
|
end
|
|
299
|
+
end
|
|
293
300
|
|
|
294
|
-
|
|
301
|
+
def extract_name_from_parent(parent)
|
|
302
|
+
return parent.name if parent.is_a?(Lutaml::Uml::Generalization) && parent.name
|
|
303
|
+
|
|
304
|
+
parent.to_s
|
|
295
305
|
end
|
|
296
306
|
|
|
297
307
|
# Resolve a class name to its qualified name
|
|
@@ -113,7 +113,7 @@ module Lutaml
|
|
|
113
113
|
# @param query [String] Query string
|
|
114
114
|
# @param fields [Array<Symbol>] Fields to search in
|
|
115
115
|
# @return [Array<SearchResult>] Matching search result objects
|
|
116
|
-
def search_classes( # rubocop:disable Metrics/
|
|
116
|
+
def search_classes( # rubocop:disable Metrics/MethodLength
|
|
117
117
|
query, fields: %i[name documentation],
|
|
118
118
|
case_sensitive: false
|
|
119
119
|
)
|
|
@@ -122,55 +122,64 @@ module Lutaml
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
indexes[:qualified_names].filter_map do |qname, entity|
|
|
125
|
-
match_field = nil
|
|
126
|
-
qualified_name = nil
|
|
127
|
-
|
|
128
125
|
next unless entity.is_a?(Lutaml::Uml::Class)
|
|
129
126
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if entity.class.attributes.key?(field) &&
|
|
133
|
-
entity.public_send(field)&.match?(pattern)
|
|
127
|
+
match_field = find_matching_field(entity, fields, pattern)
|
|
128
|
+
next unless match_field
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
130
|
+
build_search_result(entity, :class, qname, match_field)
|
|
131
|
+
end.uniq
|
|
132
|
+
end
|
|
139
133
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
match_field: match_field,
|
|
147
|
-
)
|
|
134
|
+
def find_matching_field(entity, fields, pattern)
|
|
135
|
+
last_match = nil
|
|
136
|
+
fields.each do |field|
|
|
137
|
+
if entity.class.attributes.key?(field) &&
|
|
138
|
+
entity.public_send(field)&.match?(pattern)
|
|
139
|
+
last_match = field
|
|
148
140
|
end
|
|
149
|
-
end
|
|
141
|
+
end
|
|
142
|
+
last_match
|
|
150
143
|
end
|
|
151
144
|
|
|
152
|
-
def
|
|
145
|
+
def build_search_result(entity, type, qname, match_field, context = {})
|
|
146
|
+
SearchResult.new(
|
|
147
|
+
element: entity,
|
|
148
|
+
element_type: type,
|
|
149
|
+
qualified_name: qname,
|
|
150
|
+
package_path: extract_package_path(qname),
|
|
151
|
+
match_field: match_field,
|
|
152
|
+
match_context: context,
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def search_by_stereotype(query, case_sensitive: false)
|
|
153
157
|
pattern = regex_pattern_from_query(
|
|
154
158
|
query, case_sensitive: case_sensitive
|
|
155
159
|
)
|
|
156
160
|
|
|
157
|
-
matched_entities =
|
|
161
|
+
matched_entities = find_entities_by_stereotype_pattern(pattern)
|
|
162
|
+
matched_entities.map { |entity| build_stereotype_result(entity) }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def find_entities_by_stereotype_pattern(pattern)
|
|
166
|
+
indexes[:stereotypes]
|
|
158
167
|
.filter_map do |_stereotype, entities|
|
|
159
168
|
entities.select do |entity|
|
|
160
169
|
entity.is_a?(Lutaml::Uml::Classifier) &&
|
|
161
170
|
Array(entity.stereotype).any? { |s| s&.match?(pattern) }
|
|
162
171
|
end.uniq
|
|
163
172
|
end.uniq.flatten
|
|
173
|
+
end
|
|
164
174
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
end
|
|
175
|
+
def build_stereotype_result(entity)
|
|
176
|
+
SearchResult.new(
|
|
177
|
+
element: entity,
|
|
178
|
+
element_type: entity.class.name.split("::").last.downcase,
|
|
179
|
+
qualified_name: "",
|
|
180
|
+
package_path: "",
|
|
181
|
+
match_field: :stereotype,
|
|
182
|
+
)
|
|
174
183
|
end
|
|
175
184
|
|
|
176
185
|
# Search for packages matching the query
|
|
@@ -212,61 +221,63 @@ module Lutaml
|
|
|
212
221
|
# @param query [String] Query string
|
|
213
222
|
# @param fields [Array<Symbol>] Fields to search in
|
|
214
223
|
# @return [Array<Lutaml::Uml::Class>] Matching search result objects
|
|
215
|
-
def search_attributes(query, fields: [:name], case_sensitive: false) # rubocop:disable Metrics/
|
|
224
|
+
def search_attributes(query, fields: [:name], case_sensitive: false) # rubocop:disable Metrics/MethodLength
|
|
216
225
|
pattern = regex_pattern_from_query(
|
|
217
226
|
query, case_sensitive: case_sensitive
|
|
218
227
|
)
|
|
219
228
|
|
|
220
|
-
indexes[:qualified_names].filter_map do |class_qname, entity|
|
|
229
|
+
indexes[:qualified_names].filter_map do |class_qname, entity|
|
|
221
230
|
next unless entity.is_a?(Lutaml::Uml::Classifier) && entity.attributes
|
|
222
231
|
|
|
223
|
-
match_field =
|
|
224
|
-
|
|
225
|
-
|
|
232
|
+
match_attr, match_field = find_matching_attribute(entity, fields,
|
|
233
|
+
pattern)
|
|
234
|
+
next unless match_field
|
|
226
235
|
|
|
227
|
-
entity
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if attr.class.attributes.key?(field) &&
|
|
231
|
-
attr.public_send(field)&.match?(pattern)
|
|
236
|
+
build_attribute_result(match_attr, entity, class_qname, match_field)
|
|
237
|
+
end.uniq
|
|
238
|
+
end
|
|
232
239
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
def find_matching_attribute(entity, fields, pattern)
|
|
241
|
+
match_attr = nil
|
|
242
|
+
match_field = nil
|
|
243
|
+
entity.attributes.each do |attr|
|
|
244
|
+
fields.each do |field|
|
|
245
|
+
if attr.class.attributes.key?(field) &&
|
|
246
|
+
attr.public_send(field)&.match?(pattern)
|
|
247
|
+
match_attr = attr
|
|
248
|
+
match_field = field
|
|
237
249
|
end
|
|
238
250
|
end
|
|
251
|
+
end
|
|
252
|
+
match_field ? [match_attr, match_field] : nil
|
|
253
|
+
end
|
|
239
254
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
end
|
|
253
|
-
end.uniq
|
|
255
|
+
def build_attribute_result(attr, entity, class_qname, match_field)
|
|
256
|
+
SearchResult.new(
|
|
257
|
+
element: attr,
|
|
258
|
+
element_type: :attribute,
|
|
259
|
+
qualified_name: "#{class_qname}::#{attr.name}",
|
|
260
|
+
package_path: extract_package_path(class_qname),
|
|
261
|
+
match_field: match_field,
|
|
262
|
+
match_context: {
|
|
263
|
+
"class_name" => entity.name,
|
|
264
|
+
"class_qname" => class_qname,
|
|
265
|
+
},
|
|
266
|
+
)
|
|
254
267
|
end
|
|
255
268
|
|
|
256
269
|
# Get all associations in the model
|
|
257
270
|
#
|
|
258
271
|
# @return [Array<Lutaml::Uml::Association>] All association objects
|
|
259
|
-
def get_all_associations
|
|
272
|
+
def get_all_associations
|
|
260
273
|
all_associations = []
|
|
261
274
|
|
|
262
|
-
# Get all associations defined at document level and
|
|
263
275
|
if document.is_a?(Lutaml::Uml::Document) && document.associations
|
|
264
276
|
all_associations << document.associations
|
|
265
277
|
end
|
|
266
278
|
|
|
267
|
-
# Get all associations defined within classes
|
|
268
279
|
indexes[:qualified_names].each_value do |entity|
|
|
269
|
-
next unless
|
|
280
|
+
next unless classifiable_with_associations?(entity)
|
|
270
281
|
|
|
271
282
|
all_associations << entity.associations
|
|
272
283
|
end
|
|
@@ -274,12 +285,16 @@ module Lutaml
|
|
|
274
285
|
all_associations.flatten.uniq
|
|
275
286
|
end
|
|
276
287
|
|
|
288
|
+
def classifiable_with_associations?(entity)
|
|
289
|
+
(entity.is_a?(Lutaml::Uml::Class) || entity.is_a?(Lutaml::Uml::DataType)) && entity.associations
|
|
290
|
+
end
|
|
291
|
+
|
|
277
292
|
# Search for associations matching the query
|
|
278
293
|
#
|
|
279
294
|
# @param query [String] Query string
|
|
280
295
|
# @param fields [Array<Symbol>] Fields to search in
|
|
281
296
|
# @return [Array<SearchResult>] Matching search result objects
|
|
282
|
-
def search_associations(query, # rubocop:disable Metrics/
|
|
297
|
+
def search_associations(query, # rubocop:disable Metrics/MethodLength
|
|
283
298
|
fields: %i[
|
|
284
299
|
name owner_end member_end owner_end_attribute_name
|
|
285
300
|
member_end_attribute_name documentation
|
|
@@ -291,27 +306,20 @@ module Lutaml
|
|
|
291
306
|
)
|
|
292
307
|
|
|
293
308
|
all_associations.filter_map do |assoc|
|
|
294
|
-
match_field =
|
|
309
|
+
match_field = find_matching_field(assoc, fields, pattern)
|
|
310
|
+
next unless match_field
|
|
295
311
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
package_path: "",
|
|
308
|
-
match_field: match_field,
|
|
309
|
-
match_context: {
|
|
310
|
-
"source" => assoc.owner_end,
|
|
311
|
-
"target" => assoc.member_end,
|
|
312
|
-
},
|
|
313
|
-
)
|
|
314
|
-
end
|
|
312
|
+
SearchResult.new(
|
|
313
|
+
element: assoc,
|
|
314
|
+
element_type: :association,
|
|
315
|
+
qualified_name: assoc.name || "(unnamed)",
|
|
316
|
+
package_path: "",
|
|
317
|
+
match_field: match_field,
|
|
318
|
+
match_context: {
|
|
319
|
+
"source" => assoc.owner_end,
|
|
320
|
+
"target" => assoc.member_end,
|
|
321
|
+
},
|
|
322
|
+
)
|
|
315
323
|
end.uniq
|
|
316
324
|
end
|
|
317
325
|
|
|
@@ -68,10 +68,17 @@ module Lutaml
|
|
|
68
68
|
# @param obj [Object] The object to extract path from
|
|
69
69
|
# @return [PackagePath, nil] The object's package path
|
|
70
70
|
def extract_package_path(obj)
|
|
71
|
-
return nil unless
|
|
71
|
+
return nil unless serializable_with_path?(obj)
|
|
72
|
+
|
|
73
|
+
coerce_package_path(obj.package_path)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def serializable_with_path?(obj)
|
|
77
|
+
obj.is_a?(Lutaml::Model::Serializable) &&
|
|
72
78
|
obj.class.attributes&.key?(:package_path)
|
|
79
|
+
end
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
def coerce_package_path(path)
|
|
75
82
|
return nil unless path
|
|
76
83
|
|
|
77
84
|
path.is_a?(PackagePath) ? path : PackagePath.new(path)
|
|
@@ -64,19 +64,26 @@ module Lutaml
|
|
|
64
64
|
def self.from_file_cached(xmi_path, lur_path: nil)
|
|
65
65
|
lur_path ||= xmi_path.sub(/\.xmi$/i, ".lur")
|
|
66
66
|
|
|
67
|
-
if
|
|
67
|
+
if cache_valid?(lur_path, xmi_path)
|
|
68
68
|
puts "Using cached LUR package: #{lur_path}" if $VERBOSE
|
|
69
69
|
from_package(lur_path)
|
|
70
70
|
else
|
|
71
|
-
|
|
72
|
-
repo = from_xmi(xmi_path)
|
|
73
|
-
|
|
74
|
-
puts "Caching as LUR package: #{lur_path}" if $VERBOSE
|
|
75
|
-
repo.export_to_package(lur_path)
|
|
76
|
-
repo
|
|
71
|
+
build_and_cache(xmi_path, lur_path)
|
|
77
72
|
end
|
|
78
73
|
end
|
|
79
74
|
|
|
75
|
+
def self.cache_valid?(lur_path, xmi_path)
|
|
76
|
+
File.exist?(lur_path) && File.mtime(lur_path) >= File.mtime(xmi_path)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.build_and_cache(xmi_path, lur_path)
|
|
80
|
+
puts "Building repository from XMI..." if $VERBOSE
|
|
81
|
+
repo = from_xmi(xmi_path)
|
|
82
|
+
puts "Caching as LUR package: #{lur_path}" if $VERBOSE
|
|
83
|
+
repo.export_to_package(lur_path)
|
|
84
|
+
repo
|
|
85
|
+
end
|
|
86
|
+
|
|
80
87
|
# Load a Repository from a LUR package file.
|
|
81
88
|
#
|
|
82
89
|
# @param lur_path [String] Path to the .lur package file
|