dogviz 0.0.21 → 0.0.22

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
  SHA1:
3
- metadata.gz: 7b9eb357510d3a4de43f3cc7af60919bc6540915
4
- data.tar.gz: 4bceb85921dd199a159d7f6a3340041c31ce3eb9
3
+ metadata.gz: a428fcd04a6f1cc90bf9225b887c94889c7667ef
4
+ data.tar.gz: 26363eb38c24901ae048103f99179fb7d5e2807a
5
5
  SHA512:
6
- metadata.gz: 222029facb991efdcebe0bb8877c3cbbc46d8516e130255199c973809b0a018b2a6eda659e6320ff8167d2f629353149043bb318b81a1ceca38b764d3e7db966
7
- data.tar.gz: 78912d906cea50c5861c39c658c97e5a0a9833a3113307a2d16519510c9544a5754dd59e35a7ef90382a41b3624a1edcf25cee8b9967f63086fa13bb6140533b
6
+ metadata.gz: 1e363ecd7f98e01a3218c0bcd389f952f7350fa4488fd30e1e4bc3ff7b512624208576137a05e2cd26b48f51235e9f34954b95b28b4f004eb468b16cc909f684
7
+ data.tar.gz: 5f9ae410f02002a7148fa66c0ecead46b0a8db9980cbf84eab671298cf3fb85a36e4de4ac83162a7a70dd23f7bd092322b9453ae67cf9606c8a37f372dd1b4a0
data/Rakefile CHANGED
@@ -3,7 +3,6 @@ require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new do |t|
5
5
  t.test_files = FileList['tests/test*.rb']
6
- t.verbose = true
7
6
  end
8
7
 
9
8
  require 'colorize'
data/dogviz.gemspec CHANGED
@@ -18,10 +18,12 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.10"
21
- spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_development_dependency "rake", "~> 12.0"
22
22
  spec.add_development_dependency 'simplecov', '~> 0'
23
23
  spec.add_development_dependency 'colorize', '~> 0'
24
24
  spec.add_development_dependency 'nokogiri', '~> 1.6'
25
+ spec.add_development_dependency 'test-unit'
25
26
 
26
27
  spec.add_dependency 'ruby-graphviz', '~> 1'
28
+ spec.add_dependency 'json', '~> 2.1.0'
27
29
  end
data/lib/dogviz/common.rb CHANGED
@@ -3,7 +3,7 @@ module Dogviz
3
3
  def create_id(name, parent)
4
4
  parts = []
5
5
  parts << parent.id if parent.respond_to? :id
6
- parts += name.split /\s/
6
+ parts += name.split(/\s/)
7
7
  parts.join '_'
8
8
  end
9
9
 
@@ -23,6 +23,7 @@ module Dogviz
23
23
  def info(fields)
24
24
  @info.merge! fields
25
25
  setup_render_attributes(label: label_with_info)
26
+ self
26
27
  end
27
28
 
28
29
  def doclink(url)
@@ -38,7 +39,7 @@ module Dogviz
38
39
  end
39
40
 
40
41
  def setup_render_attributes(attributes)
41
- @attributes = {} if @attributes.nil?
42
+ @attributes ||= {}
42
43
  @attributes.merge!(attributes)
43
44
  end
44
45
 
@@ -65,11 +66,11 @@ module Dogviz
65
66
  end
66
67
 
67
68
  def under_skip?
68
- ancestors.any? &:skip?
69
+ ancestors.any?(&:skip?)
69
70
  end
70
71
 
71
72
  def under_rollup?
72
- ancestors.any? &:rollup?
73
+ ancestors.any?(&:rollup?)
73
74
  end
74
75
 
75
76
  def in_rollup?
data/lib/dogviz/flow.rb CHANGED
@@ -4,19 +4,85 @@ require_relative 'process'
4
4
 
5
5
  module Dogviz
6
6
  class Flow
7
+ FLOW_RENDERERS = {
8
+ sequence: WebSequenceDiagramsSequenceRenderer,
9
+ plantuml: PlantUmlSequenceRenderer,
10
+ png: PngSequenceRenderer
11
+ }
12
+
13
+ attr_reader :sys
14
+ attr_accessor :executor
15
+
7
16
  def initialize(sys, name)
8
17
  @sys = sys
9
18
  @name = name
10
- @calls = []
19
+ @commands = []
20
+ @actors = []
21
+ @caller_stack = []
22
+ @executor = nil
11
23
  end
12
24
 
13
25
  def make_connections
