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
|
@@ -11,6 +11,27 @@ require_relative "id_generator"
|
|
|
11
11
|
module Lutaml
|
|
12
12
|
module UmlRepository
|
|
13
13
|
module StaticSite
|
|
14
|
+
# Resolves Liquid {% include %} paths to template files on disk.
|
|
15
|
+
# Unlike LocalFileSystem (which adds a "_" prefix), this resolves
|
|
16
|
+
# paths directly: "components/header" → "<root>/components/header.liquid"
|
|
17
|
+
class TemplateFileSystem
|
|
18
|
+
attr_reader :root
|
|
19
|
+
|
|
20
|
+
def initialize(root)
|
|
21
|
+
@root = root
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read_template_file(template_path)
|
|
25
|
+
full = File.expand_path("#{template_path}.liquid", @root)
|
|
26
|
+
unless full.start_with?(@root)
|
|
27
|
+
raise Liquid::FileSystemError,
|
|
28
|
+
"Illegal template path: #{template_path}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
File.read(full)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
14
35
|
# Main static site generator for LutaML UML Browser.
|
|
15
36
|
#
|
|
16
37
|
# Follows Dependency Inversion Principle by injecting dependencies
|
|
@@ -163,12 +184,9 @@ module Lutaml
|
|
|
163
184
|
end
|
|
164
185
|
|
|
165
186
|
def setup_liquid
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# Changed from :strict to handle missing includes
|
|
170
|
-
environment.error_mode = :lax
|
|
171
|
-
@liquid_environment = environment
|
|
187
|
+
@liquid_environment = Liquid::Environment.new
|
|
188
|
+
@liquid_environment.file_system = TemplateFileSystem.new(@options[:template_path])
|
|
189
|
+
@liquid_environment.error_mode = :lax
|
|
172
190
|
end
|
|
173
191
|
|
|
174
192
|
# Generate single-file SPA
|
|
@@ -226,7 +244,7 @@ module Lutaml
|
|
|
226
244
|
|
|
227
245
|
def render_components # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
228
246
|
# Set up Liquid file system for recursive includes
|
|
229
|
-
temp_file_system =
|
|
247
|
+
temp_file_system = TemplateFileSystem.new(@options[:template_path])
|
|
230
248
|
|
|
231
249
|
component_names = ["header", "sidebar", "content", "package_details",
|
|
232
250
|
"class_details"]
|
|
@@ -274,7 +292,10 @@ module Lutaml
|
|
|
274
292
|
# Render and write index.html
|
|
275
293
|
template_content = File.read(File.join(@options[:template_path],
|
|
276
294
|
"multi_file.liquid"))
|
|
277
|
-
|
|
295
|
+
file_system = TemplateFileSystem.new(@options[:template_path])
|
|
296
|
+
template = Liquid::Template.parse(template_content,
|
|
297
|
+
error_mode: :lax)
|
|
298
|
+
template.registers[:file_system] = file_system
|
|
278
299
|
html = template.render(context)
|
|
279
300
|
html = minify_html(html) if @options[:minify]
|
|
280
301
|
File.write(File.join(output_dir, "index.html"), html)
|
|
@@ -271,10 +271,7 @@ module Lutaml
|
|
|
271
271
|
current = klass
|
|
272
272
|
|
|
273
273
|
while current
|
|
274
|
-
if current.is_a?(Lutaml::Uml::TopElement)
|
|
275
|
-
path_parts.unshift(current.name)
|
|
276
|
-
current = current.namespace if current.respond_to?(:namespace)
|
|
277
|
-
elsif current.is_a?(Lutaml::Uml::Package)
|
|
274
|
+
if current.is_a?(Lutaml::Uml::TopElement) || current.is_a?(Lutaml::Uml::Package)
|
|
278
275
|
path_parts.unshift(current.name)
|
|
279
276
|
current = current.namespace if current.respond_to?(:namespace)
|
|
280
277
|
else
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../uml/model_helpers"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module UmlRepository
|
|
7
|
+
module StaticSite
|
|
8
|
+
module Serializers
|
|
9
|
+
class AttributeSerializer
|
|
10
|
+
include Lutaml::Uml::ModelHelpers
|
|
11
|
+
|
|
12
|
+
def initialize(repository, id_generator, options)
|
|
13
|
+
@repository = repository
|
|
14
|
+
@id_generator = id_generator
|
|
15
|
+
@options = options
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def build_map
|
|
19
|
+
attributes = {}
|
|
20
|
+
@repository.classes_index.each do |klass|
|
|
21
|
+
next unless klass.attributes
|
|
22
|
+
|
|
23
|
+
klass.attributes.each do |attr|
|
|
24
|
+
id = @id_generator.attribute_id(attr, klass)
|
|
25
|
+
attributes[id] = serialize(attr, klass, id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
attributes
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def serialize(attribute, owner, id)
|
|
32
|
+
{
|
|
33
|
+
id: id,
|
|
34
|
+
name: attribute.name,
|
|
35
|
+
type: attribute.type,
|
|
36
|
+
visibility: attribute.visibility,
|
|
37
|
+
owner: @id_generator.class_id(owner),
|
|
38
|
+
ownerName: owner.name,
|
|
39
|
+
cardinality: serialize_cardinality(attribute.cardinality),
|
|
40
|
+
definition: format_definition(attribute.definition),
|
|
41
|
+
stereotypes: normalize_stereotypes(
|
|
42
|
+
attribute.respond_to?(:stereotype) ? attribute.stereotype : nil,
|
|
43
|
+
),
|
|
44
|
+
isStatic: attribute.respond_to?(:is_static) ? attribute.is_static : false,
|
|
45
|
+
isReadOnly: attribute.respond_to?(:is_read_only) ? attribute.is_read_only : false,
|
|
46
|
+
defaultValue: attribute.respond_to?(:default) ? attribute.default : nil,
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def serialize_cardinality(cardinality)
|
|
53
|
+
return nil unless cardinality
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
min: cardinality.min,
|
|
57
|
+
max: cardinality.max,
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def format_definition(definition)
|
|
62
|
+
return nil if definition.nil? || definition.empty?
|
|
63
|
+
|
|
64
|
+
formatted = definition.strip
|
|
65
|
+
if @options[:max_definition_length] &&
|
|
66
|
+
formatted.length > @options[:max_definition_length]
|
|
67
|
+
formatted = "#{formatted[0...@options[:max_definition_length]]}..."
|
|
68
|
+
end
|
|
69
|
+
if @options[:format_definitions]
|
|
70
|
+
formatted = formatted.gsub(%r{(https?://[^\s]+)}, '[\1](\1)')
|
|
71
|
+
end
|
|
72
|
+
formatted
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../uml/model_helpers"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module UmlRepository
|
|
7
|
+
module StaticSite
|
|
8
|
+
module Serializers
|
|
9
|
+
class ClassSerializer
|
|
10
|
+
include Lutaml::Uml::ModelHelpers
|
|
11
|
+
|
|
12
|
+
def initialize(repository, id_generator, options,
|
|
13
|
+
inheritance_resolver)
|
|
14
|
+
@repository = repository
|
|
15
|
+
@id_generator = id_generator
|
|
16
|
+
@options = options
|
|
17
|
+
@inheritance_resolver = inheritance_resolver
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_map
|
|
21
|
+
classes = {}
|
|
22
|
+
@repository.classes_index.each do |klass|
|
|
23
|
+
id = @id_generator.class_id(klass)
|
|
24
|
+
classes[id] = serialize(klass, id)
|
|
25
|
+
end
|
|
26
|
+
classes
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def serialize(klass, id) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
32
|
+
class_associations = find_class_associations(klass)
|
|
33
|
+
sorted_associations = sort_associations(class_associations, klass)
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
id: id,
|
|
37
|
+
xmiId: klass.xmi_id,
|
|
38
|
+
name: klass.name,
|
|
39
|
+
qualifiedName: qualified_name_for(klass),
|
|
40
|
+
type: class_type_for(klass),
|
|
41
|
+
package: package_id_for_class(klass),
|
|
42
|
+
stereotypes: normalize_stereotypes(
|
|
43
|
+
klass.respond_to?(:stereotype) ? klass.stereotype : nil,
|
|
44
|
+
),
|
|
45
|
+
definition: format_definition(klass.definition),
|
|
46
|
+
attributes: (klass.attributes || []).sort_by { |a| a.name || "" }
|
|
47
|
+
.map { |attr| @id_generator.attribute_id(attr, klass) },
|
|
48
|
+
operations: serialize_class_operations(klass),
|
|
49
|
+
associations: sorted_associations,
|
|
50
|
+
generalizations: @inheritance_resolver.find_generalizations(klass),
|
|
51
|
+
specializations: @inheritance_resolver.find_specializations(klass),
|
|
52
|
+
isAbstract: klass.respond_to?(:is_abstract) ? klass.is_abstract : false,
|
|
53
|
+
literals: serialize_literals(klass),
|
|
54
|
+
inheritedAttributes: @inheritance_resolver.compute_inherited_attributes(klass),
|
|
55
|
+
inheritedAssociations: @inheritance_resolver.compute_inherited_associations(klass),
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find_class_associations(klass)
|
|
60
|
+
associations = @repository.associations_of(klass)
|
|
61
|
+
associations.map { |assoc| @id_generator.association_id(assoc) }
|
|
62
|
+
rescue StandardError
|
|
63
|
+
[]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def sort_associations(assoc_ids, klass)
|
|
67
|
+
assoc_ids.sort_by do |assoc_id|
|
|
68
|
+
assoc = @repository.associations_index.find do |a|
|
|
69
|
+
@id_generator.association_id(a) == assoc_id
|
|
70
|
+
end
|
|
71
|
+
next "" unless assoc
|
|
72
|
+
|
|
73
|
+
if assoc.owner_end_xmi_id == klass.xmi_id
|
|
74
|
+
assoc.owner_end_attribute_name || assoc.owner_end || ""
|
|
75
|
+
elsif assoc.member_end_xmi_id == klass.xmi_id
|
|
76
|
+
assoc.member_end_attribute_name || assoc.member_end || ""
|
|
77
|
+
else
|
|
78
|
+
""
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def serialize_class_operations(klass)
|
|
84
|
+
return [] unless klass.respond_to?(:operations) && klass.operations
|
|
85
|
+
|
|
86
|
+
klass.operations.map { |op| @id_generator.operation_id(op, klass) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def serialize_literals(klass)
|
|
90
|
+
return [] unless klass.is_a?(Lutaml::Uml::Enum) && klass.owned_literal
|
|
91
|
+
|
|
92
|
+
klass.owned_literal.map do |literal|
|
|
93
|
+
{ name: literal.name,
|
|
94
|
+
definition: format_definition(literal.definition) }
|
|
95
|
+
end
|
|
96
|
+
rescue StandardError
|
|
97
|
+
[]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def package_id_for_class(klass)
|
|
101
|
+
ns = klass.respond_to?(:namespace) ? klass.namespace : nil
|
|
102
|
+
return nil unless ns.is_a?(Lutaml::Uml::Package)
|
|
103
|
+
|
|
104
|
+
@id_generator.package_id(ns)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def format_definition(definition)
|
|
108
|
+
return nil if definition.nil? || definition.empty?
|
|
109
|
+
|
|
110
|
+
formatted = definition.strip
|
|
111
|
+
if @options[:max_definition_length] &&
|
|
112
|
+
formatted.length > @options[:max_definition_length]
|
|
113
|
+
formatted = "#{formatted[0...@options[:max_definition_length]]}..."
|
|
114
|
+
end
|
|
115
|
+
if @options[:format_definitions]
|
|
116
|
+
formatted = formatted.gsub(%r{(https?://[^\s]+)}, '[\1](\1)')
|
|
117
|
+
end
|
|
118
|
+
formatted
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module UmlRepository
|
|
5
|
+
module StaticSite
|
|
6
|
+
module Serializers
|
|
7
|
+
class DiagramSerializer
|
|
8
|
+
def initialize(repository, id_generator, options)
|
|
9
|
+
@repository = repository
|
|
10
|
+
@id_generator = id_generator
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build_map
|
|
15
|
+
diagrams = {}
|
|
16
|
+
@repository.diagrams_index.each do |diagram|
|
|
17
|
+
id = @id_generator.diagram_id(diagram)
|
|
18
|
+
diagrams[id] = serialize(diagram, id)
|
|
19
|
+
end
|
|
20
|
+
diagrams
|
|
21
|
+
rescue StandardError
|
|
22
|
+
{}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def serialize(diagram, id)
|
|
28
|
+
{
|
|
29
|
+
id: id,
|
|
30
|
+
xmiId: diagram.xmi_id,
|
|
31
|
+
name: diagram.name,
|
|
32
|
+
type: diagram.diagram_type,
|
|
33
|
+
package: find_diagram_package(diagram),
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_diagram_package(diagram)
|
|
38
|
+
@repository.packages_index.each do |pkg|
|
|
39
|
+
diagrams = package_diagrams(pkg)
|
|
40
|
+
if diagrams.any? { |d| d.xmi_id == diagram.xmi_id }
|
|
41
|
+
return @id_generator.package_id(pkg)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
nil
|
|
45
|
+
rescue StandardError
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def package_diagrams(package)
|
|
50
|
+
return [] unless @options[:include_diagrams]
|
|
51
|
+
|
|
52
|
+
package.diagrams || []
|
|
53
|
+
rescue StandardError
|
|
54
|
+
[]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../uml/model_helpers"
|
|
4
|
+
require_relative "../../class_lookup_index"
|
|
5
|
+
|
|
6
|
+
module Lutaml
|
|
7
|
+
module UmlRepository
|
|
8
|
+
module StaticSite
|
|
9
|
+
module Serializers
|
|
10
|
+
class InheritanceResolver
|
|
11
|
+
include Lutaml::Uml::ModelHelpers
|
|
12
|
+
|
|
13
|
+
def initialize(repository, id_generator, options, generalization_map)
|
|
14
|
+
@repository = repository
|
|
15
|
+
@id_generator = id_generator
|
|
16
|
+
@options = options
|
|
17
|
+
@generalization_map = generalization_map
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_generalizations(klass)
|
|
21
|
+
parent_xmi_ids = @generalization_map[klass.xmi_id]
|
|
22
|
+
|
|
23
|
+
if parent_xmi_ids && !parent_xmi_ids.empty?
|
|
24
|
+
parents = parent_xmi_ids.filter_map do |parent_xmi_id|
|
|
25
|
+
next if parent_xmi_id == klass.xmi_id
|
|
26
|
+
|
|
27
|
+
parent = class_lookup.by_xmi_id(parent_xmi_id)
|
|
28
|
+
parent ? @id_generator.class_id(parent) : nil
|
|
29
|
+
end
|
|
30
|
+
return parents unless parents.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
parent = @repository.supertype_of(klass)
|
|
34
|
+
return [] if parent && parent.xmi_id == klass.xmi_id
|
|
35
|
+
|
|
36
|
+
parent ? [@id_generator.class_id(parent)] : []
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
warn "Error finding generalizations for #{klass.name}: #{e.message}"
|
|
39
|
+
[]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find_specializations(klass)
|
|
43
|
+
children = @repository.subtypes_of(klass)
|
|
44
|
+
children.reject { |child| child.xmi_id == klass.xmi_id }
|
|
45
|
+
.map { |child| @id_generator.class_id(child) }
|
|
46
|
+
rescue StandardError
|
|
47
|
+
[]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def compute_inherited_attributes(klass, visited = Set.new)
|
|
51
|
+
unless klass.respond_to?(:generalization) && klass.generalization
|
|
52
|
+
return []
|
|
53
|
+
end
|
|
54
|
+
return [] if visited.include?(klass.xmi_id)
|
|
55
|
+
|
|
56
|
+
visited.add(klass.xmi_id)
|
|
57
|
+
inherited = []
|
|
58
|
+
current_gen = klass.generalization
|
|
59
|
+
parent_order = 0
|
|
60
|
+
|
|
61
|
+
while current_gen
|
|
62
|
+
parent_class = class_lookup.by_xmi_id(current_gen.general_id)
|
|
63
|
+
break unless parent_class
|
|
64
|
+
break if visited.include?(parent_class.xmi_id)
|
|
65
|
+
|
|
66
|
+
visited.add(parent_class.xmi_id)
|
|
67
|
+
|
|
68
|
+
if parent_class.attributes
|
|
69
|
+
sorted_attrs = parent_class.attributes.sort_by do |a|
|
|
70
|
+
a.name || ""
|
|
71
|
+
end
|
|
72
|
+
sorted_attrs.each do |attr|
|
|
73
|
+
attr_id = @id_generator.attribute_id(attr, parent_class)
|
|
74
|
+
inherited << {
|
|
75
|
+
attributeId: attr_id,
|
|
76
|
+
attribute: serialize_attribute(attr, parent_class, attr_id),
|
|
77
|
+
inheritedFrom: @id_generator.class_id(parent_class),
|
|
78
|
+
inheritedFromName: parent_class.name,
|
|
79
|
+
parentOrder: parent_order,
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
parent_order += 1
|
|
85
|
+
current_gen = current_gen.general if current_gen.respond_to?(:general)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
inherited
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
warn "Error computing inherited attributes: #{e.message}"
|
|
91
|
+
[]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def compute_inherited_associations(klass, visited = Set.new)
|
|
95
|
+
unless klass.respond_to?(:generalization) && klass.generalization
|
|
96
|
+
return []
|
|
97
|
+
end
|
|
98
|
+
return [] if visited.include?(klass.xmi_id)
|
|
99
|
+
|
|
100
|
+
visited.add(klass.xmi_id)
|
|
101
|
+
inherited = []
|
|
102
|
+
current_gen = klass.generalization
|
|
103
|
+
parent_order = 0
|
|
104
|
+
|
|
105
|
+
while current_gen
|
|
106
|
+
parent_class = class_lookup.by_xmi_id(current_gen.general_id)
|
|
107
|
+
break unless parent_class
|
|
108
|
+
break if visited.include?(parent_class.xmi_id)
|
|
109
|
+
|
|
110
|
+
visited.add(parent_class.xmi_id)
|
|
111
|
+
parent_associations = find_class_associations(parent_class)
|
|
112
|
+
|
|
113
|
+
assoc_with_roles = parent_associations.filter_map do |assoc_id|
|
|
114
|
+
assoc = @repository.associations_index.find do |a|
|
|
115
|
+
@id_generator.association_id(a) == assoc_id
|
|
116
|
+
end
|
|
117
|
+
next unless assoc
|
|
118
|
+
|
|
119
|
+
local_role = if assoc.owner_end_xmi_id == parent_class.xmi_id
|
|
120
|
+
assoc.owner_end_attribute_name || assoc.owner_end || ""
|
|
121
|
+
elsif assoc.member_end_xmi_id == parent_class.xmi_id
|
|
122
|
+
assoc.member_end_attribute_name || assoc.member_end || ""
|
|
123
|
+
else
|
|
124
|
+
""
|
|
125
|
+
end
|
|
126
|
+
{ id: assoc_id, role: local_role }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
assoc_with_roles.sort_by { |a| a[:role] }.each do |item|
|
|
130
|
+
inherited << {
|
|
131
|
+
associationId: item[:id],
|
|
132
|
+
inheritedFrom: @id_generator.class_id(parent_class),
|
|
133
|
+
inheritedFromName: parent_class.name,
|
|
134
|
+
parentOrder: parent_order,
|
|
135
|
+
localRole: item[:role],
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
parent_order += 1
|
|
140
|
+
current_gen = current_gen.general if current_gen.respond_to?(:general)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
inherited
|
|
144
|
+
rescue StandardError => e
|
|
145
|
+
warn "Error computing inherited associations: #{e.message}"
|
|
146
|
+
[]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def serialize_generalization(klass, visited = Set.new)
|
|
150
|
+
unless klass.respond_to?(:generalization) && klass.generalization
|
|
151
|
+
return nil
|
|
152
|
+
end
|
|
153
|
+
return nil if visited.include?(klass.xmi_id)
|
|
154
|
+
|
|
155
|
+
visited.add(klass.xmi_id)
|
|
156
|
+
gen = klass.generalization
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
generalId: gen.general_id,
|
|
160
|
+
generalName: gen.general_name,
|
|
161
|
+
generalUpperKlass: gen.respond_to?(:general_upper_klass) ? gen.general_upper_klass : nil,
|
|
162
|
+
hasGeneral: gen.respond_to?(:has_general) ? gen.has_general : false,
|
|
163
|
+
name: gen.name,
|
|
164
|
+
type: gen.type,
|
|
165
|
+
definition: format_definition(gen.definition),
|
|
166
|
+
stereotype: gen.respond_to?(:stereotype) ? gen.stereotype : nil,
|
|
167
|
+
ownedProps: serialize_general_attrs(gen, :owned_props),
|
|
168
|
+
assocProps: serialize_general_attrs(gen, :assoc_props),
|
|
169
|
+
inheritedProps: serialize_general_attrs(gen, :inherited_props),
|
|
170
|
+
inheritedAssocProps: serialize_general_attrs(gen,
|
|
171
|
+
:inherited_assoc_props),
|
|
172
|
+
}
|
|
173
|
+
rescue StandardError => e
|
|
174
|
+
warn "Error serializing generalization: #{e.message}"
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def serialize_general_attribute(attr)
|
|
179
|
+
return nil unless attr
|
|
180
|
+
|
|
181
|
+
{
|
|
182
|
+
name: attr.name,
|
|
183
|
+
type: attr.type,
|
|
184
|
+
cardinality: serialize_cardinality(attr.cardinality),
|
|
185
|
+
definition: format_definition(attr.definition),
|
|
186
|
+
upperKlass: attr.respond_to?(:upper_klass) ? attr.upper_klass : nil,
|
|
187
|
+
nameNs: attr.respond_to?(:name_ns) ? attr.name_ns : nil,
|
|
188
|
+
typeNs: attr.respond_to?(:type_ns) ? attr.type_ns : nil,
|
|
189
|
+
}
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private
|
|
193
|
+
|
|
194
|
+
def class_lookup
|
|
195
|
+
@class_lookup ||= ClassLookupIndex.new(@repository.classes_index)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def find_class_associations(klass)
|
|
199
|
+
associations = @repository.associations_of(klass)
|
|
200
|
+
associations.map { |assoc| @id_generator.association_id(assoc) }
|
|
201
|
+
rescue StandardError
|
|
202
|
+
[]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def serialize_attribute(attribute, owner, id)
|
|
206
|
+
{
|
|
207
|
+
id: id,
|
|
208
|
+
name: attribute.name,
|
|
209
|
+
type: attribute.type,
|
|
210
|
+
visibility: attribute.visibility,
|
|
211
|
+
owner: @id_generator.class_id(owner),
|
|
212
|
+
ownerName: owner.name,
|
|
213
|
+
cardinality: serialize_cardinality(attribute.cardinality),
|
|
214
|
+
definition: format_definition(attribute.definition),
|
|
215
|
+
stereotypes: normalize_stereotypes(
|
|
216
|
+
attribute.respond_to?(:stereotype) ? attribute.stereotype : nil,
|
|
217
|
+
),
|
|
218
|
+
isStatic: attribute.respond_to?(:is_static) ? attribute.is_static : false,
|
|
219
|
+
isReadOnly: attribute.respond_to?(:is_read_only) ? attribute.is_read_only : false,
|
|
220
|
+
defaultValue: attribute.respond_to?(:default) ? attribute.default : nil,
|
|
221
|
+
}
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def serialize_general_attrs(gen, method)
|
|
225
|
+
return [] unless gen.respond_to?(method)
|
|
226
|
+
|
|
227
|
+
(gen.send(method) || []).map do |attr|
|
|
228
|
+
serialize_general_attribute(attr)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def serialize_cardinality(cardinality)
|
|
233
|
+
return nil unless cardinality
|
|
234
|
+
|
|
235
|
+
{
|
|
236
|
+
min: cardinality.min,
|
|
237
|
+
max: cardinality.max,
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def format_definition(definition)
|
|
242
|
+
return nil if definition.nil? || definition.empty?
|
|
243
|
+
|
|
244
|
+
formatted = definition.strip
|
|
245
|
+
if @options[:max_definition_length] &&
|
|
246
|
+
formatted.length > @options[:max_definition_length]
|
|
247
|
+
formatted = "#{formatted[0...@options[:max_definition_length]]}..."
|
|
248
|
+
end
|
|
249
|
+
if @options[:format_definitions]
|
|
250
|
+
formatted = formatted.gsub(%r{(https?://[^\s]+)}, '[\1](\1)')
|
|
251
|
+
end
|
|
252
|
+
formatted
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module UmlRepository
|
|
5
|
+
module StaticSite
|
|
6
|
+
module Serializers
|
|
7
|
+
class MetadataBuilder
|
|
8
|
+
def initialize(repository)
|
|
9
|
+
@repository = repository
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build
|
|
13
|
+
{
|
|
14
|
+
generated: Time.now.utc.iso8601,
|
|
15
|
+
generator: "LutaML Static Site Generator",
|
|
16
|
+
version: "1.0",
|
|
17
|
+
statistics: build_statistics,
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def build_statistics
|
|
24
|
+
{
|
|
25
|
+
packages: @repository.packages_index.size,
|
|
26
|
+
classes: @repository.classes_index.size,
|
|
27
|
+
associations: @repository.associations_index.size,
|
|
28
|
+
attributes: count_total_attributes,
|
|
29
|
+
operations: count_total_operations,
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def count_total_attributes
|
|
34
|
+
@repository.classes_index.sum do |klass|
|
|
35
|
+
klass.attributes&.size || 0
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def count_total_operations
|
|
40
|
+
@repository.classes_index.sum do |klass|
|
|
41
|
+
(klass.respond_to?(:operations) ? klass.operations&.size : 0) || 0
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|