ea 0.6.106 → 0.6.107
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/lib/ea/sources/qea/diagram_builder.rb +1 -0
- data/lib/ea/sources/qea/property_builder.rb +11 -1
- data/lib/ea/svg/ea_emitter/compartment/constraints.rb +5 -14
- data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +18 -9
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +28 -8
- data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +28 -0
- data/lib/ea/svg/ea_emitter/elements.rb +2 -1
- data/lib/ea/svg/ea_emitter/marker/diamond.rb +17 -23
- data/lib/ea/version.rb +1 -1
- data/memory/deviations.md +3 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 931597d4cff3d8f491f63e12f3ed1a5ba31f8cc68d535b060222ca704388bda7
|
|
4
|
+
data.tar.gz: 70054ffff1db544c415879495386b4b45c7a1dafb88dfcc7d680325f37e4d019
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8881133d7a2443adc4dcde58dd1290b532cea8c575f22b274dc818985d6540e22d11927f0aada28a5b27f5e76748be1fd2874bbde30018c09e3dd6422a3f3263
|
|
7
|
+
data.tar.gz: c91711384dc7ebefa97bdff19923697a5eacf8f22c362dfe1ee737e8d281a3c08224d63d66d411ff77ac7edb503db1245f7d40b443c82c4468d09885fa9487f8
|
|
@@ -123,6 +123,7 @@ module Ea
|
|
|
123
123
|
def build_element(obj_row, diagram_row)
|
|
124
124
|
@current_obj_row = obj_row
|
|
125
125
|
style_hash = DiagramStyleParser.parse(obj_row.objectstyle)
|
|
126
|
+
style_hash[:stored_attribute_order] = true
|
|
126
127
|
Ea::Model::DiagramElement.new(
|
|
127
128
|
id: IdNormalizer.synthetic("de", diagram_row.diagram_id,
|
|
128
129
|
obj_row.instance_id),
|
|
@@ -16,7 +16,8 @@ module Ea
|
|
|
16
16
|
|
|
17
17
|
def build_all_for(owner_object)
|
|
18
18
|
attrs = database.attributes_for_object(owner_object.ea_object_id) || []
|
|
19
|
-
attrs.
|
|
19
|
+
attrs.each_with_index.sort_by { |row, index| attribute_sort_key(row, index) }
|
|
20
|
+
.map { |row, _index| build_one(row, owner_object) }
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def build_one(row, owner_object)
|
|
@@ -48,6 +49,15 @@ module Ea
|
|
|
48
49
|
|
|
49
50
|
private
|
|
50
51
|
|
|
52
|
+
# Equal explicit Pos values use name order in EA's published diagrams.
|
|
53
|
+
# Missing positions retain source order before the explicit zero cohort.
|
|
54
|
+
def attribute_sort_key(row, index)
|
|
55
|
+
return [row.sort_position, 0, "", "", index] if row.pos.nil?
|
|
56
|
+
|
|
57
|
+
name = row.name.to_s
|
|
58
|
+
[row.sort_position, 1, name.downcase, name, index]
|
|
59
|
+
end
|
|
60
|
+
|
|
51
61
|
# EA stores some bounds as literal text ("n"); those render
|
|
52
62
|
# verbatim in attribute lines while computed-unbounded bounds
|
|
53
63
|
# render as "*" (verified against EA-published SVGs).
|
|
@@ -11,26 +11,17 @@ module Ea
|
|
|
11
11
|
# content and suppresses them when they would overflow the
|
|
12
12
|
# stored box height (never truncates rows).
|
|
13
13
|
module Constraints
|
|
14
|
-
DEFAULT_TEXT_COLOR = "#000000"
|
|
15
|
-
|
|
16
14
|
module_function
|
|
17
15
|
|
|
18
16
|
def render(context)
|
|
19
17
|
return nil unless context.constraints&.any?
|
|
20
|
-
return nil unless context.geometry.
|
|
21
|
-
|
|
22
|
-
first_y = context.geometry.tagged_value_first_y
|
|
23
|
-
line_h = context.size + 6
|
|
24
|
-
last_y = first_y + (context.constraints.size - 1) * line_h
|
|
25
|
-
return nil if last_y + 4 > context.bounds.y + context.bounds.height
|
|
18
|
+
return nil unless context.geometry.constraints_first_y
|
|
26
19
|
|
|
27
|
-
Element::
|
|
28
|
-
context.
|
|
29
|
-
|
|
30
|
-
first_y: first_y,
|
|
31
|
-
family: context.family, size: context.size,
|
|
32
|
-
fill: DEFAULT_TEXT_COLOR
|
|
20
|
+
divider = Element::DividerRenderer.render(
|
|
21
|
+
context.bounds, y: context.geometry.constraints_divider_y,
|
|
22
|
+
stroke: context.stroke, stroke_width: context.stroke_width
|
|
33
23
|
)
|
|
24
|
+
"#{divider}\n#{Element::ConstraintRenderer.render_compartment(context)}"
|
|
34
25
|
end
|
|
35
26
|
end
|
|
36
27
|
end
|
|
@@ -87,7 +87,7 @@ module Ea
|
|
|
87
87
|
# Pass a `lookup` (any callable returning a Classifier for
|
|
88
88
|
# an id) to enable inherited-property namespace prefixing.
|
|
89
89
|
def self.lines_for(classifier, lookup: nil,
|
|
90
|
-
|
|
90
|
+
exclude_association_ids: nil, stored_order: false)
|
|
91
91
|
props = displayable_properties(classifier, exclude_association_ids)
|
|
92
92
|
return [] unless props
|
|
93
93
|
|
|
@@ -95,19 +95,14 @@ module Ea
|
|
|
95
95
|
if stereotypes.size == 1 && props.all? { |p| property_stereotype(p) }
|
|
96
96
|
# EA renders ONE compartment stereotype header for a
|
|
97
97
|
# uniformly-stereotyped attribute set (e.g. «Property»
|
|
98
|
-
# above GML property rows)
|
|
99
|
-
# alphabetically - verified against EA-published SVGs.
|
|
98
|
+
# above GML property rows).
|
|
100
99
|
header = "«#{canonical_stereotype(stereotypes.first)}»"
|
|
101
|
-
[header] + props
|
|
100
|
+
[header] + ordered_properties(props, stored_order, case_sensitive: true).map do |prop|
|
|
102
101
|
AttributeLineBuilder.new(prop, host: classifier,
|
|
103
102
|
lookup: lookup).to_s
|
|
104
103
|
end
|
|
105
104
|
else
|
|
106
|
-
|
|
107
|
-
# insensitive) — corpus-verified: 991 boxes strictly
|
|
108
|
-
# alphabetical vs 24 strictly stored-Pos order.
|
|
109
|
-
props.sort_by { |p| [p.name.to_s.downcase, p.name.to_s] }
|
|
110
|
-
.flat_map do |prop|
|
|
105
|
+
ordered_properties(props, stored_order).flat_map do |prop|
|
|
111
106
|
lines = []
|
|
112
107
|
stereotype = property_stereotype(prop)
|
|
113
108
|
lines << "«#{stereotype}»" if stereotype
|
|
@@ -117,6 +112,20 @@ module Ea
|
|
|
117
112
|
end
|
|
118
113
|
end
|
|
119
114
|
|
|
115
|
+
# QEA properties arrive in stored Pos order. Association ends retain
|
|
116
|
+
# their alphabetical fallback rather than using connector Pos values.
|
|
117
|
+
def self.ordered_properties(props, stored_order, case_sensitive: false)
|
|
118
|
+
alphabetical = lambda do |prop|
|
|
119
|
+
name = prop.name.to_s
|
|
120
|
+
case_sensitive ? [name] : [name.downcase, name]
|
|
121
|
+
end
|
|
122
|
+
return props.sort_by(&alphabetical) unless stored_order
|
|
123
|
+
|
|
124
|
+
own, ends = props.partition { |prop| !prop.association_id }
|
|
125
|
+
own + ends.sort_by(&alphabetical)
|
|
126
|
+
end
|
|
127
|
+
private_class_method :ordered_properties
|
|
128
|
+
|
|
120
129
|
STEREOTYPE_DISPLAY = {
|
|
121
130
|
"property" => "Property",
|
|
122
131
|
"featuretype" => "FeatureType",
|
|
@@ -150,14 +150,30 @@ module Ea
|
|
|
150
150
|
divider_y + size + (attr_first_offset || 7)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
# OCL constraints follow the regular content with a divider and caption.
|
|
154
|
+
# Suppress the whole compartment when its last row cannot fit.
|
|
155
|
+
def constraints_first_y
|
|
156
|
+
return nil unless constraints_count.to_i.positive?
|
|
157
|
+
|
|
158
|
+
first_y = constraints_caption_y
|
|
159
|
+
first_y if first_y + constraints_count * (size + 5) + 4 <= bounds.y + bounds.height
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def constraints_caption_y
|
|
163
|
+
(regular_content_bottom_y || raw_attr_first_y) + size + 13
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def constraints_divider_y
|
|
167
|
+
constraints_first_y - size - 7 if constraints_first_y
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def constraints_bottom_y
|
|
171
|
+
constraints_first_y + constraints_count * (size + 5) if constraints_first_y
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Tagged values follow the last visible content compartment.
|
|
158
175
|
def tagged_value_first_y
|
|
159
|
-
return nil unless tagged_values_count.to_i.positive?
|
|
160
|
-
constraints_count.to_i.positive?
|
|
176
|
+
return nil unless tagged_values_count.to_i.positive?
|
|
161
177
|
|
|
162
178
|
(content_bottom_y || raw_attr_first_y) + size + 5
|
|
163
179
|
end
|
|
@@ -169,6 +185,10 @@ module Ea
|
|
|
169
185
|
# the autosize gate must not treat the empty-compartment
|
|
170
186
|
# anchor as content.
|
|
171
187
|
def content_bottom_y
|
|
188
|
+
[regular_content_bottom_y, constraints_bottom_y].compact.max
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def regular_content_bottom_y
|
|
172
192
|
bottoms = []
|
|
173
193
|
bottoms << attr_bottom_y if attr_lines_count.to_i.positive?
|
|
174
194
|
bottoms << enum_literals_bottom_y if enum_literals_count.to_i.positive?
|
|
@@ -185,4 +205,4 @@ module Ea
|
|
|
185
205
|
end
|
|
186
206
|
end
|
|
187
207
|
end
|
|
188
|
-
end
|
|
208
|
+
end
|
|
@@ -29,6 +29,34 @@ module Ea
|
|
|
29
29
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def self.render_compartment(context)
|
|
33
|
+
rows = context.constraints.each_with_index.map do |constraint, idx|
|
|
34
|
+
compartment_row(constraint, context, idx)
|
|
35
|
+
end
|
|
36
|
+
%(<g style="#{Style::TEXT_GROUP}">\n#{([compartment_caption(context)] + rows).join("\n")}\n</g>)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.compartment_row(constraint, context, index)
|
|
40
|
+
build_text(context.bounds.x + 5,
|
|
41
|
+
context.geometry.constraints_first_y + (index + 1) * (context.size + 5),
|
|
42
|
+
"{#{constraint.name}}", context.family, context.size, context.text_fill)
|
|
43
|
+
end
|
|
44
|
+
private_class_method :compartment_row
|
|
45
|
+
|
|
46
|
+
def self.compartment_caption(context)
|
|
47
|
+
TextRenderer.new(content: "constraints", x: caption_x(context),
|
|
48
|
+
y: context.geometry.constraints_first_y, family: context.family,
|
|
49
|
+
size: context.size, fill: context.text_fill, style: "italic").to_svg
|
|
50
|
+
end
|
|
51
|
+
private_class_method :compartment_caption
|
|
52
|
+
|
|
53
|
+
def self.caption_x(context)
|
|
54
|
+
width = TextRenderer.estimate_width("constraints", context.size, nil,
|
|
55
|
+
family: context.family, style: "italic").round
|
|
56
|
+
context.bounds.x + ((context.bounds.width - width) / 2).floor
|
|
57
|
+
end
|
|
58
|
+
private_class_method :caption_x
|
|
59
|
+
|
|
32
60
|
def self.build_text(x, y, content, family, size, fill)
|
|
33
61
|
TextRenderer.new(content: content, x: x, y: y,
|
|
34
62
|
family: family, size: size, fill: fill).to_svg
|
|
@@ -135,7 +135,8 @@ module Ea
|
|
|
135
135
|
classifier.properties.to_a.any? { |pr| !pr.association_id }))
|
|
136
136
|
Element::AttributeRenderer.lines_for(
|
|
137
137
|
classifier, lookup: attribute_lookup,
|
|
138
|
-
|
|
138
|
+
exclude_association_ids: drawn_association_ids,
|
|
139
|
+
stored_order: element.style&.fetch(:stored_attribute_order, false)
|
|
139
140
|
)
|
|
140
141
|
else
|
|
141
142
|
[]
|
|
@@ -4,27 +4,11 @@ module Ea
|
|
|
4
4
|
module Svg
|
|
5
5
|
module EaEmitter
|
|
6
6
|
module Marker
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# WHOLE-END SELECTION: EA flags the whole end either through
|
|
11
|
-
# t_connector.DestAccess (1 shared / 2 composite -> whole at
|
|
12
|
-
# the TARGET; corpus-verified 333EFCCF: PersonalRight_E is
|
|
13
|
-
# the DestAccess=2 end and every diamond sits there) or
|
|
14
|
-
# through the Connector_Type itself ("Aggregation"/
|
|
15
|
-
# "Composition" -> whole at the SOURCE; basic.qea's
|
|
16
|
-
# composition diagram). DestAccess wins when both appear.
|
|
17
|
-
# Without relationship data the direction heuristic applies
|
|
18
|
-
# ("Destination -> Source" -> whole at target).
|
|
19
|
-
#
|
|
20
|
-
# ARROW: dest-flagged aggregations draw a base-12 height-15
|
|
21
|
-
# open arrow stacked on the diamond tip pointing into the
|
|
22
|
-
# whole (333EFCCF: 6 diamonds + 6 path arrows). Type-inferred
|
|
23
|
-
# source-side aggregations draw the diamond only (basic.qea:
|
|
24
|
-
# 17 diamonds, 0 arrows).
|
|
7
|
+
# EA endpoint fields select the diamond end; direction selects
|
|
8
|
+
# the arrow end for target-owned aggregations.
|
|
25
9
|
class Diamond < Kind
|
|
26
10
|
def self.handles?(effective_type)
|
|
27
|
-
|
|
11
|
+
%w[Aggregation Composition].include?(effective_type)
|
|
28
12
|
end
|
|
29
13
|
|
|
30
14
|
def self.specs_for(connector, source, target, before_target, after_source, relationship: nil)
|
|
@@ -36,10 +20,8 @@ module Ea
|
|
|
36
20
|
base: whole_base)]
|
|
37
21
|
return specs unless whole_at_target
|
|
38
22
|
|
|
39
|
-
specs
|
|
40
|
-
|
|
41
|
-
base: whole_base)
|
|
42
|
-
specs
|
|
23
|
+
specs.concat(direction_specs(connector, source, target,
|
|
24
|
+
before_target, after_source))
|
|
43
25
|
end
|
|
44
26
|
|
|
45
27
|
def self.whole_at_target?(connector, relationship)
|
|
@@ -58,6 +40,18 @@ module Ea
|
|
|
58
40
|
relationship &&
|
|
59
41
|
%w[shared composite].include?(relationship.source_aggregation.to_s)
|
|
60
42
|
end
|
|
43
|
+
|
|
44
|
+
def self.direction_specs(connector, source, target, before_target,
|
|
45
|
+
after_source)
|
|
46
|
+
if connector.direction == "Destination -> Source"
|
|
47
|
+
return [Registry::Spec.new(shape: :arrow, anchor: source,
|
|
48
|
+
base: after_source)]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
[Registry::Spec.new(shape: :arrow, anchor: target,
|
|
52
|
+
base: before_target)]
|
|
53
|
+
end
|
|
54
|
+
private_class_method :direction_specs
|
|
61
55
|
end
|
|
62
56
|
end
|
|
63
57
|
end
|
data/lib/ea/version.rb
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Focused Ruby deviations
|
|
2
|
+
|
|
3
|
+
- Issue 135 attribute ordering: RuboCop Metrics/MethodLength flags the 14-line real-QEA fixture builder, and Metrics/BlockLength flags its six-example public pipeline regression group. Keep the fixture together and the directly related controls in one group; splitting them would obscure the input collections and ordering contract.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.107
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -1012,6 +1012,7 @@ files:
|
|
|
1012
1012
|
- lib/ea/xml_escape.rb
|
|
1013
1013
|
- lib/tasks/fonts.rake
|
|
1014
1014
|
- lib/tasks/svg_bench.rake
|
|
1015
|
+
- memory/deviations.md
|
|
1015
1016
|
- reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png
|
|
1016
1017
|
- reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png
|
|
1017
1018
|
- reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png
|