14
- calls.each { |from, to, label|
15
- thing_of(from).points_to thing_of(to), label: label
26
+ commands.each { |type, from, to, label|
27
+ thing_of(from).points_to(thing_of(to), name: label.split('\n').first) if type == :call
28
+ }
29
+ end
30
+
31
+ def involves(*actors)
32
+ @actors += actors
33
+ self
34
+ end
35
+
36
+ def from(initial_actor, &flowspec)
37
+ @actors.each { |actor|
38
+ actor.start_flow self
16
39
  }
40
+ @caller_stack << initial_actor
41
+ begin
42
+ flowspec.call
43
+ rescue NoMethodError => nme
44
+ raise "Did you call #involves for all actors? It's a common cause of the caught exception: #{nme}"
45
+ ensure
46
+ @caller_stack.pop
47
+ @actors.each { |actor|
48
+ actor.stop_flow
49
+ }
50
+ end
51
+ end
52
+
53
+ def add_note(from, where, what)
54
+ # yukk next lets move to command classes, e.g. OptCommand, NoteCommand, CallCommand etc.
55
+ commands << [:note, from, where, what]
56
+ end
57
+
58
+ def optional(text, &block)
59
+ commands << [:opt, nil, nil, text]
60
+ block.call
61
+ commands << [:end, nil, nil, nil]
62
+ end
63
+
64
+ def divider(text)
65
+ commands << [:divider, nil, nil, text]
66
+ end
67
+
68
+ alias :opt :optional
69
+
70
+ def add_call(from, to, label)
71
+ commands << [:call, from, to, label]
72
+ end
73
+
74
+ def next_call(to, label)
75
+ add_call @caller_stack.last, to, label
76
+ @caller_stack << to
77
+ end
78
+
79
+ def end_call(label)
80
+ current_actor = @caller_stack.pop
81
+ add_call(current_actor, @caller_stack.last, label) unless label.nil?
17
82
  end
18
83
 
19
84
  def flows(*steps)
85
+ sys.warn_on_exit 'deprecation warning: flow#flows deprecated, should use flow#from(actor) { <nested flow spec> }'
20
86
  from = nil
21
87
  to = nil
22
88
  label = nil
@@ -29,7 +95,7 @@ module Dogviz
29
95
  to = ensure_is_thing(step)
30
96
  end
31
97
  unless to.nil?
32
- calls << [from, to, label]
98
+ add_call from, to, label
33
99
  from = to
34
100
  to = label = nil
35
101
  end
@@ -37,31 +103,46 @@ module Dogviz
37
103
  end
38
104
 
39
105
  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)
106
+ raise "Expected some thing or process: '#{step}' already got: #{commands}" unless step.is_a?(Thing) || step.is_a?(Process)
41
107
  step
42
108
  end
43
109
 
44
110
  def output(type_to_file)
45
111
  type = type_to_file.keys.first
46
- raise "Only support sequence, not: '#{type}'" unless type == :sequence
47
- render.output(type_to_file)
112
+ raise "Only support #{FLOW_RENDERERS.keys}, not: '#{type}'" unless FLOW_RENDERERS.has_key?(type)
113
+ render(FLOW_RENDERERS[type]).output(type_to_file, executor)
48
114
  end
49
115
 
50
- def render
51
- renderer = SequenceRenderer.new(@name)
52
- calls.each do |from, to, label|
53
- renderer.render_edge from, to, {label: label}
116
+ def render(renderer_class = SequenceRenderer)
117
+ renderer = renderer_class.new(@name)
118
+ commands.each do |type, from, to, label|
119
+ if type == :call
120
+ renderer.render_edge(from, to, {label: label})
121
+ elsif type == :end
122
+ renderer.end_combination
123
+ elsif type == :note
124
+ renderer.note(from, to, label)
125
+ elsif type == :divider
126
+ renderer.divider(label)
127
+ else
128
+ renderer.start_combination(type, label)
129
+ end
54
130
  end
55
131
  renderer.rendered
56
132
  end
57
133
 
134
+ def suppress_messages!
135
+ sys.suppress_messages!
136
+ end
137
+
58
138
  private
59
139
 
60
- attr_reader :calls, :sys
140
+ attr_reader :commands
61
141
 
62
142
  def thing_of(it)
63
143
  return it.processor if it.is_a?(Process)
64
144
  it
65
145
  end
146
+
66
147
  end
67
148
  end
@@ -5,5 +5,55 @@ module Dogviz
5
5
  def does(action)
6
6
  Process.new(self, action)
7
7
  end
8
+
9
+ def receives(requests, &block)
10
+
11
+ @requests = self.requests.merge requests if requests.is_a?(Hash)
12
+ self.requests[requests] = block if requests.is_a?(Symbol)
13
+
14
+ end
15
+
16
+ def note(where, what)
17
+ @flow.add_note(self, where, what)
18
+ end
19
+
20
+ def start_flow(flow)
21
+ @flow = flow
22
+ end
23
+
24
+ def stop_flow
25
+ @flow = nil
26
+ end
27
+
28
+ def method_missing(m, *args, &block)
29
+ if requests.has_key?(m)
30
+ @flow ||= nil
31
+
32
+ request_def = requests[m]
33
+ if request_def.is_a?(String)
34
+ label = request_def
35
+ return_label = nil
36
+ else
37
+ request_def = request_def.call(*args) if request_def.is_a?(Proc)
38
+ label = request_def.keys.first
39
+ return_label = request_def.values.first
40
+ end
41
+
42
+ @flow.next_call self, label
43
+ block.call if block_given?
44
+ @flow.end_call(return_label)
45
+ else
46
+ raise "this flowable does not know about receiving '#{m}', only know about: #{requests.keys}"
47
+ end
48
+ end
49
+
50
+ def requests
51
+ @requests ||= {}
52
+ end
53
+
54
+ def request_handlers
55
+ @request_handlers ||= {}
56
+ end
57
+
8
58
  end
