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
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "yaml"
|
|
5
5
|
|
|
6
|
+
require_relative "../errors"
|
|
7
|
+
require_relative "../best_effort_write"
|
|
8
|
+
|
|
6
9
|
module Elkrb
|
|
7
10
|
module Commands
|
|
8
11
|
# Command for validating ELK graph structure
|
|
@@ -19,68 +22,74 @@ module Elkrb
|
|
|
19
22
|
errors = validate_graph(graph)
|
|
20
23
|
|
|
21
24
|
if errors.empty?
|
|
22
|
-
|
|
25
|
+
marker = stream_marker($stdout, "✅", "[OK]")
|
|
26
|
+
puts "#{marker} #{@file} is valid"
|
|
23
27
|
else
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
summary = "#{@file} has #{errors.length} error(s)"
|
|
29
|
+
marker = stream_marker($stderr, "❌", "[ERROR]")
|
|
30
|
+
bullet = stream_marker($stderr, "•", "-")
|
|
31
|
+
BestEffortWrite.attempt do
|
|
32
|
+
$stderr.puts "#{marker} #{summary}:" # rubocop:disable Style/StderrPuts
|
|
33
|
+
errors.each do |error|
|
|
34
|
+
$stderr.puts " #{bullet} #{error}" # rubocop:disable Style/StderrPuts
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
raise Elkrb::CommandFailed, summary
|
|
27
38
|
end
|
|
28
39
|
end
|
|
29
40
|
|
|
30
41
|
private
|
|
31
42
|
|
|
43
|
+
def stream_marker(stream, unicode, ascii)
|
|
44
|
+
encoding = stream.external_encoding
|
|
45
|
+
return unicode unless encoding
|
|
46
|
+
|
|
47
|
+
unicode.encode(encoding)
|
|
48
|
+
unicode
|
|
49
|
+
rescue EncodingError
|
|
50
|
+
ascii
|
|
51
|
+
end
|
|
52
|
+
|
|
32
53
|
def load_any_format(file)
|
|
33
54
|
raise ArgumentError, "File not found: #{file}" unless File.exist?(file)
|
|
34
55
|
|
|
35
|
-
require_relative "../graph/graph"
|
|
36
56
|
content = File.read(file)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Elkrb::Graph::Graph.from_yaml(content)
|
|
44
|
-
when ".elkt"
|
|
45
|
-
require_relative "../parsers/elkt_parser"
|
|
46
|
-
Elkrb::Parsers::ElktParser.parse(content)
|
|
47
|
-
else
|
|
48
|
-
detect_and_parse(content)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Convert to hash for validation
|
|
52
|
-
if graph.is_a?(Hash)
|
|
53
|
-
graph
|
|
54
|
-
else
|
|
55
|
-
JSON.parse(graph.to_json,
|
|
56
|
-
symbolize_names: true)
|
|
57
|
+
|
|
58
|
+
case File.extname(file).downcase
|
|
59
|
+
when ".json" then parse_json(content)
|
|
60
|
+
when ".yml", ".yaml" then YAML.safe_load(content)
|
|
61
|
+
when ".elkt" then parse_elkt(content)
|
|
62
|
+
else sniff_format(content)
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
|
|
60
|
-
def
|
|
61
|
-
|
|
66
|
+
def parse_json(content)
|
|
67
|
+
JSON.parse(content, symbolize_names: true)
|
|
68
|
+
end
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Not JSON
|
|
68
|
-
end
|
|
70
|
+
def parse_elkt(content)
|
|
71
|
+
require_relative "../parsers/elkt_parser"
|
|
72
|
+
Elkrb::Parsers::ElktParser.parse(content)
|
|
73
|
+
end
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return Elkrb::Graph::Graph.from_yaml(content)
|
|
73
|
-
rescue Psych::SyntaxError
|
|
74
|
-
# Not YAML
|
|
75
|
-
end
|
|
75
|
+
def sniff_format(content)
|
|
76
|
+
stripped = content.lstrip
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
if stripped.start_with?("{", "[")
|
|
79
|
+
begin
|
|
80
|
+
return parse_json(content)
|
|
81
|
+
rescue JSON::ParserError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
83
84
|
end
|
|
85
|
+
|
|
86
|
+
yaml = YAML.safe_load(content)
|
|
87
|
+
return yaml if yaml.is_a?(Hash) || yaml.is_a?(Array)
|
|
88
|
+
|
|
89
|
+
parse_elkt(content)
|
|
90
|
+
rescue Psych::SyntaxError, Elkrb::ParseError
|
|
91
|
+
raise ArgumentError,
|
|
92
|
+
"Unable to parse input file. Supported formats: JSON, YAML, ELKT"
|
|
84
93
|
end
|
|
85
94
|
|
|
86
95
|
def validate_graph(graph)
|
|
@@ -93,24 +102,25 @@ module Elkrb
|
|
|
93
102
|
# Check required fields
|
|
94
103
|
errors << "Graph missing 'id' field" unless graph[:id] || graph["id"]
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
errors.concat(validate_container(graph))
|
|
106
|
+
|
|
107
|
+
errors
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def validate_container(container, path = nil)
|
|
111
|
+
errors = []
|
|
112
|
+
children = collection(container, :children, path, errors)
|
|
113
|
+
edges = collection(container, :edges, path, errors)
|
|
114
|
+
endpoint_ids = descendant_endpoint_ids(children)
|
|
115
|
+
|
|
116
|
+
errors.concat(duplicate_id_errors(children, edges))
|
|
98
117
|
children.each_with_index do |node, idx|
|
|
99
|
-
errors.concat(validate_node(node,
|
|
118
|
+
errors.concat(validate_node(node, child_path(path, idx)))
|
|
100
119
|
end
|
|
101
|
-
|
|
102
|
-
# Validate edges
|
|
103
|
-
edges = graph[:edges] || graph["edges"] || []
|
|
104
120
|
edges.each_with_index do |edge, idx|
|
|
105
|
-
errors.concat(validate_edge(edge,
|
|
106
|
-
collect_node_ids(graph)))
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Strict mode: additional checks
|
|
110
|
-
if @options[:strict]
|
|
111
|
-
errors.concat(validate_strict(graph))
|
|
121
|
+
errors.concat(validate_edge(edge, edge_path(path, idx), endpoint_ids))
|
|
112
122
|
end
|
|
113
|
-
|
|
123
|
+
errors.concat(validate_strict(container, path)) if @options[:strict]
|
|
114
124
|
errors
|
|
115
125
|
end
|
|
116
126
|
|
|
@@ -132,31 +142,26 @@ module Elkrb
|
|
|
132
142
|
errors << "#{path}: Node '#{node_id}' missing 'width'" unless width
|
|
133
143
|
errors << "#{path}: Node '#{node_id}' missing 'height'" unless height
|
|
134
144
|
|
|
135
|
-
if width &&
|
|
145
|
+
if width && !positive_finite_number?(width)
|
|
136
146
|
errors << "#{path}: Node '#{node_id}' has invalid width: #{width}"
|
|
137
147
|
end
|
|
138
148
|
|
|
139
|
-
if height &&
|
|
149
|
+
if height && !positive_finite_number?(height)
|
|
140
150
|
errors << "#{path}: Node '#{node_id}' has invalid height: #{height}"
|
|
141
151
|
end
|
|
142
152
|
end
|
|
143
153
|
|
|
144
|
-
# Validate nested children
|
|
145
|
-
children = node[:children] || node["children"] || []
|
|
146
|
-
children.each_with_index do |child, idx|
|
|
147
|
-
errors.concat(validate_node(child, "#{path}.children[#{idx}]"))
|
|
148
|
-
end
|
|
149
|
-
|
|
150
154
|
# Validate ports
|
|
151
|
-
ports = node
|
|
155
|
+
ports = collection(node, :ports, path, errors)
|
|
152
156
|
ports.each_with_index do |port, idx|
|
|
153
157
|
errors.concat(validate_port(port, "#{path}.ports[#{idx}]"))
|
|
154
158
|
end
|
|
155
159
|
|
|
160
|
+
errors.concat(validate_container(node, path))
|
|
156
161
|
errors
|
|
157
162
|
end
|
|
158
163
|
|
|
159
|
-
def validate_edge(edge, path,
|
|
164
|
+
def validate_edge(edge, path, valid_ids)
|
|
160
165
|
errors = []
|
|
161
166
|
|
|
162
167
|
errors << "#{path}: Edge must be a Hash" unless edge.is_a?(Hash)
|
|
@@ -180,17 +185,17 @@ module Elkrb
|
|
|
180
185
|
errors << "#{path}: Edge '#{edge_id}' targets must be an array"
|
|
181
186
|
end
|
|
182
187
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
{ source: sources, target: targets }.each do |role, endpoints|
|
|
189
|
+
next unless endpoints.is_a?(Array)
|
|
190
|
+
|
|
191
|
+
endpoints.each do |endpoint|
|
|
192
|
+
matches = valid_ids.fetch(endpoint, 0)
|
|
193
|
+
if matches.zero?
|
|
194
|
+
errors << "#{path}: Edge '#{edge_id}' references unknown " \
|
|
195
|
+
"#{role} node or port '#{endpoint}'"
|
|
196
|
+
elsif matches > 1
|
|
197
|
+
errors << "#{path}: Edge '#{edge_id}' references ambiguous " \
|
|
198
|
+
"#{role} node or port '#{endpoint}'"
|
|
194
199
|
end
|
|
195
200
|
end
|
|
196
201
|
end
|
|
@@ -211,31 +216,81 @@ module Elkrb
|
|
|
211
216
|
errors
|
|
212
217
|
end
|
|
213
218
|
|
|
214
|
-
def validate_strict(graph)
|
|
219
|
+
def validate_strict(graph, path)
|
|
215
220
|
errors = []
|
|
216
221
|
|
|
217
222
|
# Check for layout options
|
|
218
|
-
layout_options = graph
|
|
219
|
-
if layout_options && !layout_options.is_a?(Hash)
|
|
220
|
-
|
|
223
|
+
layout_options = value(graph, :layoutOptions)
|
|
224
|
+
if !layout_options.nil? && !layout_options.is_a?(Hash)
|
|
225
|
+
prefix = path ? "#{path}." : ""
|
|
226
|
+
errors << "#{prefix}layoutOptions must be a Hash"
|
|
221
227
|
end
|
|
222
228
|
|
|
223
229
|
errors
|
|
224
230
|
end
|
|
225
231
|
|
|
226
|
-
def
|
|
227
|
-
|
|
232
|
+
def collection(container, key, path, errors)
|
|
233
|
+
items = value(container, key)
|
|
234
|
+
return [] if items.nil?
|
|
235
|
+
return items if items.is_a?(Array)
|
|
236
|
+
|
|
237
|
+
prefix = path ? "#{path}." : ""
|
|
238
|
+
errors << "#{prefix}#{key} must be an Array"
|
|
239
|
+
[]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def value(hash, key)
|
|
243
|
+
return hash[key] if hash.key?(key)
|
|
244
|
+
|
|
245
|
+
hash[key.to_s]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def child_path(path, index)
|
|
249
|
+
[path, "children[#{index}]"].compact.join(".")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def edge_path(path, index)
|
|
253
|
+
[path, "edges[#{index}]"].compact.join(".")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def positive_finite_number?(number)
|
|
257
|
+
number.is_a?(Numeric) && number.finite? && number.positive?
|
|
258
|
+
end
|
|
228
259
|
|
|
260
|
+
def descendant_endpoint_ids(children, ids = Hash.new(0))
|
|
229
261
|
children.each do |node|
|
|
230
|
-
|
|
231
|
-
ids << node_id if node_id
|
|
262
|
+
next unless node.is_a?(Hash)
|
|
232
263
|
|
|
233
|
-
|
|
234
|
-
|
|
264
|
+
ids[value(node, :id)] += 1 if value(node, :id)
|
|
265
|
+
Array(value(node, :ports)).each do |port|
|
|
266
|
+
id = value(port, :id) if port.is_a?(Hash)
|
|
267
|
+
ids[id] += 1 if id
|
|
268
|
+
end
|
|
269
|
+
descendant_endpoint_ids(Array(value(node, :children)), ids)
|
|
235
270
|
end
|
|
236
|
-
|
|
237
271
|
ids
|
|
238
272
|
end
|
|
273
|
+
|
|
274
|
+
def duplicate_id_errors(children, edges)
|
|
275
|
+
ids = Set.new
|
|
276
|
+
errors = []
|
|
277
|
+
children.each do |node|
|
|
278
|
+
next unless node.is_a?(Hash)
|
|
279
|
+
|
|
280
|
+
record_id(value(node, :id), ids, errors)
|
|
281
|
+
Array(value(node, :ports)).each do |port|
|
|
282
|
+
record_id(value(port, :id), ids, errors) if port.is_a?(Hash)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
edges.each do |edge|
|
|
286
|
+
record_id(value(edge, :id), ids, errors) if edge.is_a?(Hash)
|
|
287
|
+
end
|
|
288
|
+
errors
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def record_id(id, ids, errors)
|
|
292
|
+
errors << "duplicate id: #{id}" if id && !ids.add?(id)
|
|
293
|
+
end
|
|
239
294
|
end
|
|
240
295
|
end
|
|
241
296
|
end
|
data/lib/elkrb/errors.rb
CHANGED
|
@@ -18,13 +18,32 @@ module Elkrb
|
|
|
18
18
|
# Exception raised when graph validation fails
|
|
19
19
|
class ValidationError < Error; end
|
|
20
20
|
|
|
21
|
+
# Exception raised when input cannot be parsed. Carries the source position
|
|
22
|
+
# so a caller can report it without re-parsing the message.
|
|
23
|
+
class ParseError < Error
|
|
24
|
+
attr_reader :line, :column
|
|
25
|
+
|
|
26
|
+
def initialize(message, line: nil, column: nil)
|
|
27
|
+
@line = line
|
|
28
|
+
@column = column
|
|
29
|
+
super(message)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
21
33
|
# Exception raised when an algorithm is not found
|
|
22
34
|
class AlgorithmNotFoundError < Error
|
|
23
35
|
attr_reader :algorithm_name
|
|
24
36
|
|
|
25
37
|
def initialize(algorithm_name)
|
|
26
38
|
@algorithm_name = algorithm_name
|
|
27
|
-
super("
|
|
39
|
+
super("Unknown layout algorithm: #{algorithm_name}")
|
|
28
40
|
end
|
|
29
41
|
end
|
|
42
|
+
|
|
43
|
+
# Raised when a command has already ATTEMPTED to report its own failure and
|
|
44
|
+
# the only thing left to decide is the exit status. Reporting is best-effort:
|
|
45
|
+
# a dead stdout leaves nothing printed and this is still raised, so do not
|
|
46
|
+
# assume the user saw anything. `exe/elkrb` turns it into `exit 1`; a library
|
|
47
|
+
# caller rescues it and keeps its process.
|
|
48
|
+
class CommandFailed < Error; end
|
|
30
49
|
end
|