lutaml 0.10.4 → 0.10.6
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/.gitignore +8 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +218 -94
- data/TODO.cleanups/01-resolve-production-todos.md +65 -0
- data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
- data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
- data/TODO.cleanups/05-replace-marshal-load.md +37 -0
- data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
- data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
- data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
- data/TODO.cleanups/10-split-large-files.md +47 -0
- data/bin/console +0 -1
- data/exe/lutaml +1 -0
- data/lib/lutaml/cli/element_identifier.rb +3 -6
- data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
- data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
- data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
- data/lib/lutaml/cli/interactive_shell.rb +116 -802
- data/lib/lutaml/cli/uml/build_command.rb +5 -5
- data/lib/lutaml/cli/uml/verify_command.rb +0 -1
- data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
- data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
- data/lib/lutaml/formatter/graphviz.rb +1 -2
- data/lib/lutaml/qea/database.rb +1 -47
- data/lib/lutaml/qea/factory/association_builder.rb +188 -0
- data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
- data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
- data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
- data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
- data/lib/lutaml/qea/lookup_indexes.rb +54 -0
- data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
- data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
- data/lib/lutaml/uml/has_members.rb +0 -1
- data/lib/lutaml/uml/inheritance_walker.rb +92 -0
- data/lib/lutaml/uml/model_helpers.rb +129 -0
- data/lib/lutaml/uml/node/attribute.rb +3 -1
- data/lib/lutaml/uml/node/class_node.rb +3 -3
- data/lib/lutaml/uml/operation.rb +2 -0
- data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
- data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
- data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
- data/lib/lutaml/uml_repository/index_builder.rb +3 -271
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
- data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
- data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
- data/lib/lutaml/uml_repository/package_loader.rb +37 -17
- data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
- data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
- data/lib/lutaml/uml_repository/repository.rb +7 -57
- data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
- data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
- data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
- data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
- data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
- data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
- data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
- data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
- data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
- data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
- data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
- data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
- data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
- data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
- data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
- data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
- data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
- data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
- data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
- data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
- data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
- data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
- data/lib/lutaml/xmi/parsers/xml.rb +7 -120
- data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
- data/lib/lutaml.rb +0 -1
- metadata +48 -21
- data/lib/lutaml/cli/commands/base_command.rb +0 -118
- data/lib/lutaml/command_line.rb +0 -272
- data/lib/lutaml/sysml/allocate.rb +0 -9
- data/lib/lutaml/sysml/allocated.rb +0 -9
- data/lib/lutaml/sysml/binding_connector.rb +0 -9
- data/lib/lutaml/sysml/block.rb +0 -32
- data/lib/lutaml/sysml/constraint_block.rb +0 -14
- data/lib/lutaml/sysml/copy.rb +0 -8
- data/lib/lutaml/sysml/derive_requirement.rb +0 -9
- data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
- data/lib/lutaml/sysml/refine.rb +0 -9
- data/lib/lutaml/sysml/requirement.rb +0 -44
- data/lib/lutaml/sysml/requirement_related.rb +0 -9
- data/lib/lutaml/sysml/satisfy.rb +0 -9
- data/lib/lutaml/sysml/test_case.rb +0 -25
- data/lib/lutaml/sysml/trace.rb +0 -9
- data/lib/lutaml/sysml/verify.rb +0 -8
- data/lib/lutaml/sysml/xmi_file.rb +0 -486
- data/lib/lutaml/sysml.rb +0 -11
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../uml/package_path"
|
|
4
4
|
require_relative "../uml/qualified_name"
|
|
5
|
+
require_relative "index_builders/package_index"
|
|
6
|
+
require_relative "index_builders/class_index"
|
|
7
|
+
require_relative "index_builders/association_index"
|
|
5
8
|
|
|
6
9
|
module Lutaml
|
|
7
10
|
module UmlRepository
|
|
@@ -179,113 +182,6 @@ module Lutaml
|
|
|
179
182
|
}.freeze
|
|
180
183
|
end
|
|
181
184
|
|
|
182
|
-
# Build the package path index
|
|
183
|
-
#
|
|
184
|
-
# Creates a hash mapping package paths to Package objects:
|
|
185
|
-
# "ModelRoot" => Package{},
|
|
186
|
-
# "ModelRoot::i-UR" => Package{},
|
|
187
|
-
# "ModelRoot::i-UR::urf" => Package{}
|
|
188
|
-
# @api public
|
|
189
|
-
def build_package_path_index
|
|
190
|
-
# Add root package if it exists
|
|
191
|
-
@package_paths[ROOT_PACKAGE_NAME] = @document if @document
|
|
192
|
-
|
|
193
|
-
# Traverse all packages recursively
|
|
194
|
-
traverse_packages(@document.packages,
|
|
195
|
-
parent_path: ROOT_PACKAGE_NAME) do |package, path|
|
|
196
|
-
@package_paths[path] = package
|
|
197
|
-
@package_to_path[package.xmi_id] = path if package.xmi_id
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# Build the qualified name index
|
|
202
|
-
#
|
|
203
|
-
# Creates a hash mapping qualified names to Class/DataType/Enum objects:
|
|
204
|
-
# "ModelRoot::i-UR::urf::Building" => Class{}
|
|
205
|
-
# @api public
|
|
206
|
-
def build_qualified_name_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
207
|
-
# Index top-level classes, data_types, and enums from document
|
|
208
|
-
if @document.classes
|
|
209
|
-
index_classifiers(@document.classes,
|
|
210
|
-
ROOT_PACKAGE_NAME)
|
|
211
|
-
end
|
|
212
|
-
if @document.data_types
|
|
213
|
-
index_classifiers(@document.data_types,
|
|
214
|
-
ROOT_PACKAGE_NAME)
|
|
215
|
-
end
|
|
216
|
-
index_classifiers(@document.enums, ROOT_PACKAGE_NAME) if @document.enums
|
|
217
|
-
|
|
218
|
-
# Traverse packages and index their classifiers
|
|
219
|
-
traverse_packages(@document.packages,
|
|
220
|
-
parent_path: ROOT_PACKAGE_NAME) do |package, path|
|
|
221
|
-
index_classifiers(package.classes, path) if package.classes
|
|
222
|
-
index_classifiers(package.data_types, path) if package.data_types
|
|
223
|
-
index_classifiers(package.enums, path) if package.enums
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def build_association_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
228
|
-
# Collect document-level associations (XMI format)
|
|
229
|
-
@document.associations&.each do |assoc|
|
|
230
|
-
next unless assoc.xmi_id
|
|
231
|
-
|
|
232
|
-
@associations[assoc.xmi_id] = assoc
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Collect class-level associations (QEA/EA format)
|
|
236
|
-
# Note: This requires qualified_names index to be built first
|
|
237
|
-
@qualified_names.each_value do |klass|
|
|
238
|
-
next unless klass.respond_to?(:associations) && klass.associations
|
|
239
|
-
|
|
240
|
-
klass.associations.each do |assoc|
|
|
241
|
-
next unless assoc.xmi_id
|
|
242
|
-
|
|
243
|
-
# Avoid duplicates - only add if not already present
|
|
244
|
-
@associations[assoc.xmi_id] ||= assoc
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Build the stereotype index
|
|
250
|
-
#
|
|
251
|
-
# Creates a hash grouping classes by their stereotype:
|
|
252
|
-
# "featureType" => [Class{}, Class{}],
|
|
253
|
-
# "dataType" => [Class{}]
|
|
254
|
-
# @api public
|
|
255
|
-
def build_stereotype_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
|
|
256
|
-
# Process top-level classes
|
|
257
|
-
index_by_stereotype(@document.classes) if @document.classes
|
|
258
|
-
index_by_stereotype(@document.data_types) if @document.data_types
|
|
259
|
-
index_by_stereotype(@document.enums) if @document.enums
|
|
260
|
-
|
|
261
|
-
# Process classes in packages
|
|
262
|
-
traverse_packages(@document.packages) do |package, _path|
|
|
263
|
-
index_by_stereotype(package.classes) if package.classes
|
|
264
|
-
index_by_stereotype(package.data_types) if package.data_types
|
|
265
|
-
index_by_stereotype(package.enums) if package.enums
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Build the inheritance graph index
|
|
270
|
-
#
|
|
271
|
-
# Creates a hash mapping parent qualified names to arrays of
|
|
272
|
-
# child qualified names:
|
|
273
|
-
# "ModelRoot::Parent" => ["ModelRoot::Child1", "ModelRoot::Child2"]
|
|
274
|
-
# @api public
|
|
275
|
-
def build_inheritance_graph_index
|
|
276
|
-
# Process top-level classes
|
|
277
|
-
if @document.classes
|
|
278
|
-
process_generalizations(@document.classes,
|
|
279
|
-
ROOT_PACKAGE_NAME)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# Process classes in packages
|
|
283
|
-
traverse_packages(@document.packages,
|
|
284
|
-
parent_path: ROOT_PACKAGE_NAME) do |package, path|
|
|
285
|
-
process_generalizations(package.classes, path) if package.classes
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
|
|
289
185
|
# Build the diagram index
|
|
290
186
|
#
|
|
291
187
|
# Creates a hash mapping package IDs/paths to arrays of Diagram objects:
|
|
@@ -303,170 +199,6 @@ module Lutaml
|
|
|
303
199
|
end
|
|
304
200
|
end
|
|
305
201
|
|
|
306
|
-
# Traverse packages recursively, yielding each package with its path
|
|
307
|
-
#
|
|
308
|
-
# @param packages [Array<Lutaml::Uml::Package>] Packages to traverse
|
|
309
|
-
# @param parent_path [String, nil] Parent package path
|
|
310
|
-
# @yield [package, path] Yields each package with its full path
|
|
311
|
-
def traverse_packages(packages, parent_path: nil, &block)
|
|
312
|
-
return unless packages
|
|
313
|
-
|
|
314
|
-
packages.each do |package|
|
|
315
|
-
path = build_package_path(package.name, parent_path)
|
|
316
|
-
yield package, path if block
|
|
317
|
-
|
|
318
|
-
# Recursively traverse nested packages
|
|
319
|
-
if package.packages
|
|
320
|
-
traverse_packages(package.packages, parent_path: path,
|
|
321
|
-
&block)
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Build a package path from a package name and parent path
|
|
327
|
-
#
|
|
328
|
-
# @param name [String] Package name
|
|
329
|
-
# @param parent_path [String, nil] Parent package path
|
|
330
|
-
# @return [String] Full package path
|
|
331
|
-
def build_package_path(name, parent_path)
|
|
332
|
-
return name unless parent_path
|
|
333
|
-
|
|
334
|
-
"#{parent_path}::#{name}"
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
# Index classifiers (classes, data types, enums) by their qualified names
|
|
338
|
-
#
|
|
339
|
-
# @param classifiers [Array] Array of classifier objects
|
|
340
|
-
# @param package_path [String] Package path for these classifiers
|
|
341
|
-
def index_classifiers(classifiers, package_path) # rubocop:disable Metrics/MethodLength
|
|
342
|
-
return unless classifiers
|
|
343
|
-
|
|
344
|
-
classifiers.each do |classifier|
|
|
345
|
-
next unless classifier.name
|
|
346
|
-
|
|
347
|
-
qualified_name = "#{package_path}::#{classifier.name}"
|
|
348
|
-
@qualified_names[qualified_name] = classifier
|
|
349
|
-
if classifier.xmi_id
|
|
350
|
-
@class_to_qname[classifier.xmi_id] =
|
|
351
|
-
qualified_name
|
|
352
|
-
end
|
|
353
|
-
@classes[classifier.xmi_id] = classifier if classifier.xmi_id
|
|
354
|
-
@simple_name_to_qnames[classifier.name] ||= []
|
|
355
|
-
@simple_name_to_qnames[classifier.name] << qualified_name
|
|
356
|
-
(@package_to_classes[package_path] ||= []) << classifier
|
|
357
|
-
end
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
# Index classifiers by their stereotypes
|
|
361
|
-
#
|
|
362
|
-
# @param classifiers [Array] Array of classifier objects
|
|
363
|
-
def index_by_stereotype(classifiers) # rubocop:disable Metrics/CyclomaticComplexity
|
|
364
|
-
return unless classifiers
|
|
365
|
-
|
|
366
|
-
classifiers.each do |classifier|
|
|
367
|
-
next unless classifier.stereotype && !classifier.stereotype.empty?
|
|
368
|
-
|
|
369
|
-
# Handle both String and Array stereotypes
|
|
370
|
-
stereotypes = Array(classifier.stereotype)
|
|
371
|
-
stereotypes.each do |stereotype|
|
|
372
|
-
@stereotypes[stereotype] ||= []
|
|
373
|
-
@stereotypes[stereotype] << classifier
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
# Process generalization relationships to build inheritance graph
|
|
379
|
-
#
|
|
380
|
-
# @param classes [Array<Lutaml::Uml::Class>] Classes to process
|
|
381
|
-
# @param package_path [String] Package path for these classes
|
|
382
|
-
def process_generalizations(classes, package_path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
383
|
-
return unless classes
|
|
384
|
-
|
|
385
|
-
classes.each do |klass|
|
|
386
|
-
next unless klass.name
|
|
387
|
-
|
|
388
|
-
child_qname = "#{package_path}::#{klass.name}"
|
|
389
|
-
|
|
390
|
-
# Handle generalization attribute
|
|
391
|
-
if klass.generalization
|
|
392
|
-
parent_name = extract_parent_name(klass.generalization)
|
|
393
|
-
if parent_name
|
|
394
|
-
parent_qname = resolve_qualified_name(parent_name, package_path)
|
|
395
|
-
if parent_qname && child_qname != parent_qname
|
|
396
|
-
@inheritance_graph[parent_qname] ||= []
|
|
397
|
-
@inheritance_graph[parent_qname] << child_qname
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
# Handle inheritance associations
|
|
403
|
-
next unless klass.associations
|
|
404
|
-
|
|
405
|
-
klass.associations.each do |assoc|
|
|
406
|
-
next unless assoc.respond_to?(:member_end_type)
|
|
407
|
-
next unless assoc.member_end_type == "inheritance"
|
|
408
|
-
|
|
409
|
-
parent_name = assoc.member_end
|
|
410
|
-
next unless parent_name
|
|
411
|
-
|
|
412
|
-
parent_name = parent_name.name if parent_name.respond_to?(:name)
|
|
413
|
-
next unless parent_name.is_a?(String) && !parent_name.empty?
|
|
414
|
-
|
|
415
|
-
parent_qname = resolve_qualified_name(parent_name, package_path)
|
|
416
|
-
next unless parent_qname
|
|
417
|
-
next if child_qname == parent_qname
|
|
418
|
-
|
|
419
|
-
@inheritance_graph[parent_qname] ||= []
|
|
420
|
-
@inheritance_graph[parent_qname] << child_qname
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
# Extract parent name from generalization object
|
|
426
|
-
#
|
|
427
|
-
# @param generalization [Lutaml::Uml::Generalization]
|
|
428
|
-
# Generalization object
|
|
429
|
-
# @return [String, nil] Parent class name
|
|
430
|
-
def extract_parent_name(generalization) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
431
|
-
return nil unless generalization
|
|
432
|
-
|
|
433
|
-
# Check for general attribute (could be a string or object)
|
|
434
|
-
if generalization.respond_to?(:general)
|
|
435
|
-
parent = generalization.general
|
|
436
|
-
return parent.name if parent.respond_to?(:name)
|
|
437
|
-
return parent.to_s if parent
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
# Check for name attribute directly
|
|
441
|
-
if generalization.respond_to?(:name) && generalization.name
|
|
442
|
-
return generalization.name
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
nil
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
# Resolve a class name to its qualified name
|
|
449
|
-
#
|
|
450
|
-
# This is a simplified resolution that checks:
|
|
451
|
-
# 1. Same package
|
|
452
|
-
# 2. Already qualified name in index
|
|
453
|
-
#
|
|
454
|
-
# @param name [String] Class name to resolve
|
|
455
|
-
# @param current_package_path [String] Current package context
|
|
456
|
-
# @return [String, nil] Resolved qualified name
|
|
457
|
-
def resolve_qualified_name(name, current_package_path)
|
|
458
|
-
# If name contains "::", it might already be qualified
|
|
459
|
-
return name if @qualified_names.key?(name)
|
|
460
|
-
|
|
461
|
-
# Try in current package
|
|
462
|
-
local_qname = "#{current_package_path}::#{name}"
|
|
463
|
-
return local_qname if @qualified_names.key?(local_qname)
|
|
464
|
-
|
|
465
|
-
# O(1) lookup using reverse index instead of O(n) scan
|
|
466
|
-
candidates = @simple_name_to_qnames[name]
|
|
467
|
-
candidates&.first
|
|
468
|
-
end
|
|
469
|
-
|
|
470
202
|
private
|
|
471
203
|
|
|
472
204
|
# Convert a hash with default proc to a plain hash (Marshal-safe)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module UmlRepository
|
|
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)
|
|
8
|
+
@document.associations&.each do |assoc|
|
|
9
|
+
next unless assoc.xmi_id
|
|
10
|
+
|
|
11
|
+
@associations[assoc.xmi_id] = assoc
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Collect class-level associations (QEA/EA format)
|
|
15
|
+
# Note: This requires qualified_names index to be built first
|
|
16
|
+
@qualified_names.each_value do |klass|
|
|
17
|
+
next unless klass.respond_to?(:associations) && klass.associations
|
|
18
|
+
|
|
19
|
+
klass.associations.each do |assoc|
|
|
20
|
+
next unless assoc.xmi_id
|
|
21
|
+
|
|
22
|
+
# Avoid duplicates - only add if not already present
|
|
23
|
+
@associations[assoc.xmi_id] ||= assoc
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Build the inheritance graph index
|
|
29
|
+
#
|
|
30
|
+
# Creates a hash mapping parent qualified names to arrays of
|
|
31
|
+
# child qualified names:
|
|
32
|
+
# "ModelRoot::Parent" => ["ModelRoot::Child1", "ModelRoot::Child2"]
|
|
33
|
+
# @api public
|
|
34
|
+
def build_inheritance_graph_index
|
|
35
|
+
# Process top-level classes
|
|
36
|
+
if @document.classes
|
|
37
|
+
process_generalizations(@document.classes,
|
|
38
|
+
ROOT_PACKAGE_NAME)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Process classes in packages
|
|
42
|
+
traverse_packages(@document.packages,
|
|
43
|
+
parent_path: ROOT_PACKAGE_NAME) do |package, path|
|
|
44
|
+
process_generalizations(package.classes, path) if package.classes
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Process generalization relationships to build inheritance graph
|
|
49
|
+
#
|
|
50
|
+
# @param classes [Array<Lutaml::Uml::Class>] Classes to process
|
|
51
|
+
# @param package_path [String] Package path for these classes
|
|
52
|
+
def process_generalizations(classes, package_path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
53
|
+
return unless classes
|
|
54
|
+
|
|
55
|
+
classes.each do |klass|
|
|
56
|
+
next unless klass.name
|
|
57
|
+
|
|
58
|
+
child_qname = "#{package_path}::#{klass.name}"
|
|
59
|
+
|
|
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
|
|
74
|
+
|
|
75
|
+
klass.associations.each do |assoc|
|
|
76
|
+
next unless assoc.respond_to?(:member_end_type)
|
|
77
|
+
next unless assoc.member_end_type == "inheritance"
|
|
78
|
+
|
|
79
|
+
parent_name = assoc.member_end
|
|
80
|
+
next unless parent_name
|
|
81
|
+
|
|
82
|
+
parent_name = parent_name.name if parent_name.respond_to?(:name)
|
|
83
|
+
next unless parent_name.is_a?(String) && !parent_name.empty?
|
|
84
|
+
|
|
85
|
+
parent_qname = resolve_qualified_name(parent_name, package_path)
|
|
86
|
+
next unless parent_qname
|
|
87
|
+
next if child_qname == parent_qname
|
|
88
|
+
|
|
89
|
+
@inheritance_graph[parent_qname] ||= []
|
|
90
|
+
@inheritance_graph[parent_qname] << child_qname
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Extract parent name from generalization object
|
|
96
|
+
#
|
|
97
|
+
# @param generalization [Lutaml::Uml::Generalization]
|
|
98
|
+
# Generalization object
|
|
99
|
+
# @return [String, nil] Parent class name
|
|
100
|
+
def extract_parent_name(generalization) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
101
|
+
return nil unless generalization
|
|
102
|
+
|
|
103
|
+
# Check for general attribute (could be a string or object)
|
|
104
|
+
if generalization.respond_to?(:general)
|
|
105
|
+
parent = generalization.general
|
|
106
|
+
return parent.name if parent.respond_to?(:name)
|
|
107
|
+
return parent.to_s if parent
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Check for name attribute directly
|
|
111
|
+
if generalization.respond_to?(:name) && generalization.name
|
|
112
|
+
return generalization.name
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Resolve a class name to its qualified name
|
|
119
|
+
#
|
|
120
|
+
# This is a simplified resolution that checks:
|
|
121
|
+
# 1. Same package
|
|
122
|
+
# 2. Already qualified name in index
|
|
123
|
+
#
|
|
124
|
+
# @param name [String] Class name to resolve
|
|
125
|
+
# @param current_package_path [String] Current package context
|
|
126
|
+
# @return [String, nil] Resolved qualified name
|
|
127
|
+
def resolve_qualified_name(name, current_package_path)
|
|
128
|
+
# If name contains "::", it might already be qualified
|
|
129
|
+
return name if @qualified_names.key?(name)
|
|
130
|
+
|
|
131
|
+
# Try in current package
|
|
132
|
+
local_qname = "#{current_package_path}::#{name}"
|
|
133
|
+
return local_qname if @qualified_names.key?(local_qname)
|
|
134
|
+
|
|
135
|
+
# O(1) lookup using reverse index instead of O(n) scan
|
|
136
|
+
candidates = @simple_name_to_qnames[name]
|
|
137
|
+
candidates&.first
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module UmlRepository
|
|
5
|
+
class IndexBuilder
|
|
6
|
+
# Build the qualified name index
|
|
7
|
+
#
|
|
8
|
+
# Creates a hash mapping qualified names to Class/DataType/Enum objects:
|
|
9
|
+
# "ModelRoot::i-UR::urf::Building" => Class{}
|
|
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
|
|
13
|
+
if @document.classes
|
|
14
|
+
index_classifiers(@document.classes,
|
|
15
|
+
ROOT_PACKAGE_NAME)
|
|
16
|
+
end
|
|
17
|
+
if @document.data_types
|
|
18
|
+
index_classifiers(@document.data_types,
|
|
19
|
+
ROOT_PACKAGE_NAME)
|
|
20
|
+
end
|
|
21
|
+
index_classifiers(@document.enums, ROOT_PACKAGE_NAME) if @document.enums
|
|
22
|
+
|
|
23
|
+
# Traverse packages and index their classifiers
|
|
24
|
+
traverse_packages(@document.packages,
|
|
25
|
+
parent_path: ROOT_PACKAGE_NAME) do |package, path|
|
|
26
|
+
index_classifiers(package.classes, path) if package.classes
|
|
27
|
+
index_classifiers(package.data_types, path) if package.data_types
|
|
28
|
+
index_classifiers(package.enums, path) if package.enums
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Build the stereotype index
|
|
33
|
+
#
|
|
34
|
+
# Creates a hash grouping classes by their stereotype:
|
|
35
|
+
# "featureType" => [Class{}, Class{}],
|
|
36
|
+
# "dataType" => [Class{}]
|
|
37
|
+
# @api public
|
|
38
|
+
def build_stereotype_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
|
|
39
|
+
# Process top-level classes
|
|
40
|
+
index_by_stereotype(@document.classes) if @document.classes
|
|
41
|
+
index_by_stereotype(@document.data_types) if @document.data_types
|
|
42
|
+
index_by_stereotype(@document.enums) if @document.enums
|
|
43
|
+
|
|
44
|
+
# Process classes in packages
|
|
45
|
+
traverse_packages(@document.packages) do |package, _path|
|
|
46
|
+
index_by_stereotype(package.classes) if package.classes
|
|
47
|
+
index_by_stereotype(package.data_types) if package.data_types
|
|
48
|
+
index_by_stereotype(package.enums) if package.enums
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Index classifiers (classes, data types, enums) by their qualified names
|
|
53
|
+
#
|
|
54
|
+
# @param classifiers [Array] Array of classifier objects
|
|
55
|
+
# @param package_path [String] Package path for these classifiers
|
|
56
|
+
def index_classifiers(classifiers, package_path) # rubocop:disable Metrics/MethodLength
|
|
57
|
+
return unless classifiers
|
|
58
|
+
|
|
59
|
+
classifiers.each do |classifier|
|
|
60
|
+
next unless classifier.name
|
|
61
|
+
|
|
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
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Index classifiers by their stereotypes
|
|
76
|
+
#
|
|
77
|
+
# @param classifiers [Array] Array of classifier objects
|
|
78
|
+
def index_by_stereotype(classifiers) # rubocop:disable Metrics/CyclomaticComplexity
|
|
79
|
+
return unless classifiers
|
|
80
|
+
|
|
81
|
+
classifiers.each do |classifier|
|
|
82
|
+
next unless classifier.stereotype && !classifier.stereotype.empty?
|
|
83
|
+
|
|
84
|
+
# Handle both String and Array stereotypes
|
|
85
|
+
stereotypes = Array(classifier.stereotype)
|
|
86
|
+
stereotypes.each do |stereotype|
|
|
87
|
+
@stereotypes[stereotype] ||= []
|
|
88
|
+
@stereotypes[stereotype] << classifier
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module UmlRepository
|
|
5
|
+
class IndexBuilder
|
|
6
|
+
# Build the package path index
|
|
7
|
+
#
|
|
8
|
+
# Creates a hash mapping package paths to Package objects:
|
|
9
|
+
# "ModelRoot" => Package{},
|
|
10
|
+
# "ModelRoot::i-UR" => Package{},
|
|
11
|
+
# "ModelRoot::i-UR::urf" => Package{}
|
|
12
|
+
# @api public
|
|
13
|
+
def build_package_path_index
|
|
14
|
+
# Add root package if it exists
|
|
15
|
+
@package_paths[ROOT_PACKAGE_NAME] = @document if @document
|
|
16
|
+
|
|
17
|
+
# Traverse all packages recursively
|
|
18
|
+
traverse_packages(@document.packages,
|
|
19
|
+
parent_path: ROOT_PACKAGE_NAME) do |package, path|
|
|
20
|
+
@package_paths[path] = package
|
|
21
|
+
@package_to_path[package.xmi_id] = path if package.xmi_id
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Traverse packages recursively, yielding each package with its path
|
|
26
|
+
#
|
|
27
|
+
# @param packages [Array<Lutaml::Uml::Package>] Packages to traverse
|
|
28
|
+
# @param parent_path [String, nil] Parent package path
|
|
29
|
+
# @yield [package, path] Yields each package with its full path
|
|
30
|
+
def traverse_packages(packages, parent_path: nil, &block)
|
|
31
|
+
return unless packages
|
|
32
|
+
|
|
33
|
+
packages.each do |package|
|
|
34
|
+
path = build_package_path(package.name, parent_path)
|
|
35
|
+
yield package, path if block
|
|
36
|
+
|
|
37
|
+
# Recursively traverse nested packages
|
|
38
|
+
if package.packages
|
|
39
|
+
traverse_packages(package.packages, parent_path: path,
|
|
40
|
+
&block)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Build a package path from a package name and parent path
|
|
46
|
+
#
|
|
47
|
+
# @param name [String] Package name
|
|
48
|
+
# @param parent_path [String, nil] Parent package path
|
|
49
|
+
# @return [String] Full package path
|
|
50
|
+
def build_package_path(name, parent_path)
|
|
51
|
+
return name unless parent_path
|
|
52
|
+
|
|
53
|
+
"#{parent_path}::#{name}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -53,8 +53,8 @@ module Lutaml
|
|
|
53
53
|
# @param options [Hash] Export options
|
|
54
54
|
# @option options [PackageMetadata, Hash] :metadata Package metadata
|
|
55
55
|
# (can be PackageMetadata object or Hash)
|
|
56
|
-
# @option options [Symbol] :serialization_format (:
|
|
57
|
-
# Document serialization (:
|
|
56
|
+
# @option options [Symbol] :serialization_format (:yaml) Format for
|
|
57
|
+
# Document serialization (:yaml)
|
|
58
58
|
# @option options [Boolean] :include_xmi (false) Include source XMI
|
|
59
59
|
# in package
|
|
60
60
|
# @option options [Integer] :compression_level (6) ZIP compression level
|
|
@@ -106,7 +106,7 @@ module Lutaml
|
|
|
106
106
|
# @return [Hash] Default options
|
|
107
107
|
def default_options
|
|
108
108
|
{
|
|
109
|
-
serialization_format: :
|
|
109
|
+
serialization_format: :yaml,
|
|
110
110
|
include_xmi: false,
|
|
111
111
|
compression_level: 6,
|
|
112
112
|
name: "UML Model",
|
|
@@ -154,10 +154,9 @@ module Lutaml
|
|
|
154
154
|
# @raise [ArgumentError] If options are invalid
|
|
155
155
|
def validate_options!
|
|
156
156
|
format = @options[:serialization_format]
|
|
157
|
-
unless
|
|
157
|
+
unless format == :yaml
|
|
158
158
|
raise ArgumentError,
|
|
159
|
-
"Invalid serialization format: #{format}. "
|
|
160
|
-
"Must be :marshal or :yaml"
|
|
159
|
+
"Invalid serialization format: #{format}. Must be :yaml"
|
|
161
160
|
end
|
|
162
161
|
end
|
|
163
162
|
|
|
@@ -191,18 +190,9 @@ module Lutaml
|
|
|
191
190
|
#
|
|
192
191
|
# @param zip [Zip::File] The ZIP archive
|
|
193
192
|
# @return [void]
|
|
194
|
-
def write_document(zip)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
case format
|
|
198
|
-
when :yaml
|
|
199
|
-
zip.get_output_stream("repository.yaml") do |io|
|
|
200
|
-
io.write(@repository.document.to_yaml)
|
|
201
|
-
end
|
|
202
|
-
when :marshal
|
|
203
|
-
zip.get_output_stream("repository.marshal") do |io|
|
|
204
|
-
io.write(Marshal.dump(@repository.document))
|
|
205
|
-
end
|
|
193
|
+
def write_document(zip)
|
|
194
|
+
zip.get_output_stream("repository.yaml") do |io|
|
|
195
|
+
io.write(@repository.document.to_yaml)
|
|
206
196
|
end
|
|
207
197
|
end
|
|
208
198
|
|
|
@@ -211,8 +201,8 @@ module Lutaml
|
|
|
211
201
|
# @param zip [Zip::File] The ZIP archive
|
|
212
202
|
# @return [void]
|
|
213
203
|
def write_indexes(zip)
|
|
214
|
-
zip.get_output_stream("indexes/all.
|
|
215
|
-
io.write(
|
|
204
|
+
zip.get_output_stream("indexes/all.yaml") do |io|
|
|
205
|
+
io.write(YAML.dump(@repository.indexes))
|
|
216
206
|
end
|
|
217
207
|
end
|
|
218
208
|
|