9
59
  end
@@ -6,8 +6,14 @@ module Dogviz
6
6
  attr_reader :graph
7
7
 
8
8
  def initialize(title, hints)
9
- @graph = GraphViz.digraph(title)
10
- @graph[hints]
9
+ construction_hints = {}
10
+ after_hints = hints.clone
11
+ if hints.has_key?(:use)
12
+ construction_hints[:use] = hints[:use]
13
+ after_hints.delete :use
14
+ end
15
+ @graph = GraphViz.digraph(title, construction_hints)
16
+ @graph[after_hints]
11
17
  @subgraphs = {}
12
18
  @nodes = {}
13
19
  @rendered_subgraph_ids = {}
@@ -25,7 +31,7 @@ module Dogviz
25
31
  clean_node_attributes attributes
26
32
  default_attributes = {:shape => 'box', :style => ''}
27
33
  merged_attributes = default_attributes.merge(attributes)
28
- node = parent_node(parent).add_nodes(id, merged_attributes)
34
+ parent_node(parent).add_nodes(id, merged_attributes)
29
35
  end
30
36
 
31
37
  def render_subgraph(parent, id, attributes)
data/lib/dogviz/parent.rb CHANGED
@@ -2,12 +2,12 @@ module Dogviz
2
2
  module Parent
3
3
  def find_all(&matcher)
4
4
  raise MissingMatchBlockError.new unless block_given?
5
- @by_name.find_all &matcher
5
+ @by_name.find_all(&matcher)
6
6
  end
7
7
 
8
8
  def find(name=nil, &matcher)
9
9
  if block_given?
10
- @by_name.find &matcher
10
+ @by_name.find(&matcher)
11
11
  else
12
12
  raise 'Need to provide name or block' if name.nil?
13
13
  @by_name.lookup name
@@ -21,12 +21,12 @@ module Dogviz
21
21
 
22
22
  def find(&matcher)
23
23
  raise LookupError.new(@context, "need to provide match block") unless block_given?
24
- @all.find &matcher
24
+ @all.find(&matcher)
25
25
  end
26
26
 
27
27
  def find_all(&matcher)
28
28
  raise MissingMatchBlockError.new(@context) unless block_given?
29
- @all.select &matcher
29
+ @all.select(&matcher)
30
30
  end
31
31
 
32
32
  def lookup(name)
@@ -4,10 +4,54 @@ module Dogviz
4
4
  @lines = lines
5
5
  end
6
6
 
7
- def output(type_to_file)
8
- text = @lines.map(&:strip).join "\n"
9
- File.write type_to_file.values.first, text
10
- text
7
+ def output(type_to_file, executor = nil)
8
+ File.write type_to_file.values.first, body
9
+ body
10
+ end
11
+
12
+ def body
13
+ @lines.map(&:rstrip).join "\n"
14
+ end
15
+ end
16
+
17
+ class WebSequenceDiagramsRenderedSequence < RenderedSequence
18
+ end
19
+
20
+ class PlantUmlRenderedSequence < RenderedSequence
21
+
22
+ def body
23
+ raw_body = super
24
+ ['@startuml', raw_body, '@enduml'].join "\n"
25
+ end
26
+ end
27
+
28
+
29
+ class Executor
30
+ def execute(cmd)
31
+ system cmd
32
+ end
33
+ end
34
+
35
+ class PngRenderedSequence < PlantUmlRenderedSequence
36
+ def output(type_to_file, executor = nil)
37
+ image_type, image_filename = type_to_file.first
38
+ plantuml_definition_filename = without_extension(image_filename) + '.plantuml'
39
+
40
+ super plantuml: plantuml_definition_filename
41
+
42
+ executor = Executor.new if executor.nil?
43
+ executor.execute(plantuml_cmd image_type, plantuml_definition_filename)
44
+ end
45
+
46
+ private
47
+
48
+ def plantuml_cmd(image_type, plantuml_definition_filename)
49
+ "plantuml -t#{image_type} #{plantuml_definition_filename}"
50
+ end
51
+
52
+ def without_extension(filename)
53
+ filename.gsub(/\.[a-zA-Z]*$/, '')
11
54
  end
12
55
  end
56
+
13
57
  end
@@ -7,28 +7,63 @@ module Dogviz
7
7
 
8
8
  def initialize(title)
9
9
  @lines = []
