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.
- checksums.yaml +4 -4
 - data/lib/dogviz/colorizer.rb +25 -0
 - data/lib/dogviz/common.rb +89 -0
 - data/lib/dogviz/container.rb +88 -0
 - data/lib/dogviz/duplicate_lookup_error.rb +7 -0
 - data/lib/dogviz/flow.rb +67 -0
 - data/lib/dogviz/flowable.rb +9 -0
 - data/lib/dogviz/graphviz_renderer.rb +60 -0
 - data/lib/dogviz/logical_container.rb +8 -0
 - data/lib/dogviz/lookup_error.rb +7 -0
 - data/lib/dogviz/missing_match_block_error.rb +7 -0
 - data/lib/dogviz/nominator.rb +18 -0
 - data/lib/dogviz/parent.rb +38 -0
 - data/lib/dogviz/process.rb +18 -0
 - data/lib/dogviz/registry.rb +39 -0
 - data/lib/dogviz/rendered_sequence.rb +13 -0
 - data/lib/dogviz/sequence_renderer.rb +47 -0
 - data/lib/dogviz/sigma_graph_hash.rb +25 -0
 - data/lib/dogviz/sigma_renderer.rb +32 -0
 - data/lib/dogviz/system.rb +95 -0
 - data/lib/dogviz/thing.rb +135 -0
 - data/lib/dogviz/version.rb +1 -1
 - data/lib/dogviz.rb +7 -687
 - data/tests/test_dogviz_sigma_rendering.rb +68 -0
 - data/todo.txt +7 -0
 - metadata +24 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: d93013b0cc858fbd5459c5fc0794cc334df4ab7f
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: ee353c1dfe91e9b10901d2305944806b62f3e5c8
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 76aa31f77889f81eae728587e3660ed9650b09a04a1fced9fe87699631870a73f98f62f0acfccb9af84399b7f68c380f177df846ce13aa2458e99561061c4499
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 7fa12c43c393cb7714e697bebdece5fb387d72df776a96aea8ec794932b56593158b906c9bb8e68e2999dd893174cea158e2e92b76c8a5968062b7cdc02e867c
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Colorizer
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 4 
     | 
    
         
            +
                  @i = 0
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @colors = %w(#9e0142
         
     | 
| 
      
 6 
     | 
    
         
            +
            #d53e4f
         
     | 
| 
      
 7 
     | 
    
         
            +
            #e45d33
         
     | 
| 
      
 8 
     | 
    
         
            +
            #ed9e61
         
     | 
| 
      
 9 
     | 
    
         
            +
            #762a83
         
     | 
| 
      
 10 
     | 
    
         
            +
            #9970ab
         
     | 
| 
      
 11 
     | 
    
         
            +
            #c6f578
         
     | 
| 
      
 12 
     | 
    
         
            +
            #abdda4
         
     | 
| 
      
 13 
     | 
    
         
            +
            #66c2a5
         
     | 
| 
      
 14 
     | 
    
         
            +
            #3288bd
         
     | 
| 
      
 15 
     | 
    
         
            +
            #5e4fa2)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def next
         
     | 
| 
      
 19 
     | 
    
         
            +
                  color = @colors[@i]
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @i += 1
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @i = 0 unless @i < @colors.length
         
     | 
| 
      
 22 
     | 
    
         
            +
                  color
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,89 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Common
         
     | 
| 
      
 3 
     | 
    
         
            +
                def create_id(name, parent)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  parts = []
         
     | 
| 
      
 5 
     | 
    
         
            +
                  parts << parent.id if parent.respond_to? :id
         
     | 
| 
      
 6 
     | 
    
         
            +
                  parts += name.split /\s/
         
     | 
| 
      
 7 
     | 
    
         
            +
                  parts.join '_'
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def root
         
     | 
| 
      
 11 
     | 
    
         
            +
                  ancestors.last
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def ancestors
         
     | 
| 
      
 15 
     | 
    
         
            +
                  ancestors = [parent]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  loop do
         
     | 
| 
      
 17 
     | 
    
         
            +
                    break unless ancestors.last.respond_to?(:parent)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ancestors << ancestors.last.parent
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  ancestors
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def info(fields)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @info.merge! fields
         
     | 
| 
      
 25 
     | 
    
         
            +
                  setup_render_attributes(label: label_with_info)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def doclink(url)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  setup_render_attributes(URL: url)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def label_with_info
         
     | 
| 
      
 33 
     | 
    
         
            +
                  lines = [name]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @info.each { |k, v|
         
     | 
| 
      
 35 
     | 
    
         
            +
                    lines << "#{k}: #{v}"
         
     | 
| 
      
 36 
     | 
    
         
            +
                  }
         
     | 
| 
      
 37 
     | 
    
         
            +
                  lines.join "\n"
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def setup_render_attributes(attributes)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @attributes = {} if @attributes.nil?
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @attributes.merge!(attributes)
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def rollup?
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @rollup
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def rollup!
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @rollup = true
         
     | 
| 
      
 51 
     | 
    
         
            +
                  self
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def skip!
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @skip = true
         
     | 
| 
      
 56 
     | 
    
         
            +
                  self
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def skip?
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @skip
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def in_skip?
         
     | 
| 
      
 64 
     | 
    
         
            +
                  skip? || under_skip?
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def under_skip?
         
     | 
| 
      
 68 
     | 
    
         
            +
                  ancestors.any? &:skip?
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def under_rollup?
         
     | 
| 
      
 72 
     | 
    
         
            +
                  ancestors.any? &:rollup?
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def in_rollup?
         
     | 
| 
      
 76 
     | 
    
         
            +
                  rollup? || under_rollup?
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def on_top_rollup?
         
     | 
| 
      
 80 
     | 
    
         
            +
                  rollup? && !under_rollup?
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                def inherited_render_options
         
     | 
| 
      
 84 
     | 
    
         
            +
                  inherited = {}
         
     | 
| 
      
 85 
     | 
    
         
            +
                  inherited[:fontname] = parent.render_options[:fontname] if parent.render_options.include?(:fontname)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  inherited
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
              end
         
     | 
| 
      
 89 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'common'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'nominator'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'parent'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Container
         
     | 
| 
      
 7 
     | 
    
         
            +
                include Common
         
     | 
| 
      
 8 
     | 
    
         
            +
                include Nominator
         
     | 
| 
      
 9 
     | 
    
         
            +
                include Parent
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :parent
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :name, :id, :node, :render_id, :render_type, :render_options, :children
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def initialize(parent, name, options = {})
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @children = []
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @by_name = Registry.new name
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @parent = parent
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @id = create_id(name, parent)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @skip = false
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @info = {}
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  init_rollup options
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  setup_render_attributes label: name
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  @render_options = options.merge inherited_render_options
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  parent.register name, self
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def register(name, thing)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @by_name.register name, thing
         
     | 
| 
      
 34 
     | 
    
         
            +
                  parent.register name, thing
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def render(renderer)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  if on_top_rollup?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    do_render_node renderer
         
     | 
| 
      
 40 
     | 
    
         
            +
                  elsif !under_rollup?
         
     | 
| 
      
 41 
     | 
    
         
            +
                    do_render_subgraph renderer
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  children.each { |c|
         
     | 
| 
      
 45 
     | 
    
         
            +
                    c.render renderer
         
     | 
| 
      
 46 
     | 
    
         
            +
                  }
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def render_edges(renderer)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  children.each { |c|
         
     | 
| 
      
 51 
     | 
    
         
            +
                    c.render_edges renderer
         
     | 
| 
      
 52 
     | 
    
         
            +
                  }
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                private
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def do_render_subgraph(renderer)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @render_type = :subgraph
         
     | 
| 
      
 59 
     | 
    
         
            +
                  render_id = cluster_prefix + id
         
     | 
| 
      
 60 
     | 
    
         
            +
                  attributes = @attributes
         
     | 
| 
      
 61 
     | 
    
         
            +
                  @render_id = render_id
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @subgraph = renderer.render_subgraph(parent, render_id, render_options, attributes)
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def do_render_node(renderer)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @render_type = :node
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @render_id = id
         
     | 
| 
      
 68 
     | 
    
         
            +
                  render_id = @render_id
         
     | 
| 
      
 69 
     | 
    
         
            +
                  attributes = @attributes
         
     | 
| 
      
 70 
     | 
    
         
            +
                  renderer.render_node(parent, render_id, render_options, attributes)
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def init_rollup(options)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @rollup = false
         
     | 
| 
      
 75 
     | 
    
         
            +
                  rollup! if options[:rollup]
         
     | 
| 
      
 76 
     | 
    
         
            +
                  options.delete(:rollup)
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def cluster_prefix
         
     | 
| 
      
 80 
     | 
    
         
            +
                  is_cluster = true
         
     | 
| 
      
 81 
     | 
    
         
            +
                  if @render_options.has_key? :cluster
         
     | 
| 
      
 82 
     | 
    
         
            +
                    is_cluster = @render_options[:cluster]
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @render_options.delete :cluster
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  cluster_prefix = (is_cluster ? 'cluster_' : '')
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/dogviz/flow.rb
    ADDED
    
    | 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'sequence_renderer'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'thing'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'process'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Flow
         
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(sys, name)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @sys = sys
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @calls = []
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def make_connections
         
     | 
| 
      
 14 
     | 
    
         
            +
                  calls.each { |from, to, label|
         
     | 
| 
      
 15 
     | 
    
         
            +
                    thing_of(from).points_to thing_of(to), label: label
         
     | 
| 
      
 16 
     | 
    
         
            +
                  }
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def flows(*steps)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  from = nil
         
     | 
| 
      
 21 
     | 
    
         
            +
                  to = nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                  label = nil
         
     | 
| 
      
 23 
     | 
    
         
            +
                  steps.each do |step|
         
     | 
| 
      
 24 
     | 
    
         
            +
                    if from.nil?
         
     | 
| 
      
 25 
     | 
    
         
            +
                      from = ensure_is_thing(step)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    elsif label.nil? && step.is_a?(String)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      label = step
         
     | 
| 
      
 28 
     | 
    
         
            +
                    elsif to.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      to = ensure_is_thing(step)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                    unless to.nil?
         
     | 
| 
      
 32 
     | 
    
         
            +
                      calls << [from, to, label]
         
     | 
| 
      
 33 
     | 
    
         
            +
                      from = to
         
     | 
| 
      
 34 
     | 
    
         
            +
                      to = label = nil
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def ensure_is_thing(step)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  raise "Expected some thing or process: '#{step}' already got: #{calls}" unless step.is_a?(Thing) || step.is_a?(Process)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  step
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def output(type_to_file)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  type = type_to_file.keys.first
         
     | 
| 
      
 46 
     | 
    
         
            +
                  raise "Only support sequence, not: '#{type}'" unless type == :sequence
         
     | 
| 
      
 47 
     | 
    
         
            +
                  render.output(type_to_file)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def render
         
     | 
| 
      
 51 
     | 
    
         
            +
                  renderer = SequenceRenderer.new(@name)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  calls.each do |from, to, label|
         
     | 
| 
      
 53 
     | 
    
         
            +
                    renderer.render_edge from, to, {label: label}
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  renderer.rendered
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                private
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                attr_reader :calls, :sys
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def thing_of(it)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  return it.processor if it.is_a?(Process)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  it
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,60 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'ruby-graphviz'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 5 
     | 
    
         
            +
              class GraphvizRenderer
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_reader :graph
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(title, hints)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @graph = GraphViz.digraph(title)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @graph[hints]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @subgraphs = {}
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @nodes = {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def render_edge(from, other, options)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  edge = graph.add_edges from.id, other.id
         
     | 
| 
      
 17 
     | 
    
         
            +
                  options.each { |key, value|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    edge[key] = value unless value.nil?
         
     | 
| 
      
 19 
     | 
    
         
            +
                  }
         
     | 
| 
      
 20 
     | 
    
         
            +
                  edge
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def render_node(parent, id, options, attributes)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  clean_node_options options
         
     | 
| 
      
 25 
     | 
    
         
            +
                  default_options = {:shape => 'box', :style => ''}
         
     | 
| 
      
 26 
     | 
    
         
            +
                  node = parent_node(parent).add_nodes(id, default_options.merge(options))
         
     | 
| 
      
 27 
     | 
    
         
            +
                  apply_render_attributes node, attributes
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def render_subgraph(parent, id, options, attributes)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  subgraph = parent_node(parent).add_graph(id, options)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  apply_render_attributes subgraph, attributes
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @subgraphs[id] = subgraph
         
     | 
| 
      
 34 
     | 
    
         
            +
                  subgraph
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                private
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def clean_node_options(options)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  options.delete(:rank)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  options.delete(:cluster)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  options
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def parent_node(parent)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  return graph unless parent.respond_to?(:render_id)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  node = graph.search_node(parent.render_id)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  return node unless node.nil?
         
     | 
| 
      
 49 
     | 
    
         
            +
                  subgraph = @subgraphs[parent.render_id]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  raise "couldn't find node or graph: #{parent.render_id}, out of graphs: #{graph_ids}" if subgraph.nil?
         
     | 
| 
      
 51 
     | 
    
         
            +
                  subgraph
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def apply_render_attributes(node, attributes)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  attributes.each do |key, value|
         
     | 
| 
      
 56 
     | 
    
         
            +
                    node[key] = value
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Nominator
         
     | 
| 
      
 3 
     | 
    
         
            +
                def nominate(names_to_nominees)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  names_to_nominees.each { |name, nominee|
         
     | 
| 
      
 5 
     | 
    
         
            +
                    self.class.send(:define_method, name) do
         
     | 
| 
      
 6 
     | 
    
         
            +
                      nominee
         
     | 
| 
      
 7 
     | 
    
         
            +
                    end
         
     | 
| 
      
 8 
     | 
    
         
            +
                  }
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def nominate_from(nominee_nominator, *nominee_names)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  nominee_names.each { |name|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    accessor_sym = name.to_s.to_sym
         
     | 
| 
      
 14 
     | 
    
         
            +
                    nominate accessor_sym => nominee_nominator.send(accessor_sym)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  }
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Parent
         
     | 
| 
      
 3 
     | 
    
         
            +
                def find_all(&matcher)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  raise MissingMatchBlockError.new unless block_given?
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @by_name.find_all &matcher
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def find(name=nil, &matcher)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @by_name.find &matcher
         
     | 
| 
      
 11 
     | 
    
         
            +
                  else
         
     | 
| 
      
 12 
     | 
    
         
            +
                    raise 'Need to provide name or block' if name.nil?
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @by_name.lookup name
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def thing(name, options={})
         
     | 
| 
      
 18 
     | 
    
         
            +
                  add Thing.new self, name, options
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def container(name, options={})
         
     | 
| 
      
 22 
     | 
    
         
            +
                  add Container.new self, name, options
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def logical_container(name, options={})
         
     | 
| 
      
 26 
     | 
    
         
            +
                  add LogicalContainer.new self, name, options
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def group(name, options={})
         
     | 
| 
      
 30 
     | 
    
         
            +
                  logical_container name, options
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def add(child)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @children << child
         
     | 
| 
      
 35 
     | 
    
         
            +
                  child
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Process
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize(processor, description)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  @processor = processor
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @description = description
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def name
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @processor.name
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def description
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @description
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                attr_reader :processor
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'lookup_error.rb'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'missing_match_block_error.rb'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'duplicate_lookup_error.rb'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Registry
         
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(context)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @context = context
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @by_name = {}
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @all = []
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def register(name, thing)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @all << thing
         
     | 
| 
      
 15 
     | 
    
         
            +
                  if @by_name.has_key?(name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @by_name[name] = DuplicateLookupError.new @context, name
         
     | 
| 
      
 17 
     | 
    
         
            +
                  else
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @by_name[name] = thing
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def find(&matcher)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  raise LookupError.new(@context, "need to provide match block") unless block_given?
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @all.find &matcher
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def find_all(&matcher)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  raise MissingMatchBlockError.new(@context) unless block_given?
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @all.select &matcher
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def lookup(name)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  found = @by_name[name]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  raise LookupError.new(@context, "could not find '#{name}'") if found.nil?
         
     | 
| 
      
 35 
     | 
    
         
            +
                  raise found if found.is_a?(Exception)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  found
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'process'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'rendered_sequence'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 5 
     | 
    
         
            +
              class SequenceRenderer
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_reader :lines
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(title)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @lines = []
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def render_edge(from, other, options)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  detail = options[:label]
         
     | 
| 
      
 15 
     | 
    
         
            +
                  receiver_label = other.name
         
     | 
| 
      
 16 
     | 
    
         
            +
                  sender_label = from.name
         
     | 
| 
      
 17 
     | 
    
         
            +
                  if other.is_a?(Process)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    detail = process_annotations(detail, sender_label, receiver_label, other.description)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    receiver_label = process_start_label(receiver_label)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  elsif from.is_a?(Process)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    receiver_label = process_end_label(receiver_label)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  lines << "#{sender_label} -> #{receiver_label}: #{detail}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def rendered
         
     | 
| 
      
 27 
     | 
    
         
            +
                  RenderedSequence.new lines
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                private
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def process_start_label(receiver_label)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  "+#{receiver_label}"
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def process_end_label(receiver_label)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  "-#{receiver_label}"
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def process_annotations(detail, sender, receiver, process_description)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  detail = [detail,
         
     | 
| 
      
 42 
     | 
    
         
            +
                            "note right of #{receiver}",
         
     | 
| 
      
 43 
     | 
    
         
            +
                            "  #{process_description}",
         
     | 
| 
      
 44 
     | 
    
         
            +
                            'end note'].join("\n")
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 4 
     | 
    
         
            +
              class SigmaGraphHash < Hash
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(hash)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  hash.each { |k, v|
         
     | 
| 
      
 7 
     | 
    
         
            +
                    self[k] = v
         
     | 
| 
      
 8 
     | 
    
         
            +
                  }
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def output(type_to_filename)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  raise StandardError.new('must provide hash (json: somejsonfilename.json)') unless type_to_filename.is_a?(Hash)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  filename = get_json_filename(type_to_filename)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  File.write filename, to_json
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def get_json_filename(type_to_filename)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  type = type_to_filename.keys.first
         
     | 
| 
      
 21 
     | 
    
         
            +
                  raise StandardError.new('json output only supported') unless type == :json
         
     | 
| 
      
 22 
     | 
    
         
            +
                  type_to_filename[type]
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'sigma_graph_hash'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dogviz
         
     | 
| 
      
 4 
     | 
    
         
            +
              class SigmaRenderer
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(title)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @title = title
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @nodes = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @edges = []
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def graph
         
     | 
| 
      
 12 
     | 
    
         
            +
                  SigmaGraphHash.new(nodes: nodes, edges: edges)
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def render_node(parent, id, render_options, attributes)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @nodes << {id: id, label: id}
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def render_edge(from, to, options)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @edges << {
         
     | 
| 
      
 21 
     | 
    
         
            +
                      id: "#{from.id}->#{to.id}",
         
     | 
| 
      
 22 
     | 
    
         
            +
                      label: "#{from.id}->#{to.id}",
         
     | 
| 
      
 23 
     | 
    
         
            +
                      source: from.id,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      target: to.id
         
     | 
| 
      
 25 
     | 
    
         
            +
                  }
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                private
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                attr_reader :nodes, :edges
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     |