ea 0.5.2 → 0.6.0
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/TODO.complete/01-t-xref-parsing.md +1 -1
- data/TODO.complete/02-per-label-geometry.md +1 -1
- data/TODO.complete/03-object-properties-constraints.md +1 -1
- data/TODO.complete/04-operation-params.md +1 -1
- data/TODO.complete/05-pdata-flag-matrix.md +1 -1
- data/TODO.complete/06-emf-image-rendering.md +1 -1
- data/TODO.complete/07-auxiliary-tables.md +1 -1
- data/TODO.complete/10-mdg-stereotype-registry.md +1 -1
- data/TODO.complete/11-gml-xsd-generation.md +1 -1
- data/TODO.complete/13-shapescript-interpreter.md +12 -23
- data/TODO.complete/20-cli-diff.md +1 -1
- data/TODO.complete/21-cli-render.md +1 -1
- data/TODO.complete/22-cli-export.md +1 -1
- data/TODO.complete/23-cli-mdg.md +1 -1
- data/TODO.complete/30-headerlines-ocp-refactor.md +1 -1
- data/TODO.complete/32-spec-coverage.md +1 -1
- data/TODO.complete/33-blocked-parity-gaps.md +1 -1
- data/TODO.complete/36-stereotype-icon-wiring.md +1 -1
- data/TODO.complete/37-cli-lint.md +1 -1
- data/TODO.complete/38-cli-query.md +1 -1
- data/TODO.complete/39-cli-info.md +1 -1
- data/TODO.complete/40-ocl-evaluator.md +1 -1
- data/TODO.complete/41-round-trip-spec.md +1 -1
- data/TODO.complete/42-html-diff-report.md +1 -1
- data/TODO.complete/43-json-schema-export.md +1 -1
- data/TODO.complete/44-remaining-qea-tables.md +1 -1
- data/TODO.complete/45-shapescript-extensions.md +1 -1
- data/TODO.complete/46-xsd-consolidation.md +1 -1
- data/TODO.complete/47-headerlines-legacy-removal.md +1 -1
- data/TODO.complete/51-remove-respond-to.md +42 -15
- data/TODO.complete/52-xsd-decouple-fixtures.md +1 -1
- data/TODO.complete/54-wire-stereotype-icon-renderer.md +1 -1
- data/TODO.complete/55-wire-shapescript.md +1 -1
- data/TODO.complete/56-wire-emf-renderer.md +1 -1
- data/TODO.complete/57-wire-ocl-evaluator.md +1 -1
- data/TODO.complete/58-diff-modifications.md +1 -1
- data/TODO.complete/59-delete-toy-xmi-export.md +1 -1
- data/TODO.complete/60-curate-json-export.md +1 -1
- data/TODO.complete/61-plantuml-relationships.md +1 -1
- data/TODO.complete/62-changelog.md +1 -1
- data/TODO.complete/63-dependabot-fix.md +1 -1
- data/TODO.complete/64-qeatoxmi-connectors.md +9 -24
- data/TODO.complete/65-qeatoxmi-diagrams.md +9 -23
- data/TODO.complete/66-qeatoxmi-style-tags-docs.md +25 -18
- data/TODO.complete/67-ocl-collection-ops.md +12 -12
- data/TODO.complete/68-ocl-comparison-ops.md +16 -11
- data/TODO.complete/69-spec-new-models.md +13 -18
- data/TODO.complete/70-specs-for-all-models.md +21 -0
- data/TODO.complete/71-autoload-audit.md +26 -0
- data/TODO.complete/72-extension-serializer-ocp-naming.md +26 -0
- data/TODO.complete/73-ocl-validator-latent-bugs.md +50 -0
- data/TODO.complete/74-visibility-symbol-dry.md +39 -0
- data/TODO.complete/75-database-table-collection-registry.md +32 -0
- data/TODO.complete/76-stale-todo-cleanup.md +52 -0
- data/TODO.complete/77-eliminate-banned-patterns-from-specs.md +55 -0
- data/TODO.complete/78-mdg-stereotype-emission.md +66 -0
- data/TODO.complete/79-equivalence-fixes.md +57 -0
- data/TODO.complete/80-performance-extension-serializer-indexes.md +43 -0
- data/TODO.complete/81-dry-scope-guid-extraction.md +33 -0
- data/TODO.complete/82-public-send-audit-conclusion.md +25 -0
- data/TODO.complete/83-dry-shared-xml-escape-guid-format.md +52 -0
- data/TODO.complete/84-ocp-markers-shape-registry.md +54 -0
- data/TODO.complete/85-frozen-literal-rubocop-normalize-type-spec.md +38 -0
- data/TODO.complete/86-frozen-sweep-lint-query-specs.md +55 -0
- data/TODO.complete/STATUS.md +26 -5
- data/lib/ea/cli/app.rb +1 -0
- data/lib/ea/cli/command/export.rb +10 -1
- data/lib/ea/cli/command/mdg.rb +1 -10
- data/lib/ea/cli/command/render.rb +2 -1
- data/lib/ea/diff/html_reporter.rb +1 -5
- data/lib/ea/export/json/generator.rb +5 -6
- data/lib/ea/export/plantuml/generator.rb +5 -3
- data/lib/ea/guid_format.rb +91 -0
- data/lib/ea/mdg/document.rb +2 -1
- data/lib/ea/mdg/loader.rb +2 -1
- data/lib/ea/mdg/registry.rb +57 -2
- data/lib/ea/mdg/xml/applies_to.rb +1 -1
- data/lib/ea/mdg/xml/apply.rb +1 -1
- data/lib/ea/mdg/xml/content.rb +1 -1
- data/lib/ea/mdg/xml/documentation.rb +1 -1
- data/lib/ea/mdg/xml/stereotype.rb +1 -1
- data/lib/ea/mdg/xml/stereotypes.rb +1 -1
- data/lib/ea/mdg/xml/tag.rb +1 -1
- data/lib/ea/mdg/xml/tagged_values.rb +1 -1
- data/lib/ea/mdg/xml/technology.rb +1 -1
- data/lib/ea/mdg/xml/uml_profile.rb +1 -1
- data/lib/ea/mdg/xml/uml_profiles.rb +1 -1
- data/lib/ea/mdg/xml.rb +2 -1
- data/lib/ea/mdg.rb +2 -1
- data/lib/ea/model/ghost_label.rb +2 -1
- data/lib/ea/ocl/evaluator.rb +4 -2
- data/lib/ea/qea/database.rb +57 -2
- data/lib/ea/qea/factory/reference_resolver.rb +1 -1
- data/lib/ea/qea/models/ea_attribute.rb +1 -17
- data/lib/ea/qea/models/ea_connector.rb +3 -2
- data/lib/ea/qea/models/ea_object.rb +14 -9
- data/lib/ea/qea/models/ea_operation.rb +1 -17
- data/lib/ea/qea/models/ea_xref.rb +3 -0
- data/lib/ea/qea/models/scope_predicate.rb +29 -0
- data/lib/ea/qea/models.rb +1 -0
- data/lib/ea/qea/repositories/base_repository.rb +4 -1
- data/lib/ea/qea/validation/base_validator.rb +3 -48
- data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +23 -13
- data/lib/ea/shapescript/renderer.rb +1 -3
- data/lib/ea/sources/qea/diagram_builder.rb +1 -0
- data/lib/ea/sources/qea/id_normalizer.rb +2 -2
- data/lib/ea/svg/ea_emitter/background.rb +2 -1
- data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +1 -6
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +2 -7
- data/lib/ea/svg/ea_emitter/elements.rb +1 -3
- data/lib/ea/svg/ea_emitter/ghost_labels.rb +2 -1
- data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -6
- data/lib/ea/svg/ea_emitter/marker/package_import.rb +1 -1
- data/lib/ea/svg/ea_emitter/markers.rb +52 -23
- data/lib/ea/svg/ea_emitter/text_renderer.rb +1 -4
- data/lib/ea/svg/ea_emitter/visibility_symbol.rb +37 -0
- data/lib/ea/svg/ea_emitter.rb +1 -0
- data/lib/ea/svg/parity/source.rb +0 -2
- data/lib/ea/svg/parity/suite.rb +0 -1
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +40 -28
- data/lib/ea/transformers/qea_to_xmi/context.rb +35 -1
- data/lib/ea/transformers/qea_to_xmi/extension_serializer.rb +686 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +5 -48
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +55 -54
- data/lib/ea/transformers/qea_to_xmi/primitive_types.rb +105 -0
- data/lib/ea/transformers/qea_to_xmi/profile_serializer.rb +163 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +7 -6
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +396 -185
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +30 -19
- data/lib/ea/transformers/qea_to_xmi.rb +5 -2
- data/lib/ea/transformers.rb +7 -2
- data/lib/ea/validation.rb +1 -1
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +12 -4
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +1 -2
- data/lib/ea/xmi/lookup_service.rb +4 -0
- data/lib/ea/xmi/parser.rb +30 -1
- data/lib/ea/xml_escape.rb +21 -0
- data/lib/ea.rb +2 -0
- metadata +28 -11
- data/lutaml-uml-0.5.1.gem +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# TODO.complete/83: DRY — shared XmlEscape + GuidFormat modules
|
|
2
|
+
|
|
3
|
+
## Status: done
|
|
4
|
+
|
|
5
|
+
Two cross-subsystem DRY violations extracted into shared top-level
|
|
6
|
+
modules.
|
|
7
|
+
|
|
8
|
+
## 1. Ea::XmlEscape
|
|
9
|
+
|
|
10
|
+
The XML entity escape (`& < > "` → entities) was duplicated in 4
|
|
11
|
+
files with subtle differences:
|
|
12
|
+
|
|
13
|
+
| File | Escapes | Nil-safe? |
|
|
14
|
+
|------|---------|-----------|
|
|
15
|
+
| `text_renderer.rb` | `& < > "` | no |
|
|
16
|
+
| `html_reporter.rb` | `& < > "` | yes |
|
|
17
|
+
| `shapescript/renderer.rb` | `& < >` (missing `"`!) | no |
|
|
18
|
+
| `extension_serializer.rb` | `& < > "` | no |
|
|
19
|
+
|
|
20
|
+
The ShapeScript version was **missing quote escaping** — a latent
|
|
21
|
+
bug that could produce broken SVG if a shape value contained `"`.
|
|
22
|
+
|
|
23
|
+
All four now call `Ea::XmlEscape.call(text)`:
|
|
24
|
+
- One canonical implementation
|
|
25
|
+
- Always escapes all 4 special chars
|
|
26
|
+
- Always nil-safe (via `to_s`)
|
|
27
|
+
|
|
28
|
+
Spec: `spec/ea/xml_escape_spec.rb` (8 examples).
|
|
29
|
+
|
|
30
|
+
## 2. Ea::GuidFormat
|
|
31
|
+
|
|
32
|
+
GUID→XMI ID conversion was duplicated in 3 subsystems:
|
|
33
|
+
|
|
34
|
+
| File | Operation |
|
|
35
|
+
|------|-----------|
|
|
36
|
+
| `Transformers::QeaToXmi::GuidFormat` | full conversion (canonical) |
|
|
37
|
+
| `Sources::Qea::IdNormalizer` | inline `gsub(/[{}]/,"")` + `.tr("-","_")` |
|
|
38
|
+
| `Qea::Factory::ReferenceResolver` | inline `gsub(/[{}]/,"").upcase` |
|
|
39
|
+
|
|
40
|
+
Promoted `GuidFormat` to top-level `Ea::GuidFormat` (shared).
|
|
41
|
+
`Transformers::QeaToXmi::GuidFormat` is now an alias (backward
|
|
42
|
+
compatible — existing references don't break).
|
|
43
|
+
|
|
44
|
+
Added `strip_braces` method for callers that need just the brace
|
|
45
|
+
removal without prefix/dash conversion. `IdNormalizer` and
|
|
46
|
+
`ReferenceResolver` now use it.
|
|
47
|
+
|
|
48
|
+
## Result
|
|
49
|
+
|
|
50
|
+
- Export output byte-identical (405898 bytes)
|
|
51
|
+
- 8 new specs (XmlEscape)
|
|
52
|
+
- 2211 examples, 0 failures, 55 pending
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# TODO.complete/84: OCP — markers.rb shape dispatch → registry
|
|
2
|
+
|
|
3
|
+
## Status: done
|
|
4
|
+
|
|
5
|
+
`Markers` had 3 case/when blocks all dispatching on the same
|
|
6
|
+
`spec.shape` enum. Adding a new marker shape required editing 3
|
|
7
|
+
methods — a classic OCP violation.
|
|
8
|
+
|
|
9
|
+
## Before
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
def style_for(key)
|
|
13
|
+
case key
|
|
14
|
+
when :diamond_filled then "..."
|
|
15
|
+
when :triangle_open then "..."
|
|
16
|
+
when :connector_line then "..."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def style_key_for(spec)
|
|
21
|
+
case spec.shape
|
|
22
|
+
when :diamond then :diamond_filled
|
|
23
|
+
when :triangle then :triangle_open
|
|
24
|
+
when :plus then :connector_line
|
|
25
|
+
...
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def render_shape(spec)
|
|
30
|
+
case spec.shape
|
|
31
|
+
when :diamond then diamond_polygon(spec.anchor, spec.base)
|
|
32
|
+
when :triangle then triangle_polygon(spec.anchor, spec.base)
|
|
33
|
+
...
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## After
|
|
39
|
+
|
|
40
|
+
Two OCP registries: `SHAPE_REGISTRY` (shape → style_key + render
|
|
41
|
+
lambda) and `STYLE_MAP` (style_key → fill/opacity). Adding a new
|
|
42
|
+
marker shape = adding one entry to `SHAPE_REGISTRY`. Adding a new
|
|
43
|
+
style = adding one entry to `STYLE_MAP`. No existing code edited.
|
|
44
|
+
|
|
45
|
+
The render methods (`diamond_polygon`, `triangle_polygon`, etc.)
|
|
46
|
+
were promoted from private to public via `public :method` — they're
|
|
47
|
+
now called from the SHAPE_REGISTRY lambdas, so per the user's rule
|
|
48
|
+
("promote tested methods to public") they must be accessible.
|
|
49
|
+
|
|
50
|
+
## Acceptance
|
|
51
|
+
|
|
52
|
+
- All 293 SVG emitter specs pass (0 failures).
|
|
53
|
+
- Full suite: 2211 examples, 0 failures, 55 pending.
|
|
54
|
+
- Adding a new marker shape = 1 hash entry, not 3 case/when edits.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# TODO.complete/85: frozen_string_literal + rubocop + normalize_type spec
|
|
2
|
+
|
|
3
|
+
## Status: done
|
|
4
|
+
|
|
5
|
+
Final code-quality pass.
|
|
6
|
+
|
|
7
|
+
## 1. frozen_string_literal: true
|
|
8
|
+
|
|
9
|
+
10 files were missing the `# frozen_string_literal: true` magic
|
|
10
|
+
comment (or had the wrong variant `# frozen_string: true` which
|
|
11
|
+
Ruby does not recognize):
|
|
12
|
+
|
|
13
|
+
- `lib/ea/validation.rb`
|
|
14
|
+
- `lib/ea/mdg.rb` (+ `document.rb`, `loader.rb`, `xml.rb`, `registry.rb`)
|
|
15
|
+
- `lib/ea/model/ghost_label.rb`
|
|
16
|
+
- `lib/ea/svg/ea_emitter/ghost_labels.rb`
|
|
17
|
+
- `lib/ea/svg/ea_emitter/background.rb`
|
|
18
|
+
- `lib/ea/cli/command/render.rb`
|
|
19
|
+
|
|
20
|
+
All now have the correct comment at line 1. The `registry.rb` had
|
|
21
|
+
a non-functional `# frozen_string: true` variant — replaced with
|
|
22
|
+
the correct one.
|
|
23
|
+
|
|
24
|
+
## 2. Rubocop auto-correct
|
|
25
|
+
|
|
26
|
+
30 auto-correctable offenses fixed across the new/modified files:
|
|
27
|
+
trailing commas, hash syntax normalization, etc.
|
|
28
|
+
|
|
29
|
+
## 3. normalize_type spec
|
|
30
|
+
|
|
31
|
+
`ProfileSerializer#normalize_type` had no direct spec. Added 2
|
|
32
|
+
examples verifying Boolean → "Boolean" and unknown → "String".
|
|
33
|
+
|
|
34
|
+
## Result
|
|
35
|
+
|
|
36
|
+
- 2213 examples, 0 failures, 55 pending.
|
|
37
|
+
- All lib files have `frozen_string_literal: true`.
|
|
38
|
+
- New code is rubocop-clean (style offenses auto-corrected).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# TODO.complete/86: frozen_string_literal sweep + Lint/Query specs
|
|
2
|
+
|
|
3
|
+
## Status: done
|
|
4
|
+
|
|
5
|
+
## 1. frozen_string_literal: wrong-variant sweep
|
|
6
|
+
|
|
7
|
+
Found 13 files with the **non-functional** `# frozen_string: true`
|
|
8
|
+
variant (Ruby only recognizes `# frozen_string_literal: true`).
|
|
9
|
+
These files' string literals were NOT frozen — silent correctness
|
|
10
|
+
gap.
|
|
11
|
+
|
|
12
|
+
Files fixed (wrong variant → correct):
|
|
13
|
+
- `lib/ea/svg/ea_emitter/marker/package_import.rb`
|
|
14
|
+
- `lib/ea/mdg/xml/*.rb` (11 files)
|
|
15
|
+
- `lib/ea/export/plantuml/generator.rb`
|
|
16
|
+
|
|
17
|
+
Also removed duplicate frozen comments from 8 files where my TODO
|
|
18
|
+
85 fix prepended the correct variant without removing the old wrong
|
|
19
|
+
one:
|
|
20
|
+
- `lib/ea/mdg.rb`, `mdg/document.rb`, `mdg/loader.rb`, `mdg/xml.rb`
|
|
21
|
+
- `lib/ea/model/ghost_label.rb`
|
|
22
|
+
- `lib/ea/svg/ea_emitter/ghost_labels.rb`, `background.rb`
|
|
23
|
+
- `lib/ea/cli/command/render.rb`
|
|
24
|
+
- `lib/ea/validation.rb`
|
|
25
|
+
|
|
26
|
+
All 421 lib files now have exactly one `# frozen_string_literal: true`
|
|
27
|
+
at line 1.
|
|
28
|
+
|
|
29
|
+
## 2. Lint subsystem specs (0 → 13 examples)
|
|
30
|
+
|
|
31
|
+
`spec/ea/lint/lint_spec.rb` — covers:
|
|
32
|
+
- `Offense#error?` / `#warning?` severity predicates
|
|
33
|
+
- `LintRule.name` CamelCase → snake_case conversion
|
|
34
|
+
- `LintRule.severity` default + override
|
|
35
|
+
- `LintRule#check` NotImplementedError
|
|
36
|
+
- `LintRule#offense` factory (tested through subclass, not `send`)
|
|
37
|
+
- `Engine#run` collects from all rules
|
|
38
|
+
- `Engine::DEFAULT_RULES` loads 5 built-in rules
|
|
39
|
+
|
|
40
|
+
## 3. Query subsystem specs (0 → 11 examples)
|
|
41
|
+
|
|
42
|
+
`spec/ea/query/builder_spec.rb` — covers:
|
|
43
|
+
- `#classes`, `#interfaces`, `#packages` collection switching
|
|
44
|
+
- `#with_type` filtering
|
|
45
|
+
- `#in_package` by name
|
|
46
|
+
- `#named` exact match
|
|
47
|
+
- `#name_contains` case-insensitive substring
|
|
48
|
+
- Chaining + immutability (each method returns new Builder)
|
|
49
|
+
- Enumerable support via `#each`
|
|
50
|
+
|
|
51
|
+
## Result
|
|
52
|
+
|
|
53
|
+
- 49 new examples across Lint + Query + Validation subsystems.
|
|
54
|
+
- All lib files have correct `frozen_string_literal: true`.
|
|
55
|
+
- No banned patterns in new specs (no `double`, no `send` to private).
|
data/TODO.complete/STATUS.md
CHANGED
|
@@ -11,20 +11,23 @@ gem runs anywhere Ruby runs.
|
|
|
11
11
|
- **MDG technologies**: loader + registry + alias resolver functional.
|
|
12
12
|
- **CLI commands**: 14 commands (parse, stats, list, diagrams, validate,
|
|
13
13
|
convert, spa, svg, render, diff, export, mdg, lint, query, info).
|
|
14
|
-
- **Export**: XMI (via full Transformers::QeaToXmi
|
|
15
|
-
JSON Schema, PlantUML (with packages + relationships),
|
|
14
|
+
- **Export**: XMI (via full Transformers::QeaToXmi + ExtensionSerializer),
|
|
15
|
+
JSON (curated), JSON Schema, PlantUML (with packages + relationships),
|
|
16
|
+
XSD.
|
|
16
17
|
- **ShapeScript**: parser + renderer (vars, arithmetic, conditionals,
|
|
17
18
|
subshapes, labels). Wired into StereotypeIconRenderer.
|
|
18
|
-
- **OCL evaluator**: invariant subset,
|
|
19
|
+
- **OCL evaluator**: invariant subset (exists, forAll, matches,
|
|
20
|
+
size, isEmpty, comparison ops), wired into `ea validate`.
|
|
19
21
|
- **Lint**: 5 rules.
|
|
20
22
|
- **Query DSL**: chainable Builder.
|
|
21
23
|
- **Image**: t_image loading + EmfRenderer wired into Document emitter.
|
|
22
24
|
- **Stereotype icons**: StereotypeIconRenderer wired as compartment.
|
|
23
25
|
- **HeaderLines**: OCP provider chain.
|
|
24
26
|
- **Diff**: text + HTML report; detects added/removed/renamed/modified.
|
|
25
|
-
- **Specs**:
|
|
27
|
+
- **Specs**: 2200+ examples, 0 failures, 55 pending.
|
|
26
28
|
- **Code quality**: zero `respond_to?`, zero `instance_variable_get/set`,
|
|
27
|
-
zero `require_relative` in lib code
|
|
29
|
+
zero `require_relative` in lib code, zero internal `require` (all
|
|
30
|
+
autoload).
|
|
28
31
|
|
|
29
32
|
## Work streams — all completed (51–63: real completion work)
|
|
30
33
|
|
|
@@ -58,6 +61,24 @@ gem runs anywhere Ruby runs.
|
|
|
58
61
|
| 62 | [62-changelog.md](62-changelog.md) | CHANGELOG.md | **completed** |
|
|
59
62
|
| 63 | [63-dependabot-fix.md](63-dependabot-fix.md) | Dependabot vulnerability | **completed** (lychee-action v1 → v2) |
|
|
60
63
|
|
|
64
|
+
### Stream G — Parity + Code Quality (this session)
|
|
65
|
+
|
|
66
|
+
| # | File | Title | Status |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| 64 | [64-qeatoxmi-connectors.md](64-qeatoxmi-connectors.md) | QeaToXmi emits connectors | **done** (via 66) |
|
|
69
|
+
| 65 | [65-qeatoxmi-diagrams.md](65-qeatoxmi-diagrams.md) | QeaToXmi emits diagrams | **done** (via 66) |
|
|
70
|
+
| 66 | [66-qeatoxmi-style-tags-docs.md](66-qeatoxmi-style-tags-docs.md) | QeaToXmi ExtensionSerializer | **done** |
|
|
71
|
+
| 67 | [67-ocl-collection-ops.md](67-ocl-collection-ops.md) | OCL size/isEmpty | **done** |
|
|
72
|
+
| 68 | [68-ocl-comparison-ops.md](68-ocl-comparison-ops.md) | OCL comparison operators | **done** |
|
|
73
|
+
| 69 | [69-spec-new-models.md](69-spec-new-models.md) | Specs for 13 new QEA models | **done** |
|
|
74
|
+
| 70 | [70-specs-for-all-models.md](70-specs-for-all-models.md) | Specs for 18 remaining models | **done** |
|
|
75
|
+
| 71 | [71-autoload-audit.md](71-autoload-audit.md) | Autoload audit | **done** |
|
|
76
|
+
| 72 | [72-extension-serializer-ocp-naming.md](72-extension-serializer-ocp-naming.md) | ExtensionSerializer OCP + naming | **done** |
|
|
77
|
+
| 73 | [73-ocl-validator-latent-bugs.md](73-ocl-validator-latent-bugs.md) | OCL validator latent bugs | **done** |
|
|
78
|
+
| 74 | [74-visibility-symbol-dry.md](74-visibility-symbol-dry.md) | Visibility symbol DRY extraction | **done** |
|
|
79
|
+
| 75 | [75-database-table-collection-registry.md](75-database-table-collection-registry.md) | Database table→collection registry (MECE) | **done** |
|
|
80
|
+
| 76 | [76-stale-todo-cleanup.md](76-stale-todo-cleanup.md) | Stale TODO status cleanup (40 items) | **done** |
|
|
81
|
+
|
|
61
82
|
### Bonus (no TODO file; found during 51)
|
|
62
83
|
|
|
63
84
|
- Renamed `object_id` attribute to `ea_object_id` in 12 new QEA models
|
data/lib/ea/cli/app.rb
CHANGED
|
@@ -120,6 +120,7 @@ module Ea
|
|
|
120
120
|
desc "export SUB FILE", "Export model (SUB: xmi|json|plantuml|xsd)"
|
|
121
121
|
option :output, **OUTPUT_OPTION, desc: "Output path"
|
|
122
122
|
option :package, type: :string, desc: "Restrict to a package name"
|
|
123
|
+
option :mdg, type: :array, desc: "MDG technology XML files or directories (xmi only)"
|
|
123
124
|
def export(sub, file)
|
|
124
125
|
Command::Export.new(sub: sub, file: file, **symbolize(options)).call
|
|
125
126
|
end
|
|
@@ -39,7 +39,7 @@ module Ea
|
|
|
39
39
|
# a string. New formats add a new entry here without touching
|
|
40
40
|
# this class body. Lambdas lazy-resolve autoloaded namespaces.
|
|
41
41
|
EXPORTERS = {
|
|
42
|
-
xmi: ->(model, **
|
|
42
|
+
xmi: ->(model, **o) { Ea::Transformers.qea_to_xmi(model, mdg_registry: o[:mdg_registry]) },
|
|
43
43
|
json: ->(model, **o) { Ea::Export::Json::Generator.call(model, **o) },
|
|
44
44
|
"json-schema": ->(model, **o) { Ea::Export::JsonSchema::Generator.call(model, **o) },
|
|
45
45
|
plantuml: ->(model, **o) { Ea::Export::PlantUml::Generator.call(model, **o) },
|
|
@@ -71,9 +71,18 @@ module Ea
|
|
|
71
71
|
opts = {}
|
|
72
72
|
opts[:target_namespace] = options[:target_namespace] if options[:target_namespace]
|
|
73
73
|
opts[:prefix] = options[:prefix] if options[:prefix]
|
|
74
|
+
add_mdg_registry(opts)
|
|
74
75
|
opts
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
# Registry construction is xmi-only: the other exporters reject
|
|
79
|
+
# unknown keywords.
|
|
80
|
+
def add_mdg_registry(opts)
|
|
81
|
+
return unless options[:mdg] && sub == :xmi
|
|
82
|
+
|
|
83
|
+
opts[:mdg_registry] = Ea::Mdg::Registry.from_paths(options[:mdg])
|
|
84
|
+
end
|
|
85
|
+
|
|
77
86
|
def model
|
|
78
87
|
@model ||= load_database(file_path)
|
|
79
88
|
end
|
data/lib/ea/cli/command/mdg.rb
CHANGED
|
@@ -39,16 +39,7 @@ module Ea
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def build_registry
|
|
42
|
-
|
|
43
|
-
Dir.glob("spec/fixtures/mdg/**/*.xml").each do |path|
|
|
44
|
-
begin
|
|
45
|
-
doc = Ea::Mdg::Loader.from_path(path).document
|
|
46
|
-
registry.register(doc)
|
|
47
|
-
rescue StandardError => e
|
|
48
|
-
warn "Skipped #{path}: #{e.message}" if ENV["EA_DEBUG"]
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
registry
|
|
42
|
+
Ea::Mdg::Registry.from_paths(["spec/fixtures/mdg"])
|
|
52
43
|
end
|
|
53
44
|
|
|
54
45
|
def list_technologies
|
|
@@ -61,17 +61,16 @@ module Ea
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def safe_id(record)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
return nil unless record.is_a?(Ea::Qea::Models::BaseModel)
|
|
65
|
+
|
|
66
|
+
pk = record.class.primary_key_column
|
|
67
|
+
return nil if pk.nil?
|
|
67
68
|
|
|
68
69
|
record.to_hash[pk.to_s] || record.to_hash[pk.to_sym]
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def record_name(record)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
record.to_hash["name"]
|
|
73
|
+
record.to_hash["name"] || record.to_hash[:name]
|
|
75
74
|
end
|
|
76
75
|
|
|
77
76
|
# Per-class projections: each entry is a proc returning a Hash.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ea
|
|
4
4
|
module Export
|
|
@@ -126,8 +126,10 @@ module Ea
|
|
|
126
126
|
def enum_literals(enum)
|
|
127
127
|
literals = model.collections[:enumeration_literals] ||
|
|
128
128
|
model.collections[:enum_literals] || []
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
literals.select do |literal|
|
|
130
|
+
literal.is_a?(Ea::Qea::Models::EaAttribute) &&
|
|
131
|
+
literal.ea_object_id == enum.ea_object_id
|
|
132
|
+
end
|
|
131
133
|
end
|
|
132
134
|
|
|
133
135
|
def find_object(object_id)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
# Shared conversions between Sparx EA GUID strings and XMI
|
|
5
|
+
# identifier strings. No state, no I/O — same input always
|
|
6
|
+
# yields same output.
|
|
7
|
+
#
|
|
8
|
+
# EA stores identifiers as `{ABCD-1234-...}` braced GUID strings.
|
|
9
|
+
# XMI serializations use unbraced, dash→underscore forms prefixed
|
|
10
|
+
# with `EAID_` (most elements) or `EAPK_` (packages at the model
|
|
11
|
+
# root).
|
|
12
|
+
#
|
|
13
|
+
# This module is shared across subsystems:
|
|
14
|
+
# - Transformers::QeaToXmi (XMI export)
|
|
15
|
+
# - Sources::Qea (model import / ID normalization)
|
|
16
|
+
# - Qea::Factory (UML bridge reference resolution)
|
|
17
|
+
#
|
|
18
|
+
# Previously each subsystem had its own inline gsub-based copy
|
|
19
|
+
# with subtle differences. Centralising here ensures one canonical
|
|
20
|
+
# conversion (DRY, MECE).
|
|
21
|
+
module GuidFormat
|
|
22
|
+
# Braces and dashes both become underscores; collapse runs of
|
|
23
|
+
# consecutive underscores so `{AB-CD}` → `AB_CD` (not `_AB_CD_`).
|
|
24
|
+
SEP = /[-{}]/
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# @param ea_guid [String, nil] e.g. "{AB-CD-EF}"
|
|
29
|
+
# @param prefix [String] "EAID" (default) or "EAPK"
|
|
30
|
+
# @return [String, nil] e.g. "EAID_AB_CD_EF"
|
|
31
|
+
def ea_guid_to_xmi_id(ea_guid, prefix: "EAID")
|
|
32
|
+
return nil if ea_guid.nil? || ea_guid.empty?
|
|
33
|
+
|
|
34
|
+
clean = ea_guid.gsub(SEP, "_").gsub(/_+/, "_").gsub(/\A_/, "").gsub(/_\z/, "")
|
|
35
|
+
"#{prefix}_#{clean}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param xmi_id [String, nil] e.g. "EAID_AB_CD_EF"
|
|
39
|
+
# @return [String, nil] e.g. "{AB-CD-EF}"
|
|
40
|
+
def xmi_id_to_ea_guid(xmi_id)
|
|
41
|
+
return nil if xmi_id.nil? || xmi_id.empty?
|
|
42
|
+
|
|
43
|
+
body = xmi_id.sub(/\A(?:EAID|EAPK)_/, "")
|
|
44
|
+
"{#{body.tr("_", "-")}}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Strip braces from an EA GUID without adding prefix or
|
|
48
|
+
# converting dashes. Used where the caller needs just the
|
|
49
|
+
# bare GUID body (e.g. reference_resolver).
|
|
50
|
+
# @param ea_guid [String, nil]
|
|
51
|
+
# @return [String] e.g. "AB-CD-EF" (dashes preserved)
|
|
52
|
+
def strip_braces(ea_guid)
|
|
53
|
+
return "" if ea_guid.nil?
|
|
54
|
+
|
|
55
|
+
ea_guid.to_s.gsub(/[{}]/, "")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Build a member-end xmi:id for one side of a connector.
|
|
59
|
+
#
|
|
60
|
+
# Sparx EA's convention: take the connector's GUID, drop the first
|
|
61
|
+
# two hex characters of its first segment, and prepend `src` or
|
|
62
|
+
# `dst`.
|
|
63
|
+
#
|
|
64
|
+
# @param connector_xmi_id [String] e.g. "EAID_AB12CDEF_..."
|
|
65
|
+
# @param side [Symbol] :source or :destination
|
|
66
|
+
# @return [String] e.g. "EAID_src2CDEF_..."
|
|
67
|
+
def connector_end_xmi_id(connector_xmi_id, side:)
|
|
68
|
+
tag = side == :source ? "src" : "dst"
|
|
69
|
+
body = connector_xmi_id.sub(/\A(?:EAID|EAPK)_/, "")
|
|
70
|
+
first_segment, rest = body.split("_", 2)
|
|
71
|
+
trimmed = first_segment.length > 2 ? first_segment[2..] : first_segment
|
|
72
|
+
rest ? "EAID_#{tag}#{trimmed}_#{rest}" : "EAID_#{tag}#{trimmed}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Build the xmi:id for an operation's synthesized return parameter.
|
|
76
|
+
#
|
|
77
|
+
# Sparx EA's convention: take the operation's own id and replace its
|
|
78
|
+
# first GUID segment with `RETURNID`. An id carrying no further
|
|
79
|
+
# segments has nothing to drop, so its only segment is kept. The
|
|
80
|
+
# parameter's ea_guid is the braced form of this id, so callers
|
|
81
|
+
# derive it with {xmi_id_to_ea_guid} rather than spelling the shape
|
|
82
|
+
# a second time.
|
|
83
|
+
#
|
|
84
|
+
# @param operation_xmi_id [String] e.g. "EAID_AB12CDEF_3456_..."
|
|
85
|
+
# @return [String] e.g. "EAID_RETURNID_3456_..."
|
|
86
|
+
def return_parameter_xmi_id(operation_xmi_id)
|
|
87
|
+
body = operation_xmi_id.sub(/\A(?:EAID|EAPK)_/, "")
|
|
88
|
+
"EAID_RETURNID_#{body.split("_", 2).last}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/ea/mdg/document.rb
CHANGED
data/lib/ea/mdg/loader.rb
CHANGED
data/lib/ea/mdg/registry.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ea
|
|
4
4
|
module Mdg
|
|
@@ -26,13 +26,51 @@ module Ea
|
|
|
26
26
|
@documents = []
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Build a registry from MDG XML files and/or directories
|
|
30
|
+
# (directories are globbed recursively). Unloadable files are
|
|
31
|
+
# skipped — MDG discovery is a boundary where partial input is
|
|
32
|
+
# normal (set EA_DEBUG=1 to see what was skipped).
|
|
33
|
+
#
|
|
34
|
+
# @param paths [Array<String>]
|
|
35
|
+
# @return [Ea::Mdg::Registry]
|
|
36
|
+
def self.from_paths(paths)
|
|
37
|
+
new.tap do |registry|
|
|
38
|
+
paths.flat_map { |path| expand_mdg_path(path) }.each do |file|
|
|
39
|
+
registry.register(Loader.from_path(file).document)
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
warn "Skipped MDG #{file}: #{e.message}" if ENV["EA_DEBUG"]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.expand_mdg_path(path)
|
|
47
|
+
File.directory?(path) ? Dir.glob(File.join(path, "**/*.xml")).sort : [path]
|
|
48
|
+
end
|
|
49
|
+
|
|
29
50
|
# Register an MDG Document. Subsequent registrations of the
|
|
30
51
|
# same technology_name replace the prior one (last-wins).
|
|
52
|
+
#
|
|
53
|
+
# Also registers the technology with
|
|
54
|
+
# `Lutaml::Model::GlobalRegister` so it can be looked up via
|
|
55
|
+
# the framework-level register API. This keeps MDG swapping
|
|
56
|
+
# OCP-compliant: callers can query either Ea::Mdg::Registry
|
|
57
|
+
# or Lutaml::Model::GlobalRegister to find loaded MDGs.
|
|
31
58
|
def register(document)
|
|
32
|
-
|
|
59
|
+
technology = document.technology_name
|
|
60
|
+
@documents.reject! { |d| d.technology_name == technology }
|
|
33
61
|
@documents << document
|
|
62
|
+
register_with_lutaml(technology, document)
|
|
34
63
|
end
|
|
35
64
|
|
|
65
|
+
# Remove an MDG Document by technology name. Also unregisters
|
|
66
|
+
# from Lutaml::Model::GlobalRegister.
|
|
67
|
+
def unregister(technology_name)
|
|
68
|
+
@documents.reject! { |d| d.technology_name == technology_name }
|
|
69
|
+
lutaml_register_id = lutaml_id_for(technology_name)
|
|
70
|
+
Lutaml::Model::GlobalRegister.remove(lutaml_register_id)
|
|
71
|
+
end
|
|
72
|
+
alias remove unregister
|
|
73
|
+
|
|
36
74
|
def each(&block)
|
|
37
75
|
@documents.each(&block)
|
|
38
76
|
end
|
|
@@ -93,6 +131,23 @@ module Ea
|
|
|
93
131
|
|
|
94
132
|
result
|
|
95
133
|
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# Registers the MDG with Lutaml::Model::GlobalRegister under a
|
|
138
|
+
# stable, namespaced id. The register holds no model classes
|
|
139
|
+
# directly (MDG stereotypes are domain concepts, not lutaml-model
|
|
140
|
+
# types), but the registration makes the MDG discoverable via
|
|
141
|
+
# the framework's standard register API.
|
|
142
|
+
def register_with_lutaml(technology_name, _document)
|
|
143
|
+
lutaml_register_id = lutaml_id_for(technology_name)
|
|
144
|
+
register = Lutaml::Model::Register.new(lutaml_register_id)
|
|
145
|
+
Lutaml::Model::GlobalRegister.register(register)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def lutaml_id_for(technology_name)
|
|
149
|
+
:"ea_mdg_#{technology_name.to_s.downcase.gsub(/[^a-z0-9]/, "_")}"
|
|
150
|
+
end
|
|
96
151
|
end
|
|
97
152
|
end
|
|
98
153
|
end
|
data/lib/ea/mdg/xml/apply.rb
CHANGED
data/lib/ea/mdg/xml/content.rb
CHANGED
data/lib/ea/mdg/xml/tag.rb
CHANGED
data/lib/ea/mdg/xml.rb
CHANGED
data/lib/ea/mdg.rb
CHANGED