dogviz 0.0.17 → 0.0.18

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.
data/lib/dogviz.rb CHANGED
@@ -1,689 +1,9 @@
1
- require 'ruby-graphviz'
1
+ require_relative 'dogviz/thing.rb'
2
+ require_relative 'dogviz/container.rb'
3
+ require_relative 'dogviz/logical_container.rb'
2
4
 
3
- module Dogviz
4
- class Process
5
- def initialize(processor, description)
6
- @processor = processor
7
- @description = description
8
- end
9
- def name
10
- @processor.name
11
- end
12
- def description
13
- @description
14
- end
15
- attr_reader :processor
16
- end
17
- module Flowable
18
- def does(action)
19
- Process.new(self, action)
20
- end
21
- end
22
- module Nominator
23
- def nominate(names_to_nominees)
24
- names_to_nominees.each {|name, nominee|
25
- self.class.send(:define_method, name) do
26
- nominee
27
- end
28
- }
29
- end
30
- def nominate_from(nominee_nominator, *nominee_names)
31
- nominee_names.each {|name|
32
- accessor_sym = name.to_s.to_sym
33
- nominate accessor_sym => nominee_nominator.send(accessor_sym)
34
- }
35
- end
36
- end
37
- module Common
38
- def create_id(name, parent)
39
- parts = []
40
- parts << parent.id if parent.respond_to? :id
41
- parts += name.split /\s/
42
- parts.join '_'
43
- end
44
- def root
45
- ancestors.last
46
- end
47
- def ancestors
48
- ancestors = [parent]
49
- loop do
50
- break unless ancestors.last.respond_to?(:parent)
51
- ancestors << ancestors.last.parent
52
- end
53
- ancestors
54
- end
55
- def info(fields)
56
- @info.merge! fields
57
- setup_render_attributes(label: label_with_info)
58
- end
59
- def doclink(url)
60
- setup_render_attributes(URL: url)
61
- end
62
- def label_with_info
63
- lines = [ name ]
64
- @info.each {|k, v|
65
- lines << "#{k}: #{v}"
66
- }
67
- lines.join "\n"
68
- end
69
- def setup_render_attributes(attributes)
70
- @attributes = {} if @attributes.nil?
71
- @attributes.merge!(attributes)
72
- end
73
- def rollup?
74
- @rollup
75
- end
76
- def rollup!
77
- @rollup = true
78
- self
79
- end
80
- def skip!
81
- @skip = true
82
- self
83
- end
5
+ require_relative 'dogviz/graphviz_renderer.rb'
6
+ require_relative 'dogviz/sigma_renderer.rb'
84
7
 
