lutaml 0.10.0 → 0.10.2
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 +73 -31
- data/Gemfile +1 -5
- data/lib/lutaml/cli/enhanced_formatter.rb +4 -2
- data/lib/lutaml/cli/tree_view_formatter.rb +1 -1
- data/lib/lutaml/cli/uml/diagram_command.rb +2 -6
- data/lib/lutaml/converter/xmi_to_uml.rb +11 -7
- data/lib/lutaml/ea/diagram/extractor.rb +8 -3
- data/lib/lutaml/model_transformations/parsers/base_parser.rb +90 -13
- data/lib/lutaml/qea/factory/class_transformer.rb +8 -19
- data/lib/lutaml/qea/factory/data_type_transformer.rb +2 -9
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/enum_transformer.rb +3 -5
- data/lib/lutaml/qea/factory/generalization_transformer.rb +1 -1
- data/lib/lutaml/qea/factory/package_transformer.rb +45 -19
- data/lib/lutaml/qea/validation/base_validator.rb +5 -0
- data/lib/lutaml/qea/verification/document_normalizer.rb +4 -12
- data/lib/lutaml/qea/verification/document_verifier.rb +25 -15
- data/lib/lutaml/uml_repository/index_builder.rb +27 -9
- data/lib/lutaml/uml_repository/package_exporter.rb +14 -3
- data/lib/lutaml/uml_repository/presenters/diagram_presenter.rb +2 -7
- data/lib/lutaml/uml_repository/queries/inheritance_query.rb +66 -0
- data/lib/lutaml/uml_repository/queries/search_query.rb +1 -1
- data/lib/lutaml/uml_repository/repository.rb +34 -0
- data/lib/lutaml/uml_repository/validators/repository_validator.rb +1 -1
- data/lib/lutaml/uml_repository/web_ui/app.rb +25 -7
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/parsers/xmi_base.rb +67 -150
- data/lutaml.gemspec +3 -0
- metadata +44 -2
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative "base_transformer"
|
|
4
4
|
require_relative "tagged_value_transformer"
|
|
5
5
|
require_relative "instance_transformer"
|
|
6
|
+
require_relative "enum_transformer"
|
|
7
|
+
require_relative "data_type_transformer"
|
|
6
8
|
require "lutaml/uml"
|
|
7
9
|
|
|
8
10
|
module Lutaml
|
|
@@ -32,7 +34,7 @@ module Lutaml
|
|
|
32
34
|
|
|
33
35
|
# Load stereotype from t_xref
|
|
34
36
|
stereotype = load_stereotype(ea_package.ea_guid)
|
|
35
|
-
pkg.stereotype = stereotype if stereotype
|
|
37
|
+
pkg.stereotype = [stereotype] if stereotype
|
|
36
38
|
|
|
37
39
|
# Note: Child packages and contents will be loaded separately
|
|
38
40
|
# to avoid circular dependencies and allow lazy loading
|
|
@@ -97,30 +99,54 @@ module Lutaml
|
|
|
97
99
|
|
|
98
100
|
ea_objects = rows.map { |row| Models::EaObject.from_db_row(row) }
|
|
99
101
|
|
|
100
|
-
# Transform classes - include ALL class-type objects,
|
|
101
|
-
# even without names
|
|
102
|
-
# Also include Text objects that appear on diagrams
|
|
103
|
-
# (EA exports these as classes in XMI)
|
|
104
102
|
class_transformer = ClassTransformer.new(database)
|
|
103
|
+
enum_transformer = EnumTransformer.new(database)
|
|
104
|
+
data_type_transformer = DataTypeTransformer.new(database)
|
|
105
|
+
instance_transformer = InstanceTransformer.new(database)
|
|
106
|
+
|
|
105
107
|
ea_objects.each do |ea_obj|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
if enum_object?(ea_obj)
|
|
109
|
+
uml_enum = enum_transformer.transform(ea_obj)
|
|
110
|
+
pkg.enums << uml_enum if uml_enum
|
|
111
|
+
elsif data_type_object?(ea_obj)
|
|
112
|
+
uml_dt = data_type_transformer.transform(ea_obj)
|
|
113
|
+
pkg.data_types << uml_dt if uml_dt
|
|
114
|
+
elsif class_object?(ea_obj)
|
|
115
|
+
uml_class = class_transformer.transform(ea_obj)
|
|
116
|
+
pkg.classes << uml_class if uml_class
|
|
117
|
+
elsif ea_obj.instance?
|
|
118
|
+
uml_instance = instance_transformer.transform(ea_obj)
|
|
119
|
+
pkg.instances << uml_instance if uml_instance
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
109
123
|
|
|
110
|
-
|
|
124
|
+
# Check if an EA object should be classified as an enum.
|
|
125
|
+
# EA stores enums as either object_type="Enumeration" or
|
|
126
|
+
# object_type="Class" with stereotype="enumeration".
|
|
127
|
+
def enum_object?(ea_obj)
|
|
128
|
+
ea_obj.enumeration? || stereotype_is?(ea_obj, "enumeration")
|
|
129
|
+
end
|
|
111
130
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
# Check if an EA object should be classified as a data type.
|
|
132
|
+
def data_type_object?(ea_obj)
|
|
133
|
+
ea_obj.data_type?
|
|
134
|
+
end
|
|
115
135
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
# Check if an EA object should be classified as a class.
|
|
137
|
+
def class_object?(ea_obj)
|
|
138
|
+
return false if enum_object?(ea_obj) || data_type_object?(ea_obj)
|
|
139
|
+
|
|
140
|
+
ea_obj.uml_class? || ea_obj.interface? ||
|
|
141
|
+
ea_obj.object_type == "Text" ||
|
|
142
|
+
ea_obj.object_type == "ProxyConnector"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Check if an object's stereotype matches (case-insensitive).
|
|
146
|
+
def stereotype_is?(ea_obj, expected)
|
|
147
|
+
return false unless ea_obj.stereotype
|
|
122
148
|
|
|
123
|
-
|
|
149
|
+
ea_obj.stereotype.downcase == expected
|
|
124
150
|
end
|
|
125
151
|
|
|
126
152
|
# Load diagrams for a package
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
3
4
|
require_relative "validation_result"
|
|
4
5
|
|
|
5
6
|
module Lutaml
|
|
@@ -247,9 +248,13 @@ module Lutaml
|
|
|
247
248
|
|
|
248
249
|
path_parts = []
|
|
249
250
|
current_id = package_id
|
|
251
|
+
visited = Set.new
|
|
250
252
|
|
|
251
253
|
# Walk up the parent chain to build full path
|
|
252
254
|
while current_id && !current_id.zero?
|
|
255
|
+
break if visited.include?(current_id)
|
|
256
|
+
|
|
257
|
+
visited.add(current_id)
|
|
253
258
|
package = database.packages.find { |p| p.package_id == current_id }
|
|
254
259
|
|
|
255
260
|
if package
|
|
@@ -11,11 +11,10 @@ module Lutaml
|
|
|
11
11
|
# @param document [Lutaml::Uml::Document] The document to normalize
|
|
12
12
|
# @return [Lutaml::Uml::Document] A normalized copy
|
|
13
13
|
def normalize(document)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
normalized
|
|
14
|
+
remove_xmi_ids(document)
|
|
15
|
+
sort_collections(document)
|
|
16
|
+
normalize_strings_in_document(document)
|
|
17
|
+
document
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
# Remove all XMI IDs from document
|
|
@@ -71,13 +70,6 @@ module Lutaml
|
|
|
71
70
|
|
|
72
71
|
private
|
|
73
72
|
|
|
74
|
-
# Deep copy document to avoid modifying original
|
|
75
|
-
def deep_copy(document)
|
|
76
|
-
# Use YAML serialization for deep copy
|
|
77
|
-
yaml = document.to_yaml
|
|
78
|
-
Lutaml::Uml::Document.from_yaml(yaml)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
73
|
# Process packages recursively to remove XMI IDs
|
|
82
74
|
def process_packages(packages) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
|
|
83
75
|
packages.each do |package|
|
|
@@ -65,23 +65,29 @@ module Lutaml
|
|
|
65
65
|
result
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Reset cached match results (call between verifications)
|
|
69
|
+
def reset_cache
|
|
70
|
+
@cached_class_matches = nil
|
|
71
|
+
@cached_package_matches = nil
|
|
72
|
+
end
|
|
73
|
+
|
|
68
74
|
# Compare element counts
|
|
69
75
|
#
|
|
70
76
|
# @param xmi_doc [Lutaml::Uml::Document] XMI document
|
|
71
77
|
# @param qea_doc [Lutaml::Uml::Document] QEA document
|
|
72
78
|
# @return [void]
|
|
73
79
|
def verify_structure(xmi_doc, qea_doc) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
74
|
-
# Compare package counts
|
|
75
|
-
|
|
76
|
-
result.add_matches(:packages,
|
|
77
|
-
result.add_xmi_only(:packages,
|
|
78
|
-
result.add_qea_only(:packages,
|
|
79
|
-
|
|
80
|
-
# Compare class counts
|
|
81
|
-
|
|
82
|
-
result.add_matches(:classes,
|
|
83
|
-
result.add_xmi_only(:classes,
|
|
84
|
-
result.add_qea_only(:classes,
|
|
80
|
+
# Compare package counts (cache for reuse in verify_properties)
|
|
81
|
+
@cached_package_matches = matcher.match_packages(xmi_doc, qea_doc)
|
|
82
|
+
result.add_matches(:packages, @cached_package_matches[:matches].size)
|
|
83
|
+
result.add_xmi_only(:packages, @cached_package_matches[:xmi_only])
|
|
84
|
+
result.add_qea_only(:packages, @cached_package_matches[:qea_only])
|
|
85
|
+
|
|
86
|
+
# Compare class counts (cache for reuse in verify_properties)
|
|
87
|
+
@cached_class_matches = matcher.match_classes(xmi_doc, qea_doc)
|
|
88
|
+
result.add_matches(:classes, @cached_class_matches[:matches].size)
|
|
89
|
+
result.add_xmi_only(:classes, @cached_class_matches[:xmi_only])
|
|
90
|
+
result.add_qea_only(:classes, @cached_class_matches[:qea_only])
|
|
85
91
|
|
|
86
92
|
# Compare enum counts
|
|
87
93
|
xmi_enums = count_all_enums(xmi_doc)
|
|
@@ -130,12 +136,16 @@ module Lutaml
|
|
|
130
136
|
# @param qea_doc [Lutaml::Uml::Document] QEA document
|
|
131
137
|
# @return [void]
|
|
132
138
|
def verify_properties(xmi_doc, qea_doc)
|
|
133
|
-
# Verify class properties
|
|
134
|
-
class_matches = matcher.match_classes(
|
|
139
|
+
# Verify class properties (reuse cached matches from verify_structure)
|
|
140
|
+
class_matches = @cached_class_matches || matcher.match_classes(
|
|
141
|
+
xmi_doc, qea_doc
|
|
142
|
+
)
|
|
135
143
|
verify_class_properties(class_matches[:matches])
|
|
136
144
|
|
|
137
|
-
# Verify package properties
|
|
138
|
-
package_matches = matcher.match_packages(
|
|
145
|
+
# Verify package properties (reuse cached matches from verify_structure)
|
|
146
|
+
package_matches = @cached_package_matches || matcher.match_packages(
|
|
147
|
+
xmi_doc, qea_doc
|
|
148
|
+
)
|
|
139
149
|
verify_package_properties(package_matches[:matches])
|
|
140
150
|
end
|
|
141
151
|
|
|
@@ -388,20 +388,38 @@ module Lutaml
|
|
|
388
388
|
|
|
389
389
|
classes.each do |klass|
|
|
390
390
|
next unless klass.name
|
|
391
|
-
next unless klass.generalization
|
|
392
391
|
|
|
393
392
|
child_qname = "#{package_path}::#{klass.name}"
|
|
394
393
|
|
|
395
|
-
# Handle generalization
|
|
396
|
-
|
|
397
|
-
|
|
394
|
+
# Handle generalization attribute
|
|
395
|
+
if klass.generalization
|
|
396
|
+
parent_name = extract_parent_name(klass.generalization)
|
|
397
|
+
if parent_name
|
|
398
|
+
parent_qname = resolve_qualified_name(parent_name, package_path)
|
|
399
|
+
if parent_qname && child_qname != parent_qname
|
|
400
|
+
@inheritance_graph[parent_qname] ||= []
|
|
401
|
+
@inheritance_graph[parent_qname] << child_qname
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Handle inheritance associations
|
|
407
|
+
next unless klass.associations
|
|
408
|
+
|
|
409
|
+
klass.associations.each do |assoc|
|
|
410
|
+
next unless assoc.respond_to?(:member_end_type)
|
|
411
|
+
next unless assoc.member_end_type == "inheritance"
|
|
412
|
+
|
|
413
|
+
parent_name = assoc.member_end
|
|
414
|
+
next unless parent_name
|
|
415
|
+
|
|
416
|
+
parent_name = parent_name.name if parent_name.respond_to?(:name)
|
|
417
|
+
next unless parent_name.is_a?(String) && !parent_name.empty?
|
|
398
418
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
419
|
+
parent_qname = resolve_qualified_name(parent_name, package_path)
|
|
420
|
+
next unless parent_qname
|
|
421
|
+
next if child_qname == parent_qname
|
|
402
422
|
|
|
403
|
-
# Avoid self-references
|
|
404
|
-
if child_qname != parent_qname
|
|
405
423
|
@inheritance_graph[parent_qname] ||= []
|
|
406
424
|
@inheritance_graph[parent_qname] << child_qname
|
|
407
425
|
end
|
|
@@ -76,9 +76,22 @@ module Lutaml
|
|
|
76
76
|
# @raise [ArgumentError] If serialization format is invalid
|
|
77
77
|
# @example
|
|
78
78
|
# exporter.export("model.lur")
|
|
79
|
-
def export(output_path)
|
|
79
|
+
def export(output_path) # rubocop:disable Metrics/MethodLength
|
|
80
80
|
validate_options!
|
|
81
81
|
|
|
82
|
+
retries = 0
|
|
83
|
+
begin
|
|
84
|
+
write_lur_package(output_path)
|
|
85
|
+
rescue Errno::EACCES
|
|
86
|
+
retries += 1
|
|
87
|
+
retry if retries < 3
|
|
88
|
+
raise
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def write_lur_package(output_path)
|
|
82
95
|
Zip::File.open(output_path, create: true) do |zip|
|
|
83
96
|
write_metadata(zip)
|
|
84
97
|
write_document(zip)
|
|
@@ -88,8 +101,6 @@ module Lutaml
|
|
|
88
101
|
end
|
|
89
102
|
end
|
|
90
103
|
|
|
91
|
-
private
|
|
92
|
-
|
|
93
104
|
# Get default export options.
|
|
94
105
|
#
|
|
95
106
|
# @return [Hash] Default options
|
|
@@ -424,14 +424,9 @@ module Lutaml
|
|
|
424
424
|
return nil unless uml_element.respond_to?(:stereotype)
|
|
425
425
|
|
|
426
426
|
stereotype = uml_element.stereotype
|
|
427
|
-
return nil unless stereotype
|
|
427
|
+
return nil unless stereotype && !stereotype.empty?
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
if stereotype.is_a?(Array)
|
|
431
|
-
stereotype.first
|
|
432
|
-
else
|
|
433
|
-
stereotype
|
|
434
|
-
end
|
|
429
|
+
stereotype.is_a?(Array) ? stereotype.first : stereotype
|
|
435
430
|
end
|
|
436
431
|
|
|
437
432
|
# Extract attributes from element
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
3
4
|
require_relative "base_query"
|
|
4
5
|
require_relative "../../uml/qualified_name"
|
|
5
6
|
|
|
@@ -171,8 +172,73 @@ module Lutaml
|
|
|
171
172
|
qname.nil? ? nil : qname
|
|
172
173
|
end
|
|
173
174
|
|
|
175
|
+
# Build inheritance tree for a class.
|
|
176
|
+
#
|
|
177
|
+
# @param class_or_id [Lutaml::Uml::Class, String] The class object,
|
|
178
|
+
# qualified name, or xmi_id
|
|
179
|
+
# @return [Hash, nil] Tree structure with :class and :children keys
|
|
180
|
+
def inheritance_tree(class_or_id)
|
|
181
|
+
klass = resolve_by_id_or_qname(class_or_id)
|
|
182
|
+
return nil unless klass
|
|
183
|
+
|
|
184
|
+
qname = resolve_qname(klass)
|
|
185
|
+
return nil unless qname
|
|
186
|
+
|
|
187
|
+
child_qnames = indexes[:inheritance_graph][qname] || []
|
|
188
|
+
child_trees = child_qnames.filter_map do |child_qname|
|
|
189
|
+
inheritance_tree(child_qname)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
{
|
|
193
|
+
class: klass,
|
|
194
|
+
children: child_trees,
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Check if a class has circular inheritance.
|
|
199
|
+
#
|
|
200
|
+
# @param class_or_id [Lutaml::Uml::Class, String] The class object,
|
|
201
|
+
# qualified name, or xmi_id
|
|
202
|
+
# @return [Boolean] true if circular inheritance detected
|
|
203
|
+
def has_circular_inheritance?(class_or_id, visited: Set.new)
|
|
204
|
+
qname = if class_or_id.is_a?(String) &&
|
|
205
|
+
indexes[:qualified_names].key?(class_or_id)
|
|
206
|
+
class_or_id
|
|
207
|
+
else
|
|
208
|
+
resolve_qname(class_or_id)
|
|
209
|
+
end
|
|
210
|
+
return false unless qname
|
|
211
|
+
|
|
212
|
+
return true if visited.include?(qname)
|
|
213
|
+
|
|
214
|
+
visited.add(qname)
|
|
215
|
+
child_qnames = indexes[:inheritance_graph][qname] || []
|
|
216
|
+
child_qnames.any? do |child_qname|
|
|
217
|
+
has_circular_inheritance?(child_qname, visited: visited.dup)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
174
221
|
private
|
|
175
222
|
|
|
223
|
+
# Resolve a class by xmi_id or qualified name
|
|
224
|
+
#
|
|
225
|
+
# @param class_or_id [String] Qualified name or xmi_id
|
|
226
|
+
# @return [Lutaml::Uml::Class, nil] The resolved class
|
|
227
|
+
def resolve_by_id_or_qname(class_or_id)
|
|
228
|
+
# Try as qualified name first
|
|
229
|
+
klass = indexes[:qualified_names][class_or_id]
|
|
230
|
+
return klass if klass
|
|
231
|
+
|
|
232
|
+
# Try as xmi_id - search in qualified_names
|
|
233
|
+
indexes[:qualified_names].each_value do |entity|
|
|
234
|
+
next unless entity.respond_to?(:xmi_id)
|
|
235
|
+
|
|
236
|
+
return entity if entity.xmi_id == class_or_id
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
nil
|
|
240
|
+
end
|
|
241
|
+
|
|
176
242
|
# Get direct subtypes of a class
|
|
177
243
|
#
|
|
178
244
|
# @param qname_string [String] Qualified name of the parent class
|
|
@@ -158,7 +158,7 @@ module Lutaml
|
|
|
158
158
|
.filter_map do |_stereotype, entities|
|
|
159
159
|
entities.select do |entity|
|
|
160
160
|
entity.respond_to?(:stereotype) &&
|
|
161
|
-
entity.stereotype&.match?(pattern)
|
|
161
|
+
Array(entity.stereotype).any? { |s| s&.match?(pattern) }
|
|
162
162
|
end.uniq
|
|
163
163
|
end.uniq.flatten
|
|
164
164
|
|
|
@@ -345,6 +345,40 @@ module Lutaml
|
|
|
345
345
|
@error_handler.class_not_found_error(qualified_name)
|
|
346
346
|
end
|
|
347
347
|
|
|
348
|
+
# Find an attribute by its qualified name.
|
|
349
|
+
#
|
|
350
|
+
# The qualified name format is "PackagePath::ClassName::attributeName".
|
|
351
|
+
# Splits off the last segment as the attribute name, finds the containing
|
|
352
|
+
# class, then returns the matching attribute.
|
|
353
|
+
#
|
|
354
|
+
# @param qualified_name [String] Qualified name of the attribute
|
|
355
|
+
# @return [Lutaml::Uml::Attribute, nil] The attribute or nil
|
|
356
|
+
# @example
|
|
357
|
+
# attr = repo.find_attribute("ModelRoot::Core::Building::name")
|
|
358
|
+
def find_attribute(qualified_name)
|
|
359
|
+
class_qname, _, attr_name = qualified_name.rpartition("::")
|
|
360
|
+
return nil if class_qname.empty?
|
|
361
|
+
|
|
362
|
+
klass = class_query.find_by_qname(class_qname)
|
|
363
|
+
return nil unless klass
|
|
364
|
+
|
|
365
|
+
attrs = klass.attributes
|
|
366
|
+
return nil unless attrs
|
|
367
|
+
|
|
368
|
+
attrs.find { |a| a.name == attr_name }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Get all attributes across all classes in the repository.
|
|
372
|
+
#
|
|
373
|
+
# @return [Array<Lutaml::Uml::Attribute>] All attribute objects
|
|
374
|
+
def all_attributes
|
|
375
|
+
indexes[:qualified_names].flat_map do |_qname, entity|
|
|
376
|
+
next [] unless entity.respond_to?(:attributes) && entity.attributes
|
|
377
|
+
|
|
378
|
+
entity.attributes
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
348
382
|
# Find all classes with a specific stereotype.
|
|
349
383
|
#
|
|
350
384
|
# @param stereotype [String] The stereotype to search for
|
|
@@ -76,13 +76,31 @@ module Lutaml
|
|
|
76
76
|
# API: Package details (on-demand, optional optimization)
|
|
77
77
|
get "/api/packages/:id" do
|
|
78
78
|
content_type :json
|
|
79
|
-
params[:id]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
requested_id = params[:id]
|
|
80
|
+
|
|
81
|
+
id_gen = UmlRepository::StaticSite::IDGenerator.new
|
|
82
|
+
|
|
83
|
+
# Search for package by matching generated ID
|
|
84
|
+
found_package = nil
|
|
85
|
+
repository.indexes[:package_paths].each_value do |package|
|
|
86
|
+
next unless package.is_a?(Lutaml::Uml::Package)
|
|
87
|
+
|
|
88
|
+
if id_gen.package_id(package) == requested_id
|
|
89
|
+
found_package = package
|
|
90
|
+
break
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
unless found_package
|
|
95
|
+
halt 404, { error: "Package not found: #{requested_id}" }.to_json
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Build package response
|
|
99
|
+
{
|
|
100
|
+
id: requested_id,
|
|
101
|
+
name: found_package.name,
|
|
102
|
+
xmi_id: found_package.xmi_id,
|
|
103
|
+
}.to_json
|
|
86
104
|
end
|
|
87
105
|
|
|
88
106
|
# API: Class details (on-demand, optional optimization)
|
data/lib/lutaml/version.rb
CHANGED