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,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Validation
|
|
6
|
+
module Formatters
|
|
7
|
+
# Formats validation results as human-readable text with colors
|
|
8
|
+
#
|
|
9
|
+
# @example Basic usage
|
|
10
|
+
# formatter = TextFormatter.new(result)
|
|
11
|
+
# puts formatter.format
|
|
12
|
+
#
|
|
13
|
+
# @example Without color
|
|
14
|
+
# formatter = TextFormatter.new(result, color: false)
|
|
15
|
+
# puts formatter.format
|
|
16
|
+
class TextFormatter
|
|
17
|
+
attr_reader :result, :options
|
|
18
|
+
|
|
19
|
+
# Creates a new text formatter
|
|
20
|
+
#
|
|
21
|
+
# @param result [ValidationResult] The validation result to format
|
|
22
|
+
# @param options [Hash] Formatting options
|
|
23
|
+
# @option options [Boolean] :color Enable colored output
|
|
24
|
+
# (default: true)
|
|
25
|
+
# @option options [Boolean] :verbose Show all messages
|
|
26
|
+
# (default: false)
|
|
27
|
+
# @option options [Integer] :limit Maximum messages per category
|
|
28
|
+
def initialize(result: nil, **options)
|
|
29
|
+
@result = result
|
|
30
|
+
@options = {
|
|
31
|
+
color: true,
|
|
32
|
+
verbose: false,
|
|
33
|
+
limit: nil,
|
|
34
|
+
}.merge(options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Formats the validation result as text
|
|
38
|
+
#
|
|
39
|
+
# @return [String] Formatted text output
|
|
40
|
+
def format # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
41
|
+
lines = []
|
|
42
|
+
lines << header
|
|
43
|
+
lines << ""
|
|
44
|
+
lines << summary
|
|
45
|
+
lines << ""
|
|
46
|
+
|
|
47
|
+
if result.has_errors?
|
|
48
|
+
lines << section_header("ERRORS", result.errors.size)
|
|
49
|
+
lines << format_messages(result.errors)
|
|
50
|
+
lines << ""
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if result.has_warnings?
|
|
54
|
+
lines << section_header("WARNINGS", result.warnings.size)
|
|
55
|
+
lines << format_messages(result.warnings)
|
|
56
|
+
lines << ""
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if result.has_info? && options[:verbose]
|
|
60
|
+
lines << section_header("INFO", result.info.size)
|
|
61
|
+
lines << format_messages(result.info)
|
|
62
|
+
lines << ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
lines << footer
|
|
66
|
+
lines.join("\n")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Formats the header
|
|
72
|
+
#
|
|
73
|
+
# @return [String]
|
|
74
|
+
def header
|
|
75
|
+
line = "=" * 80
|
|
76
|
+
title = "QEA VALIDATION REPORT"
|
|
77
|
+
if options[:color]
|
|
78
|
+
"#{line}\n#{colorize(title, :cyan, bold: true)}\n#{line}"
|
|
79
|
+
else
|
|
80
|
+
"#{line}\n#{title}\n#{line}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Formats the summary
|
|
85
|
+
#
|
|
86
|
+
# @return [String]
|
|
87
|
+
def summary # rubocop:disable Metrics/AbcSize
|
|
88
|
+
status = if result.valid?
|
|
89
|
+
colorize("✓ VALID", :green, bold: true)
|
|
90
|
+
elsif result.has_errors?
|
|
91
|
+
colorize("✗ INVALID", :red, bold: true)
|
|
92
|
+
else
|
|
93
|
+
colorize("⚠ WARNINGS", :yellow, bold: true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
[
|
|
97
|
+
"Status: #{status}",
|
|
98
|
+
"",
|
|
99
|
+
"Messages:",
|
|
100
|
+
" Errors: #{colorize(result.errors.size.to_s, :red)}",
|
|
101
|
+
" Warnings: #{colorize(result.warnings.size.to_s, :yellow)}",
|
|
102
|
+
" Info: #{result.info.size}",
|
|
103
|
+
].join("\n")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Formats a section header
|
|
107
|
+
#
|
|
108
|
+
# @param title [String] Section title
|
|
109
|
+
# @param count [Integer] Message count
|
|
110
|
+
# @return [String]
|
|
111
|
+
def section_header(title, count)
|
|
112
|
+
color = case title
|
|
113
|
+
when "ERRORS" then :red
|
|
114
|
+
when "WARNINGS" then :yellow
|
|
115
|
+
else :cyan
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
colorize("#{title} (#{count}):", color, bold: true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Formats messages grouped by category
|
|
122
|
+
#
|
|
123
|
+
# @param messages [Array<ValidationMessage>] Messages to format
|
|
124
|
+
# @return [String]
|
|
125
|
+
def format_messages(messages) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
126
|
+
lines = []
|
|
127
|
+
messages_to_show = apply_limit(messages)
|
|
128
|
+
|
|
129
|
+
messages_to_show.group_by(&:category).each do |category, msgs|
|
|
130
|
+
category_name = format_category(category)
|
|
131
|
+
lines << colorize(" #{category_name} (#{msgs.size}):", :cyan)
|
|
132
|
+
lines << ""
|
|
133
|
+
|
|
134
|
+
msgs.each do |msg|
|
|
135
|
+
lines << format_message(msg)
|
|
136
|
+
lines << ""
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if options[:limit] && messages.size > options[:limit]
|
|
141
|
+
remaining = messages.size - options[:limit]
|
|
142
|
+
lines << colorize(
|
|
143
|
+
" ... and #{remaining} more (use --verbose to see all)",
|
|
144
|
+
:yellow,
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
lines.join("\n")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Formats a single message
|
|
152
|
+
#
|
|
153
|
+
# @param message [ValidationMessage] Message to format
|
|
154
|
+
# @return [String]
|
|
155
|
+
def format_message(message)
|
|
156
|
+
icon = severity_icon(message.severity)
|
|
157
|
+
entity_info = if message.entity_name
|
|
158
|
+
"#{message.entity_type}:#{message.entity_name}"
|
|
159
|
+
else
|
|
160
|
+
message.entity_type.to_s
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
[
|
|
164
|
+
" #{icon} #{colorize(entity_info, :blue)}",
|
|
165
|
+
" #{message.message}",
|
|
166
|
+
" ID: #{message.entity_id}",
|
|
167
|
+
].join("\n")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Formats the footer
|
|
171
|
+
#
|
|
172
|
+
# @return [String]
|
|
173
|
+
def footer
|
|
174
|
+
"=" * 80
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Applies message limit
|
|
178
|
+
#
|
|
179
|
+
# @param messages [Array<ValidationMessage>]
|
|
180
|
+
# @return [Array<ValidationMessage>]
|
|
181
|
+
def apply_limit(messages)
|
|
182
|
+
return messages unless options[:limit]
|
|
183
|
+
|
|
184
|
+
messages.first(options[:limit])
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Formats a category name
|
|
188
|
+
#
|
|
189
|
+
# @param category [Symbol] Category symbol
|
|
190
|
+
# @return [String]
|
|
191
|
+
def format_category(category)
|
|
192
|
+
category.to_s.split("_").map(&:capitalize).join(" ")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Returns an icon for the severity level
|
|
196
|
+
#
|
|
197
|
+
# @param severity [Symbol] Severity level
|
|
198
|
+
# @return [String]
|
|
199
|
+
def severity_icon(severity)
|
|
200
|
+
case severity
|
|
201
|
+
when :error then colorize("✗", :red)
|
|
202
|
+
when :warning then colorize("⚠", :yellow)
|
|
203
|
+
when :info then colorize("ℹ", :blue)
|
|
204
|
+
else "•"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Colorizes text if color is enabled
|
|
209
|
+
#
|
|
210
|
+
# @param text [String] Text to colorize
|
|
211
|
+
# @param color [Symbol] Color name
|
|
212
|
+
# @param bold [Boolean] Make text bold
|
|
213
|
+
# @return [String]
|
|
214
|
+
def colorize(text, color = nil, bold: false) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
215
|
+
return text unless options[:color]
|
|
216
|
+
|
|
217
|
+
codes = []
|
|
218
|
+
codes << 1 if bold
|
|
219
|
+
|
|
220
|
+
codes << case color
|
|
221
|
+
when :red then 31
|
|
222
|
+
when :green then 32
|
|
223
|
+
when :yellow then 33
|
|
224
|
+
when :blue then 34
|
|
225
|
+
when :cyan then 36
|
|
226
|
+
else 0
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
"\e[#{codes.join(';')}m#{text}\e[0m"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Validation
|
|
6
|
+
module Formatters
|
|
7
|
+
autoload :TextFormatter, "ea/qea/validation/formatters/text_formatter"
|
|
8
|
+
autoload :JsonFormatter, "ea/qea/validation/formatters/json_formatter"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates operation references and structure
|
|
7
|
+
class OperationValidator < BaseValidator
|
|
8
|
+
def validate
|
|
9
|
+
validate_parent_object_references
|
|
10
|
+
validate_return_types
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def validate_parent_object_references # rubocop:disable Metrics/MethodLength
|
|
16
|
+
operations.each do |op|
|
|
17
|
+
unless reference_exists?("t_object", "ea_object_id",
|
|
18
|
+
op.ea_object_id)
|
|
19
|
+
result.add_error(
|
|
20
|
+
category: :missing_reference,
|
|
21
|
+
entity_type: :operation,
|
|
22
|
+
entity_id: op.operationid.to_s,
|
|
23
|
+
entity_name: op.name,
|
|
24
|
+
field: "ea_object_id",
|
|
25
|
+
reference: op.ea_object_id.to_s,
|
|
26
|
+
message: "Parent object #{op.ea_object_id} does not exist",
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_return_types # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
33
|
+
operations.each do |op|
|
|
34
|
+
next unless op.classifier && !op.classifier.empty?
|
|
35
|
+
|
|
36
|
+
# Classifier can be either an object name or a primitive type
|
|
37
|
+
next if primitive_type?(op.classifier)
|
|
38
|
+
next if classifier_exists?(op.classifier)
|
|
39
|
+
|
|
40
|
+
parent = database&.objects&.all&.find do |o|
|
|
41
|
+
o.ea_object_id == op.ea_object_id
|
|
42
|
+
end
|
|
43
|
+
result.add_warning(
|
|
44
|
+
category: :missing_reference,
|
|
45
|
+
entity_type: :operation,
|
|
46
|
+
entity_id: op.operationid.to_s,
|
|
47
|
+
entity_name: "#{parent&.name}.#{op.name}()",
|
|
48
|
+
field: "classifier",
|
|
49
|
+
reference: op.classifier,
|
|
50
|
+
message: "Return type '#{op.classifier}' not found",
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def operations
|
|
56
|
+
@operations ||= context[:operations] || []
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def classifier_exists?(classifier_id)
|
|
60
|
+
return false unless database
|
|
61
|
+
|
|
62
|
+
# Classifier field contains object_id, not name
|
|
63
|
+
database.objects.all.any? do |o|
|
|
64
|
+
o.ea_object_id.to_s == classifier_id.to_s
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates package structure and hierarchy
|
|
7
|
+
class PackageValidator < BaseValidator
|
|
8
|
+
def validate
|
|
9
|
+
validate_parent_references
|
|
10
|
+
validate_duplicate_names
|
|
11
|
+
validate_circular_hierarchy
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def validate_parent_references # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
17
|
+
packages.each do |package|
|
|
18
|
+
next if package.root?
|
|
19
|
+
|
|
20
|
+
unless parent_exists?(package.parent_id)
|
|
21
|
+
package_path = resolve_package_path(package.package_id)
|
|
22
|
+
result.add_error(
|
|
23
|
+
category: :missing_reference,
|
|
24
|
+
entity_type: :package,
|
|
25
|
+
entity_id: package.package_id.to_s,
|
|
26
|
+
entity_name: package.name,
|
|
27
|
+
field: "parent_id",
|
|
28
|
+
reference: package.parent_id.to_s,
|
|
29
|
+
message: "Parent package #{package.parent_id} does not exist",
|
|
30
|
+
location: package_path,
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def validate_duplicate_names # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
37
|
+
packages_by_parent = packages.group_by(&:parent_id)
|
|
38
|
+
|
|
39
|
+
packages_by_parent.each do |parent_id, sibling_packages|
|
|
40
|
+
names = sibling_packages.map(&:name)
|
|
41
|
+
duplicates = names.select { |name| names.count(name) > 1 }.uniq
|
|
42
|
+
|
|
43
|
+
duplicates.each do |dup_name|
|
|
44
|
+
dup_packages = sibling_packages.select { |p| p.name == dup_name }
|
|
45
|
+
dup_packages.each do |package|
|
|
46
|
+
package_path = resolve_package_path(package.package_id)
|
|
47
|
+
parent_path = if parent_id
|
|
48
|
+
resolve_package_path(parent_id)
|
|
49
|
+
else
|
|
50
|
+
"Root"
|
|
51
|
+
end
|
|
52
|
+
result.add_warning(
|
|
53
|
+
category: :duplicate,
|
|
54
|
+
entity_type: :package,
|
|
55
|
+
entity_id: package.package_id.to_s,
|
|
56
|
+
entity_name: package.name,
|
|
57
|
+
field: "name",
|
|
58
|
+
message: "Duplicate package name '#{dup_name}' " \
|
|
59
|
+
"in parent #{parent_path}",
|
|
60
|
+
location: package_path,
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate_circular_hierarchy # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
68
|
+
packages.each do |package|
|
|
69
|
+
next if package.root?
|
|
70
|
+
|
|
71
|
+
path = [package.package_id]
|
|
72
|
+
current_id = package.parent_id
|
|
73
|
+
|
|
74
|
+
while current_id && !current_id.zero?
|
|
75
|
+
if path.include?(current_id)
|
|
76
|
+
package_path = resolve_package_path(package.package_id)
|
|
77
|
+
result.add_error(
|
|
78
|
+
category: :circular_reference,
|
|
79
|
+
entity_type: :package,
|
|
80
|
+
entity_id: package.package_id.to_s,
|
|
81
|
+
entity_name: package.name,
|
|
82
|
+
field: "parent_id",
|
|
83
|
+
message: "Circular package hierarchy detected: " \
|
|
84
|
+
"#{path.join(' -> ')} -> #{current_id}",
|
|
85
|
+
location: package_path,
|
|
86
|
+
)
|
|
87
|
+
break
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
path << current_id
|
|
91
|
+
parent = packages.find { |p| p.package_id == current_id }
|
|
92
|
+
break unless parent
|
|
93
|
+
|
|
94
|
+
current_id = parent.parent_id
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def packages
|
|
100
|
+
@packages ||= context[:db_packages] || []
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parent_exists?(parent_id)
|
|
104
|
+
return true if parent_id.nil? || parent_id.zero?
|
|
105
|
+
|
|
106
|
+
packages.any? { |p| p.package_id == parent_id }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|