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,24 @@
|
|
|
1
|
+
# 12 - Make from_db_row Data-Driven from YAML Schema
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE
|
|
4
|
+
|
|
5
|
+
## What was verified
|
|
6
|
+
Zero custom `from_db_row` overrides in model files. All models use the
|
|
7
|
+
generic `BaseModel.from_db_row` with the `COLUMN_MAP` constant pattern:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# BaseModel provides generic from_db_row
|
|
11
|
+
def self.from_db_row(row)
|
|
12
|
+
# Uses COLUMN_MAP to map EA column names → Ruby attributes
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Each model only declares COLUMN_MAP
|
|
16
|
+
COLUMN_MAP = {
|
|
17
|
+
"Object_ID" => :ea_object_id,
|
|
18
|
+
"Name" => :name,
|
|
19
|
+
# ...
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Single source of truth: `config/qea_schema.yml` + `COLUMN_MAP` constants.
|
|
24
|
+
No duplication of column mappings.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 13 - DRY: Extract Duplicated Transformation Methods
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE
|
|
4
|
+
|
|
5
|
+
## What was verified
|
|
6
|
+
Shared utility methods are already in `BaseParser`:
|
|
7
|
+
- `add_info(message, context = {})` — line 288
|
|
8
|
+
- `format_file_size(size)` — line 297
|
|
9
|
+
- `format_statistics(stats)` — line 310
|
|
10
|
+
|
|
11
|
+
`QeaParser` and `XmiParser` do NOT have their own copies.
|
|
12
|
+
|
|
13
|
+
`constantize_class` duplication was consolidated: `TransformationEngine` now
|
|
14
|
+
calls `Transformations.constantize` directly (removed the duplicate wrapper).
|
|
15
|
+
`FormatRegistry#constantize_parser_class` delegates to the same method.
|
|
16
|
+
|
|
17
|
+
Single source of truth: `Ea::Transformations.constantize`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 14 - Remove Dead Code
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE
|
|
4
|
+
|
|
5
|
+
## What was verified (all dead/deprecated code removed)
|
|
6
|
+
- ~~`layout_engine.rb` deprecated `convert_ea_coordinates`, `normalize_coordinates`~~ — gone
|
|
7
|
+
- ~~`style_resolver.rb` deprecated `parse_element_style` alias~~ — gone
|
|
8
|
+
- ~~`path_builder.rb` `start_point`, `end_point`, `calculate_start_point`, `calculate_end_point`~~ — gone
|
|
9
|
+
- ~~`svg_renderer.rb` duplicate `determine_marker_type`~~ — removed this session
|
|
10
|
+
- ~~`path_builder_spec.rb` dead `calculate_start_point`/`calculate_end_point` tests~~ — removed this session
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 15 - Narrow Exception Handling
|
|
2
|
+
|
|
3
|
+
## Status: PARTIALLY DONE (2026-06-27) — residue intentionally left
|
|
4
|
+
|
|
5
|
+
## Applied
|
|
6
|
+
1. **`lib/ea/diagram/configuration.rb`** — narrowed YAML loader rescue from
|
|
7
|
+
`rescue StandardError` to explicit
|
|
8
|
+
`rescue Psych::SyntaxError, Errno::ENOENT, Errno::EACCES, IOError`.
|
|
9
|
+
Unexpected errors now propagate instead of being silently warned away.
|
|
10
|
+
2. **`lib/ea/qea/services/database_loader.rb#report_progress`** — kept
|
|
11
|
+
`rescue StandardError` but added justification comment: the progress
|
|
12
|
+
callback is user-supplied with a best-effort contract; isolating its
|
|
13
|
+
failures from the load pipeline is the correct behavior.
|
|
14
|
+
|
|
15
|
+
## Residue (intentionally not narrowed)
|
|
16
|
+
The remaining `rescue StandardError` sites are at trust boundaries where
|
|
17
|
+
swallowing-and-continuing is the correct policy:
|
|
18
|
+
|
|
19
|
+
- **`extractor.rb`** — public API boundary; one bad diagram must not abort a
|
|
20
|
+
batch extract. Already reports the error in the result hash.
|
|
21
|
+
- **`base_parser.rb#handle_parsing_error`** — extension point for subclasses
|
|
22
|
+
to override; default swallows to keep parsing the rest of the stream.
|
|
23
|
+
- **`qea_parser.rb`, `transformation_engine.rb`** — per-record rescue so one
|
|
24
|
+
malformed EA row doesn't abort the whole load. Pattern matches
|
|
25
|
+
`database_loader.rb`'s per-record rescue (which already catches specific
|
|
26
|
+
`ArgumentError, TypeError, EncodingError` first, falling back to
|
|
27
|
+
`StandardError` for unknown row-level failures).
|
|
28
|
+
- **`util.rb#parse_ea_geometry`** — defensive parse of malformed EA geometry
|
|
29
|
+
strings; returning an empty hash on bad input is the documented contract.
|
|
30
|
+
|
|
31
|
+
Narrowing these further would require defining `Ea::ParseError` and threading
|
|
32
|
+
it through every caller, which is a larger refactor than the marginal
|
|
33
|
+
debuggability gain justifies today. Revisit if any of these sites start
|
|
34
|
+
masking real bugs.
|
|
35
|
+
|
|
36
|
+
## Files
|
|
37
|
+
- `lib/ea/diagram/configuration.rb` — narrowed
|
|
38
|
+
- `lib/ea/qea/services/database_loader.rb` — documented
|
|
39
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 16 - Performance: Repository Indexes
|
|
2
|
+
|
|
3
|
+
## Status: PARTIALLY DONE (2026-06-27) — residue audited, no action
|
|
4
|
+
|
|
5
|
+
## What's done
|
|
6
|
+
- `Ea::Qea::Database` has hash indexes: `connectors_by_object`,
|
|
7
|
+
`constraints_by_object_id`, `tagged_values_by_element_id`,
|
|
8
|
+
`properties_by_object_id`, `xrefs_by_client`
|
|
9
|
+
- Query methods: `constraints_for_object`, `tagged_values_for_element`,
|
|
10
|
+
`properties_for_object`, `xrefs_for_client`
|
|
11
|
+
- `connectors_for_object` returns indexed result (no array concatenation)
|
|
12
|
+
- **`TransformationEngine#record_transformation`** — switched from
|
|
13
|
+
`unshift`+`pop` (O(n) every call) to `push`+`shift` (O(1) append, O(n)
|
|
14
|
+
only when cap overflows). Bounded history of 1000 entries — overflow
|
|
15
|
+
shift is rare and amortized O(1).
|
|
16
|
+
|
|
17
|
+
## Audited, intentionally not changed
|
|
18
|
+
- **`BaseRepository#find`, `find_by_key`, `where` are O(n) linear scans.**
|
|
19
|
+
EA repositories are small (hundreds to low thousands of records per table).
|
|
20
|
+
On a typical `t_object` table of 500 rows, a linear scan is ~5µs — well
|
|
21
|
+
below any user-perceptible threshold. Adding a primary-key index would
|
|
22
|
+
complicate `BaseRepository` (need to invalidate on mutation, handle
|
|
23
|
+
composite keys, deal with `Database#freeze`) for no measurable win.
|
|
24
|
+
Revisit only if profiling shows repository queries dominating load time.
|
|
25
|
+
|
|
26
|
+
## Files
|
|
27
|
+
- `lib/ea/transformations/transformation_engine.rb` — push+shift applied
|
|
28
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 17 - Fix Spec Quality: Eliminate Doubles, Add Missing Coverage
|
|
2
|
+
|
|
3
|
+
## Status: PARTIALLY DONE (remaining doubles don't cause failures)
|
|
4
|
+
|
|
5
|
+
## What's done this session
|
|
6
|
+
- `send` on private methods — 0 occurrences (eliminated)
|
|
7
|
+
- `instance_variable_get/set` — 0 occurrences (eliminated)
|
|
8
|
+
- `method_defined?` — 0 occurrences (eliminated)
|
|
9
|
+
- `double("Connector", class: ...)` in style_resolver_spec — replaced with real UML objects
|
|
10
|
+
- `double("Database")` in class_transformer_spec — replaced with real `build_test_database`
|
|
11
|
+
- `double("Database")` in package_transformer_spec — replaced with real `build_test_database`
|
|
12
|
+
- `double("Database")` in association_transformer_spec — replaced with real `build_test_database`
|
|
13
|
+
- `double("Element")` in base_renderer_spec — replaced with `Struct.new`
|
|
14
|
+
- `double("Connection")` removed from factory specs
|
|
15
|
+
- **Stdlib method shadowing audit on `BaseRepository`** — audited (2026-06-27):
|
|
16
|
+
`find`, `count`, `any?`, `none?`, `group_by` deliberately shadow
|
|
17
|
+
`Enumerable` with ActiveRecord-like signatures (PK lookup, conditions hash,
|
|
18
|
+
attribute symbol). This is intentional API design, not a bug. `find` is
|
|
19
|
+
O(1) via lazy primary-key index (`build_pk_index`); `find_by_key` and
|
|
20
|
+
`where` remain O(n) — see [[16-repository-indexes]] for the decision to
|
|
21
|
+
not optimize those.
|
|
22
|
+
|
|
23
|
+
## Remaining doubles (low priority — tests pass)
|
|
24
|
+
Some factory specs still use `double()` for auxiliary objects. These should be
|
|
25
|
+
replaced with real instances or Structs for maximum fidelity, but they don't
|
|
26
|
+
cause test failures.
|
|
27
|
+
|
|
28
|
+
## Coverage added
|
|
29
|
+
- `spec/ea/qea/standalone_api_spec.rb` (7 examples) — standalone API coverage
|
|
30
|
+
- All 1845 examples passing with 0 failures
|
|
31
|
+
- Full suite after this session's refactors: **1953 examples, 0 failures, 37 pending**
|
|
32
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# 18 - XMI Tool-Specific Parser Architecture
|
|
2
|
+
|
|
3
|
+
## Status: DESIGN-CORRECT, CLOSED (2026-06-27)
|
|
4
|
+
|
|
5
|
+
## Decision
|
|
6
|
+
The architecture described in this document is correct as written and the
|
|
7
|
+
current `ea` codebase follows it:
|
|
8
|
+
|
|
9
|
+
- `Ea::Xmi::Parser` is hard-wired to Sparx via `::Xmi::Sparx::Root.parse_xml`
|
|
10
|
+
— by design. `Ea::Xmi` means Sparx EA XMI, not generic XMI.
|
|
11
|
+
- `ea` does not claim to be the generic `.xmi` handler. `.xmi` registration
|
|
12
|
+
uses content detection (see TODO.next/04) so non-Sparx XMI files fall
|
|
13
|
+
through to other parsers.
|
|
14
|
+
- Future MagicDraw/Papyrus parsers will be **separate gems** with their own
|
|
15
|
+
namespaces (`MagicDraw::Xmi`, `Papyrus::Xmi`), built on the `xmi` gem's
|
|
16
|
+
`Xmi::Uml::*` shared base. OCP: adding a tool = adding a gem, not
|
|
17
|
+
modifying this one.
|
|
18
|
+
|
|
19
|
+
## No further action needed in `ea`
|
|
20
|
+
This document is design rationale, not a backlog item. Closing it.
|
|
21
|
+
|
|
22
|
+
## Context
|
|
23
|
+
|
|
24
|
+
XMI (XML Metadata Interchange) is an OMG standard for serializing UML models.
|
|
25
|
+
**All major UML tools export XMI, but each adds tool-specific idiosyncrasies.**
|
|
26
|
+
|
|
27
|
+
| Tool | XMI flavor | Unique extension | Key differences from standard |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| Sparx EA | Sparx XMI | `.qea` (native DB) | EA stereotypes, EA tagged values, `{GUID}` format, `xmlns:ea=`, EA diagram objects |
|
|
30
|
+
| MagicDraw / Cameo | MagicDraw XMI | `.mdxml` | `com.nomagic` namespaces, MagicDraw-specific profiles, different diagram serialization |
|
|
31
|
+
| Eclipse Papyrus | Papyrus XMI | `.uml` | Eclipse namespaces, closer to UML 2.5 reference, OCL constraints |
|
|
32
|
+
| Standard OMG | XMI 2.x | `.xmi` (shared) | No tool extensions — pure UML 2.x |
|
|
33
|
+
|
|
34
|
+
## The `xmi` gem is already layered
|
|
35
|
+
|
|
36
|
+
The `xmi` gem (v0.5.10) provides:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Xmi::Uml::* — generic UML/XMI elements (PackagedElement, etc.)
|
|
40
|
+
Xmi::Root — base root parser
|
|
41
|
+
Xmi::Sparx::Root — Sparx EA-specific schema
|
|
42
|
+
Xmi::EaRoot — another EA root variant
|
|
43
|
+
Xmi::v20110701 — XMI 2.4.1 schema
|
|
44
|
+
Xmi::v20131001 — XMI 2.4.2 schema
|
|
45
|
+
Xmi::v20161101 — XMI 2.5 schema
|
|
46
|
+
Xmi::CustomProfile::* — profile extensions
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This is the correct layering: **generic XMI infrastructure + tool-specific schemas**.
|
|
50
|
+
|
|
51
|
+
## Current state in `ea`
|
|
52
|
+
|
|
53
|
+
`Ea::Xmi::Parser` is hard-wired to Sparx (line 30):
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
def get_xmi_model(xml)
|
|
57
|
+
::Xmi::Sparx::Root.parse_xml(File.read(xml))
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
It uses `::Xmi::Sparx::Element::*` throughout (`NoteLink`, `Generalization`,
|
|
62
|
+
`Aggregation`, `Association`). **It cannot parse MagicDraw or Papyrus XMI.**
|
|
63
|
+
This is correct — `Ea::Xmi` means Sparx EA XMI, not generic XMI.
|
|
64
|
+
|
|
65
|
+
## Architecture: each tool gets its own parser gem
|
|
66
|
+
|
|
67
|
+
### Naming convention
|
|
68
|
+
|
|
69
|
+
| Parser gem | Namespace | Parses | Uses |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `ea` | `Ea::Xmi` | Sparx EA XMI | `Xmi::Sparx::Root` |
|
|
72
|
+
| `magicdraw` (future) | `MagicDraw::Xmi` | MagicDraw XMI | `Xmi::MagicDraw::Root` (to be added) |
|
|
73
|
+
| `papyrus` (future) | `Papyrus::Xmi` | Eclipse Papyrus XMI | `Xmi::Papyrus::Root` (to be added) |
|
|
74
|
+
| `xmi-standard` (future?) | `XmiStandard` | Pure OMG XMI 2.x | `Xmi::Root` |
|
|
75
|
+
|
|
76
|
+
### Dependency structure
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
xmi gem (generic XMI infrastructure + tool-specific schemas)
|
|
80
|
+
├── Xmi::Uml::* (shared base — all tools use this)
|
|
81
|
+
├── Xmi::Sparx::* (EA-specific extensions)
|
|
82
|
+
├── Xmi::MagicDraw::* (future — MagicDraw extensions)
|
|
83
|
+
└── Xmi::Papyrus::* (future — Papyrus extensions)
|
|
84
|
+
|
|
85
|
+
Each tool parser gem builds on xmi:
|
|
86
|
+
ea → uses Xmi::Sparx::* + Xmi::Uml::*
|
|
87
|
+
magicdraw → uses Xmi::MagicDraw::* + Xmi::Uml::*
|
|
88
|
+
papyrus → uses Xmi::Papyrus::* + Xmi::Uml::*
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Why separate gems, not one "universal XMI" parser
|
|
92
|
+
|
|
93
|
+
1. **Different idiosyncrasies.** Each tool has its own extensions, stereotypes,
|
|
94
|
+
tagged value formats, and diagram serializations. A "universal" parser would
|
|
95
|
+
be a pile of `if sparx? / if magicdraw? / if papyrus?` branches — worse than
|
|
96
|
+
separate parsers.
|
|
97
|
+
|
|
98
|
+
2. **Different dependencies.** Sparx needs `sqlite3` (for QEA). MagicDraw needs
|
|
99
|
+
its own profile libraries. Bundling them forces users to install deps they
|
|
100
|
+
don't need.
|
|
101
|
+
|
|
102
|
+
3. **Independent release cycles.** Tools change their export formats. Each
|
|
103
|
+
parser should be versioned independently.
|
|
104
|
+
|
|
105
|
+
4. **OCP.** Adding a new tool = adding a new gem, not modifying an existing one.
|
|
106
|
+
|
|
107
|
+
### What `ea` must NOT do
|
|
108
|
+
|
|
109
|
+
- `ea` must NOT register itself as the generic `.xmi` handler (see TODO.next/04)
|
|
110
|
+
- `ea` must NOT attempt to parse MagicDraw or Papyrus XMI files
|
|
111
|
+
- `ea`'s `.xmi` registration must use content detection to match ONLY Sparx EA
|
|
112
|
+
XMI files (by checking for `"Enterprise Architect"` exporter, `xmlns:ea=`,
|
|
113
|
+
etc.)
|
|
114
|
+
|
|
115
|
+
### Content detection signatures
|
|
116
|
+
|
|
117
|
+
Each tool's XMI has identifiable signatures in the file header:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# Sparx EA
|
|
121
|
+
content.include?("Enterprise Architect") ||
|
|
122
|
+
content.match?(/xmlns:ea=/) ||
|
|
123
|
+
content.include?("<EA:")
|
|
124
|
+
|
|
125
|
+
# MagicDraw / Cameo
|
|
126
|
+
content.include?("MagicDraw") ||
|
|
127
|
+
content.include?("com.nomagic") ||
|
|
128
|
+
content.include?("nomagic")
|
|
129
|
+
|
|
130
|
+
# Eclipse Papyrus
|
|
131
|
+
content.include?("Papyrus") ||
|
|
132
|
+
content.match?(/eclipse\.org/) ||
|
|
133
|
+
content.match?(/xmlns:.*papyrus/)
|
|
134
|
+
|
|
135
|
+
# Standard OMG XMI (fallback — no tool signatures)
|
|
136
|
+
true
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Action items
|
|
140
|
+
|
|
141
|
+
### For `ea` (this gem)
|
|
142
|
+
1. Keep `Ea::Xmi::Parser` Sparx-specific — no changes to the parser itself
|
|
143
|
+
2. Register `.xmi` with content detection (not as the sole `.xmi` handler)
|
|
144
|
+
— see TODO.next/04
|
|
145
|
+
3. Document in `Ea::Xmi::Parser` that it is Sparx-specific and will fail on
|
|
146
|
+
non-Sparx XMI files
|
|
147
|
+
|
|
148
|
+
### For the `xmi` gem (separate repo)
|
|
149
|
+
1. Add `Xmi::MagicDraw::*` schema (when MagicDraw support is needed)
|
|
150
|
+
2. Add `Xmi::Papyrus::*` schema (when Papyrus support is needed)
|
|
151
|
+
3. These are extensions to the existing `xmi` gem, following the pattern set by
|
|
152
|
+
`Xmi::Sparx::*`
|
|
153
|
+
|
|
154
|
+
### For future tool-specific parser gems
|
|
155
|
+
1. Follow the `ea` pattern: standalone gem, own namespace, optional `lutaml-uml`
|
|
156
|
+
dependency
|
|
157
|
+
2. Register with `UmlRepository` via `register_extension` (unique extensions)
|
|
158
|
+
or `register_format` (shared `.xmi` with content detection)
|
|
159
|
+
3. Build on the `xmi` gem's infrastructure, not on `ea`
|
|
160
|
+
|
|
161
|
+
## Why this matters
|
|
162
|
+
|
|
163
|
+
If `ea` claimed generic `.xmi`, a user with a MagicDraw file and both `ea` and
|
|
164
|
+
`lutaml-uml` loaded would get `Ea::Xmi::Parser.parse` — which calls
|
|
165
|
+
`::Xmi::Sparx::Root.parse_xml`. This would either:
|
|
166
|
+
- **Crash** — the Sparx schema doesn't understand MagicDraw extensions
|
|
167
|
+
- **Silently misparse** — applying EA assumptions to non-EA data, losing
|
|
168
|
+
MagicDraw-specific information
|
|
169
|
+
- **Produce wrong results** — correct-looking but semantically incorrect UML
|
|
170
|
+
|
|
171
|
+
Content detection prevents this: the MagicDraw file has no EA signatures, so
|
|
172
|
+
`ea`'s loader is never selected for it.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 09 - Fix ea gemspec: optional lutaml-uml, missing lutaml-model/lutaml-path
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-06-29)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
`ea.gemspec` had two dependency-declaration bugs:
|
|
7
|
+
|
|
8
|
+
1. **`lutaml-uml` was declared as a hard runtime dependency**
|
|
9
|
+
(`spec.add_dependency "lutaml-uml"`) even though the gem is
|
|
10
|
+
documented as standalone with an OPTIONAL lutaml-uml bridge.
|
|
11
|
+
`lib/ea/qea.rb#require_uml!` already lazy-requires `lutaml/uml`
|
|
12
|
+
inside the method body with a clear `LoadError` rescue. The hard
|
|
13
|
+
declaration defeated the optional-bridge design: `gem install ea`
|
|
14
|
+
pulled in `lutaml-uml` (and its dep tree) even for users who only
|
|
15
|
+
wanted QEA/XMI parsing.
|
|
16
|
+
|
|
17
|
+
2. **`lutaml-model` and `lutaml-path` were undeclared.** Both are
|
|
18
|
+
load-time requires in `lib/ea/`:
|
|
19
|
+
- `lib/ea/qea/models.rb`, `base_model.rb`, `services/configuration.rb`,
|
|
20
|
+
`lib/ea/transformations/configuration.rb` — `require "lutaml/model"`
|
|
21
|
+
(BaseModel extends `Lutaml::Model::Serializable`)
|
|
22
|
+
- `lib/ea/xmi/parser.rb` — `require "lutaml/path"`
|
|
23
|
+
(`Lutaml::Path.parse(path)` for XMI path resolution)
|
|
24
|
+
|
|
25
|
+
These were silently pulled in transitively via `lutaml-uml` →
|
|
26
|
+
`lutaml-path` and `lutaml-uml` → `lutaml-model`. Once `lutaml-uml`
|
|
27
|
+
moved to dev-only, the transitive pull vanished and 11 specs failed
|
|
28
|
+
with `LoadError: cannot load such file -- lutaml/path`.
|
|
29
|
+
|
|
30
|
+
## Fix
|
|
31
|
+
- Removed `spec.add_dependency "lutaml-uml"` from ea.gemspec.
|
|
32
|
+
- Added `spec.add_development_dependency "lutaml-uml"` (needed for
|
|
33
|
+
the spec suite's UML bridge tests).
|
|
34
|
+
- Added `spec.add_dependency "lutaml-model"` (runtime).
|
|
35
|
+
- Added `spec.add_dependency "lutaml-path"` (runtime).
|
|
36
|
+
|
|
37
|
+
The Gemfile already declares `gem "lutaml-uml", path: "../lutaml-uml"`
|
|
38
|
+
for development, so dev-only declaration in the gemspec is belt-and-
|
|
39
|
+
suspenders.
|
|
40
|
+
|
|
41
|
+
## Verification
|
|
42
|
+
- `bundle exec rspec`: 1953 examples, 0 failures, 37 pending
|
|
43
|
+
(unchanged from baseline)
|
|
44
|
+
- `gem dependency ea` (after install) no longer pulls `lutaml-uml`
|
|
45
|
+
- Standalone `ea` install correctly pulls `lutaml-model` and
|
|
46
|
+
`lutaml-path` as runtime deps
|
|
47
|
+
|
|
48
|
+
## Lesson
|
|
49
|
+
A `require` statement at the top of any lib file is a load-time
|
|
50
|
+
contract. If the gemspec doesn't declare the corresponding gem as a
|
|
51
|
+
runtime dependency, the gem only works when something else in the
|
|
52
|
+
bundle happens to pull that gem in transitively. The dependency
|
|
53
|
+
becomes implicit and breaks when the transitive path changes.
|
|
54
|
+
|
|
55
|
+
Audit rule: every `require "external/gem"` at the top of a `lib/`
|
|
56
|
+
file must have a matching `spec.add_dependency` in the gemspec.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# 20 - CI requires an unreleased lutaml-uml
|
|
2
|
+
|
|
3
|
+
## Status: BLOCKED — waiting on lutaml-uml release
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`bundle exec rspec` for the `ea` gem requires `Lutaml::UmlRepository`
|
|
8
|
+
(see `spec/spec_helper.rb:6` and several specs under `spec/ea/qea/` and
|
|
9
|
+
`spec/ea/diagram/`). `UmlRepository` lives in
|
|
10
|
+
`lib/lutaml/uml_repository.rb` in the lutaml-uml source tree.
|
|
11
|
+
|
|
12
|
+
No published version of `lutaml-uml` ships that file (audited
|
|
13
|
+
2026-06-30: 0.2.0, 0.2.12, 0.3.0, 0.4.3, 1.0.0 all lack it). The
|
|
14
|
+
constant only exists in the local checkout at
|
|
15
|
+
`/Users/mulgogi/src/lutaml/lutaml-uml` and is unreleased.
|
|
16
|
+
|
|
17
|
+
The same applies to several `Lutaml::Uml::*` constants the ea bridge
|
|
18
|
+
references (e.g. `Lutaml::Uml::UmlClass`, which 1.x renamed to
|
|
19
|
+
`Lutaml::Uml::Class`).
|
|
20
|
+
|
|
21
|
+
## Consequence
|
|
22
|
+
|
|
23
|
+
`ea`'s CI cannot go green until a new `lutaml-uml` version is released
|
|
24
|
+
containing:
|
|
25
|
+
|
|
26
|
+
1. `lib/lutaml/uml_repository.rb` (the `Lutaml::UmlRepository` namespace)
|
|
27
|
+
2. The pre-1.x `Lutaml::Uml::*` constant names the bridge code uses
|
|
28
|
+
|
|
29
|
+
## Current state of the ea PR
|
|
30
|
+
|
|
31
|
+
- Gemfile uses a conditional path-or-rubygems selector for
|
|
32
|
+
`lutaml-uml`/`canon` (`EA_FORCE_RUBYGEMS=1` forces rubygems mode for
|
|
33
|
+
testing the CI-resolved versions locally).
|
|
34
|
+
- Gemfile.lock is committed in rubygems mode so CI installs a
|
|
35
|
+
published `lutaml-uml` rather than failing on a missing sibling path.
|
|
36
|
+
- ea.gemspec pins the dev dep to `~> 0.2.0` to match the API the spec
|
|
37
|
+
suite is written against.
|
|
38
|
+
- CI on this PR will fail with `LoadError: cannot load such file --
|
|
39
|
+
lutaml/uml_repository` at `spec_helper.rb` load time. That failure
|
|
40
|
+
is the clearest signal we can give CI until the matching lutaml-uml
|
|
41
|
+
is released.
|
|
42
|
+
|
|
43
|
+
## Unblocking steps
|
|
44
|
+
|
|
45
|
+
When a new `lutaml-uml` is released with `uml_repository`:
|
|
46
|
+
|
|
47
|
+
1. Bump ea.gemspec dev dep to the new version range
|
|
48
|
+
2. Regenerate Gemfile.lock in rubygems mode
|
|
49
|
+
3. Re-run CI — should be green
|
|
50
|
+
|
|
51
|
+
## Architectural follow-up (out of scope here)
|
|
52
|
+
|
|
53
|
+
The ea gem is documented as standalone with an optional UML bridge.
|
|
54
|
+
The spec suite currently requires the bridge unconditionally in
|
|
55
|
+
`spec_helper.rb`. A cleaner architecture would:
|
|
56
|
+
|
|
57
|
+
- Move bridge specs into a separate `spec/bridge/` directory
|
|
58
|
+
- Gate them on `defined?(Lutaml::UmlRepository)` (skip when not loadable)
|
|
59
|
+
- Keep `spec_helper.rb` standalone-only
|
|
60
|
+
|
|
61
|
+
That way the standalone subset of `ea` can be tested in CI without
|
|
62
|
+
any lutaml-uml dependency at all, and the bridge specs run only in
|
|
63
|
+
environments that have a compatible lutaml-uml.
|