diagrammatron 0.4.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 393c2881f870f2371e5af2e9a33fcfac3f82d9c3da69f7f377d87606b7230557
4
- data.tar.gz: '019f726f35066af4fc209d61a2adbf36de1c6393d0089924912ca6c0e73baef0'
3
+ metadata.gz: 8c51dc3c8502bd1626d2a78bccd7e772e60fb25a26e6327879f42144849db598
4
+ data.tar.gz: 78548d0ad0af86031afecb81bf4fedca42da58420d0de3a0d5063f07cb68538e
5
5
  SHA512:
6
- metadata.gz: 737105dcbe2c8dc6d636833079d2313be3b5f2c66d86d252127664948365b92f976622b65019e9b5ddd1f7b760f35a15fef9e30fdd749fae99441a31fb03bb8e
7
- data.tar.gz: c091a6a5f862353afb8f730dbf8b35cf226c5627e6d352f1ad8366ca6d03cb338e61264c22876948806afdc0965f1df0ee81223f07750f61c73a8cd171ab0538
6
+ metadata.gz: 03d48ecd6c7e586007dcb470655fff6814e21d32f1921dcb828b35602bea208e48f58d9ba2ea5cdf5f16fb8f633a242a9d43a599c21fbf61d5ffaa631282c827
7
+ data.tar.gz: 5ed263d065a49d69a031fa140ac0159d58301da26f4b51807573e5b8eefbeca6238301f6953cca5c8b44423a83fec18903a7e9d960a5b3fcf1d4702f1b671233
@@ -6,54 +6,32 @@
6
6
 
7
7
  require_relative '../lib/common'
8
8
  require 'optparse'
9
- require 'yaml'
10
9
  require 'set'
11
- require 'pathname'
12
10
 
13
11
 
14
12
  def work_copy(src, quiet)
15
13
  work = { edges: {}, nodes: [] }
16
- # Expected nodes, edges. Other pass-through.
17
14
  label2idx = {}
18
15
  errors = false
19
16
  edge_nodes = Set.new
20
- edges = src.fetch('edges', [])
21
- unedges = []
17
+ edges = src['edges']
22
18
  selfedges = []
23
19
  edges.each_index do |k|
24
20
  edge = edges[k]
25
- labels = edge.fetch('between', [])
26
- if labels.nil? || labels.empty?
27
- unedges.push(k)
28
- elsif labels.size == 2
29
- if labels.first == labels.last
30
- selfedges.push(k)
31
- else
32
- edge_nodes.add labels.first
33
- edge_nodes.add labels.last
34
- work[:edges][k] = { idx: k, between: [ labels[0], labels[1] ] }
35
- end
21
+ labels = edge['between']
22
+ if labels.first == labels.last
23
+ selfedges.push(k)
36
24
  else
37
- aargh "Edge #{k + 1} does not have two labels in 'between'"
38
- errors = true
25
+ edge_nodes.add labels.first
26
+ edge_nodes.add labels.last
27
+ work[:edges][k] = { idx: k, between: [ labels[0], labels[1] ] }
39
28
  end
40
29
  end
41
30
  labeled_nodes = Set.new
42
- unlabeled = []
43
- nodes = src.fetch('nodes', [])
31
+ nodes = src['nodes']
44
32
  subsets = {}
45
33
  nodes.each_index do |k|
46
34
  node = nodes[k]
47
- unless node.key? 'sid'
48
- aargh "Node without sid: #{node.fetch('label', k + 1)}"
49
- errors = true
50
- next
51
- end
52
- unless node.key?('xo') && node.key?('yo')
53
- aargh "Node without xo or yo: #{node.fetch('label', k + 1)}"
54
- errors = true
55
- next
56
- end
57
35
  sid = node['sid']
58
36
  subsets[sid] = [] unless subsets.key? sid
59
37
  subsets[sid].push(k)
@@ -63,10 +41,6 @@ def work_copy(src, quiet)
63
41
  xo: node['xo'] * 2, # Make room for edge coordinates.
64
42
  yo: node['yo'] * 2
65
43
  })
66
- unless node.key? 'label'
67
- unlabeled.push k
68
- next
69
- end
70
44
  label = node['label']
71
45
  if label2idx.key?(label) && edge_nodes.member?(label)
72
46
  aargh "Edge-referred label used twice: #{label}"
