ea 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.complete/01-t-xref-parsing.md +1 -1
  3. data/TODO.complete/02-per-label-geometry.md +1 -1
  4. data/TODO.complete/03-object-properties-constraints.md +1 -1
  5. data/TODO.complete/04-operation-params.md +1 -1
  6. data/TODO.complete/05-pdata-flag-matrix.md +1 -1
  7. data/TODO.complete/06-emf-image-rendering.md +1 -1
  8. data/TODO.complete/07-auxiliary-tables.md +1 -1
  9. data/TODO.complete/10-mdg-stereotype-registry.md +1 -1
  10. data/TODO.complete/11-gml-xsd-generation.md +1 -1
  11. data/TODO.complete/13-shapescript-interpreter.md +12 -23
  12. data/TODO.complete/20-cli-diff.md +1 -1
  13. data/TODO.complete/21-cli-render.md +1 -1
  14. data/TODO.complete/22-cli-export.md +1 -1
  15. data/TODO.complete/23-cli-mdg.md +1 -1
  16. data/TODO.complete/30-headerlines-ocp-refactor.md +1 -1
  17. data/TODO.complete/32-spec-coverage.md +1 -1
  18. data/TODO.complete/33-blocked-parity-gaps.md +1 -1
  19. data/TODO.complete/36-stereotype-icon-wiring.md +1 -1
  20. data/TODO.complete/37-cli-lint.md +1 -1
  21. data/TODO.complete/38-cli-query.md +1 -1
  22. data/TODO.complete/39-cli-info.md +1 -1
  23. data/TODO.complete/40-ocl-evaluator.md +1 -1
  24. data/TODO.complete/41-round-trip-spec.md +1 -1
  25. data/TODO.complete/42-html-diff-report.md +1 -1
  26. data/TODO.complete/43-json-schema-export.md +1 -1
  27. data/TODO.complete/44-remaining-qea-tables.md +1 -1
  28. data/TODO.complete/45-shapescript-extensions.md +1 -1
  29. data/TODO.complete/46-xsd-consolidation.md +1 -1
  30. data/TODO.complete/47-headerlines-legacy-removal.md +1 -1
  31. data/TODO.complete/51-remove-respond-to.md +42 -15
  32. data/TODO.complete/52-xsd-decouple-fixtures.md +1 -1
  33. data/TODO.complete/54-wire-stereotype-icon-renderer.md +1 -1
  34. data/TODO.complete/55-wire-shapescript.md +1 -1
  35. data/TODO.complete/56-wire-emf-renderer.md +1 -1
  36. data/TODO.complete/57-wire-ocl-evaluator.md +1 -1
  37. data/TODO.complete/58-diff-modifications.md +1 -1
  38. data/TODO.complete/59-delete-toy-xmi-export.md +1 -1
  39. data/TODO.complete/60-curate-json-export.md +1 -1
  40. data/TODO.complete/61-plantuml-relationships.md +1 -1
  41. data/TODO.complete/62-changelog.md +1 -1
  42. data/TODO.complete/63-dependabot-fix.md +1 -1
  43. data/TODO.complete/64-qeatoxmi-connectors.md +9 -24
  44. data/TODO.complete/65-qeatoxmi-diagrams.md +9 -23
  45. data/TODO.complete/66-qeatoxmi-style-tags-docs.md +25 -18
  46. data/TODO.complete/67-ocl-collection-ops.md +12 -12
  47. data/TODO.complete/68-ocl-comparison-ops.md +16 -11
  48. data/TODO.complete/69-spec-new-models.md +13 -18
  49. data/TODO.complete/70-specs-for-all-models.md +21 -0
  50. data/TODO.complete/71-autoload-audit.md +26 -0
  51. data/TODO.complete/72-extension-serializer-ocp-naming.md +26 -0
  52. data/TODO.complete/73-ocl-validator-latent-bugs.md +50 -0
  53. data/TODO.complete/74-visibility-symbol-dry.md +39 -0
  54. data/TODO.complete/75-database-table-collection-registry.md +32 -0
  55. data/TODO.complete/76-stale-todo-cleanup.md +52 -0
  56. data/TODO.complete/77-eliminate-banned-patterns-from-specs.md +55 -0
  57. data/TODO.complete/78-mdg-stereotype-emission.md +66 -0
  58. data/TODO.complete/79-equivalence-fixes.md +57 -0
  59. data/TODO.complete/80-performance-extension-serializer-indexes.md +43 -0
  60. data/TODO.complete/81-dry-scope-guid-extraction.md +33 -0
  61. data/TODO.complete/82-public-send-audit-conclusion.md +25 -0
  62. data/TODO.complete/83-dry-shared-xml-escape-guid-format.md +52 -0
  63. data/TODO.complete/84-ocp-markers-shape-registry.md +54 -0
  64. data/TODO.complete/85-frozen-literal-rubocop-normalize-type-spec.md +38 -0
  65. data/TODO.complete/86-frozen-sweep-lint-query-specs.md +55 -0
  66. data/TODO.complete/STATUS.md +26 -5
  67. data/lib/ea/cli/app.rb +1 -0
  68. data/lib/ea/cli/command/export.rb +10 -1
  69. data/lib/ea/cli/command/mdg.rb +1 -10
  70. data/lib/ea/cli/command/render.rb +2 -1
  71. data/lib/ea/diff/html_reporter.rb +1 -5
  72. data/lib/ea/export/json/generator.rb +5 -6
  73. data/lib/ea/export/plantuml/generator.rb +5 -3
  74. data/lib/ea/guid_format.rb +91 -0
  75. data/lib/ea/mdg/document.rb +2 -1
  76. data/lib/ea/mdg/loader.rb +2 -1
  77. data/lib/ea/mdg/registry.rb +57 -2
  78. data/lib/ea/mdg/xml/applies_to.rb +1 -1
  79. data/lib/ea/mdg/xml/apply.rb +1 -1
  80. data/lib/ea/mdg/xml/content.rb +1 -1
  81. data/lib/ea/mdg/xml/documentation.rb +1 -1
  82. data/lib/ea/mdg/xml/stereotype.rb +1 -1
  83. data/lib/ea/mdg/xml/stereotypes.rb +1 -1
  84. data/lib/ea/mdg/xml/tag.rb +1 -1
  85. data/lib/ea/mdg/xml/tagged_values.rb +1 -1
  86. data/lib/ea/mdg/xml/technology.rb +1 -1
  87. data/lib/ea/mdg/xml/uml_profile.rb +1 -1
  88. data/lib/ea/mdg/xml/uml_profiles.rb +1 -1
  89. data/lib/ea/mdg/xml.rb +2 -1
  90. data/lib/ea/mdg.rb +2 -1
  91. data/lib/ea/model/ghost_label.rb +2 -1
  92. data/lib/ea/ocl/evaluator.rb +4 -2
  93. data/lib/ea/qea/database.rb +57 -2
  94. data/lib/ea/qea/factory/reference_resolver.rb +1 -1
  95. data/lib/ea/qea/models/ea_attribute.rb +1 -17
  96. data/lib/ea/qea/models/ea_connector.rb +3 -2
  97. data/lib/ea/qea/models/ea_object.rb +14 -9
  98. data/lib/ea/qea/models/ea_operation.rb +1 -17
  99. data/lib/ea/qea/models/ea_xref.rb +3 -0
  100. data/lib/ea/qea/models/scope_predicate.rb +29 -0
  101. data/lib/ea/qea/models.rb +1 -0
  102. data/lib/ea/qea/repositories/base_repository.rb +4 -1
  103. data/lib/ea/qea/validation/base_validator.rb +3 -48
  104. data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +23 -13
  105. data/lib/ea/shapescript/renderer.rb +1 -3
  106. data/lib/ea/sources/qea/diagram_builder.rb +1 -0
  107. data/lib/ea/sources/qea/id_normalizer.rb +2 -2
  108. data/lib/ea/svg/ea_emitter/background.rb +2 -1
  109. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +1 -6
  110. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +2 -7
  111. data/lib/ea/svg/ea_emitter/elements.rb +1 -3
  112. data/lib/ea/svg/ea_emitter/ghost_labels.rb +2 -1
  113. data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -6
  114. data/lib/ea/svg/ea_emitter/marker/package_import.rb +1 -1
  115. data/lib/ea/svg/ea_emitter/markers.rb +52 -23
  116. data/lib/ea/svg/ea_emitter/text_renderer.rb +1 -4
  117. data/lib/ea/svg/ea_emitter/visibility_symbol.rb +37 -0
  118. data/lib/ea/svg/ea_emitter.rb +1 -0
  119. data/lib/ea/svg/parity/source.rb +0 -2
  120. data/lib/ea/svg/parity/suite.rb +0 -1
  121. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +40 -28
  122. data/lib/ea/transformers/qea_to_xmi/context.rb +35 -1
  123. data/lib/ea/transformers/qea_to_xmi/extension_serializer.rb +686 -0
  124. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +5 -48
  125. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +55 -54
  126. data/lib/ea/transformers/qea_to_xmi/primitive_types.rb +105 -0
  127. data/lib/ea/transformers/qea_to_xmi/profile_serializer.rb +163 -0
  128. data/lib/ea/transformers/qea_to_xmi/run_state.rb +7 -6
  129. data/lib/ea/transformers/qea_to_xmi/transformer.rb +396 -185
  130. data/lib/ea/transformers/qea_to_xmi/visibility.rb +30 -19
  131. data/lib/ea/transformers/qea_to_xmi.rb +5 -2
  132. data/lib/ea/transformers.rb +7 -2
  133. data/lib/ea/validation.rb +1 -1
  134. data/lib/ea/version.rb +1 -1
  135. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +12 -4
  136. data/lib/ea/xmi/liquid_drops/klass_drop.rb +1 -2
  137. data/lib/ea/xmi/lookup_service.rb +4 -0
  138. data/lib/ea/xmi/parser.rb +30 -1
  139. data/lib/ea/xml_escape.rb +21 -0
  140. data/lib/ea.rb +2 -0
  141. metadata +28 -11
  142. data/lutaml-uml-0.5.1.gem +0 -0
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Maps UML visibility strings to EA's single-character symbols.
7
+ #
8
+ # Centralised so that adding a new visibility level (e.g.
9
+ # "implementation" in Java) changes one hash entry, not three
10
+ # case/when branches across separate renderer files.
11
+ #
12
+ # EA's convention:
13
+ # + public
14
+ # - private
15
+ # # protected
16
+ # ~ package
17
+ module VisibilitySymbol
18
+ SYMBOLS = {
19
+ "public" => "+",
20
+ "private" => "-",
21
+ "protected" => "#",
22
+ "package" => "~"
23
+ }.freeze
24
+
25
+ # @param visibility [String, nil] EA scope string ("Public",
26
+ # "Private", etc.). Case-insensitive; nil defaults to public.
27
+ # @param with_space [Boolean] append a trailing space (used
28
+ # by operation renderers: "+ method", not "+method").
29
+ # @return [String] the UML visibility symbol
30
+ def self.for(visibility, with_space: false)
31
+ symbol = SYMBOLS[(visibility || "public").downcase] || "+"
32
+ with_space ? "#{symbol} " : symbol
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -46,6 +46,7 @@ module Ea
46
46
  autoload :Label, "ea/svg/ea_emitter/label"
