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
data/lib/ea/qea.rb
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
autoload :Infrastructure, "ea/qea/infrastructure"
|
|
6
|
+
autoload :Services, "ea/qea/services"
|
|
7
|
+
autoload :Models, "ea/qea/models"
|
|
8
|
+
autoload :Factory, "ea/qea/factory"
|
|
9
|
+
autoload :Validation, "ea/qea/validation"
|
|
10
|
+
autoload :Verification, "ea/qea/verification"
|
|
11
|
+
autoload :Database, "ea/qea/database"
|
|
12
|
+
autoload :Repositories, "ea/qea/repositories"
|
|
13
|
+
autoload :Benchmark, "ea/qea/benchmark"
|
|
14
|
+
autoload :FileDetector, "ea/qea/file_detector"
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Get the current configuration
|
|
18
|
+
#
|
|
19
|
+
# @return [Services::Configuration] The loaded configuration
|
|
20
|
+
def configuration
|
|
21
|
+
@configuration ||= Services::Configuration.load
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Set a custom configuration
|
|
25
|
+
#
|
|
26
|
+
# @param config [Services::Configuration] The configuration to use
|
|
27
|
+
def configuration=(config)
|
|
28
|
+
@configuration = config
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Reload configuration from file
|
|
32
|
+
#
|
|
33
|
+
# @param config_path [String, nil] Optional custom config path
|
|
34
|
+
# @return [Services::Configuration] The reloaded configuration
|
|
35
|
+
def reload_configuration(config_path = nil)
|
|
36
|
+
@configuration = Services::Configuration.load(config_path)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Connect to a QEA file
|
|
40
|
+
#
|
|
41
|
+
# @param file_path [String] Path to the .qea file
|
|
42
|
+
# @return [Infrastructure::DatabaseConnection] The connection object
|
|
43
|
+
def connect(file_path)
|
|
44
|
+
Infrastructure::DatabaseConnection.new(file_path)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Open a QEA file and yield the connection
|
|
48
|
+
#
|
|
49
|
+
# @param file_path [String] Path to the .qea file
|
|
50
|
+
# @yield [Infrastructure::DatabaseConnection] The connection object
|
|
51
|
+
# @return [Object] The result of the block
|
|
52
|
+
def open(file_path)
|
|
53
|
+
connection = connect(file_path)
|
|
54
|
+
yield connection
|
|
55
|
+
ensure
|
|
56
|
+
connection&.close if connection&.connected?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get schema information from a QEA file
|
|
60
|
+
#
|
|
61
|
+
# @param file_path [String] Path to the .qea file
|
|
62
|
+
# @return [Hash] Schema information including tables and row counts
|
|
63
|
+
def schema_info(file_path)
|
|
64
|
+
connection = connect(file_path)
|
|
65
|
+
connection.with_connection do |db|
|
|
66
|
+
reader = Infrastructure::SchemaReader.new(db)
|
|
67
|
+
{
|
|
68
|
+
tables: reader.tables,
|
|
69
|
+
statistics: reader.statistics,
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
ensure
|
|
73
|
+
connection&.close if connection&.connected?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Load complete database with all tables and models (standalone API)
|
|
77
|
+
#
|
|
78
|
+
# This is the primary standalone entry point — returns EA-native data
|
|
79
|
+
# without requiring lutaml-uml.
|
|
80
|
+
#
|
|
81
|
+
# @param qea_path [String] Path to the .qea file
|
|
82
|
+
# @param config [Services::Configuration, nil] Optional custom config
|
|
83
|
+
# @return [Database] Loaded database with all collections
|
|
84
|
+
def load(qea_path, config = nil, &progress_callback)
|
|
85
|
+
load_database(qea_path, config, &progress_callback)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Load complete database with all tables and models
|
|
89
|
+
#
|
|
90
|
+
# @param qea_path [String] Path to the .qea file
|
|
91
|
+
# @param config [Services::Configuration, nil]
|
|
92
|
+
# @return [Database] Loaded database with all collections
|
|
93
|
+
def load_database(qea_path, config = nil, &progress_callback)
|
|
94
|
+
loader = Services::DatabaseLoader.new(qea_path, config)
|
|
95
|
+
loader.on_progress(&progress_callback) if progress_callback
|
|
96
|
+
loader.load
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get quick database statistics without full loading
|
|
100
|
+
#
|
|
101
|
+
# @param qea_path [String] Path to the .qea file
|
|
102
|
+
# @param config [Services::Configuration, nil]
|
|
103
|
+
# @return [Hash<String, Integer>] Collection names to record counts
|
|
104
|
+
def database_info(qea_path, config = nil)
|
|
105
|
+
loader = Services::DatabaseLoader.new(qea_path, config)
|
|
106
|
+
loader.quick_stats
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Convert an EA database to a UML Document (requires lutaml-uml)
|
|
110
|
+
#
|
|
111
|
+
# @param database [Database] Loaded EA database
|
|
112
|
+
# @param options [Hash] Transformation options
|
|
113
|
+
# @return [Lutaml::Uml::Document] UML document
|
|
114
|
+
# @raise [Ea::Error] if lutaml-uml is not available
|
|
115
|
+
def to_uml(database, options = {})
|
|
116
|
+
require_uml!
|
|
117
|
+
factory = Factory::EaToUmlFactory.new(database, options)
|
|
118
|
+
factory.create_document
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Parse QEA file to UML Document (convenience: load + to_uml)
|
|
122
|
+
#
|
|
123
|
+
# Requires lutaml-uml for the UML conversion.
|
|
124
|
+
#
|
|
125
|
+
# @param qea_path [String] Path to the .qea file
|
|
126
|
+
# @param options [Hash] Transformation options
|
|
127
|
+
# @return [Lutaml::Uml::Document, Hash] Document, or hash with
|
|
128
|
+
# :document and :validation_result
|
|
129
|
+
# @raise [Ea::Error] if lutaml-uml is not available
|
|
130
|
+
def parse(qea_path, options = {}) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
131
|
+
require_uml!
|
|
132
|
+
config = options.delete(:config)
|
|
133
|
+
validate = options.delete(:validate)
|
|
134
|
+
|
|
135
|
+
loader = Services::DatabaseLoader.new(qea_path, config)
|
|
136
|
+
ea_database = loader.load
|
|
137
|
+
|
|
138
|
+
begin
|
|
139
|
+
factory = Factory::EaToUmlFactory.new(ea_database, options)
|
|
140
|
+
document = factory.create_document
|
|
141
|
+
|
|
142
|
+
if validate
|
|
143
|
+
engine = Validation::ValidationEngine.new(
|
|
144
|
+
document,
|
|
145
|
+
database: ea_database,
|
|
146
|
+
**options,
|
|
147
|
+
)
|
|
148
|
+
validation_result = engine.validate
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
document: document,
|
|
152
|
+
validation_result: validation_result,
|
|
153
|
+
}
|
|
154
|
+
else
|
|
155
|
+
document
|
|
156
|
+
end
|
|
157
|
+
ensure
|
|
158
|
+
ea_database.close_connection unless validate
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Ensure lutaml-uml is available for UML conversion operations
|
|
165
|
+
#
|
|
166
|
+
# @raise [Ea::Error] if lutaml-uml is not loaded
|
|
167
|
+
def require_uml!
|
|
168
|
+
return if defined?(Lutaml::Uml::Document)
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
require "lutaml/uml"
|
|
172
|
+
rescue LoadError
|
|
173
|
+
raise Ea::Error,
|
|
174
|
+
"lutaml-uml is required for UML conversion. " \
|
|
175
|
+
"Add gem 'lutaml-uml' to your Gemfile."
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
return if defined?(Lutaml::Uml::Document)
|
|
179
|
+
|
|
180
|
+
raise Ea::Error,
|
|
181
|
+
"lutaml-uml failed to load Lutaml::Uml::Document."
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Ea
|
|
7
|
+
module Transformations
|
|
8
|
+
# Configuration service for model transformations using external YAML
|
|
9
|
+
# configuration.
|
|
10
|
+
#
|
|
11
|
+
# This class follows the Dependency Inversion Principle by allowing external
|
|
12
|
+
# configuration instead of hardcoded behavior. It uses lutaml-model for
|
|
13
|
+
# structured YAML parsing and validation.
|
|
14
|
+
#
|
|
15
|
+
# @example Load default configuration
|
|
16
|
+
# config = Configuration.load
|
|
17
|
+
# puts config.enabled_formats
|
|
18
|
+
#
|
|
19
|
+
# @example Load custom configuration
|
|
20
|
+
# config = Configuration.load("my_config.yml")
|
|
21
|
+
# parser_config = config.parser_config_for("xmi")
|
|
22
|
+
class Configuration < Lutaml::Model::Serializable
|
|
23
|
+
# Parser configuration model
|
|
24
|
+
class ParserConfig < Lutaml::Model::Serializable
|
|
25
|
+
attribute :format, :string
|
|
26
|
+
attribute :extension, :string
|
|
27
|
+
attribute :parser_class, :string
|
|
28
|
+
attribute :enabled, :boolean, default: -> { true }
|
|
29
|
+
attribute :priority, :integer, default: -> { 100 }
|
|
30
|
+
attribute :description, :string
|
|
31
|
+
attribute :options, :string, collection: true
|
|
32
|
+
|
|
33
|
+
yaml do
|
|
34
|
+
map "format", to: :format
|
|
35
|
+
map "extension", to: :extension
|
|
36
|
+
map "parser_class", to: :parser_class
|
|
37
|
+
map "enabled", to: :enabled
|
|
38
|
+
map "priority", to: :priority
|
|
39
|
+
map "description", to: :description
|
|
40
|
+
map "options", to: :options
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if this parser handles the given extension
|
|
44
|
+
#
|
|
45
|
+
# @param ext [String] File extension (e.g., ".xmi")
|
|
46
|
+
# @return [Boolean] true if this parser handles the extension
|
|
47
|
+
def handles_extension?(ext)
|
|
48
|
+
extension == ext.downcase
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Transformation options model
|
|
53
|
+
class TransformationOptions < Lutaml::Model::Serializable
|
|
54
|
+
attribute :validate_output, :boolean, default: -> { false }
|
|
55
|
+
attribute :include_diagrams, :boolean, default: -> { true }
|
|
56
|
+
attribute :preserve_ids, :boolean, default: -> { true }
|
|
57
|
+
attribute :resolve_references, :boolean, default: -> { true }
|
|
58
|
+
attribute :strict_mode, :boolean, default: -> { false }
|
|
59
|
+
|
|
60
|
+
yaml do
|
|
61
|
+
map "validate_output", to: :validate_output
|
|
62
|
+
map "include_diagrams", to: :include_diagrams
|
|
63
|
+
map "preserve_ids", to: :preserve_ids
|
|
64
|
+
map "resolve_references", to: :resolve_references
|
|
65
|
+
map "strict_mode", to: :strict_mode
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Format detection rules model
|
|
70
|
+
class FormatDetection < Lutaml::Model::Serializable
|
|
71
|
+
attribute :use_file_extension, :boolean, default: -> { true }
|
|
72
|
+
attribute :use_content_sniffing, :boolean, default: -> { true }
|
|
73
|
+
attribute :fallback_parser, :string
|
|
74
|
+
attribute :magic_bytes, :string, collection: true
|
|
75
|
+
|
|
76
|
+
yaml do
|
|
77
|
+
map "use_file_extension", to: :use_file_extension
|
|
78
|
+
map "use_content_sniffing", to: :use_content_sniffing
|
|
79
|
+
map "fallback_parser", to: :fallback_parser
|
|
80
|
+
map "magic_bytes", to: :magic_bytes
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Error handling configuration
|
|
85
|
+
class ErrorHandling < Lutaml::Model::Serializable
|
|
86
|
+
attribute :strategy, :string, default: -> { "continue" }
|
|
87
|
+
attribute :log_errors, :boolean, default: -> { true }
|
|
88
|
+
attribute :max_errors, :integer, default: -> { 10 }
|
|
89
|
+
attribute :fail_fast, :boolean, default: -> { false }
|
|
90
|
+
|
|
91
|
+
yaml do
|
|
92
|
+
map "strategy", to: :strategy
|
|
93
|
+
map "log_errors", to: :log_errors
|
|
94
|
+
map "max_errors", to: :max_errors
|
|
95
|
+
map "fail_fast", to: :fail_fast
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
attribute :version, :string
|
|
100
|
+
attribute :description, :string
|
|
101
|
+
attribute :parsers, ParserConfig, collection: true
|
|
102
|
+
attribute :transformation_options, TransformationOptions
|
|
103
|
+
attribute :format_detection, FormatDetection
|
|
104
|
+
attribute :error_handling, ErrorHandling
|
|
105
|
+
|
|
106
|
+
yaml do
|
|
107
|
+
map "version", to: :version
|
|
108
|
+
map "description", to: :description
|
|
109
|
+
map "parsers", to: :parsers
|
|
110
|
+
map "transformation_options", to: :transformation_options
|
|
111
|
+
map "format_detection", to: :format_detection
|
|
112
|
+
map "error_handling", to: :error_handling
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class << self
|
|
116
|
+
# Load configuration from YAML file
|
|
117
|
+
#
|
|
118
|
+
# @param config_path [String, nil] Path to configuration file
|
|
119
|
+
# Defaults to config/model_transformations.yml
|
|
120
|
+
# @return [Configuration] The loaded configuration
|
|
121
|
+
# @raise [Errno::ENOENT] if config file not found
|
|
122
|
+
# @raise [Lutaml::Model::Error] if YAML is invalid
|
|
123
|
+
def load(config_path = nil)
|
|
124
|
+
config_path ||= default_config_path
|
|
125
|
+
|
|
126
|
+
unless File.exist?(config_path)
|
|
127
|
+
# Create default configuration if none exists
|
|
128
|
+
return create_default_configuration
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
yaml_content = File.read(config_path)
|
|
132
|
+
from_yaml(yaml_content)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Get default configuration file path
|
|
136
|
+
#
|
|
137
|
+
# @return [String] Path to default config file
|
|
138
|
+
def default_config_path
|
|
139
|
+
File.expand_path("../../../config/model_transformations.yml", __dir__)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Create default configuration when no config file exists
|
|
143
|
+
#
|
|
144
|
+
# @return [Configuration] Default configuration instance
|
|
145
|
+
def create_default_configuration
|
|
146
|
+
new.tap do |config|
|
|
147
|
+
config.version = "1.0"
|
|
148
|
+
config.description = "Default Model Transformations Configuration"
|
|
149
|
+
|
|
150
|
+
# Default parsers
|
|
151
|
+
config.parsers = [
|
|
152
|
+
create_xmi_parser_config,
|
|
153
|
+
create_qea_parser_config,
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
# Default options
|
|
157
|
+
config.transformation_options = TransformationOptions.new
|
|
158
|
+
config.format_detection = FormatDetection.new
|
|
159
|
+
config.error_handling = ErrorHandling.new
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
# Create default XMI parser configuration
|
|
166
|
+
#
|
|
167
|
+
# @return [ParserConfig] XMI parser configuration
|
|
168
|
+
def create_xmi_parser_config
|
|
169
|
+
ParserConfig.new.tap do |parser|
|
|
170
|
+
parser.format = "xmi"
|
|
171
|
+
parser.extension = ".xmi"
|
|
172
|
+
parser.parser_class = "Ea::Transformations::Parsers::XmiParser"
|
|
173
|
+
parser.enabled = true
|
|
174
|
+
parser.priority = 100
|
|
175
|
+
parser.description = "XML Metadata Interchange parser"
|
|
176
|
+
parser.options = ["validate_xml", "resolve_references"]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Create default QEA parser configuration
|
|
181
|
+
#
|
|
182
|
+
# @return [ParserConfig] QEA parser configuration
|
|
183
|
+
def create_qea_parser_config
|
|
184
|
+
ParserConfig.new.tap do |parser|
|
|
185
|
+
parser.format = "qea"
|
|
186
|
+
parser.extension = ".qea"
|
|
187
|
+
parser.parser_class = "Ea::Transformations::Parsers::QeaParser"
|
|
188
|
+
parser.enabled = true
|
|
189
|
+
parser.priority = 90
|
|
190
|
+
parser.description = "Enterprise Architect database parser"
|
|
191
|
+
parser.options = ["include_diagrams", "resolve_references"]
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Get list of enabled parsers, sorted by priority
|
|
197
|
+
#
|
|
198
|
+
# @return [Array<ParserConfig>] Array of enabled parser configurations
|
|
199
|
+
def enabled_parsers
|
|
200
|
+
parsers&.select(&:enabled)&.sort_by { |p| -p.priority } || []
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Get parser configuration by format name
|
|
204
|
+
#
|
|
205
|
+
# @param format [String] The format name (e.g., "xmi", "qea")
|
|
206
|
+
# @return [ParserConfig, nil] The parser configuration or nil if not found
|
|
207
|
+
def parser_config_for(format)
|
|
208
|
+
parsers&.find { |p| p.format == format.downcase }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Get parser configuration by file extension
|
|
212
|
+
#
|
|
213
|
+
# @param extension [String] The file extension (e.g., ".xmi", ".qea")
|
|
214
|
+
# @return [ParserConfig, nil] The parser configuration or nil if not found
|
|
215
|
+
def parser_config_for_extension(extension)
|
|
216
|
+
normalized_ext = extension.downcase
|
|
217
|
+
unless normalized_ext.start_with?(".")
|
|
218
|
+
normalized_ext = ".#{normalized_ext}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
enabled_parsers.find { |p| p.handles_extension?(normalized_ext) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Check if a format is enabled
|
|
225
|
+
#
|
|
226
|
+
# @param format [String] The format name
|
|
227
|
+
# @return [Boolean] true if format is enabled
|
|
228
|
+
def format_enabled?(format)
|
|
229
|
+
parser = parser_config_for(format)
|
|
230
|
+
parser&.enabled == true
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Get all enabled format names
|
|
234
|
+
#
|
|
235
|
+
# @return [Array<String>] Array of enabled format names
|
|
236
|
+
def enabled_formats
|
|
237
|
+
enabled_parsers.map(&:format)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Get all supported file extensions
|
|
241
|
+
#
|
|
242
|
+
# @return [Array<String>] Array of supported extensions
|
|
243
|
+
def supported_extensions
|
|
244
|
+
enabled_parsers.filter_map(&:extension)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Check if content sniffing is enabled
|
|
248
|
+
#
|
|
249
|
+
# @return [Boolean] true if content sniffing should be used
|
|
250
|
+
def content_sniffing_enabled?
|
|
251
|
+
format_detection&.use_content_sniffing == true
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Check if file extension detection is enabled
|
|
255
|
+
#
|
|
256
|
+
# @return [Boolean] true if file extension should be used for detection
|
|
257
|
+
def file_extension_detection_enabled?
|
|
258
|
+
format_detection&.use_file_extension == true
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Get fallback parser when format detection fails
|
|
262
|
+
#
|
|
263
|
+
# @return [String, nil] The fallback parser class name
|
|
264
|
+
def fallback_parser
|
|
265
|
+
format_detection&.fallback_parser
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Get transformation options with defaults
|
|
269
|
+
#
|
|
270
|
+
# @return [TransformationOptions] Transformation options
|
|
271
|
+
def transformation_options
|
|
272
|
+
@transformation_options ||= TransformationOptions.new
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Get error handling configuration with defaults
|
|
276
|
+
#
|
|
277
|
+
# @return [ErrorHandling] Error handling configuration
|
|
278
|
+
def error_handling
|
|
279
|
+
@error_handling ||= ErrorHandling.new
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Merge with another configuration (this takes precedence)
|
|
283
|
+
#
|
|
284
|
+
# @param other [Configuration] Configuration to merge with
|
|
285
|
+
# @return [Configuration] New merged configuration
|
|
286
|
+
def merge(other) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
287
|
+
merged = self.class.new
|
|
288
|
+
|
|
289
|
+
# Basic attributes
|
|
290
|
+
merged.version = version || other.version
|
|
291
|
+
merged.description = description || other.description
|
|
292
|
+
|
|
293
|
+
# Merge parsers (this config takes precedence)
|
|
294
|
+
merged.parsers = merge_parsers(other.parsers)
|
|
295
|
+
|
|
296
|
+
# Use this config's options, fallback to other
|
|
297
|
+
merged.transformation_options = transformation_options ||
|
|
298
|
+
other.transformation_options
|
|
299
|
+
merged.format_detection = format_detection || other.format_detection
|
|
300
|
+
merged.error_handling = error_handling || other.error_handling
|
|
301
|
+
|
|
302
|
+
merged
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
private
|
|
306
|
+
|
|
307
|
+
# Merge parser configurations
|
|
308
|
+
#
|
|
309
|
+
# @param other_parsers [Array<ParserConfig>] Other parsers to merge
|
|
310
|
+
# @return [Array<ParserConfig>] Merged parser list
|
|
311
|
+
def merge_parsers(other_parsers) # rubocop:disable Metrics/MethodLength
|
|
312
|
+
return parsers unless other_parsers
|
|
313
|
+
return other_parsers unless parsers
|
|
314
|
+
|
|
315
|
+
# Create a hash of this config's parsers by format
|
|
316
|
+
our_parsers = {}
|
|
317
|
+
parsers.each do |parser|
|
|
318
|
+
our_parsers[parser.format] = parser
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Add other parsers that we don't have
|
|
322
|
+
merged = parsers.dup
|
|
323
|
+
other_parsers.each do |other_parser|
|
|
324
|
+
unless our_parsers.key?(other_parser.format)
|
|
325
|
+
merged << other_parser
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
merged
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|