10
+ @indents = 0
11
+ @rendered_class = RenderedSequence
12
+ add_title title
10
13
  end
11
14
 
12
15
  def render_edge(from, other, options)
13
-
14
16
  detail = options[:label]
15
17
  receiver_label = other.name
16
18
  sender_label = from.name
19
+ annotations = nil
17
20
  if other.is_a?(Process)
18
- detail = process_annotations(detail, sender_label, receiver_label, other.description)
21
+ annotations = extract_annotations(detail, sender_label, receiver_label, other.description)
19
22
  receiver_label = process_start_label(receiver_label)
20
23
  elsif from.is_a?(Process)
21
24
  receiver_label = process_end_label(receiver_label)
22
25
  end
23
- lines << "#{sender_label} -> #{receiver_label}: #{detail}"
26
+ line = "#{escape sender_label} -> #{escape receiver_label}: #{detail}"
27
+ line = [ line, annotations ].join("\n") unless annotations.nil?
28
+ add_line line
29
+ end
30
+
31
+ def start_combination(operator, guard)
32
+ add_line "#{operator} #{escape guard}"
33
+ @indents += 1
34
+ end
35
+
36
+ def end_combination
37
+ @indents -= 1
38
+ add_line 'end'
39
+ end
40
+
41
+ def note(from, where, what)
42
+ add_line "note #{where} of #{escape from.name}"
43
+ @indents += 1
44
+ add_line what
45
+ @indents -= 1
46
+ add_line "end note"
47
+ end
48
+
49
+ def divider(text)
50
+ #nop
24
51
  end
25
52
 
26
53
  def rendered
27
- RenderedSequence.new lines
54
+ @rendered_class.new lines
28
55
  end
29
56
 
30
57
  private
31
58
 
59
+ def add_line(line)
60
+ lines << (' ' * @indents + line)
61
+ end
62
+
63
+ def add_title(title)
64
+ add_line "title #{title}"
65
+ end
66
+
32
67
  def process_start_label(receiver_label)
33
68
  "+#{receiver_label}"
34
69
  end
@@ -37,11 +72,46 @@ module Dogviz
37
72
  "-#{receiver_label}"
38
73
  end
39
74
 
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")
75
+ def extract_annotations(detail, sender, receiver, process_description)
76
+ [ "note right of #{escape receiver}",
77
+ " #{escape process_description}",
78
+ 'end note' ].join("\n")
45
79
  end
80
+
81
+ def escape(s)
82
+ if (/\s/).match(s)
83
+ "\"#{s}\""
84
+ else
85
+ s
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ class WebSequenceDiagramsSequenceRenderer < SequenceRenderer
92
+ def initialize title
93
+ super title
94
+ @rendered_class = WebSequenceDiagramsRenderedSequence
95
+ end
96
+ end
97
+
98
+ class PlantUmlSequenceRenderer < SequenceRenderer
99
+ def initialize title
100
+ super title
101
+ @rendered_class = PlantUmlRenderedSequence
102
+ end
103
+
104
+ def divider(text)
105
+ add_line "== #{text} =="
106
+ end
107
+ end
108
+
109
+ class PngSequenceRenderer < PlantUmlSequenceRenderer
110
+ def initialize title
111
+ super title
112
+ @rendered_class = PngRenderedSequence
113
+ end
46
114
  end
115
+
116
+
47
117
  end
data/lib/dogviz/system.rb CHANGED
@@ -19,12 +19,23 @@ module Dogviz
19
19
  @render_hints = hints
20
20
  @title = create_title(name)
21
21
  @rendered = false
22
+
23
+ @warnings = Set.new
24
+ @messages = Set.new
25
+
26
+ @suppress_warnings = false
27
+ @suppress_messages = false
28
+
29
+ on_exit {
30
+ output_messages
31
+ output_warnings
32
+ }
22
33
  end
23
34
 
24
35
  def output(*args)
25
36
  render
26
- out = graph.output *args
27
- puts "Created output: #{args.join ' '}" if run_from_command_line?
37
+ out = graph.output(*args)
38
+ @messages << "Created output: #{args.join ' '}" unless suppress_messages?
28
39
  out
29
40
  end
30
41
 
@@ -77,10 +88,58 @@ module Dogviz
77
88
  @non_render_hints[:auto_nominate]
78
89
  end
79
90
 
91
+ def warn_on_exit(warning)
92
+ @warnings << warning
93
+ end
80
94
 
95
+ def warnings
96
+ @warnings.to_a
97
+ end
98
+
99
+ def messages
100
+ @messages.to_a
101
+ end
102
+
103
+ def suppress_warnings!
104
+ @suppress_warnings = true
105
+ self
106
+ end
107
+
108
+ def suppress_messages!
109
+ @suppress_messages = true
110
+ self
111
+ end
81
112
 
82
113
  private
83
114
 