47
47
  autoload :Compartment, "ea/svg/ea_emitter/compartment"
48
48
  autoload :RenderContext, "ea/svg/ea_emitter/render_context"
49
+ autoload :VisibilitySymbol, "ea/svg/ea_emitter/visibility_symbol"
49
50
  end
50
51
  end
51
52
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ea"
4
3
  require "xmi"
5
- require "ea/sources/xmi/adapter"
6
4
 
7
5
  module Ea
8
6
  module Svg
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "nokogiri"
4
- require "ea/svg/parity/checker"
5
4
 
6
5
  module Ea
7
6
  module Svg
@@ -13,21 +13,42 @@ module Ea
13
13
  # `<upperValue value="M"/>`) — never a range string. This module
14
14
  # translates the EA form to a `{ lower:, upper: }` pair.
15
15
  #
16
- # Default-when-empty: EA's "no bound specified" maps to UML's
17
- # unspecified multiplicity, which Sparx renders as
18
- # `<lowerValue value="0"/>` and `<upperValue value="-1"/>`.
19
- # Always emitting both is required for round-trip parity with
20
- # real Sparx XMI (see TODO 26).
16
+ # Default-when-empty: blank input maps to UML's unspecified
17
+ # multiplicity, `{ lower: "0", upper: "*" }`. Whether those
18
+ # bounds reach the document is the caller's call — EA leaves an
19
+ # association end bare when its card field is blank.
21
20
  module Cardinality
