ea 0.1.0 → 0.1.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/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- metadata +382 -9
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Load local development version
|
|
5
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
6
|
+
|
|
7
|
+
# Example: Repository Statistics and Analysis
|
|
8
|
+
#
|
|
9
|
+
# This example demonstrates how to extract and analyze comprehensive
|
|
10
|
+
# statistics from a UML repository, including package structure,
|
|
11
|
+
# class complexity, relationships, and model quality metrics.
|
|
12
|
+
|
|
13
|
+
require "lutaml"
|
|
14
|
+
require "lutaml/uml_repository"
|
|
15
|
+
require "ea"
|
|
16
|
+
|
|
17
|
+
puts "=" * 80
|
|
18
|
+
puts "LUR Repository Statistics Examples"
|
|
19
|
+
puts "=" * 80
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
# Setup: Find a test file
|
|
23
|
+
qea_file = Dir.glob("examples/qea/*.qea").first
|
|
24
|
+
xmi_file = Dir.glob("examples/qea/*.xmi").first
|
|
25
|
+
test_file = qea_file || xmi_file
|
|
26
|
+
|
|
27
|
+
unless test_file
|
|
28
|
+
puts "No test files found in examples/qea/"
|
|
29
|
+
puts
|
|
30
|
+
puts "This example demonstrates statistics extraction. To run:"
|
|
31
|
+
puts " 1. Place a .qea or .xmi file in examples/qea/"
|
|
32
|
+
puts " 2. Or examine the statistics API below"
|
|
33
|
+
puts
|
|
34
|
+
puts "Statistics API example:"
|
|
35
|
+
puts <<~RUBY
|
|
36
|
+
repo = Lutaml::UmlRepository::Repository.from_xmi("model.xmi")
|
|
37
|
+
|
|
38
|
+
stats = repo.statistics
|
|
39
|
+
|
|
40
|
+
# Core counts
|
|
41
|
+
puts "Packages: \#{stats[:total_packages]}"
|
|
42
|
+
puts "Classes: \#{stats[:total_classes]}"
|
|
43
|
+
puts "Associations: \#{stats[:total_associations]}"
|
|
44
|
+
|
|
45
|
+
# Stereotypes
|
|
46
|
+
stats[:classes_by_stereotype].each do |stereotype, count|
|
|
47
|
+
puts "\#{stereotype}: \#{count}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Complexity metrics
|
|
51
|
+
puts "Max attributes: \#{stats[:max_attributes]}"
|
|
52
|
+
puts "Avg attributes: \#{stats[:avg_attributes]}"
|
|
53
|
+
|
|
54
|
+
# Package structure
|
|
55
|
+
puts "Max package depth: \#{stats[:max_package_depth]}"
|
|
56
|
+
RUBY
|
|
57
|
+
exit 0
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
puts "Loading model: #{test_file}"
|
|
61
|
+
puts "File size: #{File.size(test_file) / 1024}KB"
|
|
62
|
+
puts
|
|
63
|
+
|
|
64
|
+
# Load repository
|
|
65
|
+
if test_file.end_with?(".qea")
|
|
66
|
+
document = Ea::Qea.parse(test_file)
|
|
67
|
+
repo = Lutaml::UmlRepository::Repository.new(document: document)
|
|
68
|
+
elsif test_file.end_with?(".xmi")
|
|
69
|
+
repo = Lutaml::UmlRepository::Repository.from_xmi(test_file)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
puts "Repository loaded successfully"
|
|
73
|
+
puts
|
|
74
|
+
|
|
75
|
+
# Statistics Section 1: Overview
|
|
76
|
+
puts "=" * 80
|
|
77
|
+
puts "1. Model Overview"
|
|
78
|
+
puts "=" * 80
|
|
79
|
+
|
|
80
|
+
stats = repo.statistics
|
|
81
|
+
|
|
82
|
+
puts "Total elements:"
|
|
83
|
+
puts " Packages: #{stats[:total_packages]}"
|
|
84
|
+
puts " Classes: #{stats[:total_classes]}"
|
|
85
|
+
puts " Data types: #{stats[:total_data_types]}"
|
|
86
|
+
puts " Enumerations: #{stats[:total_enums]}"
|
|
87
|
+
puts " Associations: #{stats[:total_associations]}"
|
|
88
|
+
puts " Diagrams: #{stats[:total_diagrams]}"
|
|
89
|
+
puts
|
|
90
|
+
|
|
91
|
+
# Statistics Section 2: Package Structure
|
|
92
|
+
puts "=" * 80
|
|
93
|
+
puts "2. Package Structure"
|
|
94
|
+
puts "=" * 80
|
|
95
|
+
|
|
96
|
+
if stats[:max_package_depth]
|
|
97
|
+
puts "Package hierarchy depth: #{stats[:max_package_depth]}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
packages = repo.packages_index
|
|
101
|
+
if packages.any?
|
|
102
|
+
puts "Total packages: #{packages.size}"
|
|
103
|
+
puts "\nSample packages:"
|
|
104
|
+
packages.first(5).each do |pkg|
|
|
105
|
+
puts " - #{pkg.name}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
puts
|
|
109
|
+
|
|
110
|
+
# Statistics Section 3: Class Stereotypes
|
|
111
|
+
puts "=" * 80
|
|
112
|
+
puts "3. Class Stereotypes"
|
|
113
|
+
puts "=" * 80
|
|
114
|
+
|
|
115
|
+
if stats[:classes_by_stereotype]
|
|
116
|
+
stereotype_count = stats[:classes_by_stereotype].size
|
|
117
|
+
puts "Unique stereotypes: #{stereotype_count}"
|
|
118
|
+
puts "\nStereotype distribution:"
|
|
119
|
+
|
|
120
|
+
stats[:classes_by_stereotype].sort_by do |_, count|
|
|
121
|
+
-count
|
|
122
|
+
end.first(10).each do |stereotype, count|
|
|
123
|
+
percentage = (count.to_f / stats[:total_classes] * 100).round(1)
|
|
124
|
+
puts " #{stereotype.ljust(30)} #{count.to_s.rjust(5)} (#{percentage}%)"
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
puts "No stereotype information available"
|
|
128
|
+
end
|
|
129
|
+
puts
|
|
130
|
+
|
|
131
|
+
# Statistics Section 4: Class Complexity
|
|
132
|
+
puts "=" * 80
|
|
133
|
+
puts "4. Class Complexity Metrics"
|
|
134
|
+
puts "=" * 80
|
|
135
|
+
|
|
136
|
+
all_classes = repo.classes_index.grep(Lutaml::Uml::UmlClass)
|
|
137
|
+
|
|
138
|
+
if all_classes.any?
|
|
139
|
+
# Attribute counts
|
|
140
|
+
attr_counts = all_classes.map { |c| c.attributes&.size || 0 }
|
|
141
|
+
|
|
142
|
+
puts "Attribute statistics:"
|
|
143
|
+
puts " Classes with attributes: #{attr_counts.count(&:positive?)}"
|
|
144
|
+
puts " Min attributes: #{attr_counts.min}"
|
|
145
|
+
puts " Max attributes: #{attr_counts.max}"
|
|
146
|
+
puts " Avg attributes: #{(attr_counts.sum.to_f / attr_counts.size).round(2)}"
|
|
147
|
+
puts " Median attributes: #{attr_counts.sort[attr_counts.size / 2]}"
|
|
148
|
+
|
|
149
|
+
# Find most complex classes
|
|
150
|
+
puts "\nMost complex classes (by attribute count):"
|
|
151
|
+
complex_classes = all_classes.map { |c| [c.name, c.attributes&.size || 0] }
|
|
152
|
+
.sort_by { |_, count| -count }
|
|
153
|
+
.first(5)
|
|
154
|
+
|
|
155
|
+
complex_classes.each do |name, count|
|
|
156
|
+
puts " #{name}: #{count} attributes"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Operation counts
|
|
160
|
+
op_counts = all_classes.map { |c| c.operations&.size || 0 }
|
|
161
|
+
|
|
162
|
+
puts "\nOperation statistics:"
|
|
163
|
+
puts " Classes with operations: #{op_counts.count(&:positive?)}"
|
|
164
|
+
puts " Max operations: #{op_counts.max}"
|
|
165
|
+
puts " Avg operations: #{(op_counts.sum.to_f / op_counts.size).round(2)}"
|
|
166
|
+
else
|
|
167
|
+
puts "No class data available"
|
|
168
|
+
end
|
|
169
|
+
puts
|
|
170
|
+
|
|
171
|
+
# Statistics Section 5: Inheritance
|
|
172
|
+
puts "=" * 80
|
|
173
|
+
puts "5. Inheritance Analysis"
|
|
174
|
+
puts "=" * 80
|
|
175
|
+
|
|
176
|
+
if all_classes.any?
|
|
177
|
+
classes_with_parent = all_classes.select do |c|
|
|
178
|
+
repo.supertype_of(c)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
puts "Inheritance statistics:"
|
|
182
|
+
puts " Classes with parent: #{classes_with_parent.size}"
|
|
183
|
+
puts " Root classes: #{all_classes.size - classes_with_parent.size}"
|
|
184
|
+
puts " Inheritance usage: " \
|
|
185
|
+
"#{(classes_with_parent.size.to_f / all_classes.size * 100).round(1)}%"
|
|
186
|
+
|
|
187
|
+
# Find deepest hierarchies
|
|
188
|
+
max_depth = 0
|
|
189
|
+
deepest_class = nil
|
|
190
|
+
|
|
191
|
+
all_classes.first(100).each do |klass|
|
|
192
|
+
ancestors = repo.ancestors_of(klass)
|
|
193
|
+
if ancestors.size > max_depth
|
|
194
|
+
max_depth = ancestors.size
|
|
195
|
+
deepest_class = klass
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if deepest_class
|
|
200
|
+
puts "\nDeepest inheritance hierarchy: #{max_depth} levels"
|
|
201
|
+
puts " Class: #{deepest_class.name}"
|
|
202
|
+
ancestors = repo.ancestors_of(deepest_class)
|
|
203
|
+
puts " Ancestors: #{ancestors.map(&:name).join(' < ')}"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
puts
|
|
207
|
+
|
|
208
|
+
# Statistics Section 6: Associations
|
|
209
|
+
puts "=" * 80
|
|
210
|
+
puts "6. Association Analysis"
|
|
211
|
+
puts "=" * 80
|
|
212
|
+
|
|
213
|
+
associations = repo.associations_index
|
|
214
|
+
|
|
215
|
+
if associations.any?
|
|
216
|
+
puts "Total associations: #{associations.size}"
|
|
217
|
+
|
|
218
|
+
# Association types
|
|
219
|
+
assoc_types = associations.group_by do |a|
|
|
220
|
+
a.member_end_type&.first || "unknown"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
puts "\nAssociation types:"
|
|
224
|
+
assoc_types.sort_by { |_, list| -list.size }.first(10).each do |type, list|
|
|
225
|
+
puts " #{type.ljust(20)} #{list.size}"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Most connected classes
|
|
229
|
+
connection_counts = Hash.new(0)
|
|
230
|
+
|
|
231
|
+
all_classes.first(100).each do |klass|
|
|
232
|
+
assocs = repo.associations_of(klass)
|
|
233
|
+
connection_counts[klass.name] = assocs.size
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
puts "\nMost connected classes:"
|
|
237
|
+
connection_counts.sort_by { |_, count| -count }.first(5).each do |name, count|
|
|
238
|
+
puts " #{name}: #{count} associations"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
puts
|
|
242
|
+
|
|
243
|
+
# Statistics Section 7: Model Quality
|
|
244
|
+
puts "=" * 80
|
|
245
|
+
puts "7. Model Quality Indicators"
|
|
246
|
+
puts "=" * 80
|
|
247
|
+
|
|
248
|
+
# Run validation
|
|
249
|
+
validation = repo.validate
|
|
250
|
+
|
|
251
|
+
puts "Validation status: #{validation.valid? ? 'PASS ✓' : 'FAIL ✗'}"
|
|
252
|
+
puts " Errors: #{validation.errors.size}"
|
|
253
|
+
puts " Warnings: #{validation.warnings.size}"
|
|
254
|
+
|
|
255
|
+
if all_classes.any?
|
|
256
|
+
# Calculate documentation coverage
|
|
257
|
+
documented_classes = all_classes.count do |c|
|
|
258
|
+
c.respond_to?(:documentation) && c.documentation && !c.documentation.empty?
|
|
259
|
+
end
|
|
260
|
+
doc_coverage = (documented_classes.to_f / all_classes.size * 100).round(1)
|
|
261
|
+
|
|
262
|
+
puts "\nDocumentation coverage:"
|
|
263
|
+
puts " Documented classes: #{documented_classes}/#{all_classes.size} " \
|
|
264
|
+
"(#{doc_coverage}%)"
|
|
265
|
+
|
|
266
|
+
# Naming conventions
|
|
267
|
+
camel_case = all_classes.count { |c| c.name =~ /^[A-Z][a-zA-Z0-9]*$/ }
|
|
268
|
+
naming_compliance = (camel_case.to_f / all_classes.size * 100).round(1)
|
|
269
|
+
|
|
270
|
+
puts "\nNaming conventions:"
|
|
271
|
+
puts " CamelCase classes: #{camel_case}/#{all_classes.size} " \
|
|
272
|
+
"(#{naming_compliance}%)"
|
|
273
|
+
|
|
274
|
+
# Abstract classes
|
|
275
|
+
abstract_classes = all_classes.count do |c|
|
|
276
|
+
c.respond_to?(:is_abstract) && c.is_abstract
|
|
277
|
+
end
|
|
278
|
+
abstract_ratio = (abstract_classes.to_f / all_classes.size * 100).round(1)
|
|
279
|
+
|
|
280
|
+
puts "\nAbstraction:"
|
|
281
|
+
puts " Abstract classes: #{abstract_classes}/#{all_classes.size} " \
|
|
282
|
+
"(#{abstract_ratio}%)"
|
|
283
|
+
end
|
|
284
|
+
puts
|
|
285
|
+
|
|
286
|
+
# Statistics Section 8: Export Summary
|
|
287
|
+
puts "=" * 80
|
|
288
|
+
puts "8. Summary Statistics (JSON format)"
|
|
289
|
+
puts "=" * 80
|
|
290
|
+
|
|
291
|
+
require "json"
|
|
292
|
+
|
|
293
|
+
summary = {
|
|
294
|
+
model: {
|
|
295
|
+
packages: stats[:total_packages],
|
|
296
|
+
classes: stats[:total_classes],
|
|
297
|
+
data_types: stats[:total_data_types],
|
|
298
|
+
enumerations: stats[:total_enums],
|
|
299
|
+
associations: stats[:total_associations],
|
|
300
|
+
diagrams: stats[:total_diagrams],
|
|
301
|
+
},
|
|
302
|
+
structure: {
|
|
303
|
+
max_package_depth: stats[:max_package_depth],
|
|
304
|
+
},
|
|
305
|
+
validation: {
|
|
306
|
+
valid: validation.valid?,
|
|
307
|
+
errors: validation.errors.size,
|
|
308
|
+
warnings: validation.warnings.size,
|
|
309
|
+
},
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if stats[:classes_by_stereotype]
|
|
313
|
+
summary[:stereotypes] = stats[:classes_by_stereotype]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
puts JSON.pretty_generate(summary)
|
|
317
|
+
puts
|
|
318
|
+
|
|
319
|
+
puts "=" * 80
|
|
320
|
+
puts "Statistics analysis complete!"
|
|
321
|
+
puts "=" * 80
|
|
322
|
+
puts
|
|
323
|
+
puts "You can also get statistics via CLI:"
|
|
324
|
+
puts " $ lutaml uml stats model.lur"
|
|
325
|
+
puts " $ lutaml uml stats model.lur --detailed"
|
|
326
|
+
puts " $ lutaml uml stats model.lur --format json"
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
= QEA Parser - Enterprise Architect Database Parser
|
|
2
|
+
|
|
3
|
+
A Ruby library for parsing Enterprise Architect .qea (SQLite) database files into structured Ruby objects using Lutaml::Model.
|
|
4
|
+
|
|
5
|
+
== Overview
|
|
6
|
+
|
|
7
|
+
This library provides direct parsing of Enterprise Architect `.qea` files (SQLite databases) without requiring XMI export. This provides significant performance improvements and access to EA-specific metadata.
|
|
8
|
+
|
|
9
|
+
You can:
|
|
10
|
+
|
|
11
|
+
* Parse QEA files directly to UML documents
|
|
12
|
+
* Access UML model elements (classes, attributes, operations, associations, packages, diagrams)
|
|
13
|
+
* Query and analyze Enterprise Architect models programmatically
|
|
14
|
+
* Build LUR packages for fast querying
|
|
15
|
+
|
|
16
|
+
== Installation
|
|
17
|
+
|
|
18
|
+
[source,bash]
|
|
19
|
+
----
|
|
20
|
+
gem install lutaml
|
|
21
|
+
----
|
|
22
|
+
|
|
23
|
+
== Usage
|
|
24
|
+
|
|
25
|
+
=== Basic Usage - Parse to UML Document
|
|
26
|
+
|
|
27
|
+
[source,ruby]
|
|
28
|
+
----
|
|
29
|
+
require "ea/qea"
|
|
30
|
+
|
|
31
|
+
# Parse QEA file to UML Document
|
|
32
|
+
document = Ea::Qea.parse("model.qea")
|
|
33
|
+
|
|
34
|
+
# Use with UmlRepository for querying
|
|
35
|
+
repo = Lutaml::UmlRepository::Repository.new(document: document)
|
|
36
|
+
|
|
37
|
+
# Query the model
|
|
38
|
+
puts "Total classes: #{repo.classes_index.size}"
|
|
39
|
+
puts "Total packages: #{repo.packages_index.size}"
|
|
40
|
+
|
|
41
|
+
# Find classes by stereotype
|
|
42
|
+
feature_types = repo.find_classes_by_stereotype("featureType")
|
|
43
|
+
puts "Feature types: #{feature_types.size}"
|
|
44
|
+
|
|
45
|
+
# Get statistics
|
|
46
|
+
stats = repo.statistics
|
|
47
|
+
puts "Packages: #{stats[:total_packages]}"
|
|
48
|
+
puts "Classes: #{stats[:total_classes]}"
|
|
49
|
+
puts "Associations: #{stats[:total_associations]}"
|
|
50
|
+
----
|
|
51
|
+
|
|
52
|
+
=== Parse with Validation
|
|
53
|
+
|
|
54
|
+
[source,ruby]
|
|
55
|
+
----
|
|
56
|
+
require "ea/qea"
|
|
57
|
+
|
|
58
|
+
# Parse with validation
|
|
59
|
+
result = Ea::Qea.parse("model.qea", validate: true)
|
|
60
|
+
|
|
61
|
+
document = result[:document]
|
|
62
|
+
validation = result[:validation_result]
|
|
63
|
+
|
|
64
|
+
if validation.valid?
|
|
65
|
+
puts "Model is valid!"
|
|
66
|
+
else
|
|
67
|
+
puts "Validation errors: #{validation.errors.size}"
|
|
68
|
+
validation.errors.first(5).each do |error|
|
|
69
|
+
puts " - #{error}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
----
|
|
73
|
+
|
|
74
|
+
=== Low-Level Database Access
|
|
75
|
+
|
|
76
|
+
[source,ruby]
|
|
77
|
+
----
|
|
78
|
+
require "ea/qea"
|
|
79
|
+
|
|
80
|
+
# Load database with progress callback
|
|
81
|
+
database = Ea::Qea.load_database("model.qea") do |table, current, total|
|
|
82
|
+
puts "Loading #{table}: #{current}/#{total}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Access raw EA tables
|
|
86
|
+
puts "Objects: #{database.objects.size}"
|
|
87
|
+
puts "Attributes: #{database.attributes.size}"
|
|
88
|
+
puts "Connectors: #{database.connectors.size}"
|
|
89
|
+
puts "Packages: #{database.packages.size}"
|
|
90
|
+
|
|
91
|
+
# Get statistics
|
|
92
|
+
stats = database.stats
|
|
93
|
+
stats.each do |table_name, count|
|
|
94
|
+
puts "#{table_name}: #{count} records"
|
|
95
|
+
end
|
|
96
|
+
----
|
|
97
|
+
|
|
98
|
+
=== Quick Database Info
|
|
99
|
+
|
|
100
|
+
[source,ruby]
|
|
101
|
+
----
|
|
102
|
+
require "ea/qea"
|
|
103
|
+
|
|
104
|
+
# Get quick statistics without full loading
|
|
105
|
+
info = Ea::Qea.database_info("model.qea")
|
|
106
|
+
puts info # => {"objects" => 693, "attributes" => 1910, ...}
|
|
107
|
+
----
|
|
108
|
+
|
|
109
|
+
== Performance
|
|
110
|
+
|
|
111
|
+
Direct QEA parsing offers several advantages over XMI:
|
|
112
|
+
|
|
113
|
+
* *Performance*: 10-20x faster than XMI parsing
|
|
114
|
+
* *No export step*: Parse directly from EA database
|
|
115
|
+
* *EA metadata*: Access to EA-specific information not available in XMI
|
|
116
|
+
* *Memory efficient*: Streams data from SQLite without loading entire file
|
|
117
|
+
|
|
118
|
+
Typical performance for a 20MB EA model:
|
|
119
|
+
|
|
120
|
+
* *QEA parsing*: 1-2 seconds
|
|
121
|
+
* *XMI parsing*: 20-40 seconds
|
|
122
|
+
* *Speedup*: 10-20x faster
|
|
123
|
+
* *Memory*: Lower memory footprint with QEA
|
|
124
|
+
|
|
125
|
+
== CLI Usage
|
|
126
|
+
|
|
127
|
+
All CLI commands accept both XMI and QEA files:
|
|
128
|
+
|
|
129
|
+
[source,bash]
|
|
130
|
+
----
|
|
131
|
+
# Build LUR from QEA (10-20x faster than XMI)
|
|
132
|
+
lutaml uml build model.qea -o model.lur
|
|
133
|
+
|
|
134
|
+
# All other commands work the same
|
|
135
|
+
lutaml uml search model.lur "Building"
|
|
136
|
+
lutaml uml tree model.lur
|
|
137
|
+
lutaml uml stats model.lur
|
|
138
|
+
----
|
|
139
|
+
|
|
140
|
+
== File Validation
|
|
141
|
+
|
|
142
|
+
[source,ruby]
|
|
143
|
+
----
|
|
144
|
+
require "ea/qea/file_detector"
|
|
145
|
+
|
|
146
|
+
# Check if file is valid QEA
|
|
147
|
+
if Ea::Qea::FileDetector.qea_file?("model.qea")
|
|
148
|
+
puts "Valid QEA file"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Validate structure
|
|
152
|
+
result = Ea::Qea::FileDetector.validate_qea("model.qea")
|
|
153
|
+
if result[:valid]
|
|
154
|
+
puts "QEA file is valid"
|
|
155
|
+
else
|
|
156
|
+
result[:errors].each { |e| puts "ERROR: #{e}" }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get file information
|
|
160
|
+
info = Ea::Qea::FileDetector.file_info("model.qea")
|
|
161
|
+
puts "Size: #{info[:size_mb]} MB"
|
|
162
|
+
puts "Tables: #{info[:table_count]}"
|
|
163
|
+
puts "Objects: #{info[:object_count]}"
|
|
164
|
+
----
|
|
165
|
+
|
|
166
|
+
== Configuration
|
|
167
|
+
|
|
168
|
+
QEA parsing is configured via `config/qea_schema.yml`:
|
|
169
|
+
|
|
170
|
+
[source,yaml]
|
|
171
|
+
----
|
|
172
|
+
tables:
|
|
173
|
+
- table_name: t_object
|
|
174
|
+
enabled: true
|
|
175
|
+
model_class: Object
|
|
176
|
+
|
|
177
|
+
- table_name: t_attribute
|
|
178
|
+
enabled: true
|
|
179
|
+
model_class: Attribute
|
|
180
|
+
|
|
181
|
+
# ... other tables
|
|
182
|
+
----
|
|
183
|
+
|
|
184
|
+
== Database Schema
|
|
185
|
+
|
|
186
|
+
The QEA parser supports all Enterprise Architect tables:
|
|
187
|
+
|
|
188
|
+
*Core UML Model:*
|
|
189
|
+
|
|
190
|
+
* `objects` - UML objects (classes, interfaces, components, etc.)
|
|
191
|
+
* `attributes` - Class attributes and properties
|
|
192
|
+
* `operations` - Class operations and methods
|
|
193
|
+
* `operationparams` - Operation parameters
|
|
194
|
+
* `connectors` - Relationships between objects
|
|
195
|
+
* `packages` - UML packages and namespaces
|
|
196
|
+
* `diagrams` - UML diagrams
|
|
197
|
+
|
|
198
|
+
*Extended Model:*
|
|
199
|
+
|
|
200
|
+
* `taggedvalues` - Tagged values (stereotypes, metadata)
|
|
201
|
+
* `stereotypes` - Stereotype definitions
|
|
202
|
+
* `datatypes` - Data type definitions
|
|
203
|
+
* `constraints` - Model constraints
|
|
204
|
+
* And 80+ more tables...
|
|
205
|
+
|
|
206
|
+
== Examples
|
|
207
|
+
|
|
208
|
+
See the `examples/` directory for complete working examples:
|
|
209
|
+
|
|
210
|
+
* `examples/lur_basic_usage.rb` - Basic repository operations with QEA files
|
|
211
|
+
* `examples/lur_cli_workflow.rb` - Command-line workflow with QEA
|
|
212
|
+
* `examples/lur_statistics.rb` - Statistics and validation
|
|
213
|
+
|
|
214
|
+
== Limitations
|
|
215
|
+
|
|
216
|
+
* QEA files must be SQLite 3 format
|
|
217
|
+
* Requires EA-specific table structure
|
|
218
|
+
* Some EA proprietary features may not be fully supported
|
|
219
|
+
* Only reads from QEA; does not write back to EA database
|
|
220
|
+
* Read-only access (safe for concurrent use)
|
|
221
|
+
|
|
222
|
+
== License
|
|
223
|
+
|
|
224
|
+
This library is part of LutaML and follows the same BSD-3 license.
|
|
225
|
+
|
|
226
|
+
== Credits
|
|
227
|
+
|
|
228
|
+
* Built with https://github.com/lutaml/lutaml-model[Lutaml::Model]
|
|
229
|
+
* Uses SQLite3 for database access
|
|
230
|
+
* Part of the LutaML project ecosystem
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Standalone QEA loading and querying (no lutaml-uml required).
|
|
5
|
+
#
|
|
6
|
+
# The `ea` gem's core is a standalone Sparx EA QEA parser. You can load a
|
|
7
|
+
# .qea file and query the EA-native data structures directly — useful when
|
|
8
|
+
# you only need EA metadata, statistics, or specific table rows and don't
|
|
9
|
+
# want the UML metamodel in your dependency tree.
|
|
10
|
+
#
|
|
11
|
+
# Run: bundle exec ruby examples/qea_standalone_query.rb
|
|
12
|
+
|
|
13
|
+
require "bundler/setup"
|
|
14
|
+
require "ea"
|
|
15
|
+
|
|
16
|
+
QEA_PATH = ENV.fetch(
|
|
17
|
+
"QEA_PATH",
|
|
18
|
+
"/Users/mulgogi/src/mn/plateau-model/20251010_current_plateau_v5.1.qea",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
abort "QEA not found: #{QEA_PATH}" unless File.exist?(QEA_PATH)
|
|
22
|
+
|
|
23
|
+
# Load the complete database. Returns an Ea::Qea::Database — an immutable
|
|
24
|
+
# container with lazy-built indexes for O(1) lookups.
|
|
25
|
+
#
|
|
26
|
+
# NOTE: EA objects expose their database primary key as `ea_object_id`, NOT
|
|
27
|
+
# `object_id`. Ruby's built-in `Object#object_id` (memory address) shadows
|
|
28
|
+
# any attribute named `object_id`, so always use `ea_object_id`.
|
|
29
|
+
database = Ea::Qea.load(QEA_PATH)
|
|
30
|
+
|
|
31
|
+
# Each EA table is exposed as a repository (Enumerable, with find/where/pluck).
|
|
32
|
+
puts "Loaded #{database.total_records} records across #{database.collection_names.size} collections"
|
|
33
|
+
puts
|
|
34
|
+
|
|
35
|
+
# Example queries ----------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# 1. Find a class with attributes inside a real package.
|
|
38
|
+
# Use `ea_object_id` (the DB primary key), never `object_id`.
|
|
39
|
+
sample = database.objects
|
|
40
|
+
.select { |o| o.object_type == "Class" && o.name && !o.name.empty? }
|
|
41
|
+
.select { |o| database.find_package(o.package_id) }
|
|
42
|
+
.select { |o| database.attributes_for_object(o.ea_object_id).any? }
|
|
43
|
+
.first
|
|
44
|
+
puts "Sample class: #{sample.name} (ea_object_id=#{sample.ea_object_id})"
|
|
45
|
+
puts
|
|
46
|
+
|
|
47
|
+
# 2. All connectors for that class
|
|
48
|
+
connectors = database.connectors_for_object(sample.ea_object_id)
|
|
49
|
+
puts "Connectors for #{sample.name}: #{connectors.size}"
|
|
50
|
+
connectors.first(3).each do |c|
|
|
51
|
+
puts " type=#{c.connector_type} dest=#{c.destelement.inspect} src=#{c.sourceelement.inspect}"
|
|
52
|
+
end
|
|
53
|
+
puts
|
|
54
|
+
|
|
55
|
+
# 3. All attributes for that class
|
|
56
|
+
attributes = database.attributes_for_object(sample.ea_object_id)
|
|
57
|
+
puts "Attributes for #{sample.name}: #{attributes.size}"
|
|
58
|
+
attributes.first(3).each { |a| puts " #{a.name}: #{a.type}" }
|
|
59
|
+
puts
|
|
60
|
+
|
|
61
|
+
# 4. Tagged values (stereotype properties) for the class
|
|
62
|
+
tags = database.tagged_values_for_element(sample.ea_guid)
|
|
63
|
+
puts "Tagged values for #{sample.name}: #{tags.size}"
|
|
64
|
+
tags.first(3).each { |t| puts " #{t.property}: #{t.value}" if t.value && !t.value.empty? }
|
|
65
|
+
puts
|
|
66
|
+
|
|
67
|
+
# 5. Packages hierarchy
|
|
68
|
+
root_packages = database.packages.select { |p| p.parent_id.nil? || p.parent_id.zero? }
|
|
69
|
+
puts "Root packages: #{root_packages.size}"
|
|
70
|
+
root_packages.each do |pkg|
|
|
71
|
+
children = database.child_packages_for(pkg.package_id)
|
|
72
|
+
puts " #{pkg.name} (id=#{pkg.package_id}, #{children.size} children)"
|
|
73
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: QEA → UML Document → UmlRepository composition.
|
|
5
|
+
#
|
|
6
|
+
# This is the recommended composition pattern for getting from a .qea file to
|
|
7
|
+
# a queryable Lutaml::UmlRepository::Repository. Each gem does one job:
|
|
8
|
+
#
|
|
9
|
+
# ea parses .qea → Lutaml::Uml::Document
|
|
10
|
+
# lutaml-uml wraps the Document in a queryable Repository
|
|
11
|
+
#
|
|
12
|
+
# No gem reaches into another at load time. The caller composes.
|
|
13
|
+
#
|
|
14
|
+
# Run: bundle exec ruby examples/qea_to_repository.rb
|
|
15
|
+
|
|
16
|
+
require "bundler/setup"
|
|
17
|
+
require "ea"
|
|
18
|
+
require "lutaml/uml"
|
|
19
|
+
require "lutaml/uml_repository"
|
|
20
|
+
|
|
21
|
+
QEA_PATH = ENV.fetch(
|
|
22
|
+
"QEA_PATH",
|
|
23
|
+
"/Users/mulgogi/src/mn/plateau-model/20251010_current_plateau_v5.1.qea",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
abort "QEA not found: #{QEA_PATH}" unless File.exist?(QEA_PATH)
|
|
27
|
+
|
|
28
|
+
# Step 1: ea parses the .qea into a UML Document.
|
|
29
|
+
puts "Parsing #{File.basename(QEA_PATH)}..."
|
|
30
|
+
document = Ea::Qea.parse(QEA_PATH)
|
|
31
|
+
puts " → #{document.class} (#{document.packages.size} root packages)"
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
# Step 2: lutaml-uml wraps the document in a queryable repository.
|
|
35
|
+
# This is composition — no load-time registration, no hidden coupling.
|
|
36
|
+
repo = Lutaml::UmlRepository::Repository.from_document(document)
|
|
37
|
+
puts "Wrapped in #{repo.class}"
|
|
38
|
+
puts
|
|
39
|
+
|
|
40
|
+
# Step 3: use the repository's query API.
|
|
41
|
+
puts "Repository queries:"
|
|
42
|
+
puts " total classes: #{repo.all_classes.size}"
|
|
43
|
+
puts " total diagrams: #{repo.all_diagrams.size}"
|
|
44
|
+
|
|
45
|
+
# find_class takes a qualified name like "ModelRoot::Pkg::ClassName".
|
|
46
|
+
# Here we just take the first one from the index to demonstrate.
|
|
47
|
+
first_id, first_class = repo.all_classes.first
|
|
48
|
+
if first_class
|
|
49
|
+
puts
|
|
50
|
+
puts "Sample class from repository: #{first_class.respond_to?(:name) ? first_class.name : first_id}"
|
|
51
|
+
end
|