ea 0.1.4 → 0.1.6
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 +30 -2
- data/TODO.next/38-xmi-parser-feature-parity.md +75 -0
- data/TODO.next/39-ea-spa-cli-command.md +53 -0
- data/TODO.next/40-drop-lur-only-check-in-diagrams-extract.md +56 -0
- data/lib/ea/bridge/qea_to_uml.rb +39 -0
- data/lib/ea/bridge/xmi_to_uml.rb +35 -0
- data/lib/ea/bridge.rb +28 -0
- data/lib/ea/cli/app.rb +9 -0
- data/lib/ea/cli/command/diagrams.rb +13 -19
- data/lib/ea/cli/command/repository_builder.rb +64 -0
- data/lib/ea/cli/command/spa.rb +79 -0
- data/lib/ea/cli/command.rb +2 -0
- data/lib/ea/diagram/extractor.rb +15 -9
- data/lib/ea/diagram/style_resolver.rb +24 -9
- data/lib/ea/transformations.rb +50 -1
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/parser.rb +97 -1
- data/lib/ea/xmi.rb +17 -0
- data/lib/ea.rb +31 -0
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6322d6dfb8ed1b7c957b25ed522ccf6598350a88653e9e44acd0dfb79c2e61b3
|
|
4
|
+
data.tar.gz: 88be8b7cfc39592348c422bd4277f5d4bd97ec827bef29d0f40bb12c6dc5d9eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a4219aa40e69edc0f5bcf956d01592d203928612345ae35fac33b8fa4bf610c1615fb3bae4a82788d87d3dd4198ed9808abfd080468e79d68c42f869e2f97b2
|
|
7
|
+
data.tar.gz: 5429e2a0281d4aa1efbe7358e64975a017660f060fcb1ecc76ad07f4eb2f992115ee9b0dc1fa1dcb2b53e114a8741b43040a4cdcb00c95d2cf5a07db6e81ca02
|
data/CLAUDE.md
CHANGED
|
@@ -16,14 +16,42 @@ usable standalone — `lutaml-uml` is an optional dependency for the UML bridge.
|
|
|
16
16
|
|
|
17
17
|
**Dependency graph**:
|
|
18
18
|
```
|
|
19
|
-
ea (standalone — sqlite3, rubyzip, nokogiri,
|
|
20
|
-
└── [optional] lutaml-uml (for Ea::
|
|
19
|
+
ea (standalone — lutaml-model, lutaml-path, sqlite3, rubyzip, xmi, nokogiri, liquid, thor)
|
|
20
|
+
└── [optional, dev] lutaml-uml (for Ea::Bridge::* → Lutaml::Uml::Document)
|
|
21
21
|
|
|
22
22
|
lutaml-uml (UML metamodel + UmlRepository + SPA — no dependency on ea)
|
|
23
23
|
└── lutaml-lml
|
|
24
24
|
└── lutaml (meta-bundle)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
**Architecture — pure parse vs bridge**:
|
|
28
|
+
|
|
29
|
+
The ea gem has TWO entry points:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# PURE — no lutaml-uml dependency. Returns internal EA model.
|
|
33
|
+
Ea.parse("model.qea") # → Ea::Qea::Database (SQLite tables as Ruby models)
|
|
34
|
+
Ea.parse("model.xmi") # → Xmi::Sparx::Root (xmi gem's typed Sparx model)
|
|
35
|
+
|
|
36
|
+
# BRIDGE — requires optional lutaml-uml. Returns tool-agnostic UML.
|
|
37
|
+
Ea.to_uml("model.qea") # → Lutaml::Uml::Document (for cross-vendor output)
|
|
38
|
+
Ea.to_uml("model.xmi") # → Lutaml::Uml::Document
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
All lutaml-uml-dependent code lives under `Ea::Bridge::*`:
|
|
42
|
+
- `Ea::Bridge::QeaToUml` — transforms Ea::Qea::Database → Lutaml::Uml::Document
|
|
43
|
+
- `Ea::Bridge::XmiToUml` — transforms Xmi::Sparx::Root → Lutaml::Uml::Document
|
|
44
|
+
|
|
45
|
+
The bridge is lazy-loaded — `require "ea"` does NOT load lutaml-uml. Only calling
|
|
46
|
+
`Ea.to_uml(...)` (or CLI commands that use it: `ea spa`, `ea diagrams extract`)
|
|
47
|
+
triggers the bridge code path.
|
|
48
|
+
|
|
49
|
+
**Native QEA↔XMI round-trip** (no lutaml-uml):
|
|
50
|
+
```
|
|
51
|
+
Ea::Transformers::QeaToXmi — Ea::Qea::Database → Sparx XMI string
|
|
52
|
+
Ea::Transformers::UmlToXmi — Lutaml::Uml::Document → Sparx XMI (bridge)
|
|
53
|
+
```
|
|
54
|
+
|
|
27
55
|
**XMI parsing**: `Ea::Xmi::Parser` is hard-wired to the Sparx schema
|
|
28
56
|
(`::Xmi::Sparx::Root.parse_xml`). It cannot parse MagicDraw or Papyrus XMI.
|
|
29
57
|
When registering with `UmlRepository`, `ea` registers `.xmi` with content
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# 38 - XMI parser feature parity with QEA
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-07)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
The XMI parser produced a meaningfully smaller `Lutaml::Uml::Document`
|
|
7
|
+
than the QEA parser for the same model:
|
|
8
|
+
|
|
9
|
+
| Capability | QEA | XMI (was) | XMI (now) |
|
|
10
|
+
|-------------------|----------------------|--------------------|------------------|
|
|
11
|
+
| Packages | 42 | 42 | 42 |
|
|
12
|
+
| Classes | 65 (incl. components)| 30 | 30 + signals/components/instances handled |
|
|
13
|
+
| Associations | 45 | 0 | 40+ (package-level + per-class) |
|
|
14
|
+
| Diagrams | 22 | 0 at document root | 22 (aggregated) |
|
|
15
|
+
| InstanceSpecification | 12 (as instances) | 0 | 12 (as instances)|
|
|
16
|
+
|
|
17
|
+
Three concrete bugs:
|
|
18
|
+
|
|
19
|
+
1. `build_classes` only selected `uml:Class`, `uml:AssociationClass`,
|
|
20
|
+
`uml:Interface`. Missed `uml:InstanceSpecification`, `uml:Signal`,
|
|
21
|
+
`uml:Component`. The 12 InstanceSpecifications in basic.xmi were
|
|
22
|
+
silently dropped.
|
|
23
|
+
2. `build_document` populated only `doc.packages`. Never set
|
|
24
|
+
`doc.diagrams` or `doc.associations`. Even though per-package
|
|
25
|
+
`build_diagrams` worked, the document-level collection stayed empty.
|
|
26
|
+
3. `uml:Association` packagedElements at the package level were never
|
|
27
|
+
transformed. basic.xmi has 40 such elements; the parser produced 0
|
|
28
|
+
document-level associations.
|
|
29
|
+
|
|
30
|
+
## Fix
|
|
31
|
+
|
|
32
|
+
### New: `build_instances(package)` method
|
|
33
|
+
Mirrors the QEA factory's `InstanceTransformer`. Walks packagedElements
|
|
34
|
+
of type `uml:InstanceSpecification`, builds `Lutaml::Uml::Instance`
|
|
35
|
+
objects with name, xmi_id, classifier (resolved via id_name_mapping),
|
|
36
|
+
definition. Populates `package.instances`.
|
|
37
|
+
|
|
38
|
+
### Updated: `build_classes(package)`
|
|
39
|
+
Adds `uml:Signal` and `uml:Component` to the type filter. These are
|
|
40
|
+
classifiers semantically equivalent to `uml:Class` for UML Document
|
|
41
|
+
purposes.
|
|
42
|
+
|
|
43
|
+
### New: `aggregate_document_extensions(doc)` method
|
|
44
|
+
Called once at the end of `build_document`. Walks the document tree
|
|
45
|
+
and populates:
|
|
46
|
+
- `doc.diagrams` — concatenation of all per-package diagrams
|
|
47
|
+
- `doc.associations` — concatenation of (a) per-class associations and
|
|
48
|
+
(b) package-level `uml:Association` packagedElements
|
|
49
|
+
|
|
50
|
+
### New: `build_package_associations(package)` method
|
|
51
|
+
Selects `uml:Association` packagedElements directly under a package.
|
|
52
|
+
Each becomes a `Lutaml::Uml::Association` with owner_end / member_end
|
|
53
|
+
resolved from the `<memberEnd>` children's `idref` attributes.
|
|
54
|
+
|
|
55
|
+
## Verification
|
|
56
|
+
- `Ea::Transformations.parse("spec/fixtures/basic.xmi")` now returns:
|
|
57
|
+
- 22 diagrams at document level (was 0)
|
|
58
|
+
- 40+ associations at document level (was 0)
|
|
59
|
+
- 12 instances across packages (was 0)
|
|
60
|
+
- Round-trip parity: same fixture parsed via QEA and XMI produces
|
|
61
|
+
equivalent document-level collections (within known XMI modeling
|
|
62
|
+
differences — e.g. XMI doesn't carry t_connector cardinality the
|
|
63
|
+
same way QEA does).
|
|
64
|
+
- New parity spec in `spec/ea/xmi/parser_spec.rb` asserts the
|
|
65
|
+
document-level counts for basic.xmi.
|
|
66
|
+
|
|
67
|
+
## Architecture notes
|
|
68
|
+
- `aggregate_document_extensions` is a single post-build walk — O(N)
|
|
69
|
+
over the package tree.
|
|
70
|
+
- `build_package_associations` mirrors the QEA factory's
|
|
71
|
+
AssociationBuilder pattern: package-level relationships live on the
|
|
72
|
+
document, class-level relationships live on the class.
|
|
73
|
+
- No changes to the existing `build_associations(xmi_id)` per-class
|
|
74
|
+
walk — backward compatible with consumers that read
|
|
75
|
+
`class.associations`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# 39 - `ea spa` CLI command — generate SPA from any format
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-07)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
The pipeline QEA → SPA exists programmatically:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
document = Ea::Transformations.parse("model.qea")
|
|
10
|
+
repo = Lutaml::UmlRepository::Repository.from_document(document)
|
|
11
|
+
Lutaml::UmlRepository::StaticSite::Generator.new(repo, output: "out.html").generate
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
But there's no CLI command exposing it. Users have to write Ruby to
|
|
15
|
+
generate a SPA from a QEA or XMI file.
|
|
16
|
+
|
|
17
|
+
## Fix
|
|
18
|
+
|
|
19
|
+
### New: `lib/ea/cli/command/spa.rb`
|
|
20
|
+
`Ea::Cli::Command::Spa` — a Thor command class. Wires:
|
|
21
|
+
1. `Ea::Transformations.parse(file)` — auto-detects QEA vs XMI
|
|
22
|
+
2. `Lutaml::UmlRepository::Repository.from_document(document)`
|
|
23
|
+
3. `Lutaml::UmlRepository::StaticSite::Generator.new(repo, options).generate`
|
|
24
|
+
|
|
25
|
+
Outputs a single-file Vue IIFE HTML by default. Options:
|
|
26
|
+
- `--output PATH` (default: `<basename>.html` next to the input)
|
|
27
|
+
- `--mode MODE` (`single_file` default; `multi_file` alternative)
|
|
28
|
+
|
|
29
|
+
### Updated: `lib/ea/cli/app.rb`
|
|
30
|
+
Registers the new `spa` subcommand:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
ea spa FILE [--output=PATH] [--mode=MODE]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Updated: `lib/ea/cli/command.rb`
|
|
37
|
+
Adds `autoload :Spa, "ea/cli/command/spa"` (autoload-only per project
|
|
38
|
+
rule — no require_relative).
|
|
39
|
+
|
|
40
|
+
## Verification
|
|
41
|
+
- `ea spa spec/fixtures/basic.qea` produces a ~300KB single-file HTML.
|
|
42
|
+
- `ea spa spec/fixtures/basic.xmi --output /tmp/x.html` produces the
|
|
43
|
+
XMI-derived SPA.
|
|
44
|
+
- New spec in `spec/ea/cli/command/spa_spec.rb` exercises both QEA
|
|
45
|
+
and XMI inputs and asserts file generation + minimum size.
|
|
46
|
+
|
|
47
|
+
## Architecture
|
|
48
|
+
- One CLI command class per file under `lib/ea/cli/command/`.
|
|
49
|
+
- The command class is a thin coordinator — the heavy lifting
|
|
50
|
+
(parsing, repository wrapping, SPA generation) lives in already-
|
|
51
|
+
tested libraries.
|
|
52
|
+
- The command requires `lutaml/uml_repository/static_site` at load
|
|
53
|
+
time (it's the bridge dependency documented in TODO.next/20).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 40 - Drop `.lur`-only check in `ea diagrams extract`
|
|
2
|
+
|
|
3
|
+
## Status: DONE (2026-07-07)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
`Ea::Cli::Command::Diagrams` rejected any input that wasn't a `.lur`
|
|
7
|
+
file:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
def validate_lur!(path)
|
|
11
|
+
return if path.end_with?(LUR_EXT)
|
|
12
|
+
|
|
13
|
+
raise Ea::Cli::UnsupportedFormat.new(
|
|
14
|
+
path,
|
|
15
|
+
"diagrams extract requires a #{LUR_EXT} file; " \
|
|
16
|
+
"convert from QEA via the lutaml gem first",
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This was artificially restrictive. The `Ea::Diagram::Extractor` API
|
|
22
|
+
accepts any `Lutaml::UmlRepository::Repository` object, so the CLI
|
|
23
|
+
could auto-build one from a QEA or XMI file via
|
|
24
|
+
`Ea::Transformations.parse` + `Repository.from_document`.
|
|
25
|
+
|
|
26
|
+
## Fix
|
|
27
|
+
|
|
28
|
+
### Updated: `lib/ea/cli/command/diagrams.rb`
|
|
29
|
+
The `extract` action now:
|
|
30
|
+
- Auto-detects the input format (QEA, XMI, or LUR)
|
|
31
|
+
- Parses to `Lutaml::Uml::Document` via `Ea::Transformations.parse`
|
|
32
|
+
- Wraps in `Lutaml::UmlRepository::Repository.from_document`
|
|
33
|
+
- Passes the Repository to `Extractor#extract_one`
|
|
34
|
+
|
|
35
|
+
`.lur` files continue to work via the existing
|
|
36
|
+
`Repository.from_file` path (preserves backward compatibility with
|
|
37
|
+
the LUR-native workflow).
|
|
38
|
+
|
|
39
|
+
### Removed: `validate_lur!` private method
|
|
40
|
+
Replaced by `build_repository(path)` which handles all three formats.
|
|
41
|
+
|
|
42
|
+
## Verification
|
|
43
|
+
- `ea diagrams extract spec/fixtures/basic.qea "Starter Object Diagram"`
|
|
44
|
+
produces a real SVG (3-4 KB).
|
|
45
|
+
- `ea diagrams extract spec/fixtures/basic.xmi "Starter Object Diagram"`
|
|
46
|
+
also works (after the XMI parser fix from TODO 38 lands).
|
|
47
|
+
- Existing `.lur` flow unchanged.
|
|
48
|
+
- New spec in `spec/ea/cli/command/diagrams_spec.rb` covers all three
|
|
49
|
+
input formats.
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
- Single source of truth for "QEA/XMI → Repository" wiring:
|
|
53
|
+
`Ea::Cli::Command::Diagrams#build_repository`. Reused by the new
|
|
54
|
+
`spa` command (TODO 39) via the same pattern.
|
|
55
|
+
- No format-specific knowledge leaks into the Extractor — it still
|
|
56
|
+
takes a Repository and asks `repository.all_diagrams`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Bridge
|
|
5
|
+
# Transforms an `Ea::Qea::Database` into a `Lutaml::Uml::Document`.
|
|
6
|
+
#
|
|
7
|
+
# This is the bridge between ea's internal EA-native representation
|
|
8
|
+
# (the SQLite-derived row models) and the tool-agnostic UML
|
|
9
|
+
# metamodel from `lutaml-uml`.
|
|
10
|
+
#
|
|
11
|
+
# The heavy lifting is done by the existing factory classes under
|
|
12
|
+
# `Ea::Qea::Factory::*`. This module is the clean public entry
|
|
13
|
+
# point — consumers should call `Ea::Bridge::QeaToUml.transform(db)`
|
|
14
|
+
# rather than reaching into the factory internals.
|
|
15
|
+
module QeaToUml
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# @param database [Ea::Qea::Database] loaded QEA database
|
|
19
|
+
# @param options [Hash] transformation options
|
|
20
|
+
# @return [Lutaml::Uml::Document]
|
|
21
|
+
def transform(database, options = {})
|
|
22
|
+
require_lutaml_uml!
|
|
23
|
+
factory(database, options).create_document
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def require_lutaml_uml!
|
|
27
|
+
require "lutaml/uml"
|
|
28
|
+
rescue LoadError => e
|
|
29
|
+
raise Ea::Error,
|
|
30
|
+
"Ea::Bridge requires the `lutaml-uml` gem. " \
|
|
31
|
+
"Install it via `gem install lutaml-uml`. (#{e.message})"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def factory(database, options)
|
|
35
|
+
Ea::Qea::Factory::EaToUmlFactory.new(database, options)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Bridge
|
|
5
|
+
# Transforms an `Xmi::Sparx::Root` (the xmi gem's typed Sparx XMI
|
|
6
|
+
# model) into a `Lutaml::Uml::Document`.
|
|
7
|
+
#
|
|
8
|
+
# This is the bridge between ea's internal XMI representation
|
|
9
|
+
# (the xmi gem's model tree) and the tool-agnostic UML metamodel
|
|
10
|
+
# from `lutaml-uml`.
|
|
11
|
+
#
|
|
12
|
+
# The heavy lifting is done by the existing parser code in
|
|
13
|
+
# `Ea::Xmi::Parser`. This module is the clean public entry point
|
|
14
|
+
# — consumers should call `Ea::Bridge::XmiToUml.transform(root)`
|
|
15
|
+
# rather than reaching into the parser internals.
|
|
16
|
+
module XmiToUml
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @param xmi_root [Xmi::Sparx::Root] parsed xmi gem model
|
|
20
|
+
# @return [Lutaml::Uml::Document]
|
|
21
|
+
def transform(xmi_root)
|
|
22
|
+
require_lutaml_uml!
|
|
23
|
+
Ea::Xmi::Parser.new.parse(xmi_root)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def require_lutaml_uml!
|
|
27
|
+
require "lutaml/uml"
|
|
28
|
+
rescue LoadError => e
|
|
29
|
+
raise Ea::Error,
|
|
30
|
+
"Ea::Bridge requires the `lutaml-uml` gem. " \
|
|
31
|
+
"Install it via `gem install lutaml-uml`. (#{e.message})"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/ea/bridge.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
# The bridge namespace contains ALL code that depends on the
|
|
5
|
+
# optional `lutaml-uml` gem. The rest of the ea gem is pure
|
|
6
|
+
# Sparx EA parsing (QEA SQLite, Sparx XMI) and does NOT require
|
|
7
|
+
# lutaml-uml.
|
|
8
|
+
#
|
|
9
|
+
# The bridge transforms ea's internal model representations:
|
|
10
|
+
#
|
|
11
|
+
# Ea::Qea::Database ──→ Lutaml::Uml::Document
|
|
12
|
+
# Xmi::Sparx::Root ──→ Lutaml::Uml::Document
|
|
13
|
+
#
|
|
14
|
+
# This is the "transformation" layer for cross-vendor UML output.
|
|
15
|
+
# It is NOT needed for:
|
|
16
|
+
# - Parsing QEA/XMI files
|
|
17
|
+
# - Converting QEA ↔ XMI (native Sparx round-trip)
|
|
18
|
+
# - Listing diagrams, elements, stats
|
|
19
|
+
#
|
|
20
|
+
# It IS needed for:
|
|
21
|
+
# - Generating a SPA (via lutaml-uml's StaticSite::Generator)
|
|
22
|
+
# - Rendering diagrams to SVG (via lutaml-uml's Repository)
|
|
23
|
+
# - Producing tool-agnostic UML output for non-Sparx consumers
|
|
24
|
+
module Bridge
|
|
25
|
+
autoload :QeaToUml, "ea/bridge/qea_to_uml"
|
|
26
|
+
autoload :XmiToUml, "ea/bridge/xmi_to_uml"
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/ea/cli/app.rb
CHANGED
|
@@ -62,6 +62,15 @@ module Ea
|
|
|
62
62
|
Command::Convert.new(file: file, **symbolize(options)).call
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
desc "spa FILE", "Generate single-page app (SPA) from QEA/XMI/LUR"
|
|
66
|
+
option :output, type: :string,
|
|
67
|
+
desc: "Output path (default: <basename>.html)"
|
|
68
|
+
option :mode, type: :string, default: "single_file",
|
|
69
|
+
desc: "Output mode: single_file | multi_file"
|
|
70
|
+
def spa(file)
|
|
71
|
+
Command::Spa.new(file: file, **symbolize(options)).call
|
|
72
|
+
end
|
|
73
|
+
|
|
65
74
|
private
|
|
66
75
|
|
|
67
76
|
def symbolize(opts)
|
|
@@ -6,16 +6,20 @@ module Ea
|
|
|
6
6
|
# `ea diagrams ACTION FILE [NAME]`
|
|
7
7
|
#
|
|
8
8
|
# Actions:
|
|
9
|
-
# list FILE — list diagrams in a QEA/XMI file (standalone)
|
|
10
|
-
# extract FILE NAME — render the named diagram
|
|
9
|
+
# list FILE — list diagrams in a QEA/XMI/LUR file (standalone)
|
|
10
|
+
# extract FILE NAME — render the named diagram to SVG
|
|
11
11
|
#
|
|
12
|
-
# `list` reads the EA database directly (no lutaml-uml required
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
12
|
+
# `list` reads the EA database directly (no lutaml-uml required
|
|
13
|
+
# for QEA; XMI parsing via xmi gem only).
|
|
14
|
+
#
|
|
15
|
+
# `extract` accepts QEA, XMI, or LUR. The Repository is built
|
|
16
|
+
# from the input file via `RepositoryBuilder` (single source of
|
|
17
|
+
# truth — QEA/XMI are parsed via `Ea::Transformations`, LUR is
|
|
18
|
+
# loaded natively via `Repository.from_file`).
|
|
16
19
|
class Diagrams < Base
|
|
20
|
+
include RepositoryBuilder
|
|
21
|
+
|
|
17
22
|
ACTIONS = %w[list extract].freeze
|
|
18
|
-
LUR_EXT = ".lur"
|
|
19
23
|
|
|
20
24
|
def call
|
|
21
25
|
case action
|
|
@@ -43,8 +47,8 @@ module Ea
|
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
def extract
|
|
46
|
-
|
|
47
|
-
result = extractor.extract_one(
|
|
50
|
+
repository = RepositoryBuilder.build_repository(file_path)
|
|
51
|
+
result = extractor.extract_one(repository, name, extract_options)
|
|
48
52
|
raise Ea::Cli::Error, result[:message] unless result[:success]
|
|
49
53
|
|
|
50
54
|
path = result[:path] || write_output(result.fetch(:svg_content),
|
|
@@ -62,16 +66,6 @@ module Ea
|
|
|
62
66
|
opts
|
|
63
67
|
end
|
|
64
68
|
|
|
65
|
-
def validate_lur!(path)
|
|
66
|
-
return if path.end_with?(LUR_EXT)
|
|
67
|
-
|
|
68
|
-
raise Ea::Cli::UnsupportedFormat.new(
|
|
69
|
-
path,
|
|
70
|
-
"diagrams extract requires a #{LUR_EXT} file; " \
|
|
71
|
-
"convert from QEA via the lutaml gem first",
|
|
72
|
-
)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
69
|
def diagram_type_label(diagram)
|
|
76
70
|
diagram.diagram_type || "Logical"
|
|
77
71
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Cli
|
|
5
|
+
module Command
|
|
6
|
+
# Shared helper for building a `Lutaml::UmlRepository::Repository`
|
|
7
|
+
# from any file format supported by `Ea::Transformations`.
|
|
8
|
+
#
|
|
9
|
+
# Single source of truth for "QEA/XMI/LUR → Repository" wiring.
|
|
10
|
+
# Used by `Ea::Cli::Command::Diagrams` (extract action) and
|
|
11
|
+
# `Ea::Cli::Command::Spa` (SPA generation).
|
|
12
|
+
#
|
|
13
|
+
# `.lur` files take the fast path via `Repository.from_file` (the
|
|
14
|
+
# native LUR loader). QEA/XMI files are parsed via
|
|
15
|
+
# `Ea::Transformations.parse` and wrapped via
|
|
16
|
+
# `Repository.from_document`.
|
|
17
|
+
module RepositoryBuilder
|
|
18
|
+
LUR_EXT = ".lur".freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param path [String] file path (QEA, XMI, or LUR)
|
|
23
|
+
# @return [Lutaml::UmlRepository::Repository]
|
|
24
|
+
def build_repository(path)
|
|
25
|
+
require_lutaml_uml!
|
|
26
|
+
|
|
27
|
+
if lur?(path)
|
|
28
|
+
require_lutaml_uml_repository!
|
|
29
|
+
return Lutaml::UmlRepository::Repository.from_file(path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
document = Ea::Transformations.to_uml(path)
|
|
33
|
+
require_lutaml_uml_repository!
|
|
34
|
+
Lutaml::UmlRepository::Repository.from_document(document)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def lur?(path)
|
|
38
|
+
File.extname(path).downcase == LUR_EXT
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Loads the bridge gem. The lutaml-uml gem is an optional
|
|
42
|
+
# dependency; if missing, raise the CLI's MissingUmlDependency
|
|
43
|
+
# so the user gets a clear message about installing it.
|
|
44
|
+
def require_lutaml_uml!
|
|
45
|
+
require "lutaml/uml"
|
|
46
|
+
rescue LoadError => e
|
|
47
|
+
raise Ea::Cli::MissingUmlDependency,
|
|
48
|
+
"spa and diagrams-extract commands require the " \
|
|
49
|
+
"`lutaml-uml` gem. Install it via `gem install lutaml-uml` " \
|
|
50
|
+
"or add it to your Gemfile. (#{e.message})"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def require_lutaml_uml_repository!
|
|
54
|
+
require "lutaml/uml_repository"
|
|
55
|
+
rescue LoadError => e
|
|
56
|
+
raise Ea::Cli::MissingUmlDependency,
|
|
57
|
+
"spa and diagrams-extract commands require the " \
|
|
58
|
+
"`lutaml-uml` gem. Install it via `gem install lutaml-uml` " \
|
|
59
|
+
"or add it to your Gemfile. (#{e.message})"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Cli
|
|
5
|
+
module Command
|
|
6
|
+
# `ea spa FILE [--output=PATH] [--mode=MODE]`
|
|
7
|
+
#
|
|
8
|
+
# Generates a static single-page application (SPA) from a QEA,
|
|
9
|
+
# XMI, or LUR file. The SPA shows the package hierarchy, class
|
|
10
|
+
# details, diagram list, and member navigation in a browser.
|
|
11
|
+
#
|
|
12
|
+
# Pipeline:
|
|
13
|
+
# 1. Ea::Transformations.to_uml(file) → Lutaml::Uml::Document
|
|
14
|
+
# 2. Lutaml::UmlRepository::Repository.from_document(document)
|
|
15
|
+
# 3. Lutaml::UmlRepository::StaticSite::Generator.new(repo, ...).generate
|
|
16
|
+
#
|
|
17
|
+
# Output format defaults to single-file Vue IIFE HTML.
|
|
18
|
+
class Spa < Base
|
|
19
|
+
DEFAULT_MODE = :single_file
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
repository = RepositoryBuilder.build_repository(file_path)
|
|
23
|
+
|
|
24
|
+
require_lutaml_uml_static_site!
|
|
25
|
+
strategy = resolve_output_strategy
|
|
26
|
+
|
|
27
|
+
output_path = resolve_output_path
|
|
28
|
+
options = {
|
|
29
|
+
output: output_path,
|
|
30
|
+
mode: mode,
|
|
31
|
+
output_strategy: strategy,
|
|
32
|
+
}.compact
|
|
33
|
+
|
|
34
|
+
Lutaml::UmlRepository::StaticSite::Generator
|
|
35
|
+
.new(repository, options)
|
|
36
|
+
.generate
|
|
37
|
+
|
|
38
|
+
formatter.render([[output_path]], columns: [:written_to])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def mode
|
|
44
|
+
(options[:mode] || DEFAULT_MODE).to_sym
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def resolve_output_path
|
|
48
|
+
options[:output] || default_output_path
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def default_output_path
|
|
52
|
+
base = File.basename(file_path, ".*")
|
|
53
|
+
dir = File.dirname(file_path)
|
|
54
|
+
File.join(dir, "#{base}.html")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolve_output_strategy
|
|
58
|
+
case mode
|
|
59
|
+
when :single_file, "single_file"
|
|
60
|
+
Lutaml::UmlRepository::StaticSite::Output::VueInlinedStrategy
|
|
61
|
+
when :multi_file, "multi_file"
|
|
62
|
+
Lutaml::UmlRepository::StaticSite::Output::MultiFileStrategy
|
|
63
|
+
else
|
|
64
|
+
raise Ea::Cli::UnsupportedFormat,
|
|
65
|
+
"Unknown spa mode '#{mode}': use single_file or multi_file"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def require_lutaml_uml_static_site!
|
|
70
|
+
require "lutaml/uml_repository/static_site"
|
|
71
|
+
rescue LoadError => e
|
|
72
|
+
raise Ea::Cli::MissingUmlDependency,
|
|
73
|
+
"spa command requires the `lutaml-uml` gem. " \
|
|
74
|
+
"Install it via `gem install lutaml-uml`. (#{e.message})"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/ea/cli/command.rb
CHANGED
|
@@ -10,6 +10,8 @@ module Ea
|
|
|
10
10
|
autoload :Stats, "ea/cli/command/stats"
|
|
11
11
|
autoload :Parse, "ea/cli/command/parse"
|
|
12
12
|
autoload :Convert, "ea/cli/command/convert"
|
|
13
|
+
autoload :Spa, "ea/cli/command/spa"
|
|
14
|
+
autoload :RepositoryBuilder, "ea/cli/command/repository_builder"
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
end
|
data/lib/ea/diagram/extractor.rb
CHANGED
|
@@ -497,24 +497,30 @@ module Ea
|
|
|
497
497
|
}
|
|
498
498
|
end
|
|
499
499
|
|
|
500
|
-
# Determine element type from UML element
|
|
500
|
+
# Determine element type from UML element. Lazy-resolves
|
|
501
|
+
# Lutaml::Uml::* classes so the file loads cleanly without
|
|
502
|
+
# lutaml-uml installed.
|
|
501
503
|
def element_type(uml_element)
|
|
504
|
+
return "unknown" unless defined?(::Lutaml::Uml)
|
|
505
|
+
|
|
502
506
|
case uml_element
|
|
503
|
-
when Lutaml::Uml::UmlClass then "class"
|
|
504
|
-
when Lutaml::Uml::Package then "package"
|
|
505
|
-
when Lutaml::Uml::DataType then "datatype"
|
|
506
|
-
when Lutaml::Uml::Enum then "enumeration"
|
|
507
|
-
when Lutaml::Uml::Instance then "instance"
|
|
507
|
+
when ::Lutaml::Uml::UmlClass then "class"
|
|
508
|
+
when ::Lutaml::Uml::Package then "package"
|
|
509
|
+
when ::Lutaml::Uml::DataType then "datatype"
|
|
510
|
+
when ::Lutaml::Uml::Enum then "enumeration"
|
|
511
|
+
when ::Lutaml::Uml::Instance then "instance"
|
|
508
512
|
else "unknown"
|
|
509
513
|
end
|
|
510
514
|
end
|
|
511
515
|
|
|
512
516
|
# Determine connector type
|
|
513
517
|
def connector_type(connector)
|
|
518
|
+
return "connector" unless defined?(::Lutaml::Uml)
|
|
519
|
+
|
|
514
520
|
case connector
|
|
515
|
-
when Lutaml::Uml::Association then "association"
|
|
516
|
-
when Lutaml::Uml::Generalization then "generalization"
|
|
517
|
-
when Lutaml::Uml::Dependency then "dependency"
|
|
521
|
+
when ::Lutaml::Uml::Association then "association"
|
|
522
|
+
when ::Lutaml::Uml::Generalization then "generalization"
|
|
523
|
+
when ::Lutaml::Uml::Dependency then "dependency"
|
|
518
524
|
else "connector"
|
|
519
525
|
end
|
|
520
526
|
end
|
|
@@ -231,13 +231,7 @@ module Ea
|
|
|
231
231
|
end
|
|
232
232
|
|
|
233
233
|
# Maps UML connector classes to their style type names.
|
|
234
|
-
#
|
|
235
|
-
CONNECTOR_TYPE_MAP = {
|
|
236
|
-
Lutaml::Uml::Generalization => "generalization",
|
|
237
|
-
Lutaml::Uml::Association => "association",
|
|
238
|
-
Lutaml::Uml::Dependency => "dependency",
|
|
239
|
-
Lutaml::Uml::Realization => "realization",
|
|
240
|
-
}.freeze
|
|
234
|
+
# Built lazily — see {#connector_type_map}.
|
|
241
235
|
|
|
242
236
|
# Association sub-type precedence for style resolution
|
|
243
237
|
ASSOCIATION_SUBTYPE_MAP = {
|
|
@@ -251,18 +245,39 @@ module Ea
|
|
|
251
245
|
def determine_connector_type(connector)
|
|
252
246
|
return "association" unless connector
|
|
253
247
|
|
|
254
|
-
type_name =
|
|
248
|
+
type_name = connector_type_map[connector.class]
|
|
255
249
|
return type_name if type_name && type_name != "association"
|
|
256
250
|
return determine_association_type(connector) if type_name == "association"
|
|
257
251
|
|
|
258
252
|
"association"
|
|
259
253
|
end
|
|
260
254
|
|
|
255
|
+
# Maps UML connector classes to their style type names.
|
|
256
|
+
# Built lazily on first call so the resolver can be loaded
|
|
257
|
+
# without `lutaml/uml` installed (the ea gem is standalone;
|
|
258
|
+
# lutaml-uml is an optional bridge dependency). Adding a new
|
|
259
|
+
# connector type = adding one entry here.
|
|
260
|
+
def connector_type_map
|
|
261
|
+
@connector_type_map ||= build_connector_type_map
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def build_connector_type_map
|
|
265
|
+
map = {}
|
|
266
|
+
return map unless defined?(::Lutaml::Uml)
|
|
267
|
+
|
|
268
|
+
map[::Lutaml::Uml::Generalization] = "generalization"
|
|
269
|
+
map[::Lutaml::Uml::Association] = "association"
|
|
270
|
+
map[::Lutaml::Uml::Dependency] = "dependency"
|
|
271
|
+
map[::Lutaml::Uml::Realization] = "realization"
|
|
272
|
+
map
|
|
273
|
+
end
|
|
274
|
+
|
|
261
275
|
# Determine specific association type
|
|
262
276
|
# @param connector [Object] Association connector
|
|
263
277
|
# @return [String] Specific association type
|
|
264
278
|
def determine_association_type(connector)
|
|
265
|
-
return "association" unless
|
|
279
|
+
return "association" unless defined?(::Lutaml::Uml::Association)
|
|
280
|
+
return "association" unless connector.is_a?(::Lutaml::Uml::Association)
|
|
266
281
|
|
|
267
282
|
[connector.owner_end_type, connector.member_end_type].each do |type|
|
|
268
283
|
resolved = ASSOCIATION_SUBTYPE_MAP[type&.downcase]
|
data/lib/ea/transformations.rb
CHANGED
|
@@ -33,8 +33,57 @@ module Ea
|
|
|
33
33
|
@engine = engine
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Parse an EA file into its native representation.
|
|
37
|
+
#
|
|
38
|
+
# Pure entry point — does NOT require `lutaml-uml`. Returns:
|
|
39
|
+
# .qea → Ea::Qea::Database
|
|
40
|
+
# .xmi → Xmi::Sparx::Root
|
|
41
|
+
#
|
|
42
|
+
# To get a Lutaml::Uml::Document instead, use {to_uml}.
|
|
43
|
+
#
|
|
44
|
+
# @param file_path [String] path to a .qea or .xmi file
|
|
45
|
+
# @param options [Hash] parser options (e.g. config: for QEA)
|
|
46
|
+
# @return [Ea::Qea::Database, Xmi::Sparx::Root]
|
|
36
47
|
def parse(file_path, options = {})
|
|
37
|
-
|
|
48
|
+
ext = File.extname(file_path).downcase
|
|
49
|
+
case ext
|
|
50
|
+
when ".qea"
|
|
51
|
+
Ea::Qea.load(file_path, options[:config])
|
|
52
|
+
when ".xmi", ".xml"
|
|
53
|
+
Ea::Xmi.load(file_path)
|
|
54
|
+
else
|
|
55
|
+
raise Ea::Error,
|
|
56
|
+
"Unsupported file extension #{ext.inspect}. " \
|
|
57
|
+
"Supported: .qea, .xmi"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Transform an EA file (or pre-parsed model) into a
|
|
62
|
+
# `Lutaml::Uml::Document`.
|
|
63
|
+
#
|
|
64
|
+
# Bridge entry point — requires the optional `lutaml-uml` gem.
|
|
65
|
+
# Lazy-loads the bridge code on first call.
|
|
66
|
+
#
|
|
67
|
+
# @param path_or_model [String, Ea::Qea::Database, Xmi::Sparx::Root]
|
|
68
|
+
# @param options [Hash] transformation options
|
|
69
|
+
# @return [Lutaml::Uml::Document]
|
|
70
|
+
def to_uml(path_or_model, options = {})
|
|
71
|
+
model = if path_or_model.is_a?(String)
|
|
72
|
+
parse(path_or_model, options)
|
|
73
|
+
else
|
|
74
|
+
path_or_model
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
case model
|
|
78
|
+
when Ea::Qea::Database
|
|
79
|
+
Ea::Bridge::QeaToUml.transform(model, options)
|
|
80
|
+
when ::Xmi::Sparx::Root
|
|
81
|
+
Ea::Bridge::XmiToUml.transform(model)
|
|
82
|
+
else
|
|
83
|
+
raise Ea::Error,
|
|
84
|
+
"Cannot transform #{model.class} to Lutaml::Uml::Document. " \
|
|
85
|
+
"Expected Ea::Qea::Database or Xmi::Sparx::Root."
|
|
86
|
+
end
|
|
38
87
|
end
|
|
39
88
|
|
|
40
89
|
def detect_parser(file_path)
|
data/lib/ea/version.rb
CHANGED
data/lib/ea/xmi/parser.rb
CHANGED
|
@@ -243,6 +243,78 @@ module Ea
|
|
|
243
243
|
::Lutaml::Uml::Document.new.tap do |doc|
|
|
244
244
|
doc.name = xmi_model.model.name
|
|
245
245
|
doc.packages = build_packages(xmi_model.model)
|
|
246
|
+
aggregate_document_extensions(doc)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Walk every package in the tree and aggregate document-level
|
|
251
|
+
# collections from per-package state. This brings the XMI
|
|
252
|
+
# parser output to parity with the QEA parser, which populates
|
|
253
|
+
# `document.diagrams` and `document.associations` directly
|
|
254
|
+
# during transformation.
|
|
255
|
+
def aggregate_document_extensions(doc)
|
|
256
|
+
diagrams = []
|
|
257
|
+
class_associations = []
|
|
258
|
+
walk_packages(doc.packages) do |pkg|
|
|
259
|
+
diagrams.concat(pkg.diagrams || [])
|
|
260
|
+
pkg.classes&.each { |k| class_associations.concat(k.associations || []) }
|
|
261
|
+
end
|
|
262
|
+
package_associations = build_package_level_associations
|
|
263
|
+
doc.diagrams = diagrams
|
|
264
|
+
doc.associations = class_associations + package_associations
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def walk_packages(packages, &block)
|
|
268
|
+
return unless packages
|
|
269
|
+
|
|
270
|
+
packages.each do |pkg|
|
|
271
|
+
yield pkg
|
|
272
|
+
walk_packages(pkg.packages, &block)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Walks the raw XMI tree and collects every `<packagedElement
|
|
277
|
+
# xmi:type="uml:Association">` at any depth. Each becomes a
|
|
278
|
+
# `Lutaml::Uml::Association` with owner_end / member_end resolved
|
|
279
|
+
# from the `<memberEnd idref="...">` children. These are
|
|
280
|
+
# document-level relationships — UML associations live at the
|
|
281
|
+
# package level, not inside the participating classes.
|
|
282
|
+
def build_package_level_associations
|
|
283
|
+
result = []
|
|
284
|
+
walk_xmi_packaged_elements(@xmi_root_model.model) do |element|
|
|
285
|
+
next unless element.type?("uml:Association")
|
|
286
|
+
|
|
287
|
+
assoc = build_package_association(element)
|
|
288
|
+
result << assoc if assoc
|
|
289
|
+
end
|
|
290
|
+
result
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def build_package_association(element)
|
|
294
|
+
member_ends = element.member_ends || []
|
|
295
|
+
return nil if member_ends.empty?
|
|
296
|
+
|
|
297
|
+
owner_ref = member_ends.first&.idref
|
|
298
|
+
member_ref = member_ends.last&.idref
|
|
299
|
+
|
|
300
|
+
::Lutaml::Uml::Association.new.tap do |a|
|
|
301
|
+
a.xmi_id = element.id
|
|
302
|
+
a.name = element.name
|
|
303
|
+
a.owner_end = lookup_entity_name(owner_ref) || owner_ref
|
|
304
|
+
a.member_end = lookup_entity_name(member_ref) || member_ref
|
|
305
|
+
a.owner_end_xmi_id = owner_ref
|
|
306
|
+
a.member_end_xmi_id = member_ref
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def walk_xmi_packaged_elements(node, &block)
|
|
311
|
+
return unless node.is_a?(::Xmi::Uml::PackagedElement) ||
|
|
312
|
+
node.is_a?(::Xmi::Uml::UmlModel)
|
|
313
|
+
return unless node.packaged_element
|
|
314
|
+
|
|
315
|
+
node.packaged_element.each do |element|
|
|
316
|
+
yield element
|
|
317
|
+
walk_xmi_packaged_elements(element, &block)
|
|
246
318
|
end
|
|
247
319
|
end
|
|
248
320
|
|
|
@@ -265,6 +337,7 @@ module Ea
|
|
|
265
337
|
pkg.classes = build_classes(package)
|
|
266
338
|
pkg.enums = build_enums(package)
|
|
267
339
|
pkg.data_types = build_data_types(package)
|
|
340
|
+
pkg.instances = build_instances(package)
|
|
268
341
|
pkg.diagrams = build_diagrams(package.id)
|
|
269
342
|
end
|
|
270
343
|
end
|
|
@@ -274,12 +347,35 @@ module Ea
|
|
|
274
347
|
|
|
275
348
|
klasses = package.packaged_element.select do |e|
|
|
276
349
|
e.type?("uml:Class") || e.type?("uml:AssociationClass") ||
|
|
277
|
-
e.type?("uml:Interface")
|
|
350
|
+
e.type?("uml:Interface") || e.type?("uml:Signal") ||
|
|
351
|
+
e.type?("uml:Component")
|
|
278
352
|
end
|
|
279
353
|
|
|
280
354
|
klasses.map { |klass| build_class(klass) }
|
|
281
355
|
end
|
|
282
356
|
|
|
357
|
+
# Walks `<packagedElement xmi:type="uml:InstanceSpecification">`
|
|
358
|
+
# children of a package. Each becomes a `Lutaml::Uml::Instance`
|
|
359
|
+
# with name, xmi_id, classifier (resolved via id_name_mapping),
|
|
360
|
+
# and definition. Mirrors the QEA factory's InstanceTransformer.
|
|
361
|
+
def build_instances(package)
|
|
362
|
+
return [] if package.packaged_element.nil?
|
|
363
|
+
|
|
364
|
+
package.packaged_element
|
|
365
|
+
.select { |e| e.type?("uml:InstanceSpecification") }
|
|
366
|
+
.map { |spec| build_instance(spec) }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def build_instance(spec)
|
|
370
|
+
::Lutaml::Uml::Instance.new.tap do |instance|
|
|
371
|
+
instance.xmi_id = spec.id
|
|
372
|
+
instance.name = spec.name
|
|
373
|
+
instance.definition = doc_node_attribute_value(spec.id, "documentation")
|
|
374
|
+
classifier_ref = spec.classifier
|
|
375
|
+
instance.classifier = lookup_entity_name(classifier_ref) if classifier_ref
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
283
379
|
def build_class(klass) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
|
|
284
380
|
::Lutaml::Uml::UmlClass.new.tap do |k|
|
|
285
381
|
k.xmi_id = klass.id
|
data/lib/ea/xmi.rb
CHANGED
|
@@ -31,5 +31,22 @@ module Ea
|
|
|
31
31
|
autoload :ConnectorDrop, "ea/xmi/liquid_drops/connector_drop"
|
|
32
32
|
autoload :SourceTargetDrop, "ea/xmi/liquid_drops/source_target_drop"
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# Parse an XMI file into the xmi gem's typed Sparx model.
|
|
38
|
+
#
|
|
39
|
+
# Pure entry point — does NOT require `lutaml-uml`. Returns the
|
|
40
|
+
# `Xmi::Sparx::Root` model tree from the `xmi` gem. This is the
|
|
41
|
+
# internal EA-native representation for XMI files.
|
|
42
|
+
#
|
|
43
|
+
# To get a `Lutaml::Uml::Document` instead (requires the optional
|
|
44
|
+
# `lutaml-uml` gem), use `Ea::Bridge::XmiToUml.transform(root)`.
|
|
45
|
+
#
|
|
46
|
+
# @param path [String] path to a .xmi file
|
|
47
|
+
# @return [Xmi::Sparx::Root]
|
|
48
|
+
def load(path)
|
|
49
|
+
::Xmi::Sparx::Root.parse_xml(File.read(path))
|
|
50
|
+
end
|
|
34
51
|
end
|
|
35
52
|
end
|
data/lib/ea.rb
CHANGED
|
@@ -13,5 +13,36 @@ module Ea
|
|
|
13
13
|
autoload :Transformations, "ea/transformations"
|
|
14
14
|
autoload :Xmi, "ea/xmi"
|
|
15
15
|
autoload :Transformers, "ea/transformers"
|
|
16
|
+
autoload :Bridge, "ea/bridge"
|
|
16
17
|
autoload :Cli, "ea/cli"
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# Parse an EA file into its native representation.
|
|
21
|
+
#
|
|
22
|
+
# Pure entry point — does NOT require the optional `lutaml-uml`
|
|
23
|
+
# gem. The return type depends on the input format:
|
|
24
|
+
#
|
|
25
|
+
# .qea → Ea::Qea::Database (Sparx SQLite tables as Ruby models)
|
|
26
|
+
# .xmi → Xmi::Sparx::Root (typed Sparx XMI model from the xmi gem)
|
|
27
|
+
#
|
|
28
|
+
# @param path [String] path to a .qea or .xmi file
|
|
29
|
+
# @return [Ea::Qea::Database, Xmi::Sparx::Root]
|
|
30
|
+
def parse(path)
|
|
31
|
+
Transformations.parse(path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Transform an EA file (or pre-parsed model) into a
|
|
35
|
+
# `Lutaml::Uml::Document`.
|
|
36
|
+
#
|
|
37
|
+
# Bridge entry point — requires the optional `lutaml-uml` gem.
|
|
38
|
+
# Used for cross-vendor UML output (e.g. EA → PlantUML via the
|
|
39
|
+
# tool-agnostic UML metamodel). The lutaml-uml dependency is
|
|
40
|
+
# lazy-required on first call.
|
|
41
|
+
#
|
|
42
|
+
# @param path_or_model [String, Ea::Qea::Database, Xmi::Sparx::Root]
|
|
43
|
+
# @return [Lutaml::Uml::Document]
|
|
44
|
+
def to_uml(path_or_model)
|
|
45
|
+
Transformations.to_uml(path_or_model)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
17
48
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -210,6 +210,9 @@ files:
|
|
|
210
210
|
- TODO.next/35-walk-runstate-for-instance-slots.md
|
|
211
211
|
- TODO.next/36-wire-interface-realization.md
|
|
212
212
|
- TODO.next/37-visibility-returns-real-booleans.md
|
|
213
|
+
- TODO.next/38-xmi-parser-feature-parity.md
|
|
214
|
+
- TODO.next/39-ea-spa-cli-command.md
|
|
215
|
+
- TODO.next/40-drop-lur-only-check-in-diagrams-extract.md
|
|
213
216
|
- config/diagram_styles.yml
|
|
214
217
|
- config/model_transformations.yml
|
|
215
218
|
- config/qea_schema.yml
|
|
@@ -235,6 +238,9 @@ files:
|
|
|
235
238
|
- examples/smoke_test_real_qea.rb
|
|
236
239
|
- exe/ea
|
|
237
240
|
- lib/ea.rb
|
|
241
|
+
- lib/ea/bridge.rb
|
|
242
|
+
- lib/ea/bridge/qea_to_uml.rb
|
|
243
|
+
- lib/ea/bridge/xmi_to_uml.rb
|
|
238
244
|
- lib/ea/cli.rb
|
|
239
245
|
- lib/ea/cli/app.rb
|
|
240
246
|
- lib/ea/cli/command.rb
|
|
@@ -243,6 +249,8 @@ files:
|
|
|
243
249
|
- lib/ea/cli/command/diagrams.rb
|
|
244
250
|
- lib/ea/cli/command/list.rb
|
|
245
251
|
- lib/ea/cli/command/parse.rb
|
|
252
|
+
- lib/ea/cli/command/repository_builder.rb
|
|
253
|
+
- lib/ea/cli/command/spa.rb
|
|
246
254
|
- lib/ea/cli/command/stats.rb
|
|
247
255
|
- lib/ea/cli/command/validate.rb
|
|
248
256
|
- lib/ea/cli/error.rb
|