22
21
  # Tokens EA uses for "unbounded". Matched case-insensitively.
23
- UNLIMITED_TOKENS = %w[* *-1 unbounded].freeze
22
+ UNLIMITED_TOKENS = %w[* *-1 -1 unbounded].freeze
24
23
 
25
- # UML defaults when EA carries no explicit bound.
24
+ # UML defaults when EA carries no explicit bound. EA's wire
25
+ # form for unlimited is "*" (never "-1" — the reference
26
+ # exports contain no value="-1" at all).
26
27
  DEFAULT_LOWER = "0"
27
- DEFAULT_UPPER = "-1"
28
+ DEFAULT_UPPER = "*"
29
+
30
+ # EA writes an explicit 1..1 for a Property whose t_attribute
31
+ # bound columns are BOTH blank, rather than falling back to the
32
+ # UML unspecified multiplicity.
33
+ DEFAULT_ATTRIBUTE_BOUNDS = { lower: "1", upper: "1" }.freeze
28
34
 
29
35
  module_function
30
36
 
37
+ # The bound pair for a t_attribute row. The 1..1 default is a
38
+ # property of the PAIR — with one column set, the missing side
39
+ # keeps the normal UML fallback, so `2` and a blank upper is
40
+ # 2..* rather than the invalid 2..1.
41
+ #
42
+ # @param lower [String, Integer, nil] t_attribute.lowerbound
43
+ # @param upper [String, Integer, nil] t_attribute.upperbound
44
+ # @return [Hash{Symbol=>String}] `{ lower:, upper: }`
45
+ def attribute_bounds(lower, upper)
46
+ both_blank = lower.to_s.strip.empty? && upper.to_s.strip.empty?
47
+ return DEFAULT_ATTRIBUTE_BOUNDS if both_blank
48
+
49
+ { lower: normalize_lower(lower), upper: normalize_upper(upper) }.freeze
50
+ end
51
+
31
52
  # @param raw [String, nil] e.g. "1..*", "0..1", "1", "*", nil
