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,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Cli
|
|
7
|
+
module Output
|
|
8
|
+
class YamlFormatter < Formatter
|
|
9
|
+
def render(rows, columns: [])
|
|
10
|
+
data = rows.map do |row|
|
|
11
|
+
normalize_row(row, columns)
|
|
12
|
+
end
|
|
13
|
+
puts data.to_yaml
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Ea::Cli::Output.register(:yaml, Ea::Cli::Output::YamlFormatter)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Cli
|
|
5
|
+
# Output formatters for the CLI. Each formatter self-registers via
|
|
6
|
+
# `Ea::Cli::Output.register(:name, Class)` at file-load time. Lookup via
|
|
7
|
+
# `Ea::Cli::Output.for(:name)` triggers autoloads on first access.
|
|
8
|
+
module Output
|
|
9
|
+
autoload :Formatter, "ea/cli/output/formatter"
|
|
10
|
+
autoload :TableFormatter, "ea/cli/output/table_formatter"
|
|
11
|
+
autoload :JsonFormatter, "ea/cli/output/json_formatter"
|
|
12
|
+
autoload :YamlFormatter, "ea/cli/output/yaml_formatter"
|
|
13
|
+
|
|
14
|
+
@formatters = {}
|
|
15
|
+
@formatters_loaded = false
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def register(name, klass)
|
|
19
|
+
@formatters[name.to_sym] = klass
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def for(name)
|
|
23
|
+
ensure_formatters_loaded
|
|
24
|
+
klass = @formatters[name.to_sym]
|
|
25
|
+
klass ||
|
|
26
|
+
raise(ArgumentError,
|
|
27
|
+
"No output formatter '#{name}'. " \
|
|
28
|
+
"Registered: #{@formatters.keys.join(', ')}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Convenience: returns a fresh formatter instance for `name`.
|
|
32
|
+
def instance_for(name)
|
|
33
|
+
self.for(name).new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def registered_formats
|
|
37
|
+
ensure_formatters_loaded
|
|
38
|
+
@formatters.keys
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Trigger autoload of each known formatter so its self-register
|
|
44
|
+
# block runs. Referencing the constant is enough.
|
|
45
|
+
def ensure_formatters_loaded
|
|
46
|
+
return if @formatters_loaded
|
|
47
|
+
|
|
48
|
+
TableFormatter
|
|
49
|
+
JsonFormatter
|
|
50
|
+
YamlFormatter
|
|
51
|
+
@formatters_loaded = true
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/ea/cli.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Cli
|
|
5
|
+
autoload :App, "ea/cli/app"
|
|
6
|
+
autoload :Command, "ea/cli/command"
|
|
7
|
+
autoload :Output, "ea/cli/output"
|
|
8
|
+
|
|
9
|
+
# All error classes live in ea/cli/error.rb — declare each so any
|
|
10
|
+
# reference (not just Ea::Cli::Error) triggers the autoload.
|
|
11
|
+
autoload :Error, "ea/cli/error"
|
|
12
|
+
autoload :FileNotFound, "ea/cli/error"
|
|
13
|
+
autoload :UnsupportedFormat, "ea/cli/error"
|
|
14
|
+
autoload :MissingUmlDependency, "ea/cli/error"
|
|
15
|
+
autoload :UnknownAction, "ea/cli/error"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Diagram
|
|
7
|
+
# Configuration management for diagram styling
|
|
8
|
+
#
|
|
9
|
+
# This class provides hierarchical style resolution with the
|
|
10
|
+
# following priority:
|
|
11
|
+
# 1. EA Data (QEA/XMI) - Highest priority
|
|
12
|
+
# 2. Class-specific overrides (user config)
|
|
13
|
+
# 3. Package-based styling (wildcard support)
|
|
14
|
+
# 4. Stereotype-based styling
|
|
15
|
+
# 5. Global defaults - Lowest priority
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# config = Configuration.new("config/diagram_styles.yml")
|
|
19
|
+
# fill_color = config.style_for(element, "colors.fill")
|
|
20
|
+
# font_family = config.style_for(element, "fonts.class_name.family")
|
|
21
|
+
class Configuration
|
|
22
|
+
attr_reader :config_data
|
|
23
|
+
|
|
24
|
+
# Default configuration file paths in order of preference
|
|
25
|
+
DEFAULT_CONFIG_PATHS = [
|
|
26
|
+
"config/diagram_styles.yml",
|
|
27
|
+
File.expand_path("~/.lutaml/diagram_styles.yml"),
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
# Initialize configuration with optional custom path
|
|
31
|
+
#
|
|
32
|
+
# @param config_path [String, nil] Path to custom configuration file
|
|
33
|
+
def initialize(config_path = nil)
|
|
34
|
+
@config_data = load_configuration(config_path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get style for a specific element
|
|
38
|
+
#
|
|
39
|
+
# @param element [Lutaml::Uml::TopElement, Object] UML element
|
|
40
|
+
# @param property [String] Style property path (e.g., "colors.fill")
|
|
41
|
+
# @return [Object] Style value
|
|
42
|
+
def style_for(element, property) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
43
|
+
return nil if property.nil? || property.empty?
|
|
44
|
+
|
|
45
|
+
# Priority: Class-specific > Package > Stereotype > Defaults
|
|
46
|
+
value = nil
|
|
47
|
+
|
|
48
|
+
# 1. Try class-specific override (highest priority)
|
|
49
|
+
if element.is_a?(Lutaml::Uml::TopElement) && element.name
|
|
50
|
+
class_config = dig_config("classes.#{element.name}.#{property}")
|
|
51
|
+
value = class_config if class_config
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# 2. Try package-based styling
|
|
55
|
+
if !value &&
|
|
56
|
+
element.is_a?(Lutaml::Uml::Diagram) && element.package_name
|
|
57
|
+
# Support wildcards: "CityGML::*"
|
|
58
|
+
package_configs = config_data["packages"] || {}
|
|
59
|
+
package_configs.each do |pattern, pkg_config|
|
|
60
|
+
if matches_package?(element.package_name, pattern)
|
|
61
|
+
pkg_value = dig_hash(pkg_config, property)
|
|
62
|
+
value = pkg_value if pkg_value
|
|
63
|
+
break
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 3. Try stereotype-based styling
|
|
69
|
+
if !value && element.is_a?(Lutaml::Uml::TopElement) && element.stereotype
|
|
70
|
+
stereotypes = Array(element.stereotype)
|
|
71
|
+
stereotypes.each do |stereo|
|
|
72
|
+
stereo_value = dig_config("stereotypes.#{stereo}.#{property}")
|
|
73
|
+
if stereo_value
|
|
74
|
+
value = stereo_value
|
|
75
|
+
break
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# 4. Fall back to defaults (lowest priority)
|
|
81
|
+
# Handle property name aliases
|
|
82
|
+
# (e.g., "colors.fill" -> "colors.default_fill")
|
|
83
|
+
unless value
|
|
84
|
+
value = dig_config("defaults.#{property}")
|
|
85
|
+
|
|
86
|
+
# If not found, try with "default_" prefix for certain properties
|
|
87
|
+
unless value
|
|
88
|
+
if property.start_with?("colors.fill")
|
|
89
|
+
value = dig_config("defaults.colors.default_fill")
|
|
90
|
+
elsif property.start_with?("colors.stroke") &&
|
|
91
|
+
!property.include?("stroke_")
|
|
92
|
+
value = dig_config("defaults.colors.default_stroke")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
value
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get connector style
|
|
101
|
+
#
|
|
102
|
+
# @param connector_type [String]
|
|
103
|
+
# Type of connector (generalization, association, etc.)
|
|
104
|
+
# @param property [String] Style property path
|
|
105
|
+
# @return [Object] Style value
|
|
106
|
+
def connector_style(connector_type, property)
|
|
107
|
+
dig_config("connectors.#{connector_type}.#{property}")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get legend configuration
|
|
111
|
+
#
|
|
112
|
+
# @return [Hash] Legend configuration
|
|
113
|
+
def legend_config
|
|
114
|
+
config_data["legend"] || {}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get the entire configuration data
|
|
118
|
+
#
|
|
119
|
+
# @return [Hash] Complete configuration data
|
|
120
|
+
def to_h
|
|
121
|
+
config_data
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Navigate hash using dot notation
|
|
125
|
+
#
|
|
126
|
+
# @param hash [Hash] Hash to navigate
|
|
127
|
+
# @param path [String] Path with dot notation
|
|
128
|
+
# @return [Object] Value at path
|
|
129
|
+
def dig_hash(hash, path) # rubocop:disable Metrics/CyclomaticComplexity
|
|
130
|
+
return nil if path.nil? || path.empty?
|
|
131
|
+
return nil unless hash.is_a?(Hash)
|
|
132
|
+
|
|
133
|
+
keys = path.split(".")
|
|
134
|
+
return nil if keys.empty?
|
|
135
|
+
|
|
136
|
+
keys.reduce(hash) do |h, key|
|
|
137
|
+
return nil unless h.is_a?(Hash)
|
|
138
|
+
|
|
139
|
+
h[key]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if package name matches pattern (supports wildcards)
|
|
144
|
+
#
|
|
145
|
+
# @param package_name [String] Package name to test
|
|
146
|
+
# @param pattern [String] Pattern with wildcards (e.g., "CityGML::*")
|
|
147
|
+
# @return [Boolean] True if matches
|
|
148
|
+
def matches_package?(package_name, pattern)
|
|
149
|
+
return false unless package_name && pattern
|
|
150
|
+
|
|
151
|
+
# Support wildcards: "CityGML::*" matches "CityGML::Core"
|
|
152
|
+
regex_pattern = pattern.gsub("*", ".*")
|
|
153
|
+
Regexp.new("^#{regex_pattern}$").match?(package_name)
|
|
154
|
+
rescue RegexpError => e
|
|
155
|
+
warn "Warning: Invalid package pattern '#{pattern}': #{e.message}"
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Deep merge two hashes
|
|
160
|
+
#
|
|
161
|
+
# @param hash1 [Hash] Base hash
|
|
162
|
+
# @param hash2 [Hash] Hash to merge in
|
|
163
|
+
# @return [Hash] Merged hash
|
|
164
|
+
def deep_merge(hash1, hash2)
|
|
165
|
+
hash1.merge(hash2) do |_key, old_val, new_val|
|
|
166
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
167
|
+
deep_merge(old_val, new_val)
|
|
168
|
+
else
|
|
169
|
+
new_val
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
# Load configuration from files
|
|
177
|
+
#
|
|
178
|
+
# @param custom_path [String, nil] Custom configuration path
|
|
179
|
+
# @return [Hash] Merged configuration data
|
|
180
|
+
def load_configuration(custom_path = nil) # rubocop:disable Metrics/MethodLength
|
|
181
|
+
paths = custom_path ? [custom_path] : DEFAULT_CONFIG_PATHS
|
|
182
|
+
merged_config = default_config
|
|
183
|
+
|
|
184
|
+
paths.each do |path|
|
|
185
|
+
next unless File.exist?(path)
|
|
186
|
+
|
|
187
|
+
begin
|
|
188
|
+
user_config = YAML.load_file(path)
|
|
189
|
+
if user_config.is_a?(Hash)
|
|
190
|
+
merged_config = deep_merge(merged_config, user_config)
|
|
191
|
+
end
|
|
192
|
+
rescue Psych::SyntaxError, Errno::ENOENT, Errno::EACCES,
|
|
193
|
+
IOError => e
|
|
194
|
+
warn "Warning: Failed to load configuration from " \
|
|
195
|
+
"#{path}: #{e.message}"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
merged_config
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Built-in default configuration (minimal, as fallback)
|
|
203
|
+
#
|
|
204
|
+
# @return [Hash] Default configuration data
|
|
205
|
+
def default_config # rubocop:disable Metrics/MethodLength
|
|
206
|
+
{
|
|
207
|
+
"defaults" => {
|
|
208
|
+
"colors" => {
|
|
209
|
+
"background" => "#FFFFFF",
|
|
210
|
+
"default_fill" => "#E0E0E0",
|
|
211
|
+
"default_stroke" => "#000000",
|
|
212
|
+
"text" => "#000000",
|
|
213
|
+
},
|
|
214
|
+
"fonts" => {
|
|
215
|
+
"default" => {
|
|
216
|
+
"family" => "Carlito, Arial, sans-serif",
|
|
217
|
+
"size" => 9,
|
|
218
|
+
"weight" => 400,
|
|
219
|
+
"style" => "normal",
|
|
220
|
+
},
|
|
221
|
+
"class_name" => {
|
|
222
|
+
"family" => "Carlito, Arial, sans-serif",
|
|
223
|
+
"size" => 9,
|
|
224
|
+
"weight" => 700,
|
|
225
|
+
"style" => "normal",
|
|
226
|
+
},
|
|
227
|
+
"stereotype" => {
|
|
228
|
+
"family" => "Carlito, Arial, sans-serif",
|
|
229
|
+
"size" => 9,
|
|
230
|
+
"weight" => 400,
|
|
231
|
+
"style" => "normal",
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
"box" => {
|
|
235
|
+
"stroke_width" => 2,
|
|
236
|
+
"stroke_linecap" => "round",
|
|
237
|
+
"stroke_linejoin" => "bevel",
|
|
238
|
+
"corner_radius" => 0,
|
|
239
|
+
"padding" => 5,
|
|
240
|
+
},
|
|
241
|
+
"text" => {
|
|
242
|
+
"visibility_public" => "+",
|
|
243
|
+
"visibility_private" => "-",
|
|
244
|
+
"visibility_protected" => "#",
|
|
245
|
+
"visibility_package" => "~",
|
|
246
|
+
"cardinality_format" => "[%s]",
|
|
247
|
+
"stereotype_format" => "«%s»",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
"stereotypes" => {
|
|
251
|
+
"DataType" => {
|
|
252
|
+
"colors" => {
|
|
253
|
+
"fill" => "#FFCCFF",
|
|
254
|
+
"stroke" => "#000000",
|
|
255
|
+
},
|
|
256
|
+
"fonts" => {
|
|
257
|
+
"class_name" => {
|
|
258
|
+
"weight" => 700,
|
|
259
|
+
"style" => "italic",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
"FeatureType" => {
|
|
264
|
+
"colors" => {
|
|
265
|
+
"fill" => "#FFFFCC",
|
|
266
|
+
"stroke" => "#000000",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
"GMLType" => {
|
|
270
|
+
"colors" => {
|
|
271
|
+
"fill" => "#CCFFCC",
|
|
272
|
+
"stroke" => "#000000",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
"Interface" => {
|
|
276
|
+
"colors" => {
|
|
277
|
+
"fill" => "#FFFFEE",
|
|
278
|
+
"stroke" => "#000000",
|
|
279
|
+
},
|
|
280
|
+
"fonts" => {
|
|
281
|
+
"class_name" => {
|
|
282
|
+
"style" => "italic",
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
"connectors" => {
|
|
288
|
+
"generalization" => {
|
|
289
|
+
"arrow" => {
|
|
290
|
+
"type" => "hollow_triangle",
|
|
291
|
+
"size" => 10,
|
|
292
|
+
},
|
|
293
|
+
"line" => {
|
|
294
|
+
"stroke_width" => 1,
|
|
295
|
+
"stroke" => "#000000",
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
"association" => {
|
|
299
|
+
"arrow" => {
|
|
300
|
+
"type" => "open_arrow",
|
|
301
|
+
"size" => 8,
|
|
302
|
+
},
|
|
303
|
+
"line" => {
|
|
304
|
+
"stroke_width" => 1,
|
|
305
|
+
"stroke" => "#000000",
|
|
306
|
+
},
|
|
307
|
+
"labels" => {
|
|
308
|
+
"show_role_names" => true,
|
|
309
|
+
"show_cardinality" => true,
|
|
310
|
+
"font" => {
|
|
311
|
+
"family" => "Carlito, Arial, sans-serif",
|
|
312
|
+
"size" => 8,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
"dependency" => {
|
|
317
|
+
"arrow" => {
|
|
318
|
+
"type" => "open_arrow",
|
|
319
|
+
"size" => 8,
|
|
320
|
+
},
|
|
321
|
+
"line" => {
|
|
322
|
+
"stroke_width" => 1,
|
|
323
|
+
"stroke" => "#000000",
|
|
324
|
+
"stroke_dasharray" => "5,5",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
"aggregation" => {
|
|
328
|
+
"arrow" => {
|
|
329
|
+
"type" => "diamond",
|
|
330
|
+
"size" => 10,
|
|
331
|
+
},
|
|
332
|
+
"line" => {
|
|
333
|
+
"stroke_width" => 1,
|
|
334
|
+
"stroke" => "#000000",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
"composition" => {
|
|
338
|
+
"arrow" => {
|
|
339
|
+
"type" => "filled_diamond",
|
|
340
|
+
"size" => 10,
|
|
341
|
+
},
|
|
342
|
+
"line" => {
|
|
343
|
+
"stroke_width" => 1,
|
|
344
|
+
"stroke" => "#000000",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
"legend" => {
|
|
349
|
+
"enabled" => true,
|
|
350
|
+
"position" => "bottom_right",
|
|
351
|
+
"title" => "Legend",
|
|
352
|
+
"entries" => [
|
|
353
|
+
{
|
|
354
|
+
"label" => "i-UR DataTypes",
|
|
355
|
+
"color" => "#FFCCFF",
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"label" => "CityGML FeatureTypes",
|
|
359
|
+
"color" => "#FFFFCC",
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"label" => "GML Types",
|
|
363
|
+
"color" => "#CCFFCC",
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
}
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Navigate configuration using dot notation
|
|
371
|
+
#
|
|
372
|
+
# @param path [String] Configuration path (e.g., "colors.fill")
|
|
373
|
+
# @return [Object] Configuration value
|
|
374
|
+
def dig_config(path)
|
|
375
|
+
dig_hash(config_data, path)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Diagram
|
|
5
|
+
module ElementRenderers
|
|
6
|
+
# Base renderer for diagram elements
|
|
7
|
+
class BaseRenderer
|
|
8
|
+
include Ea::Diagram::Util
|
|
9
|
+
|
|
10
|
+
attr_reader :element, :style_resolver
|
|
11
|
+
|
|
12
|
+
def initialize(element, style_resolver)
|
|
13
|
+
@element = element
|
|
14
|
+
@style_resolver = style_resolver
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Render the element as SVG
|
|
18
|
+
# @return [String] SVG content for the element
|
|
19
|
+
def render
|
|
20
|
+
style = style_resolver.resolve_element_style(element)
|
|
21
|
+
|
|
22
|
+
<<~SVG
|
|
23
|
+
<g class="lutaml-diagram-element lutaml-diagram-#{element[:type]}"
|
|
24
|
+
data-element-id="#{element[:id]}"
|
|
25
|
+
data-element-type="#{element[:type]}">
|
|
26
|
+
#{render_shape(style)}
|
|
27
|
+
#{render_label(style)}
|
|
28
|
+
</g>
|
|
29
|
+
SVG
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def render_shape(_style)
|
|
33
|
+
""
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def render_label(style) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
37
|
+
return "" unless element[:name]
|
|
38
|
+
|
|
39
|
+
x = element[:x] || 0
|
|
40
|
+
y = element[:y] || 0
|
|
41
|
+
width = element[:width] || 120
|
|
42
|
+
height = element[:height] || 80
|
|
43
|
+
|
|
44
|
+
# Center the label
|
|
45
|
+
text_x = x + (width / 2)
|
|
46
|
+
# Slight vertical offset for better centering
|
|
47
|
+
text_y = y + (height / 2) + 5
|
|
48
|
+
|
|
49
|
+
<<~SVG
|
|
50
|
+
<text x="#{text_x}"
|
|
51
|
+
y="#{text_y}"
|
|
52
|
+
text-anchor="middle"
|
|
53
|
+
dominant-baseline="middle"
|
|
54
|
+
font-family="#{style[:font_family]}"
|
|
55
|
+
font-size="#{style[:font_size]}"
|
|
56
|
+
font-weight="#{style[:font_weight] || 'normal'}"
|
|
57
|
+
fill="#{style[:text_color] || '#000000'}"
|
|
58
|
+
class="lutaml-diagram-label">
|
|
59
|
+
#{escape_text(element[:name])}
|
|
60
|
+
</text>
|
|
61
|
+
SVG
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def escape_text(text)
|
|
65
|
+
return "" unless text
|
|
66
|
+
|
|
67
|
+
text.to_s
|
|
68
|
+
.gsub("&", "&")
|
|
69
|
+
.gsub("<", "<")
|
|
70
|
+
.gsub(">", ">")
|
|
71
|
+
.gsub('"', """)
|
|
72
|
+
.gsub("'", "'")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|