85
- def skip?
86
- @skip
87
- end
88
-
89
- def in_skip?
90
- skip? || under_skip?
91
- end
92
-
93
- def under_skip?
94
- ancestors.any? &:skip?
95
- end
96
-
97
- def under_rollup?
98
- ancestors.any? &:rollup?
99
- end
100
- def in_rollup?
101
- rollup? || under_rollup?
102
- end
103
- def on_top_rollup?
104
- rollup? && !under_rollup?
105
- end
106
- def inherited_render_options
107
- inherited = {}
108
- inherited[:fontname] = parent.render_options[:fontname] if parent.render_options.include?(:fontname)
109
- inherited
110
- end
111
- end
112
- module Parent
113
- def find_all(&matcher)
114
- raise MissingMatchBlockError.new unless block_given?
115
- @by_name.find_all &matcher
116
- end
117
- def find(name=nil, &matcher)
118
- if block_given?
119
- @by_name.find &matcher
120
- else
121
- raise 'Need to provide name or block' if name.nil?
122
- @by_name.lookup name
123
- end
124
- end
125
- def thing(name, options={})
126
- add Thing.new self, name, options
127
- end
128
- def container(name, options={})
129
- add Container.new self, name, options
130
- end
131
- def logical_container(name, options={})
132
- add LogicalContainer.new self, name, options
133
- end
134
- def group(name, options={})
135
- logical_container name, options
136
- end
137
- def add(child)
138
- @children << child
139
- child
140
- end
141
- end
142
-
143
- class Colorizer
144
- def initialize
145
- @i = 0
146
- @colors = %w(#9e0142
147
- #d53e4f
148
- #e45d33
149
- #ed9e61
150
- #762a83
151
- #9970ab
152
- #c6f578
153
- #abdda4
154
- #66c2a5
155
- #3288bd
156
- #5e4fa2)
157
- end
158
-
159
- def next
160
- color = @colors[@i]
161
- @i += 1
162
- @i = 0 unless @i < @colors.length
163
- color
164
- end
165
- end
166
-
167
- class Thing
168
- include Common
169
- include Nominator
170
- include Flowable
171
- attr_reader :parent
172
- attr_reader :name, :id, :pointers, :edge_heads
173
-
174
- @@colorizer = Colorizer.new
175
-
176
- def initialize(parent, name, options = {})
177
- @parent = parent
178
- @name = name
179
- @id = create_id(name, parent)
180
- @pointers = []
181
- @rollup = false
182
- @skip = false
183
- @info = {}
184
- @edge_heads = []
185
-
186
- rollup! if options[:rollup]
187
- options.delete(:rollup)
188
-
189
- @render_options = options
190
- setup_render_attributes({label: name}.merge inherited_render_options)
191
-
192
- parent.register name, self
193
- end
194
-
195
- def points_to_all(*others)
196
- others.each {|other|
197
- points_to other
198
- }
199
- end
200
-
201
- def points_to(other, options = {})
202
- setup_render_edge(other, options)
203
- other
204
- end
205
-
206
- def render(renderer)
207
- do_render_node(renderer) unless in_rollup? || in_skip?
208
- end
209
-
210
- def render_edges(renderer)
211
- pointers.each {|p|
212
- render_pointer p, renderer
213
- }
214
- end
215
-
216
- private
217
-
218
- def do_render_node(renderer)
219
- render_options = @render_options
220
- attributes = @attributes
221
- renderer.render_node(parent, id, render_options, attributes)
222
- end
223
-
224
- def setup_render_edge(other, options)
225
- pointers << {
226
- other: other,
227
- options: {
228
- xlabel: options[:name],
229
- style: options[:style]
230
- }.merge(inherited_render_options)
231
- }
232
-
233
- if options[:colorize] || root.colorize_edges?
234
- edge_color = next_colorizer_color
235
- pointers.last[:options].merge!({
236
- color: edge_color,
237
- fontcolor: edge_color
238
- })
239
- end
240
-
241
- end
242
-
243
- def render_pointer(pointer, renderer)
244
- other = pointer[:other]
245
- while (other.in_rollup? && !other.on_top_rollup?) do
246
- other = other.parent
247
- end
248
- return if other.under_rollup?
249
-
250
- from = self
251
- while (from.in_rollup? && !from.on_top_rollup?) do
252
- from = from.parent
253
- end
254
-
255
- return if from.in_skip?
256
-
257
- return if from == self && from.in_rollup?
258
- return if from == other
259
- return if already_added_connection?(other)
260
-
261
- if other.in_skip?
262
- others = resolve_skipped_others other
263
- else
264
- others = [other]
265
- end
266
-
267
- others.each do |other|
268
- edge_heads << other
269
- render_options = pointer[:options]
270
- renderer.render_edge(from, other, render_options)
271
- end
272
- end
273
-
274
- def already_added_connection?(other)
275
- edge_heads.include? other
276
- end
277
-
278
- def resolve_skipped_others(skipped)
279
- resolved = []
280
- skipped.pointers.each {|pointer|
281
- next_in_line = pointer[:other]
282
- if next_in_line.in_skip?
283
- resolved += resolve_skipped_others next_in_line
284
- else
285
- resolved << next_in_line
286
- end
287
- }
288
- resolved
289
- end
290
-
291
- def next_colorizer_color
292
- @@colorizer.next
293
- end
294
- end
295
-
296
- class Container
297
- include Common
298
- include Nominator
299
- include Parent
300
- attr_reader :parent
301
- attr_reader :name, :id, :node, :render_id, :render_type, :render_options, :children
302
-
303
- def initialize(parent, name, options = {})
304
- @children = []
305
- @by_name = Registry.new name
306
- @parent = parent
307
- @name = name
308
- @id = create_id(name, parent)
309
- @skip = false
310
- @info = {}
311
-
312
- init_rollup options
313
-
314
- setup_render_attributes label: name
315
-
316
- @render_options = options.merge inherited_render_options
317
-
318
- parent.register name, self
319
- end
320
-
321
- def register(name, thing)
322
- @by_name.register name, thing
323
- parent.register name, thing
324
- end
325
-
326
- def render(renderer)
327
- if on_top_rollup?
328
- do_render_node renderer
329
- elsif !under_rollup?
330
- do_render_subgraph renderer
331
- end
332
-
333
- children.each {|c|
334
- c.render renderer
335
- }
336
- end
337
-
338
- def render_edges(renderer)
339
- children.each {|c|
340
- c.render_edges renderer
341
- }
342
- end
343
-
344
- private
345
-
346
- def do_render_subgraph(renderer)
347
- @render_type = :subgraph
348
- render_id = cluster_prefix + id
349
- attributes = @attributes
350
- @render_id = render_id
351
- @subgraph = renderer.render_subgraph(parent, render_id, render_options, attributes)
352
- end
353
-
354
- def do_render_node(renderer)
355
- @render_type = :node
356
- @render_id = id
357
- render_id = @render_id
358
- attributes = @attributes
359
- renderer.render_node(parent, render_id, render_options, attributes)
360
- end
361
-
362
- def init_rollup(options)
363
- @rollup = false
364
- rollup! if options[:rollup]
365
- options.delete(:rollup)
366
- end
367
-
368
- def cluster_prefix
369
- is_cluster = true
370
- if @render_options.has_key? :cluster
371
- is_cluster = @render_options[:cluster]
372
- @render_options.delete :cluster
373
- end
374
- cluster_prefix = (is_cluster ? 'cluster_' : '')
375
- end
376
-
377
- end
378
-
379
- class LogicalContainer < Container
380
- def initialize(parent, name, options)
381
- super parent, name, options.merge(cluster: false)
382
- end
383
- end
384
-
385
- require 'date'
386
-
387
- class GraphvizRenderer
388
- attr_reader :graph
389
-
390
- def initialize(title, hints)
391
- @graph = GraphViz.digraph(title)
392
- @graph[hints]
393
- @subgraphs = {}
394
- @nodes = {}
395
- end
396
-
397
- def render_edge(from, other, options)
398
- edge = graph.add_edges from.id, other.id
399
- options.each { |key, value|
400
- edge[key] = value unless value.nil?
401
- }
402
- edge
403
- end
404
-
405
- def render_node(parent, id, options, attributes)
406
- clean_node_options options
407
- default_options = {:shape => 'box', :style => ''}
408
- node = parent_node(parent).add_nodes(id, default_options.merge(options))
409
- apply_render_attributes node, attributes
410
- end
411
-
412
- def render_subgraph(parent, id, options, attributes)
413
- subgraph = parent_node(parent).add_graph(id, options)
414
- apply_render_attributes subgraph, attributes
415
- @subgraphs[id] = subgraph
416
- subgraph
417
- end
418
-
419
- private
420
-
421
- def clean_node_options(options)
422
- options.delete(:rank)
423
- options.delete(:cluster)
424
- options
425
- end
426
-
427
- def parent_node(parent)
428
- return graph unless parent.respond_to?(:render_id)
429
- node = graph.search_node(parent.render_id)
430
- return node unless node.nil?
431
- subgraph = @subgraphs[parent.render_id]
432
- raise "couldn't find node or graph: #{parent.render_id}, out of graphs: #{graph_ids}" if subgraph.nil?
433
- subgraph
434
- end
435
-
436
- def apply_render_attributes(node, attributes)
437
- attributes.each do |key, value|
438
- node[key] = value
439
- end
440
- end
441
- end
442
-
443
- class Flow
444
- def initialize(sys, name)
445
- @sys = sys
446
- @name = name
447
- @calls = []
448
- end
449
-
450
- def make_connections
451
- calls.each {|from, to, label|
452
- thing_of(from).points_to thing_of(to), label: label
453
- }
454
- end
455
-
456
- def flows(*steps)
457
- from = nil
458
- to = nil
459
- label = nil
460
- steps.each do |step|
461
- if from.nil?
462
- from = ensure_is_thing(step)
463
- elsif label.nil? && step.is_a?(String)
464
- label = step
465
- elsif to.nil?
466
- to = ensure_is_thing(step)
467
- end
468
- unless to.nil?
469
- calls << [from, to, label]
470
- from = to
471
- to = label = nil
472
- end
473
- end
474
- end
475
-
476
- def ensure_is_thing(step)
477
- raise "Expected some thing or process: '#{step}' already got: #{calls}" unless step.is_a?(Thing) || step.is_a?(Process)
478
- step
479
- end
480
-
481
- def output(type_to_file)
482
- type = type_to_file.keys.first
483
- raise "Only support sequence, not: '#{type}'" unless type == :sequence
484
- render.output(type_to_file)
485
- end
486
-
487
- def render
488
- renderer = SequenceRenderer.new(@name)
489
- calls.each do |from, to, label|
490
- renderer.render_edge from, to, {label: label}
491
- end
492
- renderer.rendered
493
- end
494
-
495
- private
496
-
497
- attr_reader :calls, :sys
498
-
499
- def thing_of(it)
500
- return it.processor if it.is_a?(Process)
501
- it
502
- end
503
- end
504
-
505
-
506
- class RenderedSequence
507
- def initialize(lines)
508
- @lines = lines
509
- end
510
- def output(type_to_file)
511
- text = @lines.map(&:strip).join "\n"
512
- File.write type_to_file.values.first, text
513
- text
514
- end
515
- end
516
-
517
- class SequenceRenderer
518
- attr_reader :lines
519
- def initialize(title)
520
- @lines = []
521
- end
522
-
523
- def render_edge(from, other, options)
524
-
525
- detail = options[:label]
526
- receiver_label = other.name
527
- sender_label = from.name
528
- if other.is_a?(Process)
529
- detail = process_annotations(detail, sender_label, receiver_label, other.description)
530
- receiver_label = process_start_label(receiver_label)
531
- elsif from.is_a?(Process)
532
- receiver_label = process_end_label(receiver_label)
533
- end
534
- lines << "#{sender_label} -> #{receiver_label}: #{detail}"
535
- end
536
-
537
- def rendered
538
- RenderedSequence.new lines
539
- end
540
-
541
- private
542
-
543
- def process_start_label(receiver_label)
544
- "+#{receiver_label}"
545
- end
546
-
547
- def process_end_label(receiver_label)
548
- "-#{receiver_label}"
549
- end
550
-
551
- def process_annotations(detail, sender, receiver, process_description)
552
- detail = [detail,
553
- "note right of #{receiver}",
554
- " #{process_description}",
555
- 'end note'].join("\n")
556
- end
557
- end
558
-
559
- class System
560
- include Parent
561
- include Nominator
562
-
563
- attr_reader :render_hints, :title, :children, :graph
564
-
565
- alias :name :title
566
- alias :render_options :render_hints
567
-
568
- def initialize(name, hints = {splines: 'line'})
569
- @children = []
570
- @by_name = Registry.new name
571
- @non_render_hints = remove_dogviz_hints!(hints)
572
- @render_hints = hints
573
- @title = create_title(name)
574
- @rendered = false
575
- end
576
-
577
- def output(*args)
578
- render
579
- out = graph.output *args
580
- puts "Created output: #{args.join ' '}" if run_from_command_line?
581
- out
582
- end
583
-
584
- def flow(name)
585
- Flow.new self, name
586
- end
587
-
588
- def render(type=:graphviz)
589
- return @graph if @rendered
590
- raise "dunno bout that '#{type}', only know :graphviz" unless type == :graphviz
591
-
592
- renderer = GraphvizRenderer.new @title, render_hints
593
-
594
- children.each {|c|
595
- c.render renderer
596
- }
597
- children.each {|c|
598
- c.render_edges renderer
599
- }
600
- @rendered = true
601
- @graph = renderer.graph
602
- end
603
-
604
- def rollup?
605
- false
606
- end
607
-
608
- def skip?
609
- false
610
- end
611
-
612
- def register(name, thing)
613
- @by_name.register name, thing
614
- end
615
-
616
- def colorize_edges?
617
- @non_render_hints[:colorize_edges]
618
- end
619
-
620
- private
621
-
622
- def remove_dogviz_hints!(hints)
623
- dogviz_only_hints = {}
624
- %i(colorize_edges).each {|k|
625
- dogviz_only_hints[k] = hints.delete k
626
- }
627
- dogviz_only_hints
628
- end
629
-
630
- def create_title(name)
631
- now = DateTime.now
632
- "#{now.strftime '%H:%M'} #{name} #{now.strftime '%F'}"
633
- end
634
-
635
- def run_from_command_line?
636
- $stdout.isatty
637
- end
638
- end
639
-
640
- class LookupError < StandardError
641
- def initialize(context, message)
642
- super "(in context '#{context}') #{message}"
643
- end
644
- end
645
- class MissingMatchBlockError < LookupError
646
- def initialize(context)
647
- super context, 'need to provide match block'
648
- end
649
- end
650
- class DuplicateLookupError < LookupError
651
- def initialize(context, name)
652
- super context, "More than one object registered of name '#{name}' - you'll need to search in a narrower context"
653
- end
654
- end
655
- class Registry
656
- def initialize(context)
657
- @context = context
658
- @by_name = {}
659
- @all = []
660
- end
661
-
662
- def register(name, thing)
663
- @all << thing
664
- if @by_name.has_key?(name)
665
- @by_name[name] = DuplicateLookupError.new @context, name
666
- else
667
- @by_name[name] = thing
668
- end
669
- end
670
-
671
- def find(&matcher)
672
- raise LookupError.new(@context, "need to provide match block") unless block_given?
673
- @all.find &matcher
674
- end
675
-
676
- def find_all(&matcher)
677
- raise MissingMatchBlockError.new(@context) unless block_given?
678
- @all.select &matcher
679
- end
680
-
681
- def lookup(name)
682
- found = @by_name[name]
683
- raise LookupError.new(@context, "could not find '#{name}'") if found.nil?
684
- raise found if found.is_a?(Exception)
685
- found
686
- end
687
- end
688
-
689
- end
8
+ require_relative 'dogviz/flow.rb'
9
+ require_relative 'dogviz/system.rb'