32
53
  # @return [Hash{Symbol=>String}] `{ lower:, upper: }` always
33
54
  # populated; never nil. Empty/nil input returns the UML default.
@@ -38,17 +59,14 @@ module Ea
38
59
  return parse_range(stripped) if stripped.include?("..")
39
60
 
40
61
  # Bare unlimited token (e.g. "*") means "many" — lower bound
41
- # is unspecified, which UML renders as 0..-1. Returning
42
- # `{ lower: "-1", upper: "-1" }` here would be invalid:
43
- # LiteralInteger cannot hold -1.
62
+ # is unspecified, which renders as 0..*.
44
63
  return defaults if UNLIMITED_TOKENS.include?(stripped.downcase)
45
64
 
46
- single = normalize_bound(stripped)
47
- { lower: single, upper: single }
65
+ { lower: stripped, upper: stripped }
48
66
  end
49
67
 
50
- # Normalise an upper-bound token: `*` / `unbounded` → `-1`
51
- # (UML LiteralUnlimitedNatural wire form).
68
+ # Normalise an upper-bound token: `*` / `unbounded` → `*`
69
+ # (the LiteralUnlimitedNatural wire form EA also uses).
52
70
  # @param raw [String, Integer, nil]
53
71
  # @return [String]
54
72
  def normalize_upper(raw)
@@ -57,17 +75,21 @@ module Ea
57
75
  stripped = raw.to_s.strip
58
76
  return DEFAULT_UPPER if stripped.empty?
59
77
 
60
- UNLIMITED_TOKENS.include?(stripped.downcase) ? "-1" : stripped
78
+ UNLIMITED_TOKENS.include?(stripped.downcase) ? "*" : stripped
61
79
  end
62
80
 
63
81
  # Normalise a lower-bound token: empty/nil → "0" (UML default).
82
+ # Unlimited tokens also → "0" — a lower bound serializes as
83
+ # uml:LiteralInteger, which cannot hold `*` or `-1`.
64
84
  # @param raw [String, Integer, nil]
65
85
  # @return [String]
66
86
  def normalize_lower(raw)
67
87
  return DEFAULT_LOWER if raw.nil?
68
88
 
69
89
  stripped = raw.to_s.strip
