ea 0.1.4 → 0.1.5
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.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/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/version.rb +1 -1
- data/lib/ea/xmi/parser.rb +97 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c45bd1d6024b9307f1d67422299289da426b353aa7ab7b9797d8d577b61529a9
|
|
4
|
+
data.tar.gz: afaf525893ce02307e0fe4f6ab2242b8f0d27d9b3e618db56f1d026b8a0e17ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae6d56dde262515befe5225a721fa2fca37f50e6162efd48eca52cce324508f18df1f0d916264ba531a57e3cc5a4c6a88818bd03fb33b0639129afce0e3a5aef
|
|
7
|
+
data.tar.gz: 3ef8943ff38d4ed3c5494f27b29dffc525d1311a47d1452e49f6e34f5147571175d11a26c3931c0d37344b440764584eb184e5e1433b7bbe8d8b90abd40ac8e7
|
|
@@ -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`.
|
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.parse(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.parse(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/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
|
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.5
|
|
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-07 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
|
|
@@ -243,6 +246,8 @@ files:
|
|
|
243
246
|
- lib/ea/cli/command/diagrams.rb
|
|
244
247
|
- lib/ea/cli/command/list.rb
|
|
245
248
|
- lib/ea/cli/command/parse.rb
|
|
249
|
+
- lib/ea/cli/command/repository_builder.rb
|
|
250
|
+
- lib/ea/cli/command/spa.rb
|
|
246
251
|
- lib/ea/cli/command/stats.rb
|
|
247
252
|
- lib/ea/cli/command/validate.rb
|
|
248
253
|
- lib/ea/cli/error.rb
|