115
+ def on_exit(&block)
116
+ Kernel.at_exit(&block)
117
+ end
118
+
119
+ def output_messages
120
+ unless suppress_messages?
121
+ messages.each {|message|
122
+ STDERR.puts message
123
+ }
124
+ end
125
+ end
126
+
127
+ def output_warnings
128
+ unless suppress_warnings?
129
+ warnings.each {|warning|
130
+ STDERR.puts warning
131
+ }
132
+ end
133
+ end
134
+
135
+ def suppress_warnings?
136
+ @suppress_warnings
137
+ end
138
+
139
+ def suppress_messages?
140
+ @suppress_messages
141
+ end
142
+
84
143
  def remove_dogviz_hints!(hints)
85
144
  dogviz_only_hints = {}
86
145
  %i(colorize_edges auto_nominate).each { |k|
@@ -93,9 +152,5 @@ module Dogviz
93
152
  now = DateTime.now
94
153
  "#{now.strftime '%H:%M'} #{name} #{now.strftime '%F'}"
95
154
  end
96
-
97
- def run_from_command_line?
98
- $stdout.isatty
99
- end
100
155
  end
101
156
  end
data/lib/dogviz/thing.rb CHANGED
@@ -63,11 +63,17 @@ module Dogviz
63
63
  end
64
64
 
65
65
  def setup_render_edge(other, options)
66
+ fontsize = 14
67
+ fontsize += options[:stroke] if options.has_key?(:stroke)
66
68
  pointers << {
67
69
  other: other,
68
70
  options: {
69
71
  xlabel: options[:name],
70
- style: options[:style]
72
+ style: options[:style],
73
+ color: options[:color],
74
+ fontcolor: options[:color],
75
+ penwidth: options[:stroke],
76
+ fontsize: fontsize
71
77
  }.merge(inherited_render_options)
72
78
  }
73
79
 
@@ -105,10 +111,10 @@ module Dogviz
105
111
  others = [other]
106
112
  end
107
113
 
108
- others.each do |other|
109
- edge_heads << other
114
+ others.each do |other_to_render|
115
+ edge_heads << other_to_render
110
116
  render_options = pointer[:options]
111
- renderer.render_edge(from, other, render_options)
117
+ renderer.render_edge(from, other_to_render, render_options)
112
118
  end
113
119
  end
114
120
 
@@ -1,3 +1,3 @@
1
1
  module Dogviz
2
- VERSION = '0.0.21'
2
+ VERSION = '0.0.22'
3
3
  end
@@ -4,7 +4,7 @@ module GraphChecking
4
4
  end
5
5
 
6
6
  def subgraph_ids_without_cluster_prefixes
7
- subgraph_ids.map {|id| id.gsub /^cluster_/, '' }
7
+ subgraph_ids.map {|id| id.gsub(/^cluster_/, '') }
8
8
  end
9
9
 
10
10
  def subgraph(id)
data/tests/setup_tests.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require "test/unit"
2
2
  require_relative '../lib/dogviz'
3
- SimpleCov.root('..') if Object.const_defined?('SimpleCov')
3
+ SimpleCov.root('..') if Object.const_defined?('SimpleCov')
data/tests/svg_graph.rb CHANGED
@@ -66,7 +66,7 @@ module Tests
66
66
  private
67
67
 
68
68
  def comments
69
- @doc.children.select &:comment?
69
+ @doc.children.select(&:comment?)
70
70
  end
71
71
  end
72
72
  end
@@ -9,83 +9,289 @@ module Tests
9
9
  "/tmp/dogviz_flow_test.#{ext}"
10
10
  end
11
11
 
12
+ def delete_outfile(ext)
13
+ FileUtils.rm_f outfile(ext)
14
+ end
15
+
12
16
  def read_outfile(ext)
13
17
  File.read outfile(ext)
14
18
  end
15
19
 
16
20
  include Dogviz
17
21
 
18
- def test_flow_generates_precise_sequence
19
- sys = System.new 'takeaway'
20
- eater = sys.thing 'eater'
21
- server = sys.thing 'server'
22
- cook = sys.thing 'chef'
22
+ def test_nested_flow_syntax
23
+ create_takeaway
23
24
 
24
- order = sys.flow 'order'
25
- order.flows eater, 'gimme burger',
26
- server, 'passes order',
27
- cook, server, eater
25
+ order = sys.flow('order').involves(sys.server, sys.cook)
28
26
 
29
- order.output sequence: outfile('seq.txt')
27
+ sys.server.receives burger: { 'gimme burger' => 'here ya go' },
28
+ dessert: 'gimme dessert'
29
+ sys.cook.receives order: { 'passes order' => '' }
30
+
31
+ order.from(sys.eater) {
32
+ sys.server.burger {
33
+ sys.cook.order
34
+ }
35
+ sys.server.dessert
36
+ }
37
+
38
+ definition = sequence_definition(order)
39
+
40
+ assert_equal [
41
+ 'eater -> server: gimme burger',
42
+ 'server -> cook: passes order',
43
+ 'cook -> server:',
44
+ 'server -> eater: here ya go',
45
+ 'eater -> server: gimme dessert'
46
+ ].join("\n"), definition
47
+ end
48
+
49
+ def test_get_useful_error_if_not_called_involves_for_called_actor
50
+ create_takeaway
51
+
52
+ order = sys.flow('order')
53
+
54
+ sys.server.receives burger: 'gimme burger'
55
+
56
+ assert_raise_message(/call #involves for all actors/) {
57
+
58
+ order.from(sys.eater) {
59
+ sys.server.burger
60
+ }
61
+
62
+ }
63
+ end
64
+
65
+ def test_nested_flow_with_optional_part_of_sequence
66
+ create_takeaway
67
+
68
+ order = sys.flow('order').involves sys.server
69
+
70
+ sys.server.receives burger: { 'gimme burger' => 'here you go' }
71
+
72
+ order.from(sys.eater) {
73
+ order.opt('if hungry') {
74
+ sys.server.burger
75
+ }
76
+ }
77
+
78
+ definition = sequence_definition(order)
79
+
80
+ assert_equal [
81
+ 'opt "if hungry"',
82
+ ' eater -> server: gimme burger',
83
+ ' server -> eater: here you go',
84
+ 'end'
85
+ ].join("\n"), definition
86
+ end
87
+
88
+ def test_nested_flow_dynamic_receives_definition
89
+ create_takeaway
90
+
91
+ order = sys.flow('order').involves sys.server
92
+
93
+ sys.server.receives(:order) do |order_no|
94
+ { "make order #{order_no}" => "deliver order #{order_no}" }
95
+ end
96
+
97
+ order.from(sys.eater) {
98
+ sys.server.order(1)
99
+ sys.server.order(2)
100
+ }
101
+
102
+ definition = sequence_definition(order)
103
+
104
+ assert_equal [
105
+ 'eater -> server: make order 1',
106
+ 'server -> eater: deliver order 1',
107
+ 'eater -> server: make order 2',
108
+ 'server -> eater: deliver order 2'
109
+ ].join("\n"), definition
110
+ end
111
+
112
+ def test_plantuml_text_output
113
+ create_takeaway
114
+
115
+ order = sys.flow('the order').involves sys.server
116
+
117
+ sys.server.receives burger: 'gimme'
118
+
119
+ order.from(sys.eater) {
120
+ sys.server.burger
121
+ }
122
+
123
+ order.output plantuml: outfile('seq.plantuml')
124
+ definition = read_outfile('seq.plantuml')
125
+
126
+ assert_equal [
127
+ '@startuml',
128
+ 'title the order',
129
+ 'eater -> server: gimme',
130
+ '@enduml'
131
+ ].join("\n"), definition
132
+ end
133
+
134
+ def test_plantuml_dividers
135
+ create_takeaway
136
+
137
+ order = sys.flow('order')
138
+
139
+ sys.server.receives burger: 'gimme'
140
+
141
+ order.from(sys.eater) {
142
+ order.divider('bob')
143
+ }
144
+
145
+ definition = order.output plantuml: outfile('seq.plantuml')
146
+
147
+ assert_equal [
148
+ '@startuml',
149
+ 'title order',
150
+ '== bob ==',
151
+ '@enduml'
152
+ ].join("\n"), definition
153
+ end
154
+
155
+
156
+ class MockExecutor
157
+ def execute(cmd)
158
+ @cmd = cmd
159
+ end
160
+ attr_reader :cmd
161
+ end
162
+
163
+
164
+ def test_flow_png_image_output_via_plantuml
165
+ create_takeaway
166
+
167
+ order = sys.flow('order').involves sys.server
168
+
169
+ sys.server.receives burger: 'gimme'
170
+
171
+ order.from(sys.eater) {
172
+ sys.server.burger
173
+ }
174
+
175
+ plantuml_definition_file = outfile('seq.plantuml')
176
+ FileUtils.rm_f plantuml_definition_file
177
+ mock_executor = MockExecutor.new
178
+ order.executor = mock_executor
179
+ order.output png: outfile('seq.png')
180
+
181
+ assert_equal true, File.exist?(plantuml_definition_file)
182
+ assert_equal 'plantuml -tpng ' + plantuml_definition_file, mock_executor.cmd
183
+ end
184
+
185
+ def test_nested_flow_with_note_on_right
186
+ create_takeaway
187
+
188
+ order = sys.flow('order').involves sys.server, sys.eater
189
+
190
+ sys.server.receives burger: 'gimme burger'
30
191
 
31
- definition = read_outfile('seq.txt')
192
+ order.from(sys.eater) {
193
+ sys.server.burger
194
+ sys.server.note(:right, 'a note')
195
+ }
196
+
197
+ definition = sequence_definition(order)
32
198
 
33
199
  assert_equal [
34
- 'eater -> server: gimme burger',
35
- 'server -> chef: passes order',
36
- 'chef -> server:',
37
- 'server -> eater:',
200
+ 'eater -> server: gimme burger',
201
+ 'note right of server',
202
+ ' a note',
203
+ 'end note',
38
204
  ].join("\n"), definition
39
205
  end
40
206
 
41
- def create_food_flow
42
- @sys = System.new 'takeaway'
43
- eater = sys.thing 'eater'
44
- server = sys.thing 'server'
45
- cook = sys.thing 'cook'
207
+ def test_flow_generates_precise_sequence_with_deprecated_flows
208
+ create_takeaway
209
+ sys.suppress_warnings!
46
210
 
47
211
  order = sys.flow 'order'
48
- order.flows eater, 'orders',
49
- server, 'creates order',
50
- cook.does('cooks burger'),
51
- 'burger', server,
52
- 'burger', eater
53
- order
212
+ order.flows sys.eater, 'gimme burger',
213
+ sys.server, 'passes order',
214
+ sys.cook, sys.server, sys.eater
215
+
216
+ definition = sequence_definition(order, without_lines_starting: [])
217
+
218
+ assert_equal [
219
+ 'title order',
220
+ 'eater -> server: gimme burger',
221
+ 'server -> cook: passes order',
222
+ 'cook -> server:',
223
+ 'server -> eater:',
224
+ ].join("\n"), definition
54
225
  end
55
226
 
56
- def test_flow_generates_precise_sequence_with_action
57
- order = create_food_flow
227
+ def test_flow_generates_precise_sequence_with_action_with_deprecated_flows
228
+ order = create_food_flow_with_deprecated_flows
58
229
 
59
- order.output sequence: outfile('seq.txt')
60
- definition = read_outfile('seq.txt')
230
+ definition = sequence_definition order
61
231
 
62
232
  assert_equal([
63
233
  'eater -> server: orders',
64
234
  'server -> +cook: creates order',
65
235
  'note right of cook',
66
- ' cooks burger',
236
+ ' "cooks burger"',
67
237
  'end note',
68
238
  'cook -> -server: burger',
69
239
  'server -> eater: burger',
70
240
  ].join("\n"), definition)
71
241
  end
72
242
 
73
- def test_flow_can_be_used_to_make_connections_in_dog
74
- order = create_food_flow
243
+ def test_flow_can_be_used_to_make_connections_in_dog_with_deprecated_flows
244
+ order = create_food_flow_with_deprecated_flows
75
245
 
76
246
  order.make_connections
77
247
 
78
248
  assert_equal('eater->server server->cook server->eater cook->server', connections)
79
249
  end
80
250
 
251
+ def test_deprecated_flows_generates_warnings
252
+ order = create_food_flow_with_deprecated_flows
253
+
254
+ assert_equal(1, order.sys.warnings.select {|w| w.include?('flow#flows deprecated')}.size)
255
+ end
256
+
81
257
  private
82
258
 
83
259
  attr_accessor :sys
84
260
 
261
+ def sequence_definition(order, without_lines_starting: ['title'])
262
+ order.output sequence: outfile('seq.txt')
263
+
264
+ lines = read_outfile('seq.txt').split "\n"
265
+ without_lines_starting.each {|prefix|
266
+ lines = lines.reject {|line| line.start_with?(prefix) }
267
+ }
268
+ lines.join "\n"
269
+ end
270
+
85
271
  def graph
86
272
  g = sys.render
87
273
  sys.output svg: outfile('svg')
88
274
  g
89
275
  end
276
+
277
+ def create_takeaway
278
+ @sys = System.new('takeaway', auto_nominate: true).suppress_messages!
279
+ sys.thing 'eater'
280
+ sys.thing 'server'
281
+ sys.thing 'cook'
282
+ end
283
+
284
+ def create_food_flow_with_deprecated_flows
285
+ create_takeaway
286
+ order = sys.flow 'order'
287
+ sys.suppress_warnings!
288
+ order.flows sys.eater, 'orders',
289
+ sys.server, 'creates order',
290
+ sys.cook.does('cooks burger'),
291
+ 'burger', sys.server,
292
+ 'burger', sys.eater
293
+ order
294
+ end
295
+
90
296
  end
91
297
  end
@@ -18,9 +18,10 @@ module Tests
18
18
 
19
19
  include Dogviz
20
20
  class Family < Dogviz::System
21
- attr_reader *%i(cat dog mum son)
21
+ attr_reader(*%i(cat dog mum son))
22
22
  def initialize
23
23
  super 'family'
24
+ suppress_messages!
24
25
 
25
26
  house = container 'household'
26
27
 
@@ -60,7 +61,7 @@ module Tests
60
61
 
61
62
  dotspec = File.read outfile('dot')
62
63
 
63
- assert_match /rank=sink/, dotspec
64
+ assert_match(/rank=sink/, dotspec)
64
65
  end
65
66
 
66
67
  def test_can_render_auto_nominate_graph
@@ -70,7 +71,7 @@ module Tests
70
71
  end
71
72
 
72
73
  def system_with_auto_nominate
73
- Dogviz::System.new 'test', auto_nominate: true
74
+ Dogviz::System.new('test', auto_nominate: true).suppress_messages!
74
75
  end
75
76
  end
76
77
  end
@@ -163,7 +163,7 @@ class TestDogvizGraph < Test::Unit::TestCase
163
163
  def test_root
164
164
  group = sys.group('g')
165
165
  nested_group = group.group('nested group')
166
- thing1 = group.thing('n1')
166
+ thing1 = nested_group.thing('n1')
167
167
 
168
168
  assert_equal sys, thing1.root
169
169
  end
@@ -34,7 +34,7 @@ class TestDogvizGraphvizRendering < Test::Unit::TestCase
34
34
 
35
35
  def test_containers_are_subgraphs_prefixed_with_cluster_for_visual_containment_in_GraphViz
36
36
  top = sys.container('top')
37
- nested = top.container('nested')
37
+ top.container('nested')
38
38
 
39
39
  assert_equal('cluster_top', subgraph_ids.first)
40
40
  assert_equal('cluster_top_nested', subgraph_ids.last)
data/todo.txt CHANGED
@@ -1,7 +1,21 @@
1
- * spike 'autostrap': automatically strap-on reader methods using normalised name
1
+ * [DONE] spike 'autostrap': automatically strap-on reader methods using normalised name (auto_nominate)
2
2
  * [DONE] render generic output (sigma.js json) for ingestion -> space-dog
3
- * sort out fore vs back color
4
3
  * [DONE] move 'cluster_' prefix to graphviz renderer
4
+ * flow / sequence improvements
5
+ [DONE] new flow syntax
6
+ [DONE] opt combined fragments
7
+ [DONE] titles
8
+ [DONE] indent text output
9
+ - alt combined fragments
10
+ - allow unbalanced combined fragments (explicit start + end, not just by block)
11
+ * plantuml flow output
12
+ [DONE] text output
13
+ [DONE] direct image output if plantuml installed
14
+ [DONE] sort double quotes
15
+ [ ] allow multiline
16
+ * output generated flow file
17
+ * deprecate old flows
18
+ * sort out HACK (search for commit in git log) around flow detail used in make_connections
5
19
  * clean up init options + render attributes - not v clear / overlapping
6
20
  * make style attributes well defined -> less leaky + explicit graphviz passthroughs
7
21
  * separate style from domain-specific elements (can use same view-manipulation selection mechanisms)
@@ -15,6 +29,33 @@
15
29
  - maybe makes more seamless when collapsing/hiding for different views?
16
30
  * add sankey diagrams?
17
31
  * other renderer integrations:
18
- - plantuml?
19
32
  - cyctoscape?
20
- * switch to proper graph lib under covers - this is just a builder + view wrapper...
33
+ * switch to proper graph lib under covers - this is just a builder + view wrapper... https://github.com/monora/rgl?
34
+ * #repeat(count) on containers to give multiple instances, by default expanded (to show each instance)
35
+ * ...then can #collapse (1 instance showing, number labelled)
36
+ * ...then can #template or #prototype or ?? (1 instance detailed, rest simplified)
37
+ * ...make count of repeat be complex e.g. to show auto-scaling, 1 per customer etc. etc.
38
+ * make render 2 stages (rollups + repeats, then render) enabling:
39
+ - render multiple views from same dog instance
40
+ - separate and simplify rollup logic, which is a bit complex
41
+ - pave way for simplified repeat expansion logic, which is similarly complex (+ almost the reverse)
42
+ - pave way for cleaning graphviz specifics out of main dog ('cluster_', subgraphs)
43
+ * edge attributes?
44
+ i - pass on any attributes passed as _graphviz: on to graphviz renderer for relevant entity
45
+ ii - map in simpler named attributes
46
+ - color -> edge + font color
47
+ - stroke -> edge size and font size
48
+ * render to d3 force directed graph
49
+ * "rollup edges": where many edges from a node, rollup those to certain destination (e.g. from 'website' to all
50
+ in '3rd party' container) so that not so crowded around source (would add extra view node nearer target)
51
+ * "via": app -> service, via: load_balancer... then can omit load_balancer and get direct app -> service connection
52
+ * capture arbitrary info
53
+ - filter primitives for views
54
+ - default info_level to 'all', could be set to 'none'
55
+ - info_level applies to:
56
+ - entire view by default
57
+ - specified per node
58
+ - optionally inherited?
59
+ - allow nodes to assign summary fields? then could be set to 'summary'
60
+ * sort out fore vs back color
61
+ * check out structurizr.com
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dogviz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - damned
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-27 00:00:00.000000000 Z
11
+ date: 2017-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '12.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: simplecov
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: test-unit
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: ruby-graphviz
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: json
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.1.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.1.0
97
125
  description: leverages graphviz to generate multiple views of a domain-specific graph
98
126
  email:
99
127
  - writetodan@yahoo.com