70
- stripped.empty? ? DEFAULT_LOWER : stripped
90
+ return DEFAULT_LOWER if stripped.empty?
91
+
92
+ UNLIMITED_TOKENS.include?(stripped.downcase) ? DEFAULT_LOWER : stripped
71
93
  end
72
94
 
73
95
  # ---- Internal helpers ----
@@ -78,17 +100,7 @@ module Ea
78
100
 
79
101
  def parse_range(stripped)
80
102
  lower, upper = stripped.split("..", 2)
81
- { lower: normalize_bound(lower), upper: normalize_bound(upper) }
82
- end
83
-
84
- # A single bound token (one side of `..` or a bare scalar).
85
- # Empty / `*` → UML unlimited (`-1`).
86
- def normalize_bound(token)
87
- return "-1" if token.nil?
88
- return "-1" if token.strip.empty?
89
-
90
- stripped = token.strip
91
- UNLIMITED_TOKENS.include?(stripped.downcase) ? "-1" : stripped
103
+ { lower: normalize_lower(lower), upper: normalize_upper(upper) }
92
104
  end
93
105
  end
94
106
  end
@@ -9,6 +9,11 @@ module Ea
9
9
  # - ID-derivation helpers (`xmi_id_for`, `end_xmi_id_for`) backed by
10
10
  # {GuidFormat}
11
11
  # - delegated database lookups (objects, packages, attributes, etc.)
12
+ # - the walk's position ordering (`sorted_by_position`), applied at
13
+ # the lookup for operation parameters so the UML tree and the
14
+ # extension block cannot disagree about their order. Attributes
15
+ # and operations are deliberately left for their callers to
16
+ # order — see `attributes_for`.
12
17
  #
13
18
  # The {Ea::Qea::Database} already maintains its own lookup indexes
14
19
  # (object-by-id, connectors-by-object, attributes-by-object, etc.).
@@ -33,6 +38,17 @@ module Ea
33
38
  GuidFormat.ea_guid_to_xmi_id(record.ea_guid, prefix: prefix)
34
39
  end
35
40
 
41
+ # Whether a record can anchor a synthesized id. EA derives a
42
+ # synthesized id's tail from its owner's GUID, so an owner
43
+ # without one yields a tailless id. Both the UML tree and the
44
+ # extension block ask this, so they cannot disagree about
45
+ # whether to emit the thing that id would have named.
46
+ # @param record [#ea_guid]
47
+ # @return [Boolean]
48
+ def identifiable?(record)
49
+ !record&.ea_guid.to_s.strip.empty?
50
+ end
51
+
36
52
  # @param connector_xmi_id [String]
37
53
  # @param side [Symbol] :source or :destination
38
54
  # @return [String]
@@ -66,6 +82,11 @@ module Ea
66
82
  database.objects_in_package(package_id)
67
83
  end
68
84
 
85
+ # Deliberately NOT ordered here, unlike params_for_operation:
86
+ # the extension block emits attributes in database order while
87
+ # the LI-bound preallocation sorts them by name, so each caller
88
+ # orders for itself. Sorting at this lookup would move export
89
+ # bytes.
69
90
  # @param object_id [Integer]
70
91
  # @return [Array<Ea::Qea::Models::EaAttribute>]
71
92
  def attributes_for(object_id)
@@ -78,10 +99,23 @@ module Ea
78
99
  database.operations_for_object(object_id)
79
100
  end
80
101
 
102
+ # t_operationparams is loaded with an unordered SELECT, and both
103
+ # the UML tree and the extension block emit parameters in Pos
104
+ # order. Ordering here means no caller can obtain them unordered.
81
105
  # @param operation_id [Integer]
82
106
  # @return [Array<Ea::Qea::Models::EaOperationParam>]
83
107
  def params_for_operation(operation_id)
84
- database.operation_params_for(operation_id)
108
+ sorted_by_position(database.operation_params_for(operation_id))
109
+ end
110
+
111
+ # ---- Ordering -----------------------------------------------------
112
+
113
+ # EA emits a record's children in tree-position order (TPos / Pos),
114
+ # ties broken by name.
115
+ # @param records [Array<Ea::Qea::Models::BaseModel>]
116
+ # @return [Array<Ea::Qea::Models::BaseModel>]
117
+ def sorted_by_position(records)
118
+ records.sort_by { |record| [record.sort_position, record.name.to_s] }
85
119
  end
86
120
 
87
121
  # Connectors where the object is on either side.