@@ -80,12 +54,14 @@ def work_copy(src, quiet)
80
54
  aargh "Edges refer to missing node labels: #{missing.to_a.join(' ')}"
81
55
  errors = true
82
56
  end
57
+ unless selfedges.empty?
58
+ info = selfedges.map { |k| "#{edges[k]['between'].first} (#{k})" }
59
+ aargh "Edges from node to itself: #{info.join(' ')}"
60
+ errors = true
61
+ end
83
62
  return nil if errors
84
63
  unused = labeled_nodes - edge_nodes
85
- [ [ unused.to_a, 'unconnected labeled nodes' ],
86
- [ unlabeled, 'unlabeled nodes' ],
87
- [ selfedges, 'edges from node to itself' ],
88
- [ unedges, 'edges without end-points' ]
64
+ [ [ unused.to_a, 'unconnected labeled nodes' ]
89
65
  ].each do |x|
90
66
  next if quiet || x.first.empty?
91
67
  aargh("Note, #{x.last}: #{x.first.join(' ')}")
@@ -125,7 +101,7 @@ Segment = Struct.new(:vertical, :cc, :range, :edge_index, :at_node, :segment_ind
125
101
  node_subset.each do |n|
126
102
  node = work[:nodes][n]
127
103
  next unless cc == node[ck]
128
- (0..1).each do |k|
104
+ 2.times do |k|
129
105
  return true if range[i0] < node[rk] && node[rk] < range[i1]
130
106
  next if at_node[k]
131
107
  return true if range[k] == node[rk]
@@ -158,6 +134,10 @@ Segment = Struct.new(:vertical, :cc, :range, :edge_index, :at_node, :segment_ind
158
134
  def increase?
159
135
  range[0] < range[1]
160
136
  end
137
+
138
+ def within(s)
139
+ s.range.min < range.min && range.max < s.range.max
140
+ end
161
141
  end
162
142
 
163
143
  def segment(x0, y0, x1, y1)
@@ -415,40 +395,16 @@ def path_order_at_side(a, b, conn)
415
395
  d
416
396
  end
417
397
 
418
- def segment_order(a, b)
419
- d = a[1] <=> b[1]
398
+ def length_order(a, b)
399
+ d = a[2].length <=> b[2].length
420
400
  return d unless d.zero?
421
- case a[1]
422
- when 0 # Ascending on length, range minimum. =|
423
- d = a[2].length <=> b[2].length
424
- return d unless d.zero?
425
- d = a[2].range.min <=> b[2].range.min
426
- return d unless d.zero?
427
- when 1 # Top left, vertical, bottom right: -|_
428
- d = a[2].range.min <=> b[2].range.min # Ascend on minimum.
429
- return d unless d.zero?
430
- d = a[2].length <=> b[2].length # Ascend on length.
431
- return d unless d.zero?
432
- when 2 # From top right, down, bottom left: _|-
433
- d = a[2].range.max <=> b[2].range.max # Descend on maximum.
434
- return -d unless d.zero?
435
- d = a[2].length <=> b[2].length # Ascend on length.
436
- return d unless d.zero?
437
- when 3 # Descending on length, range maximum. |=
438
- d = a[2].length <=> b[2].length
439
- return -d unless d.zero?
440
- d = a[2].range.max <=> b[2].range.max
441
- return -d unless d.zero?
442
- end
443
401
  a[0].edge_index <=> b[0].edge_index
444
402
  end
445
403
 
446
- def zigzag_order(from_right_up_to_left, from_left_up_to_right)
447
- # Eventually interleave the two sets, if it makes sense.
448
- out = []
449
- from_right_up_to_left.each { |x| out.push x }
450
- from_left_up_to_right.each { |x| out.push x }
451
- out
404
+ def min_order(a, b)
405
+ d = a[2].range.min <=> b[2].range.min
406
+ return d unless d.zero?
407
+ a[0].edge_index <=> b[0].edge_index
452
408
  end
453
409
 
454
410
  def overlaps_set(c, others)
@@ -458,47 +414,59 @@ def overlaps_set(c, others)
458
414
  false
459
415
  end
460
416
 
461
- def group_minimal_offsets(group)
462
- return 0 if group.empty?
463
- off = { 1 => [] }
464
- cands = off.keys
465
- group.each do |a|
466
- fit = false
467
- cands.each do |k|
468
- next if overlaps_set(a[2], off[k])
469
- off[k].push(a)
470
- fit = true
471
- break
417
+ def layered_order(u_shaped, z_shaped)
418
+ u_shaped.sort! { |a, b| length_order(a, b) }
419
+ z_shaped.sort! { |a, b| min_order(a, b) }
420
+ out = []
421
+ layer = []
422
+ until u_shaped.empty?
423
+ rejects = []
424
+ found = false
425
+ u_shaped.each do |a|
426
+ unless overlaps_set(a[2], layer)
427
+ found = true
428
+ # Creates crossing by putting in front of shorter segment fully within?
429
+ rejects.each do |r|
430
+ if r[2].within(a[2])
431
+ found = false
432
+ break
433
+ end
434
+ end
435
+ end
436
+ if found
437
+ layer.push a
438
+ found = false
439
+ else
440
+ rejects.push a
441
+ end
442
+ end
443
+ u_shaped = rejects
444
+ unless layer.empty?
445
+ out.concat layer
446
+ out.push nil
447
+ layer = []
472
448
  end
473
- next if fit
474
- cands.push(cands.last + 1)
475
- off[cands.last] = [ a ]
476
449
  end
477
- off.each_pair do |offset, fitting|
478
- fitting.each do |sg|
479
- sg[0].offset = offset
450
+ until z_shaped.empty?
451
+ rejects = []
452
+ found = false
453
+ z_shaped.each do |a|
454
+ if overlaps_set(a[2], layer)
455
+ rejects.push a
456
+ else
457
+ layer.push a
458
+ found = true
459
+ end
460
+ end
461
+ z_shaped = rejects
462
+ unless layer.empty?
463
+ out.concat layer
464
+ out.push nil
465
+ layer = []
480
466
  end
481
467
  end
482
- off.keys.max
483
- end
484
-
485
- def group_stacked_offsets(group)
486
- return 0 if group.empty?
487
- group.each_index do |k|
488
- group[k][0].offset = k + 1
489
- end
490
- return group.size
491
- %q(
492
- # This produces narrower gaps but they may be less clear.
493
- prev = group[0]
494
- prev[0].offset = 1
495
- (1...group.size).each do |k|
496
- g = group[k]
497
- g[0].offset = prev[0].offset + (g[2].range_overlap(prev[2]) ? 1 : 0)
498
- prev = g
499
- end
500
- prev[0].offset
501
- )
468
+ out.concat(layer) unless layer.empty?
469
+ out
502
470
  end
503
471
 
504
472
  def direct_range(paths)
@@ -631,36 +599,22 @@ def place_edges(work)
631
599
  end
632
600
  gaps.each_value do |direction|
633
601
  direction.each_value do |gap|
634
- #gap.sort! { |a, b| segment_order(a, b) }
635
602
  gleft = gap.select { |a| a[1].zero? }
636
- gleft.sort! { |a, b| segment_order(a, b) }
603
+ grul = gap.select { |a| a[1] == 1 }
604
+ glur = gap.select { |a| a[1] == 2 }
637
605
  gright = gap.select { |a| a[1] == 3 }
638
- gright.sort! { |a, b| segment_order(a, b) }
639
- gright.reverse!
640
- grul = gap.select() { |a| a[1] == 1 }
641
- grul.sort! { |a, b| segment_order(a, b) }
642
- glur = gap.select() { |a| a[1] == 2 }
643
- glur.sort! { |a, b| segment_order(a, b) }
644
- gmiddle = zigzag_order(grul, glur)
645
- gmiddle.each do |s|
646
- s[1] = 1
647
- end
648
- gap = gleft + gmiddle + gright
649
- c = [
650
- group_minimal_offsets(gleft),
651
- group_stacked_offsets(gmiddle),
652
- 0,
653
- group_minimal_offsets(gright)
654
- ]
655
- before = [ 0 ]
656
- denominator = 1 + c[0]
657
- (1...c.size).each do |k|
658
- denominator += c[k]
659
- before.push(c[k - 1] + before.last)
660
- end
661
- gap.each do |sg|
662
- sg[0].offset = c[sg[1]] + 1 - sg[0].offset if sg[1] > 1
663
- sg[0].offset = Rational(sg[0].offset + before[sg[1]], denominator)
606
+ all = layered_order(gleft, grul)
607
+ all.push nil
608
+ all.concat(layered_order(gright, glur).reverse)
609
+ # Give each rational offset using layer index + 1 and layer count + 2.
610
+ denominator = 2 + all.count(&:nil?)
611
+ layer = 1
612
+ all.each do |sg|
613
+ if sg.nil?
614
+ layer += 1
615
+ else
616
+ sg[0].offset = Rational(layer, denominator)
617
+ end
664
618
  end
665
619
  end
666
620
  end
@@ -759,6 +713,8 @@ end
759
713
  def main
760
714
  input = nil
761
715
  output = nil
716
+ input_schema = 'edges'
717
+ output_schema = 'place'
762
718
  quiet = false
763
719
  parser = OptionParser.new do |opts|
764
720
  opts.summary_indent = ' '
@@ -779,13 +735,21 @@ def main
779
735
  $stdout.puts %(#{opts}
780
736
 
781
737
  Input YAML file is expected to be the output of diagrammatron-nodes.
738
+
739
+ Input YAML file schema is returned by:
740
+ diagrammatron-schema #{input_schema}
741
+
742
+ Output YAML file schema is returned by:
743
+ diagrammatron-schema #{output_schema}
744
+
745
+ There can be other fields present but they are ignored and retained.
782
746
  )
783
747
  exit 0
784
748
  end
785
749
  end
786
750
  parser.parse! ARGV
787
751
 
788
- doc = load_source(input)
752
+ doc = load_verified(input, input_schema)
789
753
  return 2 if doc.nil?
790
754
 
791
755
  begin
@@ -797,7 +761,7 @@ Input YAML file is expected to be the output of diagrammatron-nodes.
797
761
 
798
762
  place_edges(work)
799
763
  prepare_output(doc, work)
800
- dump_result(output, YAML.dump(doc, line_width: 1_000_000), 4)
764
+ save_verified(output, doc, 4, output_schema)
801
765
  end
802
766
 
803
767
  exit(main) if (defined? $unit_test).nil?
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021, 2022 Ismo Kärkkäinen
4
+ # Copyright © 2021-2023 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/common'
8
8
  require 'optparse'
9
- require 'pathname'
10
9
 
11
10
 
12
11
  def template(name = nil)
@@ -28,8 +27,8 @@ def main
28
27
  end
29
28
  opts.on('-h', '--help', 'Print this help and exit.') do
30
29
  $stdout.puts %(#{opts}
31
- This is used to easily access template files included in the gem without need
32
- to clone the original repository.
30
+ This is used to easily access template files included in the gem without
31
+ the need to clone the original repository.
33
32
 
34
33
  Without arguments, lists all templates, template root, and content files
35
34
  included in the gem.
@@ -41,7 +40,7 @@ Given a name of a included file, saves it to --output.
41
40
  end
42
41
  parser.parse! ARGV
43
42
 
44
- if ARGV.size.zero?
43
+ if ARGV.empty?
45
44
  # List all files in templates directory.
46
45
  Dir.entries(template).sort.each do |name|
47
46
  next if name.start_with? '.'
@@ -6,9 +6,7 @@
6
6
 
7
7
  require_relative '../lib/common'
8
8
  require 'optparse'
9
- require 'yaml'
10
9
  require 'set'
11
- require 'pathname'
12
10
 
13
11
 
14
12
  def vertical(work)
@@ -139,7 +137,7 @@ def shifts(count)
139
137
  side = (count / 2.0).ceil
140
138
  side = ((side / 4) + ((side % 4).positive? ? 1 : 0)) * 4
141
139
  xs = Array.new(side) { |index| Integer((index - side / 2).round) }
142
- (0..3).each { |k| xs.push(-xs[k]) }
140
+ 4.times { |k| xs.push(-xs[k]) }
143
141
  ys = Array.new(xs)
144
142
  xs.rotate!(side / 2 - 1)
145
143
  ys.rotate!(side - 1) # First half-way to offset with xs, then like xs.
@@ -281,36 +279,23 @@ def work_copy(src, quiet)
281
279
  errors = false
282
280
  edge_nodes = Set.new
283
281
  edges = src.fetch('edges', [])
284
- unedges = []
285
282
  selfedges = []
286
283
  edges.each_index do |k|
287
284
  edge = edges[k]
288
- labels = edge.fetch('between', [])
289
- if labels.nil? || labels.empty?
290
- unedges.push(k)
291
- elsif labels.size == 2
292
- if labels.first == labels.last
293
- selfedges.push(k)
294
- else
295
- edge_nodes.add labels.first
296
- edge_nodes.add labels.last
297
- work[:edges].push({ idx: k, between: [ labels[0], labels[1] ] })
298
- end
285
+ labels = edge['between']
286
+ if labels.first == labels.last
287
+ selfedges.push(k)
299
288
  else
300
- aargh "Edge #{k + 1} does not have two labels in 'between'"
301
- errors = true
289
+ edge_nodes.add labels.first
290
+ edge_nodes.add labels.last
291
+ work[:edges].push({ idx: k, between: [ labels[0], labels[1] ] })
302
292
  end
303
293
  end
304
294
  labeled_nodes = Set.new
305
- unlabeled = []
306
295
  nodes = src.fetch('nodes', [])
307
296
  nodes.each_index do |k|
308
297
  work[:nodes].push({ idx: k })
309
298
  node = nodes[k]
310
- unless node.key? 'label'
311
- unlabeled.push k
312
- next
313
- end
314
299
  label = node['label']
315
300
  if label2idx.key?(label) && edge_nodes.member?(label)
316
301
  aargh "Edge-referred label used twice: #{label}"
@@ -327,9 +312,7 @@ def work_copy(src, quiet)
327
312
  return nil if errors
328
313
  unused = labeled_nodes - edge_nodes
329
314
  [ [ unused.to_a, 'unconnected labeled nodes' ],
330
- [ unlabeled, 'unlabeled nodes' ],
331
- [ selfedges, 'edges from node to itself' ],
332
- [ unedges, 'edges without end-points' ]
315
+ [ selfedges, 'edges from node to itself' ]
333
316
  ].each do |x|
334
317
  next if quiet || x.first.empty?
335
318
  aargh("Note, #{x.last}: #{x.first.join(' ')}")
@@ -351,6 +334,8 @@ def prepare_output(doc, work)
351
334
  end
352
335
 
353
336
  def main
337
+ input_schema = 'nodes'
338
+ output_schema = 'edges'
354
339
  input = nil
355
340
  output = nil
356
341
  algo = 'pathlength'
@@ -378,22 +363,15 @@ def main
378
363
  $stdout.puts %(
379
364
  Algorithm names are: #{$algorithms.keys.sort.join(' ')}
380
365
 
381
- Input YAML file is expected to be like:
382
- ---
383
- nodes:
384
- - label: something
385
- - label: another
386
- - ignored: "Since no label. Still placed."
387
- - label: "Unused and ok. Still placed."
388
- edges:
389
- - between: [ something, another ]
390
- - between: [ something, something ] # Ignored.
391
- - between: [ ] # Ignored.
392
- - ignored: "Since no between."
393
- ...
366
+ Input YAML file schema is returned by:
367
+ diagrammatron-schema #{input_schema}
368
+
394
369
  There can be other fields present but they are ignored. The nodes will
395
370
  receive values xo and yo that indicate horizontal and vertical coordinates.
396
371
 
372
+ Output YAML file schema is returned by:
373
+ diagrammatron-schema #{output_schema}
374
+
397
375
  Output is the input file with 'xo', 'yo' and 'sid' added to each node.
398
376
  The 'xo' and 'yo' indicate which unique x- and y-coordinate the value is.
399
377
  The 'sid' indicates the sub-diagram consisting of connected nodes.
@@ -408,7 +386,7 @@ The 'sid' indicates the sub-diagram consisting of connected nodes.
408
386
  end
409
387
  algo = $algorithms[algo]
410
388
 
411
- doc = load_source(input)
389
+ doc = load_verified(input, input_schema)
412
390
  return 2 if doc.nil?
413
391
 
414
392
  begin
@@ -420,7 +398,7 @@ The 'sid' indicates the sub-diagram consisting of connected nodes.
420
398
 
421
399
  algo.call(work)
422
400
  prepare_output(doc, work)
423
- dump_result(output, YAML.dump(doc, line_width: 1_000_000), 4)
401
+ save_verified(output, doc, 4, output_schema)
424
402
  end
425
403
 
426
404
  exit(main) if (defined? $unit_test).nil?
@@ -1,18 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright © 2021, 2022 Ismo Kärkkäinen
4
+ # Copyright © 2021-2023 Ismo Kärkkäinen
5
5
  # Licensed under Universal Permissive License. See LICENSE.txt.
6
6
 
7
7
  require_relative '../lib/common'
8
8
  require 'optparse'
9
- require 'yaml'
10
9
  require 'set'
11
- require 'pathname'
12
10
 
13
- def info(msg, return_value = nil)
11
+
12
+ def info(msg)
14
13
  $stderr.puts(msg) unless $QUIET
15
- return_value
16
14
  end
17
15
 
18
16
  BoundingBox = Struct.new(:xmin, :ymin, :xmax, :ymax) do
@@ -91,22 +89,19 @@ end
91
89
 
92
90
  def work_copy(src)
93
91
  work = { edges: {}, nodes: {} }
94
- # Expected nodes, edges. Other pass-through.
95
- nodes = src.fetch('nodes', [])
92
+ nodes = src['nodes']
96
93
  nodes.each_index do |k|
97
94
  node = nodes[k]
98
- sid = node.fetch('sid', nil)
99
- xo = node.fetch('xo', nil)
100
- yo = node.fetch('yo', nil)
101
- next if sid.nil? || xo.nil? || yo.nil?
95
+ sid = node['sid']
96
+ xo = node['xo']
97
+ yo = node['yo']
102
98
  work[:nodes][sid] = work[:nodes].fetch(sid, []).push(Node.new(k, sid, xo, yo))
103
99
  end
104
- edges = src.fetch('edges', [])
100
+ edges = src['edges']
105
101
  edges.each_index do |k|
106
102
  edge = edges[k]
107
- path = edge.fetch('path', nil)
108
- sid = edge.fetch('sid', nil)
109
- next if path.nil? || sid.nil?
103
+ path = edge['path']
104
+ sid = edge['sid']
110
105
  work[:edges][sid] = work[:edges].fetch(sid, []).push(Edge.new(k, sid, path))
111
106
  end
112
107
  work[:subsets] = work[:nodes].keys.to_set.merge(work[:edges].keys.to_set).to_a
@@ -140,7 +135,7 @@ def area_order(bbs)
140
135
  end
141
136
  order.sort! do |a, b|
142
137
  d = area_compare(a[1], b[1])
143
- (d != 0) ? d : (a[0] <=> b[0])
138
+ d.zero? ? (a[0] <=> b[0]) : d
144
139
  end
145
140
  order
146
141
  end
@@ -362,6 +357,7 @@ $QUIET = false
362
357
  def main
363
358
  input = nil
364
359
  output = nil
360
+ input_output_schema = 'edges'
365
361
  algo = 'tallwide'
366
362
  parser = OptionParser.new do |opts|
367
363
  opts.summary_indent = ' '
@@ -389,10 +385,13 @@ def main
389
385
  $stdout.puts %(
390
386
  Algorithm names are: #{$algorithms.keys.sort.join(' ')}
391
387
 
392
- Input YAML file is expected to be the output of diagrammatron-edges.
388
+ Input and output YAML file schema is returned by:
389
+ diagrammatron-schema #{input_output_schema}
390
+
391
+ There can be other fields present but they are ignored and retained.
393
392
 
394
- Output is the input file with 'xo' and 'yo' modified so that the sub-diagrams
395
- do not overlap.
393
+ Output is the input file with 'xo', 'yo', and 'path' modified to remove
394
+ overlap between sub-diagrams and edges.
396
395
  )
397
396
  exit 0
398
397
  end
@@ -415,7 +414,7 @@ do not overlap.
415
414
  end
416
415
  algo = $algorithms[algo]
417
416
 
418
- doc = load_source(input)
417
+ doc = load_verified(input, input_output_schema)
419
418
  return 2 if doc.nil?
420
419
 
421
420
  begin
@@ -427,7 +426,7 @@ do not overlap.
427
426
 
428
427
  algo.call(work)
429
428
  prepare_output(doc, work)
430
- dump_result(output, YAML.dump(doc, line_width: 1_000_000), 4)
429
+ save_verified(output, doc, 4, input_output_schema)
431
430
  end
432
431
 
433
432
  exit(main) if (defined? $unit_test).nil?