elkrb 1.0.2 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +364 -0
- data/LICENSE +25 -0
- data/README.adoc +695 -557
- data/exe/elkrb +36 -2
- data/lib/elkrb/atomic_destination.rb +53 -0
- data/lib/elkrb/best_effort_write.rb +49 -0
- data/lib/elkrb/cli/layout_flag_options.rb +54 -0
- data/lib/elkrb/cli.rb +155 -82
- data/lib/elkrb/command_resolver.rb +186 -0
- data/lib/elkrb/commands/batch_command.rb +28 -5
- data/lib/elkrb/commands/convert_command.rb +22 -52
- data/lib/elkrb/commands/diagram_command.rb +234 -102
- data/lib/elkrb/commands/options_command.rb +87 -0
- data/lib/elkrb/commands/render_command.rb +6 -1
- data/lib/elkrb/commands/validate_command.rb +146 -91
- data/lib/elkrb/errors.rb +20 -1
- data/lib/elkrb/format_sniffer.rb +362 -0
- data/lib/elkrb/geometry/bezier.rb +14 -4
- data/lib/elkrb/geometry/point.rb +5 -6
- data/lib/elkrb/geometry/rectangle.rb +2 -2
- data/lib/elkrb/graph/deep_stringify_keys.rb +30 -0
- data/lib/elkrb/graph/edge.rb +89 -3
- data/lib/elkrb/graph/graph.rb +43 -15
- data/lib/elkrb/graph/label.rb +16 -2
- data/lib/elkrb/graph/node.rb +13 -2
- data/lib/elkrb/graph/node_constraints.rb +139 -8
- data/lib/elkrb/graph/normalize_option_keys.rb +39 -0
- data/lib/elkrb/graph/port.rb +29 -21
- data/lib/elkrb/graph/read_only_mapping.rb +21 -0
- data/lib/elkrb/graphviz_wrapper.rb +152 -44
- data/lib/elkrb/layout/algorithm_registry.rb +65 -8
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +74 -74
- data/lib/elkrb/layout/algorithms/box.rb +47 -20
- data/lib/elkrb/layout/algorithms/disco.rb +150 -61
- data/lib/elkrb/layout/algorithms/fixed.rb +70 -10
- data/lib/elkrb/layout/algorithms/force.rb +145 -86
- data/lib/elkrb/layout/algorithms/layered/bk/aligner.rb +104 -0
- data/lib/elkrb/layout/algorithms/layered/bk/compactor.rb +87 -0
- data/lib/elkrb/layout/algorithms/layered/bk/view.rb +103 -0
- data/lib/elkrb/layout/algorithms/layered/bk_node_placer.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/components.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/crossing_minimizer.rb +306 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +118 -49
- data/lib/elkrb/layout/algorithms/layered/discovery_order.rb +75 -0
- data/lib/elkrb/layout/algorithms/layered/dummy_slot.rb +31 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +188 -48
- data/lib/elkrb/layout/algorithms/layered/layer_constraints.rb +82 -0
- data/lib/elkrb/layout/algorithms/layered/layer_sweep.rb +195 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +214 -36
- data/lib/elkrb/layout/algorithms/layered/orthogonal/gap_solver.rb +219 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/hyper_edge_segment.rb +152 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/junction_points.rb +58 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/segment_order.rb +134 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal_router.rb +253 -0
- data/lib/elkrb/layout/algorithms/layered/port_order.rb +188 -0
- data/lib/elkrb/layout/algorithms/layered/port_spread.rb +42 -0
- data/lib/elkrb/layout/algorithms/layered.rb +548 -13
- data/lib/elkrb/layout/algorithms/libavoid.rb +951 -187
- data/lib/elkrb/layout/algorithms/mrtree.rb +585 -65
- data/lib/elkrb/layout/algorithms/radial.rb +188 -29
- data/lib/elkrb/layout/algorithms/random.rb +131 -17
- data/lib/elkrb/layout/algorithms/rectpacking.rb +28 -40
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +57 -100
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +78 -84
- data/lib/elkrb/layout/algorithms/stress.rb +391 -84
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +95 -31
- data/lib/elkrb/layout/algorithms/vertiflex.rb +12 -47
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +49 -12
- data/lib/elkrb/layout/constraints/base_constraint.rb +3 -15
- data/lib/elkrb/layout/constraints/constraint_processor.rb +9 -23
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +22 -13
- data/lib/elkrb/layout/constraints/layer_constraint.rb +10 -33
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +94 -50
- data/lib/elkrb/layout/edge_router.rb +533 -337
- data/lib/elkrb/layout/hierarchical_processor.rb +19 -276
- data/lib/elkrb/layout/java_random.rb +59 -0
- data/lib/elkrb/layout/label_placer.rb +165 -157
- data/lib/elkrb/layout/layout_engine.rb +46 -23
- data/lib/elkrb/layout/node_index.rb +149 -0
- data/lib/elkrb/layout/polyomino/component_compactor.rb +138 -0
- data/lib/elkrb/layout/polyomino/footprint.rb +94 -0
- data/lib/elkrb/layout/polyomino/grid.rb +106 -0
- data/lib/elkrb/layout/polyomino/packer.rb +95 -0
- data/lib/elkrb/layout/polyomino/piece.rb +52 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +131 -44
- data/lib/elkrb/layout/spore/body.rb +65 -0
- data/lib/elkrb/layout/spore/depth_first_compaction.rb +127 -0
- data/lib/elkrb/layout/spore/edge_cost.rb +69 -0
- data/lib/elkrb/layout/spore/gap.rb +76 -0
- data/lib/elkrb/layout/spore/geometry.rb +104 -0
- data/lib/elkrb/layout/spore/overlap_sweep.rb +90 -0
- data/lib/elkrb/layout/spore/placement.rb +57 -0
- data/lib/elkrb/layout/spore/rays.rb +119 -0
- data/lib/elkrb/layout/spore/spanning_tree.rb +53 -0
- data/lib/elkrb/layout/spore/triangulation.rb +159 -0
- data/lib/elkrb/layout_flags.rb +69 -0
- data/lib/elkrb/options/decimal.rb +50 -0
- data/lib/elkrb/options/element_options.rb +43 -0
- data/lib/elkrb/options/elk_padding.rb +20 -14
- data/lib/elkrb/options/k_vector.rb +6 -7
- data/lib/elkrb/options/k_vector_chain.rb +12 -20
- data/lib/elkrb/options/option_map.rb +85 -0
- data/lib/elkrb/options/registry.rb +335 -0
- data/lib/elkrb/options/resolver.rb +151 -0
- data/lib/elkrb/options/spellings.rb +62 -0
- data/lib/elkrb/options/unhonoured_report.rb +194 -0
- data/lib/elkrb/parsers/elkt/lexer.rb +309 -0
- data/lib/elkrb/parsers/elkt/parser.rb +424 -0
- data/lib/elkrb/parsers/elkt/resolver.rb +142 -0
- data/lib/elkrb/parsers/elkt/token.rb +40 -0
- data/lib/elkrb/parsers/elkt_parser.rb +32 -230
- data/lib/elkrb/serializers/dot_serializer.rb +342 -54
- data/lib/elkrb/serializers/elkt_serializer.rb +386 -155
- data/lib/elkrb/version.rb +1 -1
- data/lib/elkrb.rb +60 -61
- metadata +71 -49
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/Gemfile +0 -13
- data/Rakefile +0 -64
- data/benchmarks/README.md +0 -172
- data/benchmarks/elkjs_benchmark.js +0 -140
- data/benchmarks/elkrb_benchmark.rb +0 -145
- data/benchmarks/fixtures/graphs.json +0 -10777
- data/benchmarks/generate_report.rb +0 -241
- data/benchmarks/generate_test_graphs.rb +0 -154
- data/benchmarks/results/elkrb_results.json +0 -280
- data/benchmarks/results/elkrb_summary.json +0 -285
- data/elkrb.gemspec +0 -39
- data/examples/dot_export_demo.rb +0 -133
- data/examples/hierarchical_graph.rb +0 -19
- data/examples/layout_constraints_demo.rb +0 -272
- data/examples/port_constraints_demo.rb +0 -291
- data/examples/self_loop_demo.rb +0 -391
- data/examples/simple_graph.rb +0 -50
- data/examples/spline_routing_demo.rb +0 -235
- data/lib/elkrb/graph/layout_options.rb +0 -247
- data/sig/elkrb/constraints.rbs +0 -114
- data/sig/elkrb/geometry.rbs +0 -61
- data/sig/elkrb/graph.rbs +0 -112
- data/sig/elkrb/layout.rbs +0 -107
- data/sig/elkrb/options.rbs +0 -81
- data/sig/elkrb.rbs +0 -32
|
@@ -1,247 +1,49 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../errors"
|
|
4
|
+
require_relative "elkt/token"
|
|
5
|
+
require_relative "elkt/lexer"
|
|
6
|
+
require_relative "elkt/resolver"
|
|
7
|
+
require_relative "elkt/parser"
|
|
8
|
+
|
|
3
9
|
module Elkrb
|
|
4
10
|
module Parsers
|
|
5
|
-
# Parser for ELKT (ELK Text) format
|
|
6
|
-
#
|
|
11
|
+
# Parser for the ELKT (ELK Text) format.
|
|
12
|
+
#
|
|
13
|
+
# Tokenizes, then parses by recursive descent, then resolves endpoints and
|
|
14
|
+
# edge ids. Raises Elkrb::ParseError, carrying a line and column, for input
|
|
15
|
+
# that is not valid ELKT.
|
|
16
|
+
#
|
|
17
|
+
# The returned Hash is the full ELK graph. `Graph.from_hash` reads the
|
|
18
|
+
# parts the models have today: ids, sizes, positions, layout options,
|
|
19
|
+
# children, labels, ports and edges. Labels nested inside a label and edge
|
|
20
|
+
# section options are parsed but the models drop them.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# Elkrb::Parsers::ElktParser.parse("node n1\nnode n2\nedge n1 -> n2\n")
|
|
24
|
+
# # => {id: "root", layoutOptions: {}, children: [...], edges: [...]}
|
|
25
|
+
#
|
|
26
|
+
# @param input [String] ELKT source
|
|
27
|
+
# @return [Hash] an ELK graph Hash
|
|
28
|
+
# @raise [Elkrb::ParseError] if the input is not valid ELKT
|
|
7
29
|
class ElktParser
|
|
8
|
-
|
|
30
|
+
# Kept so old code that rescues ElktParser::ParseError keeps working.
|
|
31
|
+
ParseError = Elkrb::ParseError
|
|
9
32
|
|
|
10
33
|
def self.parse(input)
|
|
11
|
-
new(input).parse
|
|
34
|
+
Elkt::Parser.new(Elkt::Lexer.new(input).tokenize).parse
|
|
12
35
|
end
|
|
13
36
|
|
|
37
|
+
# The instance API of the old parser. It is kept so
|
|
38
|
+
# `ElktParser.new(source).parse` still works.
|
|
14
39
|
def initialize(input)
|
|
15
40
|
@input = input
|
|
16
|
-
@line_number = 0
|
|
17
|
-
@graph = {
|
|
18
|
-
id: "root",
|
|
19
|
-
layoutOptions: {},
|
|
20
|
-
children: [],
|
|
21
|
-
edges: [],
|
|
22
|
-
}
|
|
23
|
-
@node_map = {}
|
|
24
|
-
@current_context = [@graph]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def parse
|
|
28
|
-
lines = preprocess(@input)
|
|
29
|
-
parse_lines(lines)
|
|
30
|
-
@graph
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def preprocess(input)
|
|
36
|
-
# Remove block comments
|
|
37
|
-
input = input.gsub(%r{/\*.*?\*/}m, "")
|
|
38
|
-
|
|
39
|
-
# Split into lines and remove line comments
|
|
40
|
-
lines = input.split("\n").map do |line|
|
|
41
|
-
line.sub(%r{//.*$}, "").strip
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Remove empty lines
|
|
45
|
-
lines.reject(&:empty?)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def parse_lines(lines)
|
|
49
|
-
lines.each_with_index do |line, idx|
|
|
50
|
-
@line_number = idx + 1
|
|
51
|
-
parse_line(line)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def parse_line(line)
|
|
56
|
-
case line
|
|
57
|
-
when /^algorithm:\s*(.+)$/
|
|
58
|
-
parse_algorithm($1.strip)
|
|
59
|
-
when /^direction:\s*(.+)$/
|
|
60
|
-
parse_direction($1.strip)
|
|
61
|
-
when /^([\w.]+):\s*(.+)$/
|
|
62
|
-
parse_property($1.strip, $2.strip)
|
|
63
|
-
when /^node\s+(\w+)\s*\{/
|
|
64
|
-
parse_node_with_block($1)
|
|
65
|
-
when /^node\s+(\w+)\s*$/
|
|
66
|
-
parse_simple_node($1)
|
|
67
|
-
when /^\}/
|
|
68
|
-
close_block
|
|
69
|
-
when /^edge\s+(.+)$/
|
|
70
|
-
parse_edge($1.strip)
|
|
71
|
-
when /^layout\s*\[\s*(.+?)\s*\]/
|
|
72
|
-
parse_layout_block($1)
|
|
73
|
-
when /^port\s+(\w+)/
|
|
74
|
-
parse_port(line)
|
|
75
|
-
when /^label\s+"([^"]+)"/
|
|
76
|
-
parse_label($1)
|
|
77
|
-
else
|
|
78
|
-
# Ignore unknown lines or handle as needed
|
|
79
|
-
end
|
|
80
41
|
end
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
current_node[:layoutOptions] ||= {}
|
|
84
|
-
current_node[:layoutOptions]["elk.algorithm"] = value
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def parse_direction(value)
|
|
88
|
-
current_node[:layoutOptions] ||= {}
|
|
89
|
-
current_node[:layoutOptions]["elk.direction"] = value
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def parse_property(key, value)
|
|
93
|
-
current_node[:layoutOptions] ||= {}
|
|
94
|
-
|
|
95
|
-
# Convert property name to ELK format
|
|
96
|
-
elk_key = key.start_with?("elk.") ? key : "elk.#{key}"
|
|
97
|
-
|
|
98
|
-
# Parse value
|
|
99
|
-
parsed_value = parse_value(value)
|
|
100
|
-
current_node[:layoutOptions][elk_key] = parsed_value
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def parse_value(value)
|
|
104
|
-
case value
|
|
105
|
-
when /^-?\d+\.\d+$/
|
|
106
|
-
value.to_f
|
|
107
|
-
when /^-?\d+$/
|
|
108
|
-
value.to_i
|
|
109
|
-
when /^true$/i
|
|
110
|
-
true
|
|
111
|
-
when /^false$/i
|
|
112
|
-
false
|
|
113
|
-
else
|
|
114
|
-
value
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def parse_simple_node(node_id)
|
|
119
|
-
node = create_node(node_id)
|
|
120
|
-
current_node[:children] ||= []
|
|
121
|
-
current_node[:children] << node
|
|
122
|
-
@node_map[node_id] = node
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def parse_node_with_block(node_id)
|
|
126
|
-
node = create_node(node_id)
|
|
127
|
-
current_node[:children] ||= []
|
|
128
|
-
current_node[:children] << node
|
|
129
|
-
@node_map[node_id] = node
|
|
130
|
-
@current_context.push(node)
|
|
131
|
-
end
|
|
43
|
+
attr_reader :input
|
|
132
44
|
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
id: node_id,
|
|
136
|
-
width: 40,
|
|
137
|
-
height: 40,
|
|
138
|
-
layoutOptions: {},
|
|
139
|
-
}
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def parse_layout_block(content)
|
|
143
|
-
# Parse layout [ size: 30, 30 ] or similar
|
|
144
|
-
if content =~ /size:\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)/
|
|
145
|
-
width = $1.to_f
|
|
146
|
-
height = $2.to_f
|
|
147
|
-
current_node[:width] = width
|
|
148
|
-
current_node[:height] = height
|
|
149
|
-
elsif content =~ /position:\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)/
|
|
150
|
-
x = $1.to_f
|
|
151
|
-
y = $2.to_f
|
|
152
|
-
current_node[:x] = x
|
|
153
|
-
current_node[:y] = y
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def parse_edge(edge_spec)
|
|
158
|
-
# Parse: node1 -> node2
|
|
159
|
-
# Or: edge_id: node1 -> node2
|
|
160
|
-
# Or: node1.port1 -> node2.port2
|
|
161
|
-
|
|
162
|
-
if edge_spec =~ /^(\w+):\s*(.+)$/
|
|
163
|
-
edge_id = $1
|
|
164
|
-
edge_spec = $2
|
|
165
|
-
else
|
|
166
|
-
edge_id = generate_edge_id
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
unless edge_spec =~ /^(.+?)\s*->\s*(.+)$/
|
|
170
|
-
raise ParseError,
|
|
171
|
-
"Invalid edge syntax at line #{@line_number}: #{edge_spec}"
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
source_spec = $1.strip
|
|
175
|
-
target_spec = $2.strip
|
|
176
|
-
|
|
177
|
-
edge = create_edge(edge_id, source_spec, target_spec)
|
|
178
|
-
current_node[:edges] ||= []
|
|
179
|
-
current_node[:edges] << edge
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def create_edge(edge_id, source_spec, target_spec)
|
|
183
|
-
source_parts = source_spec.split(".")
|
|
184
|
-
target_parts = target_spec.split(".")
|
|
185
|
-
|
|
186
|
-
edge = {
|
|
187
|
-
id: edge_id,
|
|
188
|
-
sources: [source_parts[0]],
|
|
189
|
-
targets: [target_parts[0]],
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
# Add port references if present
|
|
193
|
-
if source_parts.length > 1
|
|
194
|
-
edge[:sourcePort] = source_parts[1]
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
if target_parts.length > 1
|
|
198
|
-
edge[:targetPort] = target_parts[1]
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
edge
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def parse_port(line)
|
|
205
|
-
# Parse: port port_id { ... }
|
|
206
|
-
if line =~ /^port\s+(\w+)\s*\{/
|
|
207
|
-
port_id = $1
|
|
208
|
-
port = {
|
|
209
|
-
id: port_id,
|
|
210
|
-
layoutOptions: {},
|
|
211
|
-
}
|
|
212
|
-
current_node[:ports] ||= []
|
|
213
|
-
current_node[:ports] << port
|
|
214
|
-
@current_context.push(port)
|
|
215
|
-
elsif line =~ /^port\s+(\w+)\s*$/
|
|
216
|
-
port_id = $1
|
|
217
|
-
port = {
|
|
218
|
-
id: port_id,
|
|
219
|
-
}
|
|
220
|
-
current_node[:ports] ||= []
|
|
221
|
-
current_node[:ports] << port
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def parse_label(text)
|
|
226
|
-
label = {
|
|
227
|
-
text: text,
|
|
228
|
-
width: text.length * 7.0,
|
|
229
|
-
height: 14.0,
|
|
230
|
-
}
|
|
231
|
-
current_node[:labels] ||= []
|
|
232
|
-
current_node[:labels] << label
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def close_block
|
|
236
|
-
@current_context.pop if @current_context.length > 1
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def current_node
|
|
240
|
-
@current_context.last
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def generate_edge_id
|
|
244
|
-
"e#{current_node[:edges]&.length || 0}"
|
|
45
|
+
def parse
|
|
46
|
+
self.class.parse(@input)
|
|
245
47
|
end
|
|
246
48
|
end
|
|
247
49
|
end
|