ea 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,89 @@
1
+ # EA Object Type → UML Mapping
2
+
3
+ This document describes how `ea` maps Sparx EA object types (from the QEA
4
+ `t_object` table's `Object_Type` column) to UML model elements during the
5
+ `Ea::Qea.to_uml` transformation.
6
+
7
+ The mapping is implemented in
8
+ `Ea::Qea::Models::EaObject#transformer_type`, which returns a
9
+ `TransformerRegistry` key or `nil`.
10
+
11
+ ## Principles
12
+
13
+ 1. **Hub-and-spoke, not format-to-format.** Both QEA and XMI parse *into* a
14
+ common `Lutaml::Uml::Document`. There is no direct QEA↔XMI translator.
15
+ 2. **Model elements only.** Only EA object types that correspond to genuine
16
+ UML model elements are transformed. Rendering hints and tool-internal
17
+ plumbing are dropped.
18
+ 3. **Stereotype can override type.** A Class with stereotype `enumeration`
19
+ transforms as an Enum, not a Class.
20
+
21
+ ## Mapping table
22
+
23
+ | EA `Object_Type` | UML element | Transformer key | Notes |
24
+ |--------------------|---------------------|-----------------|-------------------------------------------|
25
+ | `Class` | `UmlClass` | `:class` | |
26
+ | `Interface` | `UmlClass` | `:class` | |
27
+ | `Enumeration` | `Enum` | `:enumeration` | Also: Class with `<<enumeration>>` |
28
+ | `DataType` | `DataType` | `:data_type` | |
29
+ | `Instance`/`Object`| `Instance` | `:instance` | |
30
+ | `Package` | `Package` | `:package` | From `t_package`, not `t_object` |
31
+ | `Text` | _(dropped)_ | `nil` | See below |
32
+ | `Note` | _(dropped)_ | `nil` | See below |
33
+ | `ProxyConnector` | _(dropped)_ | `nil` | See below |
34
+
35
+ Connectors (`t_connector`) are handled separately from objects and map to
36
+ `Association`, `Generalization`, or `Dependency` depending on their type.
37
+
38
+ ## Dropped types — rationale
39
+
40
+ ### `Text`
41
+
42
+ A free-form text box placed on a diagram for labeling or annotation. It is a
43
+ **rendering hint**, not a model element — it has no semantic relationship to
44
+ the classes and packages in the model.
45
+
46
+ - **Current behavior:** dropped. Content is not preserved in the UML document.
47
+ - **Why not `UmlClass`:** Text has no attributes, operations, or type
48
+ semantics. Treating it as a class (the previous behavior) polluted the class
49
+ collection with non-classes and broke class counts.
50
+ - **Future:** map to `Lutaml::Uml::Comment`. This requires adding a `comments`
51
+ collection to `Lutaml::Uml::Package` (currently only `Document` has one, and
52
+ it is a flat string list). Until then, Text content is lost.
53
+
54
+ ### `Note`
55
+
56
+ A note element on a diagram. Same category as `Text` — a rendering/annotation
57
+ hint, not a model element.
58
+
59
+ - **Current behavior:** dropped.
60
+ - **Future:** same as `Text` — map to `Comment` when the metamodel supports it.
61
+
62
+ ### `ProxyConnector`
63
+
64
+ EA's internal stub representing a connector that crosses a package boundary.
65
+ When a connector in package A points at an element in package B, EA may insert
66
+ a `ProxyConnector` in one package as a stand-in for the foreign element.
67
+
68
+ - **Current behavior:** dropped.
69
+ - **Why:** it is structural plumbing. The actual relationship is already
70
+ captured by the real connector (in `t_connector`) and resolved by
71
+ `ReferenceResolver`. A `ProxyConnector` in `t_object` is a rendering/UX
72
+ artifact, not a second model element.
73
+ - **Future:** no plan to preserve. If diagram rendering needs the proxy
74
+ geometry, it should come from `t_diagram_objects`, not from treating the
75
+ proxy as a class.
76
+
77
+ ## Verifying counts
78
+
79
+ After transformation:
80
+
81
+ ```
82
+ EA t_object (Class) = UML UmlClass count (in packages + orphan root)
83
+ EA t_object (Enumeration) = UML Enum count
84
+ EA t_object (DataType) = UML DataType count
85
+ ```
86
+
87
+ If counts don't match, check for orphaned classes (objects whose `package_id`
88
+ has no row in `t_package`) — these surface at the document root via
89
+ `EaToUmlFactory#transform_orphan_classes`, not inside any package.
@@ -0,0 +1,99 @@
1
+ # Format Conversion Capabilities: QEA, XMI, and UML
2
+
3
+ ## Short answer
4
+
5
+ **No.** The `ea` gem cannot convert XMI→QEA or QEA→XMI. Neither direction
6
+ exists. Both formats are parsed *into* a common `Lutaml::Uml::Document`
7
+ (hub-and-spoke), but there is no write-back path to either native format.
8
+
9
+ ## Current capability matrix
10
+
11
+ | From | To | Status | API |
12
+ |-------------------|----------------------|--------------|-------------------------------|
13
+ | `.qea` (SQLite) | `Ea::Qea::Database` | ✅ Full | `Ea::Qea.load(path)` |
14
+ | `.qea` | `Lutaml::Uml::Document` | ✅ Full | `Ea::Qea.parse(path)` / `to_uml(db)` |
15
+ | Sparx `.xmi` | `::Xmi::Sparx::Root` model | ✅ Full | `Ea::Xmi::Parser.parse(path)` |
16
+ | Sparx `.xmi` | Liquid drops (display) | ✅ Full | `Parser.serialize_to_liquid` |
17
+ | Sparx `.xmi` | `Lutaml::Uml::Document` | ❌ None | — |
18
+ | `Lutaml::Uml::Document` | Sparx `.xmi` | ❌ None | — |
19
+ | `Lutaml::Uml::Document` | `.qea` (SQLite) | ❌ None | — |
20
+
21
+ ## Why neither direction exists
22
+
23
+ ### QEA is read-only
24
+
25
+ `Ea::Qea::Infrastructure::DatabaseConnection` opens SQLite with
26
+ `readonly: true`. There is no `INSERT`/`UPDATE` path, no writer class, no
27
+ `to_qea`. Building one requires reverse-mapping a UML Document into EA's
28
+ ~30 interrelated tables (`t_object`, `t_package`, `t_connector`, `t_attribute`,
29
+ `t_diagram`, `t_objectproperties`, `t_xref`, `t_taggedvalue`, …), generating
30
+ valid EA GUIDs and object_ids, and populating tool-specific columns
31
+ (style strings, diagram geometry, visibility flags) that have no UML source.
32
+
33
+ ### XMI is parse-only
34
+
35
+ `Ea::Xmi::Parser` consumes Sparx XMI via `::Xmi::Sparx::Root.parse_xml` and
36
+ exposes the result through Liquid drops for *display* rendering (HTML/JSON
37
+ templates). The drops wrap XMI-derived data — they do not serialize a
38
+ `Lutaml::Uml::Document` back to XMI. There is no `UmlDocument → XMI`
39
+ serializer.
40
+
41
+ ### The drops are display, not generation
42
+
43
+ `Ea::Xmi::LiquidDrops::*` are Liquid `Drop` wrappers around the parsed XMI
44
+ model. They exist to render XMI content in templates (for browsing,
45
+ documentation, HTML export). A Liquid drop reads from a model; it does not
46
+ write one. To generate XMI, you'd need XMI-schema-aware serialization
47
+ (`xmi:Documentation`, `xmi:Extension`, `EAStub`, profile application, etc.),
48
+ which is a separate, substantial piece of work.
49
+
50
+ ## What "conversion" would actually require
51
+
52
+ ### QEA → XMI (export)
53
+
54
+ 1. Build a `Lutaml::Uml::Document → Sparx XMI` serializer.
55
+ 2. Lossy: the UML Document does not carry EA-specific data (diagram layout,
56
+ style strings, tagged value geometry, t_xref cross-references). The
57
+ exported XMI would be semantically faithful but visually bare.
58
+ 3. The Sparx XMI schema is documented but large; `::Xmi::Sparx::Root`
59
+ parses it but cannot (currently) serialize it.
60
+
61
+ ### XMI → QEA (import)
62
+
63
+ 1. Build `XMI → Lutaml::Uml::Document` (does not exist — `Ea::Xmi::Parser`
64
+ produces display drops, not a UML Document).
65
+ 2. Build `Lutaml::Uml::Document → QEA SQLite` writer (does not exist —
66
+ see "QEA is read-only" above).
67
+ 3. Highly lossy: EA's QEA schema has ~30 tables with tool-specific columns.
68
+ Sparx's own XMI import into QEA is lossy; reimplementing it faithfully
69
+ is a major project with marginal value (you'd produce a QEA that opens
70
+ in EA but has no diagrams, no layout, no EA-specific metadata).
71
+
72
+ ## Recommendation
73
+
74
+ Do **not** position the gem as offering XMI↔QEA conversion. Instead:
75
+
76
+ - **Read both formats into UML** — this is the gem's core value.
77
+ - **Query and analyze** the resulting UML Document (directly or via
78
+ `Lutaml::UmlRepository::Repository.from_document`).
79
+ - **Export to neutral formats** — JSON, CSV, HTML (via Liquid drops),
80
+ `.lur` package (via Repository). These are one-way, lossy, and honest
81
+ about what they are.
82
+
83
+ If true XMI↔QEA round-trip is needed, the pragmatic path is to use Sparx EA
84
+ itself (which does both natively) and treat this gem as the
85
+ extract/query/analyze layer that sits alongside EA, not as a replacement
86
+ for its file-format converters.
87
+
88
+ ## If we ever build write paths
89
+
90
+ The clean order would be:
91
+
92
+ 1. **`Lutaml::Uml::Document → Sparx XMI`** — most useful, least schema
93
+ complexity. Reuses `::Xmi::Sparx::Root`'s type system if serialization
94
+ support is added upstream.
95
+ 2. **`Lutaml::Uml::Document → .lur`** — already exists via
96
+ `Repository.export_to_package`. Neutral, lossless for the UML subset.
97
+ 3. **`Lutaml::Uml::Document → .qea`** — last resort. Only if EA-native
98
+ interchange is a hard requirement and `.lur` + XMI export are
99
+ insufficient.
Binary file
Binary file
Binary file
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Basic LUR Repository Operations
5
+ #
6
+ # This example demonstrates basic repository operations including:
7
+ # - Loading from XMI or LUR files
8
+ # - Finding packages and classes
9
+ # - Navigating inheritance hierarchies
10
+ # - Querying associations
11
+
12
+ # Load local development version
13
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
14
+
15
+ require "lutaml"
16
+ require "lutaml/uml_repository"
17
+ require "ea"
18
+
19
+ # Example 1: Load from XMI file
20
+ puts "=" * 80
21
+ puts "Example 1: Load from XMI"
22
+ puts "=" * 80
23
+
24
+ # Build repository from XMI (slow, 5-8 seconds for large models)
25
+ # repo = Lutaml::UmlRepository::Repository.from_xmi("model.xmi")
26
+
27
+ # Or use smart file detection (auto-detects .xmi or .lur)
28
+ # repo = Lutaml::UmlRepository::Repository.from_file("model.xmi")
29
+
30
+ # For this example, we'll show the API without actual file
31
+ puts "repo = Lutaml::UmlRepository::Repository.from_xmi('model.xmi')"
32
+ puts "# Builds indexes from XMI (5-8s for large models)"
33
+ puts
34
+
35
+ # Example 2: Load from LUR package (fast loading)
36
+ puts "=" * 80
37
+ puts "Example 2: Load from LUR package (< 100ms)"
38
+ puts "=" * 80
39
+
40
+ puts "repo = Lutaml::UmlRepository::Repository.from_package('model.lur')"
41
+ puts "# Fast loading from pre-indexed package"
42
+ puts
43
+
44
+ # Example 3: Smart caching
45
+ puts "=" * 80
46
+ puts "Example 3: Smart caching"
47
+ puts "=" * 80
48
+
49
+ puts "repo = Lutaml::UmlRepository::Repository.from_file_cached('model.xmi')"
50
+ puts "# Uses cached .lur if newer than .xmi, otherwise rebuilds"
51
+ puts
52
+
53
+ # For demonstration, let's use a QEA file if available
54
+ qea_file = Dir.glob("examples/qea/*.qea").first
55
+ puts "=" * 80
56
+ if qea_file
57
+ puts "Example 4: Loading from QEA file (10-20x faster than XMI)"
58
+ puts "=" * 80
59
+ puts "QEA file found: #{qea_file}"
60
+
61
+ # Parse QEA directly
62
+ document = Ea::Qea.parse(qea_file)
63
+ repo = Lutaml::UmlRepository::Repository.new(document: document)
64
+
65
+ puts "Loaded successfully!"
66
+ puts
67
+
68
+ # Example 5: Finding packages
69
+ puts "=" * 80
70
+ puts "Example 5: Finding packages"
71
+ puts "=" * 80
72
+
73
+ # Find root package
74
+ root = repo.find_package("ModelRoot")
75
+ if root
76
+ puts "Root package: #{root.name}"
77
+ puts "Root type: #{root.class}"
78
+ end
79
+ puts
80
+
81
+ # List top-level packages
82
+ packages = repo.list_packages("ModelRoot", recursive: false)
83
+ puts "Top-level packages (#{packages.size}):"
84
+ packages.first(5).each do |pkg|
85
+ puts " - #{pkg.name}"
86
+ end
87
+ puts " ..." if packages.size > 5
88
+ puts
89
+
90
+ # Example 6: Finding classes
91
+ puts "=" * 80
92
+ puts "Example 6: Finding classes"
93
+ puts "=" * 80
94
+
95
+ # Get all classes
96
+ all_classes = repo.classes_index
97
+ puts "Total classes: #{all_classes.size}"
98
+ puts
99
+
100
+ # Find by stereotype
101
+ if all_classes.any?
102
+ first_class = all_classes.first
103
+ puts "First class: #{first_class.name}"
104
+ if first_class.respond_to?(:stereotype) && first_class.stereotype
105
+ puts " Stereotype: #{first_class.stereotype}"
106
+ end
107
+ end
108
+ puts
109
+
110
+ # Example 7: Working with associations
111
+ puts "=" * 80
112
+ puts "Example 7: Associations"
113
+ puts "=" * 80
114
+
115
+ associations = repo.associations_index
116
+ puts "Total associations: #{associations.size}"
117
+
118
+ if associations.any?
119
+ assoc = associations.first
120
+ puts "\nFirst association:"
121
+ member_end = assoc.member_end
122
+ if member_end.is_a?(Array) && member_end.any?
123
+ puts " Name: #{member_end.first&.name || 'unnamed'}"
124
+ elsif member_end.respond_to?(:name)
125
+ puts " Name: #{member_end.name}"
126
+ else
127
+ puts " Name: unnamed"
128
+ end
129
+
130
+ member_end_type = assoc.member_end_type
131
+ if member_end_type.is_a?(Array) && member_end_type.any?
132
+ puts " Type: #{member_end_type.first}"
133
+ elsif member_end_type
134
+ puts " Type: #{member_end_type}"
135
+ end
136
+ end
137
+ puts
138
+
139
+ # Example 8: Statistics
140
+ puts "=" * 80
141
+ puts "Example 8: Repository statistics"
142
+ puts "=" * 80
143
+
144
+ stats = repo.statistics
145
+ puts "Packages: #{stats[:total_packages]}"
146
+ puts "Classes: #{stats[:total_classes]}"
147
+ puts "Associations: #{stats[:total_associations]}"
148
+ puts "Diagrams: #{stats[:total_diagrams]}"
149
+
150
+ if stats[:classes_by_stereotype]
151
+ puts "\nClasses by stereotype:"
152
+ stats[:classes_by_stereotype].first(5).each do |stereotype, count|
153
+ puts " #{stereotype}: #{count}"
154
+ end
155
+ end
156
+ puts
157
+
158
+ # Example 9: Search
159
+ puts "=" * 80
160
+ puts "Example 9: Search"
161
+ puts "=" * 80
162
+
163
+ # Search for elements
164
+ results = repo.search("Object", types: [:class])
165
+ puts "Search for 'Object' in class names:"
166
+ puts " Found #{results[:total]} matches"
167
+ if results[:classes]
168
+ puts " Classes (#{results[:classes].size}):"
169
+ results[:classes].first(3).each do |result|
170
+ puts " - #{result.element.name}"
171
+ end
172
+ end
173
+ puts
174
+
175
+ else
176
+ puts "No QEA files found in examples/qea/"
177
+ puts "=" * 80
178
+ puts
179
+ puts "To use this example:"
180
+ puts "1. Place a .qea or .xmi file in examples/qea/"
181
+ puts "2. Or modify the script to point to your model file"
182
+ puts
183
+ puts "Example API usage (without actual file):"
184
+ puts <<~RUBY
185
+
186
+ # Load repository
187
+ repo = Lutaml::UmlRepository::Repository.from_xmi("model.xmi")
188
+
189
+ # Find package
190
+ package = repo.find_package("ModelRoot::MyPackage")
191
+
192
+ # Find class
193
+ klass = repo.find_class("ModelRoot::MyPackage::MyClass")
194
+
195
+ # Get class attributes
196
+ klass.attributes.each do |attr|
197
+ puts "\#{attr.name}: \#{attr.type}"
198
+ end
199
+
200
+ # Find by stereotype
201
+ feature_types = repo.find_classes_by_stereotype("featureType")
202
+
203
+ # Get inheritance
204
+ parent = repo.supertype_of(klass)
205
+ children = repo.subtypes_of(klass)
206
+
207
+ # Get associations
208
+ assocs = repo.associations_of(klass)
209
+
210
+ # Search
211
+ results = repo.search("Building")
212
+
213
+ # Get statistics
214
+ stats = repo.statistics
215
+ puts "Total classes: \#{stats[:total_classes]}"
216
+ RUBY
217
+ end
218
+
219
+ puts "=" * 80
220
+ puts "Example complete!"
221
+ puts "=" * 80
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: CLI Workflow Patterns
5
+ #
6
+ # This example demonstrates common command-line workflow patterns for
7
+ # working with LUR packages, including building, validation, and export.
8
+
9
+ # Load local development version
10
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
11
+
12
+ require "lutaml"
13
+ require "lutaml/uml_repository"
14
+ require "ea"
15
+ require "fileutils"
16
+
17
+ puts "=" * 80
18
+ puts "LUR CLI Workflow Examples"
19
+ puts "=" * 80
20
+ puts
21
+
22
+ # Setup: Find a test file
23
+ qea_file = Dir.glob("examples/qea/*.qea").first
24
+ xmi_file = Dir.glob("examples/qea/*.xmi").first
25
+ test_file = qea_file || xmi_file
26
+
27
+ unless test_file
28
+ puts "No test files found in examples/qea/"
29
+ puts
30
+ puts "This example demonstrates CLI workflows. To run:"
31
+ puts " 1. Place a .qea or .xmi file in examples/qea/"
32
+ puts " 2. Or use the CLI commands directly:"
33
+ puts
34
+ puts "CLI command examples:"
35
+ puts <<~TEXT
36
+ # Build LUR package from XMI
37
+ $ lutaml uml build model.xmi -o model.lur
38
+
39
+ # Build from QEA (10-20x faster)
40
+ $ lutaml uml build model.qea -o model.lur
41
+
42
+ # Show package info
43
+ $ lutaml uml info model.lur
44
+
45
+ # List packages
46
+ $ lutaml uml ls model.lur
47
+
48
+ # Show package tree
49
+ $ lutaml uml tree model.lur --depth 3
50
+
51
+ # Search
52
+ $ lutaml uml search model.lur "Building"
53
+
54
+ # Find by stereotype
55
+ $ lutaml uml find model.lur --stereotype featureType
56
+
57
+ # Get statistics
58
+ $ lutaml uml stats model.lur
59
+
60
+ # Validate
61
+ $ lutaml uml validate model.lur
62
+
63
+ # Export to CSV
64
+ $ lutaml uml export model.lur --format csv -o classes.csv
65
+
66
+ # Generate documentation site
67
+ $ lutaml uml docs model.lur -o docs/
68
+
69
+ # Start interactive shell
70
+ $ lutaml uml repl model.lur
71
+
72
+ # Watch mode (auto-rebuild on changes)
73
+ $ lutaml uml watch model.xmi -o model.lur
74
+ TEXT
75
+ exit 0
76
+ end
77
+
78
+ puts "Test file found: #{test_file}"
79
+ puts "File size: #{File.size(test_file) / 1024}KB"
80
+ puts
81
+
82
+ # Workflow 1: Build LUR package
83
+ puts "=" * 80
84
+ puts "Workflow 1: Building LUR package"
85
+ puts "=" * 80
86
+
87
+ lur_output = "examples/workflow_test.lur"
88
+ FileUtils.rm_f(lur_output)
89
+
90
+ puts "Loading from: #{test_file}"
91
+ start_time = Time.now
92
+
93
+ # Parse the source file
94
+ if test_file.end_with?(".qea")
95
+ puts "Parsing QEA file (fast)..."
96
+ document = Ea::Qea.parse(test_file)
97
+ elsif test_file.end_with?(".xmi")
98
+ puts "Parsing XMI file..."
99
+ document = Lutaml::Parser.parse([File.new(test_file)]).first
100
+ else
101
+ puts "Unknown file type"
102
+ exit 1
103
+ end
104
+
105
+ parse_time = Time.now - start_time
106
+ puts "Parsed in #{parse_time.round(2)}s"
107
+
108
+ # Build repository
109
+ puts "Building repository..."
110
+ repo = Lutaml::UmlRepository::Repository.new(document: document)
111
+ build_time = Time.now - start_time - parse_time
112
+ puts "Built in #{build_time.round(2)}s"
113
+
114
+ # Export to LUR package
115
+ puts "Exporting to LUR package..."
116
+ export_start = Time.now
117
+ repo.export_to_package(lur_output,
118
+ name: "Test Model",
119
+ version: "1.0",
120
+ serialization_format: :marshal)
121
+ export_time = Time.now - export_start
122
+ puts "Exported in #{export_time.round(2)}s"
123
+ puts "Package created: #{lur_output} (#{File.size(lur_output) / 1024}KB)"
124
+ puts
125
+
126
+ # Workflow 2: Load from LUR (fast)
127
+ puts "=" * 80
128
+ puts "Workflow 2: Fast loading from LUR"
129
+ puts "=" * 80
130
+
131
+ load_start = Time.now
132
+ loaded_repo = Lutaml::UmlRepository::Repository.from_package(lur_output)
133
+ load_time = Time.now - load_start
134
+ puts "Loaded in #{load_time.round(2)}s (vs #{parse_time.round(2)}s for parsing)"
135
+ puts "Speedup: #{(parse_time / load_time).round(1)}x faster"
136
+ puts
137
+
138
+ # Workflow 3: Validation
139
+ puts "=" * 80
140
+ puts "Workflow 3: Model validation"
141
+ puts "=" * 80
142
+
143
+ result = loaded_repo.validate
144
+ puts "Validation result: #{result.valid? ? 'VALID ✓' : 'INVALID ✗'}"
145
+ puts "Errors: #{result.errors.size}"
146
+ puts "Warnings: #{result.warnings.size}"
147
+
148
+ if result.errors.any?
149
+ puts "\nFirst 3 errors:"
150
+ result.errors.first(3).each do |error|
151
+ puts " - #{error}"
152
+ end
153
+ end
154
+
155
+ if result.warnings.any?
156
+ puts "\nFirst 3 warnings:"
157
+ result.warnings.first(3).each do |warning|
158
+ puts " - #{warning}"
159
+ end
160
+ end
161
+ puts
162
+
163
+ # Workflow 4: Statistics
164
+ puts "=" * 80
165
+ puts "Workflow 4: Model statistics"
166
+ puts "=" * 80
167
+
168
+ stats = loaded_repo.statistics
169
+ puts "Model overview:"
170
+ puts " Packages: #{stats[:total_packages]}"
171
+ puts " Classes: #{stats[:total_classes]}"
172
+ puts " Data types: #{stats[:total_data_types]}"
173
+ puts " Enumerations: #{stats[:total_enums]}"
174
+ puts " Associations: #{stats[:total_associations]}"
175
+ puts " Diagrams: #{stats[:total_diagrams]}"
176
+
177
+ if stats[:max_package_depth]
178
+ puts "\nPackage structure:"
179
+ puts " Max depth: #{stats[:max_package_depth]}"
180
+ end
181
+
182
+ if stats[:classes_by_stereotype]
183
+ puts "\nTop stereotypes:"
184
+ stats[:classes_by_stereotype].first(5).each do |stereotype, count|
185
+ puts " #{stereotype}: #{count}"
186
+ end
187
+ end
188
+ puts
189
+
190
+ # Workflow 5: Query and export
191
+ puts "=" * 80
192
+ puts "Workflow 5: Query and export"
193
+ puts "=" * 80
194
+
195
+ # Find all classes
196
+ all_classes = loaded_repo.classes_index
197
+ puts "Found #{all_classes.size} classes"
198
+
199
+ if all_classes.any?
200
+ # Sample some classes
201
+ sample_classes = all_classes.first(5)
202
+ puts "\nSample classes:"
203
+ sample_classes.each do |klass|
204
+ puts " - #{klass.name}"
205
+ if klass.respond_to?(:stereotype) && klass.stereotype
206
+ puts " Stereotype: #{klass.stereotype}"
207
+ end
208
+ if klass.respond_to?(:attributes) && klass.attributes
209
+ puts " Attributes: #{klass.attributes.size}"
210
+ end
211
+ end
212
+
213
+ # Search example
214
+ puts "\nSearch for 'Object':"
215
+ results = loaded_repo.search("Object", types: [:class])
216
+ puts " Found #{results[:total]} matches in class names"
217
+ end
218
+ puts
219
+
220
+ # Workflow 6: Smart caching demonstration
221
+ puts "=" * 80
222
+ puts "Workflow 6: Smart caching"
223
+ puts "=" * 80
224
+
225
+ puts "Smart caching automatically uses LUR if newer than source:"
226
+ puts
227
+ puts "# First run - builds from source"
228
+ puts "repo = Repository.from_file_cached('#{test_file}')"
229
+ puts "# Creates #{test_file.sub(/\.(xmi|qea)$/, '.lur')}"
230
+ puts
231
+ puts "# Subsequent runs - loads from cache"
232
+ puts "repo = Repository.from_file_cached('#{test_file}')"
233
+ puts "# Uses cached .lur (< 100ms)"
234
+ puts
235
+
236
+ # Cleanup
237
+ puts "=" * 80
238
+ puts "Cleanup"
239
+ puts "=" * 80
240
+ FileUtils.rm_f(lur_output)
241
+ puts "Removed test file: #{lur_output}"
242
+ puts
243
+
244
+ puts "=" * 80
245
+ puts "Workflow examples complete!"
246
+ puts "=" * 80
247
+ puts
248
+ puts "Summary of CLI commands:"
249
+ puts " build - Build LUR package from XMI/QEA"
250
+ puts " info - Show package metadata"
251
+ puts " validate - Validate model integrity"
252
+ puts " ls - List elements"
253
+ puts " tree - Show package hierarchy"
254
+ puts " inspect - Show element details"
255
+ puts " search - Full-text search"
256
+ puts " find - Find by criteria"
257
+ puts " stats - Model statistics"
258
+ puts " export - Export to various formats"
259
+ puts " docs - Generate documentation site"
260
+ puts " serve - Start web UI"
261
+ puts " repl - Interactive shell"
262
+ puts
263
+ puts "Run 'lutaml uml --help' for full command reference"