lib/dogviz.rb in dogviz-0.0.17 vs lib/dogviz.rb in dogviz-0.0.18
- old
+ new
@@ -1,689 +1,9 @@
-require 'ruby-graphviz'
+require_relative 'dogviz/thing.rb'
+require_relative 'dogviz/container.rb'
+require_relative 'dogviz/logical_container.rb'
-module Dogviz
- class Process
- def initialize(processor, description)
- @processor = processor
- @description = description
- end
- def name
- @processor.name
- end
- def description
- @description
- end
- attr_reader :processor
- end
- module Flowable
- def does(action)
- Process.new(self, action)
- end
- end
- module Nominator
- def nominate(names_to_nominees)
- names_to_nominees.each {|name, nominee|
- self.class.send(:define_method, name) do
- nominee
- end
- }
- end
- def nominate_from(nominee_nominator, *nominee_names)
- nominee_names.each {|name|
- accessor_sym = name.to_s.to_sym
- nominate accessor_sym => nominee_nominator.send(accessor_sym)
- }
- end
- end
- module Common
- def create_id(name, parent)
- parts = []
- parts << parent.id if parent.respond_to? :id
- parts += name.split /\s/
- parts.join '_'
- end
- def root
- ancestors.last
- end
- def ancestors
- ancestors = [parent]
- loop do
- break unless ancestors.last.respond_to?(:parent)
- ancestors << ancestors.last.parent
- end
- ancestors
- end
- def info(fields)
- @info.merge! fields
- setup_render_attributes(label: label_with_info)
- end
- def doclink(url)
- setup_render_attributes(URL: url)
- end
- def label_with_info
- lines = [ name ]
- @info.each {|k, v|
- lines << "#{k}: #{v}"
- }
- lines.join "\n"
- end
- def setup_render_attributes(attributes)
- @attributes = {} if @attributes.nil?
- @attributes.merge!(attributes)
- end
- def rollup?
- @rollup
- end
- def rollup!
- @rollup = true
- self
- end
- def skip!
- @skip = true
- self
- end
+require_relative 'dogviz/graphviz_renderer.rb'
+require_relative 'dogviz/sigma_renderer.rb'
- def skip?
- @skip
- end
-
- def in_skip?
- skip? || under_skip?
- end
-
- def under_skip?
- ancestors.any? &:skip?
- end
-
- def under_rollup?
- ancestors.any? &:rollup?
- end
- def in_rollup?
- rollup? || under_rollup?
- end
- def on_top_rollup?
- rollup? && !under_rollup?
- end
- def inherited_render_options
- inherited = {}
- inherited[:fontname] = parent.render_options[:fontname] if parent.render_options.include?(:fontname)
- inherited
- end
- end
- module Parent
- def find_all(&matcher)
- raise MissingMatchBlockError.new unless block_given?
- @by_name.find_all &matcher
- end
- def find(name=nil, &matcher)
- if block_given?
- @by_name.find &matcher
- else
- raise 'Need to provide name or block' if name.nil?
- @by_name.lookup name
- end
- end
- def thing(name, options={})
- add Thing.new self, name, options
- end
- def container(name, options={})
- add Container.new self, name, options
- end
- def logical_container(name, options={})
- add LogicalContainer.new self, name, options
- end
- def group(name, options={})
- logical_container name, options
- end
- def add(child)
- @children << child
- child
- end
- end
-
- class Colorizer
- def initialize
- @i = 0
- @colors = %w(#9e0142
-#d53e4f
-#e45d33
-#ed9e61
-#762a83
-#9970ab
-#c6f578
-#abdda4
-#66c2a5
-#3288bd
-#5e4fa2)
- end
-
- def next
- color = @colors[@i]
- @i += 1
- @i = 0 unless @i < @colors.length
- color
- end
- end
-
- class Thing
- include Common
- include Nominator
- include Flowable
- attr_reader :parent
- attr_reader :name, :id, :pointers, :edge_heads
-
- @@colorizer = Colorizer.new
-
- def initialize(parent, name, options = {})
- @parent = parent
- @name = name
- @id = create_id(name, parent)
- @pointers = []
- @rollup = false
- @skip = false
- @info = {}
- @edge_heads = []
-
- rollup! if options[:rollup]
- options.delete(:rollup)
-
- @render_options = options
- setup_render_attributes({label: name}.merge inherited_render_options)
-
- parent.register name, self
- end
-
- def points_to_all(*others)
- others.each {|other|
- points_to other
- }
- end
-
- def points_to(other, options = {})
- setup_render_edge(other, options)
- other
- end
-
- def render(renderer)
- do_render_node(renderer) unless in_rollup? || in_skip?
- end
-
- def render_edges(renderer)
- pointers.each {|p|
- render_pointer p, renderer
- }
- end
-
- private
-
- def do_render_node(renderer)
- render_options = @render_options
- attributes = @attributes
- renderer.render_node(parent, id, render_options, attributes)
- end
-
- def setup_render_edge(other, options)
- pointers << {
- other: other,
- options: {
- xlabel: options[:name],
- style: options[:style]
- }.merge(inherited_render_options)
- }
-
- if options[:colorize] || root.colorize_edges?
- edge_color = next_colorizer_color
- pointers.last[:options].merge!({
- color: edge_color,
- fontcolor: edge_color
- })
- end
-
- end
-
- def render_pointer(pointer, renderer)
- other = pointer[:other]
- while (other.in_rollup? && !other.on_top_rollup?) do
- other = other.parent
- end
- return if other.under_rollup?
-
- from = self
- while (from.in_rollup? && !from.on_top_rollup?) do
- from = from.parent
- end
-
- return if from.in_skip?
-
- return if from == self && from.in_rollup?
- return if from == other
- return if already_added_connection?(other)
-
- if other.in_skip?
- others = resolve_skipped_others other
- else
- others = [other]
- end
-
- others.each do |other|
- edge_heads << other
- render_options = pointer[:options]
- renderer.render_edge(from, other, render_options)
- end
- end
-
- def already_added_connection?(other)
- edge_heads.include? other
- end
-
- def resolve_skipped_others(skipped)
- resolved = []
- skipped.pointers.each {|pointer|
- next_in_line = pointer[:other]
- if next_in_line.in_skip?
- resolved += resolve_skipped_others next_in_line
- else
- resolved << next_in_line
- end
- }
- resolved
- end
-
- def next_colorizer_color
- @@colorizer.next
- end
- end
-
- class Container
- include Common
- include Nominator
- include Parent
- attr_reader :parent
- attr_reader :name, :id, :node, :render_id, :render_type, :render_options, :children
-
- def initialize(parent, name, options = {})
- @children = []
- @by_name = Registry.new name
- @parent = parent
- @name = name
- @id = create_id(name, parent)
- @skip = false
- @info = {}
-
- init_rollup options
-
- setup_render_attributes label: name
-
- @render_options = options.merge inherited_render_options
-
- parent.register name, self
- end
-
- def register(name, thing)
- @by_name.register name, thing
- parent.register name, thing
- end
-
- def render(renderer)
- if on_top_rollup?
- do_render_node renderer
- elsif !under_rollup?
- do_render_subgraph renderer
- end
-
- children.each {|c|
- c.render renderer
- }
- end
-
- def render_edges(renderer)
- children.each {|c|
- c.render_edges renderer
- }
- end
-
- private
-
- def do_render_subgraph(renderer)
- @render_type = :subgraph
- render_id = cluster_prefix + id
- attributes = @attributes
- @render_id = render_id
- @subgraph = renderer.render_subgraph(parent, render_id, render_options, attributes)
- end
-
- def do_render_node(renderer)
- @render_type = :node
- @render_id = id
- render_id = @render_id
- attributes = @attributes
- renderer.render_node(parent, render_id, render_options, attributes)
- end
-
- def init_rollup(options)
- @rollup = false
- rollup! if options[:rollup]
- options.delete(:rollup)
- end
-
- def cluster_prefix
- is_cluster = true
- if @render_options.has_key? :cluster
- is_cluster = @render_options[:cluster]
- @render_options.delete :cluster
- end
- cluster_prefix = (is_cluster ? 'cluster_' : '')
- end
-
- end
-
- class LogicalContainer < Container
- def initialize(parent, name, options)
- super parent, name, options.merge(cluster: false)
- end
- end
-
- require 'date'
-
- class GraphvizRenderer
- attr_reader :graph
-
- def initialize(title, hints)
- @graph = GraphViz.digraph(title)
- @graph[hints]
- @subgraphs = {}
- @nodes = {}
- end
-
- def render_edge(from, other, options)
- edge = graph.add_edges from.id, other.id
- options.each { |key, value|
- edge[key] = value unless value.nil?
- }
- edge
- end
-
- def render_node(parent, id, options, attributes)
- clean_node_options options
- default_options = {:shape => 'box', :style => ''}
- node = parent_node(parent).add_nodes(id, default_options.merge(options))
- apply_render_attributes node, attributes
- end
-
- def render_subgraph(parent, id, options, attributes)
- subgraph = parent_node(parent).add_graph(id, options)
- apply_render_attributes subgraph, attributes
- @subgraphs[id] = subgraph
- subgraph
- end
-
- private
-
- def clean_node_options(options)
- options.delete(:rank)
- options.delete(:cluster)
- options
- end
-
- def parent_node(parent)
- return graph unless parent.respond_to?(:render_id)
- node = graph.search_node(parent.render_id)
- return node unless node.nil?
- subgraph = @subgraphs[parent.render_id]
- raise "couldn't find node or graph: #{parent.render_id}, out of graphs: #{graph_ids}" if subgraph.nil?
- subgraph
- end
-
- def apply_render_attributes(node, attributes)
- attributes.each do |key, value|
- node[key] = value
- end
- end
- end
-
- class Flow
- def initialize(sys, name)
- @sys = sys
- @name = name
- @calls = []
- end
-
- def make_connections
- calls.each {|from, to, label|
- thing_of(from).points_to thing_of(to), label: label
- }
- end
-
- def flows(*steps)
- from = nil
- to = nil
- label = nil
- steps.each do |step|
- if from.nil?
- from = ensure_is_thing(step)
- elsif label.nil? && step.is_a?(String)
- label = step
- elsif to.nil?
- to = ensure_is_thing(step)
- end
- unless to.nil?
- calls << [from, to, label]
- from = to
- to = label = nil
- end
- end
- end
-
- def ensure_is_thing(step)
- raise "Expected some thing or process: '#{step}' already got: #{calls}" unless step.is_a?(Thing) || step.is_a?(Process)
- step
- end
-
- def output(type_to_file)
- type = type_to_file.keys.first
- raise "Only support sequence, not: '#{type}'" unless type == :sequence
- render.output(type_to_file)
- end
-
- def render
- renderer = SequenceRenderer.new(@name)
- calls.each do |from, to, label|
- renderer.render_edge from, to, {label: label}
- end
- renderer.rendered
- end
-
- private
-
- attr_reader :calls, :sys
-
- def thing_of(it)
- return it.processor if it.is_a?(Process)
- it
- end
- end
-
-
- class RenderedSequence
- def initialize(lines)
- @lines = lines
- end
- def output(type_to_file)
- text = @lines.map(&:strip).join "\n"
- File.write type_to_file.values.first, text
- text
- end
- end
-
- class SequenceRenderer
- attr_reader :lines
- def initialize(title)
- @lines = []
- end
-
- def render_edge(from, other, options)
-
- detail = options[:label]
- receiver_label = other.name
- sender_label = from.name
- if other.is_a?(Process)
- detail = process_annotations(detail, sender_label, receiver_label, other.description)
- receiver_label = process_start_label(receiver_label)
- elsif from.is_a?(Process)
- receiver_label = process_end_label(receiver_label)
- end
- lines << "#{sender_label} -> #{receiver_label}: #{detail}"
- end
-
- def rendered
- RenderedSequence.new lines
- end
-
- private
-
- def process_start_label(receiver_label)
- "+#{receiver_label}"
- end
-
- def process_end_label(receiver_label)
- "-#{receiver_label}"
- end
-
- def process_annotations(detail, sender, receiver, process_description)
- detail = [detail,
- "note right of #{receiver}",
- " #{process_description}",
- 'end note'].join("\n")
- end
- end
-
- class System
- include Parent
- include Nominator
-
- attr_reader :render_hints, :title, :children, :graph
-
- alias :name :title
- alias :render_options :render_hints
-
- def initialize(name, hints = {splines: 'line'})
- @children = []
- @by_name = Registry.new name
- @non_render_hints = remove_dogviz_hints!(hints)
- @render_hints = hints
- @title = create_title(name)
- @rendered = false
- end
-
- def output(*args)
- render
- out = graph.output *args
- puts "Created output: #{args.join ' '}" if run_from_command_line?
- out
- end
-
- def flow(name)
- Flow.new self, name
- end
-
- def render(type=:graphviz)
- return @graph if @rendered
- raise "dunno bout that '#{type}', only know :graphviz" unless type == :graphviz
-
- renderer = GraphvizRenderer.new @title, render_hints
-
- children.each {|c|
- c.render renderer
- }
- children.each {|c|
- c.render_edges renderer
- }
- @rendered = true
- @graph = renderer.graph
- end
-
- def rollup?
- false
- end
-
- def skip?
- false
- end
-
- def register(name, thing)
- @by_name.register name, thing
- end
-
- def colorize_edges?
- @non_render_hints[:colorize_edges]
- end
-
- private
-
- def remove_dogviz_hints!(hints)
- dogviz_only_hints = {}
- %i(colorize_edges).each {|k|
- dogviz_only_hints[k] = hints.delete k
- }
- dogviz_only_hints
- end
-
- def create_title(name)
- now = DateTime.now
- "#{now.strftime '%H:%M'} #{name} #{now.strftime '%F'}"
- end
-
- def run_from_command_line?
- $stdout.isatty
- end
- end
-
- class LookupError < StandardError
- def initialize(context, message)
- super "(in context '#{context}') #{message}"
- end
- end
- class MissingMatchBlockError < LookupError
- def initialize(context)
- super context, 'need to provide match block'
- end
- end
- class DuplicateLookupError < LookupError
- def initialize(context, name)
- super context, "More than one object registered of name '#{name}' - you'll need to search in a narrower context"
- end
- end
- class Registry
- def initialize(context)
- @context = context
- @by_name = {}
- @all = []
- end
-
- def register(name, thing)
- @all << thing
- if @by_name.has_key?(name)
- @by_name[name] = DuplicateLookupError.new @context, name
- else
- @by_name[name] = thing
- end
- end
-
- def find(&matcher)
- raise LookupError.new(@context, "need to provide match block") unless block_given?
- @all.find &matcher
- end
-
- def find_all(&matcher)
- raise MissingMatchBlockError.new(@context) unless block_given?
- @all.select &matcher
- end
-
- def lookup(name)
- found = @by_name[name]
- raise LookupError.new(@context, "could not find '#{name}'") if found.nil?
- raise found if found.is_a?(Exception)
- found
- end
- end
-
-end
+require_relative 'dogviz/flow.rb'
+require_relative 'dogviz/system.rb'