lutaml 0.10.2 → 0.10.4
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.yml +1 -1
- data/.rubocop_todo.yml +40 -35
- data/CHANGELOG.md +108 -0
- data/lib/lutaml/cli/uml_commands.rb +2 -2
- data/lib/lutaml/command_line.rb +1 -1
- data/lib/lutaml/converter/xmi_to_uml.rb +12 -3
- data/lib/lutaml/model_transformations/parsers/base_parser.rb +1 -1
- data/lib/lutaml/model_transformations/transformation_engine.rb +1 -1
- data/lib/lutaml/qea/database.rb +157 -4
- data/lib/lutaml/qea/factory/association_transformer.rb +1 -5
- data/lib/lutaml/qea/factory/attribute_transformer.rb +4 -10
- data/lib/lutaml/qea/factory/base_transformer.rb +1 -5
- data/lib/lutaml/qea/factory/class_transformer.rb +26 -62
- data/lib/lutaml/qea/factory/data_type_transformer.rb +7 -21
- data/lib/lutaml/qea/factory/diagram_transformer.rb +6 -20
- data/lib/lutaml/qea/factory/document_builder.rb +1 -1
- data/lib/lutaml/qea/factory/enum_transformer.rb +3 -5
- data/lib/lutaml/qea/factory/generalization_transformer.rb +2 -7
- data/lib/lutaml/qea/factory/instance_transformer.rb +2 -10
- data/lib/lutaml/qea/factory/operation_transformer.rb +2 -8
- data/lib/lutaml/qea/factory/package_transformer.rb +4 -13
- data/lib/lutaml/qea/repositories/base_repository.rb +6 -6
- data/lib/lutaml/qea/validation/base_validator.rb +2 -3
- data/lib/lutaml/qea/validation/validation_message.rb +2 -2
- data/lib/lutaml/qea/validation/validation_result.rb +2 -2
- data/lib/lutaml/sysml.rb +1 -1
- data/lib/lutaml/uml/has_members.rb +1 -1
- data/lib/lutaml/uml/parsers/dsl.rb +1 -1
- data/lib/lutaml/uml.rb +1 -1
- data/lib/lutaml/uml_repository/index_builder.rb +19 -17
- data/lib/lutaml/uml_repository/queries/class_query.rb +44 -10
- data/lib/lutaml/uml_repository/queries/inheritance_query.rb +0 -1
- data/lib/lutaml/uml_repository/repository.rb +14 -10
- data/lib/lutaml/uml_repository/statistics_calculator.rb +28 -16
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/parsers/xmi_base.rb +28 -5
- data/lib/lutaml.rb +3 -1
- data/lutaml.gemspec +2 -2
- metadata +5 -4
|
@@ -69,38 +69,72 @@ module Lutaml
|
|
|
69
69
|
def in_package(package_path_string, recursive: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
70
70
|
return [] if package_path_string.nil? || package_path_string.empty?
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
pkg_to_classes = indexes[:package_to_classes]
|
|
73
|
+
if pkg_to_classes
|
|
74
|
+
in_package_indexed(package_path_string, pkg_to_classes,
|
|
75
|
+
recursive: recursive)
|
|
76
|
+
else
|
|
77
|
+
in_package_scan(package_path_string, recursive: recursive)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# O(1) indexed lookup for in_package
|
|
84
|
+
def in_package_indexed(package_path_string, pkg_to_classes, recursive:) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
85
|
+
is_absolute = package_path_string.start_with?("::")
|
|
86
|
+
search_segs = package_path_string.split("::").reject(&:empty?)
|
|
87
|
+
|
|
73
88
|
results = []
|
|
89
|
+
pkg_to_classes.each do |path, classes|
|
|
90
|
+
path_segs = path.split("::")
|
|
91
|
+
matched = if is_absolute
|
|
92
|
+
if recursive
|
|
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
|
|
74
107
|
|
|
75
|
-
|
|
108
|
+
results.concat(classes) if matched
|
|
109
|
+
end
|
|
110
|
+
results
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Fallback: original O(n) scan
|
|
114
|
+
def in_package_scan(package_path_string, recursive:)
|
|
115
|
+
package_path = Lutaml::Uml::PackagePath.new(package_path_string)
|
|
116
|
+
results = []
|
|
76
117
|
is_absolute = package_path.absolute?
|
|
77
118
|
|
|
78
|
-
indexes[:qualified_names].each do |qname_string, klass|
|
|
119
|
+
indexes[:qualified_names].each do |qname_string, klass|
|
|
79
120
|
qname = Lutaml::Uml::QualifiedName.new(qname_string)
|
|
80
121
|
|
|
81
122
|
matched = if is_absolute
|
|
82
|
-
# Absolute path - exact match
|
|
83
123
|
if recursive
|
|
84
124
|
qname.package_path.starts_with?(package_path)
|
|
85
125
|
else
|
|
86
126
|
qname.package_path == package_path
|
|
87
127
|
end
|
|
88
128
|
else
|
|
89
|
-
# Relative path - match if the class's package path ends with
|
|
90
|
-
# the given path
|
|
91
129
|
class_pkg_segs = qname.package_path.segments
|
|
92
130
|
search_segs = package_path.segments
|
|
93
131
|
|
|
94
132
|
if recursive
|
|
95
|
-
# For recursive, check if any suffix of the class path
|
|
96
|
-
# starts with search_segs
|
|
97
133
|
(0..(class_pkg_segs.size - search_segs.size))
|
|
98
134
|
.any? do |i|
|
|
99
135
|
class_pkg_segs[i, search_segs.size] == search_segs
|
|
100
136
|
end
|
|
101
137
|
else
|
|
102
|
-
# For non-recursive, check if class path ends with
|
|
103
|
-
# search_segs
|
|
104
138
|
class_pkg_segs.size >= search_segs.size &&
|
|
105
139
|
class_pkg_segs[-search_segs.size..] == search_segs
|
|
106
140
|
end
|
|
@@ -625,8 +625,8 @@ module Lutaml
|
|
|
625
625
|
# results = repo.query! do |q|
|
|
626
626
|
# q.classes.where(stereotype: 'featureType')
|
|
627
627
|
# end
|
|
628
|
-
def query!(&
|
|
629
|
-
query(&
|
|
628
|
+
def query!(&)
|
|
629
|
+
query(&).execute
|
|
630
630
|
end
|
|
631
631
|
|
|
632
632
|
# Convenience methods for SPA data transformer
|
|
@@ -646,25 +646,29 @@ module Lutaml
|
|
|
646
646
|
# Get all associations as an array
|
|
647
647
|
# Collects from both document-level (XMI) and class-level (QEA/EA)
|
|
648
648
|
# @return [Array<Lutaml::Uml::Association>] All associations
|
|
649
|
-
def associations_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics
|
|
649
|
+
def associations_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics:MethodLength,Metrics:PerceivedComplexity
|
|
650
650
|
# Use cached index if available (built by IndexBuilder)
|
|
651
651
|
return @indexes[:associations].values if @indexes[:associations]
|
|
652
652
|
|
|
653
653
|
# Fallback for edge cases: collect from document and classes
|
|
654
|
+
seen = Set.new
|
|
654
655
|
associations = []
|
|
655
656
|
|
|
656
|
-
|
|
657
|
-
|
|
657
|
+
(@document.associations || []).each do |assoc|
|
|
658
|
+
if assoc.xmi_id && !seen.include?(assoc.xmi_id)
|
|
659
|
+
seen << assoc.xmi_id
|
|
660
|
+
associations << assoc
|
|
661
|
+
end
|
|
662
|
+
end
|
|
658
663
|
|
|
659
|
-
# Class-level associations (QEA/EA format)
|
|
660
664
|
classes_index.each do |klass|
|
|
661
665
|
next unless klass.respond_to?(:associations) && klass.associations
|
|
662
666
|
|
|
663
667
|
klass.associations.each do |assoc|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
+
if assoc.xmi_id && !seen.include?(assoc.xmi_id)
|
|
669
|
+
seen << assoc.xmi_id
|
|
670
|
+
associations << assoc
|
|
671
|
+
end
|
|
668
672
|
end
|
|
669
673
|
end
|
|
670
674
|
|
|
@@ -262,12 +262,14 @@ module Lutaml
|
|
|
262
262
|
def max_inheritance_depth
|
|
263
263
|
return 0 if @indexes[:inheritance_graph].empty?
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
@inheritance_depth_cache ||= {}
|
|
266
|
+
max_depth = 0
|
|
267
|
+
|
|
266
268
|
@indexes[:qualified_names].each_key do |qname|
|
|
267
|
-
depth =
|
|
268
|
-
|
|
269
|
+
depth = memoized_inheritance_depth(qname)
|
|
270
|
+
max_depth = depth if depth > max_depth
|
|
269
271
|
end
|
|
270
|
-
|
|
272
|
+
max_depth
|
|
271
273
|
end
|
|
272
274
|
|
|
273
275
|
# Calculate inheritance depth for a class
|
|
@@ -275,23 +277,33 @@ module Lutaml
|
|
|
275
277
|
# @param qname [String] Qualified name of the class
|
|
276
278
|
# @param visited [Set] Set of visited classes (to detect cycles)
|
|
277
279
|
# @return [Integer] Depth of inheritance chain
|
|
278
|
-
def calculate_inheritance_depth(qname, visited = Set.new)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
visited.add(qname)
|
|
280
|
+
def calculate_inheritance_depth(qname, visited = Set.new)
|
|
281
|
+
memoized_inheritance_depth(qname, visited)
|
|
282
|
+
end
|
|
282
283
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
@
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
# Build reverse index: child_qname => parent_qname
|
|
285
|
+
def child_to_parent_index
|
|
286
|
+
@child_to_parent_index ||= begin
|
|
287
|
+
idx = {}
|
|
288
|
+
@indexes[:inheritance_graph].each do |parent, children|
|
|
289
|
+
children.each { |child| idx[child] = parent }
|
|
289
290
|
end
|
|
291
|
+
idx
|
|
290
292
|
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Memoized inheritance depth calculation using reverse index
|
|
296
|
+
def memoized_inheritance_depth(qname, visited = Set.new)
|
|
297
|
+
return 0 if visited.include?(qname)
|
|
298
|
+
return @inheritance_depth_cache[qname] if @inheritance_depth_cache.key?(qname)
|
|
291
299
|
|
|
292
|
-
|
|
300
|
+
parent = child_to_parent_index[qname]
|
|
301
|
+
return 0 unless parent
|
|
293
302
|
|
|
294
|
-
|
|
303
|
+
visited.add(qname)
|
|
304
|
+
depth = 1 + memoized_inheritance_depth(parent, visited)
|
|
305
|
+
@inheritance_depth_cache[qname] = depth
|
|
306
|
+
depth
|
|
295
307
|
end
|
|
296
308
|
|
|
297
309
|
# Get count of abstract classes
|
data/lib/lutaml/version.rb
CHANGED
|
@@ -485,9 +485,7 @@ module Lutaml
|
|
|
485
485
|
# @return [Array<Hash>]
|
|
486
486
|
# @note xpath %(//diagrams/diagram/model[@package="#{node['xmi:id']}"])
|
|
487
487
|
def serialize_model_diagrams(node_id, with_package: false) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
488
|
-
diagrams =
|
|
489
|
-
d.model.package == node_id
|
|
490
|
-
end
|
|
488
|
+
diagrams = diagram_lookup[node_id]
|
|
491
489
|
|
|
492
490
|
diagrams.map do |diagram|
|
|
493
491
|
h = {
|
|
@@ -506,6 +504,17 @@ module Lutaml
|
|
|
506
504
|
end
|
|
507
505
|
end
|
|
508
506
|
|
|
507
|
+
# Lazy-built hash index for O(1) diagram lookups by package
|
|
508
|
+
# @return [Hash] Mapping of package_id => [diagrams]
|
|
509
|
+
def diagram_lookup
|
|
510
|
+
@diagram_lookup ||= begin
|
|
511
|
+
idx = Hash.new { |h, k| h[k] = [] }
|
|
512
|
+
diagrams = @xmi_root_model.extension&.diagrams&.diagram || []
|
|
513
|
+
diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
|
|
514
|
+
idx
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
509
518
|
# @param xmi_id [String]
|
|
510
519
|
# @return [Array<Hash>]
|
|
511
520
|
# @note xpath %(//element[@xmi:idref="#{xmi_id}"]/links/*)
|
|
@@ -932,8 +941,22 @@ module Lutaml
|
|
|
932
941
|
# @param source_or_target [String]
|
|
933
942
|
# @return [String]
|
|
934
943
|
def connector_node_by_id(xmi_id, source_or_target)
|
|
935
|
-
|
|
936
|
-
|
|
944
|
+
connector_lookup[[source_or_target.to_sym, xmi_id]]
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
# Lazy-built hash index for O(1) connector lookups
|
|
948
|
+
# @return [Hash] Mapping of [direction, idref] => connector
|
|
949
|
+
def connector_lookup
|
|
950
|
+
@connector_lookup ||= begin
|
|
951
|
+
lookup = {}
|
|
952
|
+
connectors = @xmi_root_model.extension&.connectors&.connector || []
|
|
953
|
+
connectors.each do |con|
|
|
954
|
+
%i[source target].each do |dir|
|
|
955
|
+
idref = con.send(dir)&.idref
|
|
956
|
+
lookup[[dir, idref]] = con if idref
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
lookup
|
|
937
960
|
end
|
|
938
961
|
end
|
|
939
962
|
|
data/lib/lutaml.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "lutaml/version"
|
|
4
|
-
require_relative "lutaml/parser"
|
|
5
4
|
|
|
6
5
|
module Lutaml
|
|
6
|
+
class Error < StandardError; end
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
require_relative "lutaml/parser"
|
|
10
|
+
|
|
9
11
|
require_relative "lutaml/express"
|
|
10
12
|
require_relative "lutaml/formatter"
|
|
11
13
|
require_relative "lutaml/layout"
|
data/lutaml.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
spec.name = "lutaml"
|
|
5
5
|
spec.version = Lutaml::VERSION
|
|
6
6
|
spec.authors = ["Ribose Inc."]
|
|
7
|
-
spec.email = ["open.source@ribose.com
|
|
7
|
+
spec.email = ["open.source@ribose.com"]
|
|
8
8
|
|
|
9
9
|
spec.summary = "LutaML: data models in textual form"
|
|
10
10
|
spec.description = "LutaML: data models in textual form"
|
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
28
28
|
spec.require_paths = ["lib"]
|
|
29
29
|
|
|
30
|
-
spec.required_ruby_version = ">= 2.
|
|
30
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
31
31
|
|
|
32
32
|
spec.add_dependency "expressir", "~> 2.3"
|
|
33
33
|
# TODO: remove once reline declares fiddle as a dependency
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: expressir
|
|
@@ -312,7 +312,7 @@ dependencies:
|
|
|
312
312
|
version: 0.5.2
|
|
313
313
|
description: 'LutaML: data models in textual form'
|
|
314
314
|
email:
|
|
315
|
-
- open.source@ribose.com
|
|
315
|
+
- open.source@ribose.com
|
|
316
316
|
executables:
|
|
317
317
|
- lutaml
|
|
318
318
|
- lutaml-sysml
|
|
@@ -330,6 +330,7 @@ files:
|
|
|
330
330
|
- ".rspec"
|
|
331
331
|
- ".rubocop.yml"
|
|
332
332
|
- ".rubocop_todo.yml"
|
|
333
|
+
- CHANGELOG.md
|
|
333
334
|
- Gemfile
|
|
334
335
|
- Makefile
|
|
335
336
|
- README.adoc
|
|
@@ -960,7 +961,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
960
961
|
requirements:
|
|
961
962
|
- - ">="
|
|
962
963
|
- !ruby/object:Gem::Version
|
|
963
|
-
version: 2.
|
|
964
|
+
version: 3.2.0
|
|
964
965
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
965
966
|
requirements:
|
|
966
967
|
- - ">="
|