state_machines-graphviz 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b375be8fce2b404843e60ffc947edb7d7b37a5e4
4
+ data.tar.gz: e46a7734205fd1ff0946dacb72195e82e1863d5a
5
+ SHA512:
6
+ metadata.gz: db7731a6a0236d51ca0a888870ab4f0bf1670ade1832051b2e84bd7220290b8ac5b86d473c089608aeb667388c70665fb9f2acf10cc89d59ee57d7f76fe59965
7
+ data.tar.gz: 854bb07b3eff8a50165b6bbc7012e2304259711d768238d5a0fbf88f02898f6e5a9730f36971a5c5d6f810cc87d30a649e9bfbf33a62230e1ff1ef99c7eda6c8
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ install: sudo apt-get install graphviz -y
3
+ before_install: gem install bundler
4
+ script: bundle exec rake
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - 2.1.1
9
+ - jruby-19mode
10
+ - rbx-2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in state_machine2-graphviz.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2006-2012 Aaron Pfeifer
2
+ Copyright (c) 2014 Abdelkader Boudih
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # StateMachines::Graphviz
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'state_machine2-graphviz' , group: :development
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install state_machine2-graphviz
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/seuros/state_machine2-graphviz/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
8
+
9
+ desc 'Default: run all tests.'
10
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ require 'state_machines'
2
+ require 'graphviz'
3
+ require 'state_machines/graphviz/monkeypatch'
4
+ require 'state_machines/graphviz/graph'
5
+ require 'state_machines/graphviz/version'
@@ -0,0 +1,72 @@
1
+ module StateMachines
2
+ # Provides a set of higher-order features on top of the raw GraphViz graphs
3
+ class Graph < GraphViz
4
+ # The name of the font to draw state names in
5
+ attr_reader :font
6
+
7
+ # The graph's full filename
8
+ attr_reader :file_path
9
+
10
+ # The image format to generate the graph in
11
+ attr_reader :file_format
12
+
13
+ # Creates a new graph with the given name.
14
+ #
15
+ # Configuration options:
16
+ # * <tt>:path</tt> - The path to write the graph file to. Default is the
17
+ # current directory (".").
18
+ # * <tt>:format</tt> - The image format to generate the graph in.
19
+ # Default is "png'.
20
+ # * <tt>:font</tt> - The name of the font to draw state names in.
21
+ # Default is "Arial".
22
+ # * <tt>:orientation</tt> - The direction of the graph ("portrait" or
23
+ # "landscape"). Default is "portrait".
24
+ def initialize(name, options = {})
25
+ options = { path: 'doc/state_machines', format: 'png', font: 'Arial', orientation: 'portrait' }.merge(options)
26
+ options.assert_valid_keys(:path, :format, :font, :orientation)
27
+
28
+ # TODO fail if path cannot be created or readonly
29
+ unless Dir.exist? options[:path]
30
+ FileUtils.mkpath(options[:path])
31
+ end
32
+ @font = options[:font]
33
+ @file_path = File.join(options[:path], "#{name}.#{options[:format]}")
34
+ @file_format = options[:format]
35
+
36
+ super('G', rankdir: options[:orientation] == 'landscape' ? 'LR' : 'TB')
37
+ end
38
+
39
+ # Generates the actual image file based on the nodes / edges added to the
40
+ # graph. The path to the file is based on the configuration options for
41
+ # this graph.
42
+ def output
43
+ super(@file_format => @file_path)
44
+ end
45
+
46
+ # Adds a new node to the graph. The font for the node will be automatically
47
+ # set based on the graph configuration. The generated node will be returned.
48
+ #
49
+ # For example,
50
+ #
51
+ # graph = StateMachines::Graph.new('test')
52
+ # graph.add_nodes('parked', :label => 'Parked', :width => '1', :height => '1', :shape => 'ellipse')
53
+ def add_nodes(*args)
54
+ node = super
55
+ node.fontname = @font
56
+ node
57
+ end
58
+
59
+ # Adds a new edge to the graph. The font for the edge will be automatically
60
+ # set based on the graph configuration. The generated edge will be returned.
61
+ #
62
+ # For example,
63
+ #
64
+ # graph = StateMachines::Graph.new('test')
65
+ # graph.add_edges('parked', 'idling', :label => 'ignite')
66
+ def add_edges(*args)
67
+ edge = super
68
+ edge.fontname = @font
69
+ edge
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,161 @@
1
+ #TODO register graphviz as render engine
2
+ module StateMachines
3
+ class Machine
4
+ class << self
5
+ # Draws the state machines defined in the given classes using GraphViz.
6
+ # The given classes must be a comma-delimited string of class names.
7
+ #
8
+ # Configuration options:
9
+ # * <tt>:file</tt> - A comma-delimited string of files to load that
10
+ # contain the state machine definitions to draw
11
+ # * <tt>:path</tt> - The path to write the graph file to
12
+ # * <tt>:format</tt> - The image format to generate the graph in
13
+ # * <tt>:font</tt> - The name of the font to draw state names in
14
+ def draw(class_names, options = {})
15
+ raise ArgumentError, 'At least one class must be specified' unless class_names && class_names.split(',').any?
16
+
17
+ # Load any files
18
+ if files = options.delete(:file)
19
+ files.split(',').each { |file| require file }
20
+ end
21
+
22
+ class_names.split(',').each do |class_name|
23
+ # Navigate through the namespace structure to get to the class
24
+ klass = Object
25
+ class_name.split('::').each do |name|
26
+ klass = klass.const_defined?(name) ? klass.const_get(name) : klass.const_missing(name)
27
+ end
28
+
29
+ # Draw each of the class's state machines
30
+ klass.state_machines.each_value do |machine|
31
+ machine.draw(options)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # Draws a directed graph of the machine for visualizing the various events,
38
+ # states, and their transitions.
39
+ #
40
+ # This requires both the Ruby graphviz gem and the graphviz library be
41
+ # installed on the system.
42
+ #
43
+ # Configuration options:
44
+ # * <tt>:name</tt> - The name of the file to write to (without the file extension).
45
+ # Default is "#{owner_class.name}_#{name}"
46
+ # * <tt>:path</tt> - The path to write the graph file to. Default is the
47
+ # current directory (".").
48
+ # * <tt>:format</tt> - The image format to generate the graph in.
49
+ # Default is "png'.
50
+ # * <tt>:font</tt> - The name of the font to draw state names in.
51
+ # Default is "Arial".
52
+ # * <tt>:orientation</tt> - The direction of the graph ("portrait" or
53
+ # "landscape"). Default is "portrait".
54
+ # * <tt>:human_names</tt> - Whether to use human state / event names for
55
+ # node labels on the graph instead of the internal name. Default is false.
56
+ def draw(graph_options = {})
57
+ name = graph_options.delete(:name) || "#{owner_class.name}_#{self.name}"
58
+ draw_options = {:human_name => false}
59
+ draw_options[:human_name] = graph_options.delete(:human_names) if graph_options.include?(:human_names)
60
+
61
+ graph = Graph.new(name, graph_options)
62
+
63
+ # Add nodes / edges
64
+ states.by_priority.each { |state| state.draw(graph, draw_options) }
65
+ events.each { |event| event.draw(graph, draw_options) }
66
+
67
+ # Output result
68
+ graph.output
69
+ graph
70
+ end
71
+ end
72
+
73
+ class State
74
+ # Draws a representation of this state on the given machine. This will
75
+ # create a new node on the graph with the following properties:
76
+ # * +label+ - The human-friendly description of the state.
77
+ # * +width+ - The width of the node. Always 1.
78
+ # * +height+ - The height of the node. Always 1.
79
+ # * +shape+ - The actual shape of the node. If the state is a final
80
+ # state, then "doublecircle", otherwise "ellipse".
81
+ #
82
+ # Configuration options:
83
+ # * <tt>:human_name</tt> - Whether to use the state's human name for the
84
+ # node's label that gets drawn on the graph
85
+ def draw(graph, options = {})
86
+ node = graph.add_nodes(name ? name.to_s : 'nil',
87
+ :label => description(options),
88
+ :width => '1',
89
+ :height => '1',
90
+ :shape => final? ? 'doublecircle' : 'ellipse'
91
+ )
92
+
93
+ # Add open arrow for initial state
94
+ graph.add_edges(graph.add_nodes('starting_state', :shape => 'point'), node) if initial?
95
+
96
+ true
97
+ end
98
+ end
99
+
100
+ class Event
101
+ # Draws a representation of this event on the given graph. This will
102
+ # create 1 or more edges on the graph for each branch (i.e. transition)
103
+ # configured.
104
+ #
105
+ # Configuration options:
106
+ # * <tt>:human_name</tt> - Whether to use the event's human name for the
107
+ # node's label that gets drawn on the graph
108
+ def draw(graph, options = {})
109
+ valid_states = machine.states.by_priority.map {|state| state.name}
110
+ branches.each do |branch|
111
+ branch.draw(graph, options[:human_name] ? human_name : name, valid_states)
112
+ end
113
+
114
+ true
115
+ end
116
+ end
117
+
118
+ class Branch
119
+ # Draws a representation of this branch on the given graph. This will draw
120
+ # an edge between every state this branch matches *from* to either the
121
+ # configured to state or, if none specified, then a loopback to the from
122
+ # state.
123
+ #
124
+ # For example, if the following from states are configured:
125
+ # * +idling+
126
+ # * +first_gear+
127
+ # * +backing_up+
128
+ #
129
+ # ...and the to state is +parked+, then the following edges will be created:
130
+ # * +idling+ -> +parked+
131
+ # * +first_gear+ -> +parked+
132
+ # * +backing_up+ -> +parked+
133
+ #
134
+ # Each edge will be labeled with the name of the event that would cause the
135
+ # transition.
136
+ def draw(graph, event, valid_states)
137
+ state_requirements.each do |state_requirement|
138
+ # From states determined based on the known valid states
139
+ from_states = state_requirement[:from].filter(valid_states)
140
+
141
+ # If a to state is not specified, then it's a loopback and each from
142
+ # state maps back to itself
143
+ if state_requirement[:to].values.empty?
144
+ loopback = true
145
+ else
146
+ to_state = state_requirement[:to].values.first
147
+ to_state = to_state ? to_state.to_s : 'nil'
148
+ loopback = false
149
+ end
150
+
151
+ # Generate an edge between each from and to state
152
+ from_states.each do |from_state|
153
+ from_state = from_state ? from_state.to_s : 'nil'
154
+ graph.add_edges(from_state, loopback ? from_state : to_state, :label => event.to_s)
155
+ end
156
+ end
157
+
158
+ true
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,6 @@
1
+ module StateMachines
2
+ # Gem version
3
+ module Graphviz
4
+ VERSION = '0.0.1'
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ class Switch
2
+ def self.name
3
+ @name ||= "Switch_#{rand(1_000_000)}"
4
+ end
5
+
6
+ state_machine do
7
+ event :turn_on do
8
+ transition all => :on
9
+ end
10
+
11
+ event :turn_off do
12
+ transition all => :off
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ describe StateMachines::Graph do
3
+ context 'GraphDefault' do
4
+ before(:each) do
5
+ @graph = StateMachines::Graph.new('test')
6
+ end
7
+
8
+ it 'should_have_a_default_font' do
9
+ expect('Arial').to eq(@graph.font)
10
+ end
11
+
12
+ it 'should_use_current_directory_for_filepath' do
13
+ expect('doc/state_machines/test.png').to eq(@graph.file_path)
14
+ end
15
+
16
+ it 'should_have_a_default_file_format' do
17
+ expect('png').to eq(@graph.file_format)
18
+ end
19
+
20
+ it 'should_have_a_default_orientation' do
21
+ expect('TB').to eq(@graph[:rankdir].source)
22
+ end
23
+ end
24
+
25
+ context 'GraphNodes' do
26
+ before(:each) do
27
+ @graph = StateMachines::Graph.new('test')
28
+ @node = @graph.add_nodes('parked', :shape => 'ellipse')
29
+ end
30
+
31
+ it 'should_return_generated_node' do
32
+ expect(@node).to_not be_nil
33
+ end
34
+
35
+ it 'should_use_specified_name' do
36
+ expect(@node).to eq(@graph.get_node('parked'))
37
+ end
38
+
39
+ it 'should_use_specified_options' do
40
+ expect('ellipse').to eq(@node['shape'].to_s.gsub('"', ''))
41
+ end
42
+
43
+ it 'should_set_default_font' do
44
+ expect('Arial').to eq(@node['fontname'].to_s.gsub('"', ''))
45
+ end
46
+ end
47
+
48
+ context 'GraphEdges' do
49
+ before(:each) do
50
+ @graph = StateMachines::Graph.new('test')
51
+ @graph.add_nodes('parked', :shape => 'ellipse')
52
+ @graph.add_nodes('idling', :shape => 'ellipse')
53
+ @edge = @graph.add_edges('parked', 'idling', :label => 'ignite')
54
+ end
55
+
56
+ it 'should_return_generated_edge' do
57
+ expect(@edge).to_not be_nil
58
+ end
59
+
60
+ it 'should_use_specified_nodes' do
61
+ expect('parked').to eq(@edge.node_one(false))
62
+ expect('idling').to eq(@edge.node_two(false))
63
+ end
64
+
65
+ it 'should_use_specified_options' do
66
+ expect('ignite').to eq(@edge['label'].to_s.gsub('"', ''))
67
+ end
68
+
69
+ it 'should_set_default_font' do
70
+ expect('Arial').to eq(@edge['fontname'].to_s.gsub('"', ''))
71
+ end
72
+ end
73
+
74
+ context 'GraphOutput' do
75
+ before(:each) do
76
+ @graph_name = "test_#{rand(1000000)}"
77
+ @graph = StateMachines::Graph.new(@graph_name)
78
+ @graph.add_nodes('parked', :shape => 'ellipse')
79
+ @graph.add_nodes('idling', :shape => 'ellipse')
80
+ @graph.add_edges('parked', 'idling', :label => 'ignite')
81
+ @graph.output
82
+ end
83
+
84
+ it 'should_save_file' do
85
+ expect(File.exist?("doc/state_machines/#{@graph_name}.png")).to be_truthy
86
+ end
87
+
88
+ after(:each) do
89
+ FileUtils.rm Dir["doc/state_machines/#{@graph_name}.png"]
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,215 @@
1
+ describe StateMachines::Graphviz do
2
+ context 'Drawing' do
3
+ let(:klass) do
4
+ Class.new do
5
+ def self.name
6
+ @name ||= "Vehicle_#{rand(1_000_000)}"
7
+ end
8
+ end
9
+ end
10
+ let(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
11
+
12
+ before(:each) do
13
+ machine.event :ignite do
14
+ transition parked: :idling
15
+ end
16
+ end
17
+
18
+ it 'should_raise_exception_if_invalid_option_specified' do
19
+ expect { machine.draw(invalid: true) }.to raise_error(ArgumentError)
20
+ end
21
+
22
+ it 'should_save_file_with_class_name_by_default' do
23
+ machine.draw
24
+ expect(File.exist?("doc/state_machines/#{klass.name}_state.png")).to be_truthy
25
+ end
26
+
27
+ it 'should_allow_base_name_to_be_customized' do
28
+ name = "machine_#{rand(1_000_000)}"
29
+ machine.draw(name: name)
30
+ @path = "doc/state_machines/#{name}.png"
31
+ expect(File.exist?(@path)).to be_truthy
32
+ end
33
+
34
+ it 'should_allow_format_to_be_customized' do
35
+ machine.draw(format: 'jpg')
36
+ @path = "doc/state_machines/#{klass.name}_state.jpg"
37
+ expect(File.exist?(@path)).to be_truthy
38
+ end
39
+
40
+ it 'should_allow_path_to_be_customized' do
41
+ machine.draw(path: "#{File.dirname(__FILE__)}/")
42
+ @path = "#{File.dirname(__FILE__)}/#{klass.name}_state.png"
43
+ expect(File.exist?(@path)).to be_truthy
44
+ end
45
+
46
+ it 'should_allow_orientation_to_be_landscape' do
47
+ graph = machine.draw(orientation: 'landscape')
48
+ expect(graph['rankdir'].to_s.gsub('"', '')).to eq('LR')
49
+ end
50
+
51
+ it 'should_allow_orientation_to_be_portrait' do
52
+ graph = machine.draw(orientation: 'portrait')
53
+ expect(graph['rankdir'].to_s.gsub('"', '')).to eq('TB')
54
+ end
55
+
56
+ it 'should_allow_human_names_to_be_displayed' do
57
+ machine.event :ignite, human_name: 'Ignite'
58
+ machine.state :parked, human_name: 'Parked'
59
+ machine.state :idling, human_name: 'Idling'
60
+ graph = machine.draw(human_names: true)
61
+
62
+ parked_node = graph.get_node('parked')
63
+ expect(parked_node['label'].to_s.gsub('"', '')).to eq('Parked')
64
+
65
+ idling_node = graph.get_node('idling')
66
+ expect(idling_node['label'].to_s.gsub('"', '')).to eq('Idling')
67
+ end
68
+
69
+ after(:each) do
70
+ FileUtils.rm Dir[@path || "doc/state_machines/#{klass.name}_state.png"]
71
+ end
72
+ end
73
+
74
+ context 'DrawingWithIntegerStates' do
75
+ let(:klass) do
76
+ Class.new do
77
+ def self.name
78
+ @name ||= "Vehicle_#{rand(1_000_000)}"
79
+ end
80
+ end
81
+ end
82
+
83
+ let(:machine) { StateMachines::Machine.new(klass, :state_id, initial: :parked) }
84
+ before(:each) do
85
+ machine.event :ignite do
86
+ transition parked: :idling
87
+ end
88
+ machine.state :parked, value: 1
89
+ machine.state :idling, value: 2
90
+ end
91
+
92
+ let!(:graph) { machine.draw }
93
+
94
+ it 'should_draw_all_states' do
95
+ expect(graph.node_count).to eq(3)
96
+ end
97
+
98
+ it 'should_draw_all_events' do
99
+ expect(graph.edge_count).to eq(2)
100
+ end
101
+
102
+ it 'should_draw_machine' do
103
+ expect(File.exist?("doc/state_machines/#{klass.name}_state_id.png")).to be_truthy
104
+ end
105
+
106
+ after(:each) do
107
+ FileUtils.rm Dir["doc/state_machines/#{klass.name}_state_id.png"]
108
+ end
109
+ end
110
+
111
+ context 'DrawingWithNilStates' do
112
+ let(:klass) do
113
+ Class.new do
114
+ def self.name
115
+ @name ||= "Vehicle_#{rand(1_000_000)}"
116
+ end
117
+ end
118
+ end
119
+ let(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
120
+
121
+ before(:each) do
122
+ machine.event :ignite do
123
+ transition parked: :idling
124
+ end
125
+ machine.state :parked, value: nil
126
+ end
127
+
128
+ let!(:graph) { machine.draw }
129
+
130
+ it 'should_draw_all_states' do
131
+ expect(graph.node_count).to eq(3)
132
+ end
133
+
134
+ it 'should_draw_all_events' do
135
+ expect(graph.edge_count).to eq(2)
136
+ end
137
+
138
+ it 'should_draw_machine' do
139
+ expect(File.exist?("doc/state_machines/#{klass.name}_state.png")).to be_truthy
140
+ end
141
+
142
+ after(:each) do
143
+ FileUtils.rm Dir["doc/state_machines/#{klass.name}_state.png"]
144
+ end
145
+ end
146
+
147
+ context 'DrawingWithDynamicStates' do
148
+ let(:klass) do
149
+ Class.new do
150
+ def self.name
151
+ @name ||= "Vehicle_#{rand(1_000_000)}"
152
+ end
153
+ end
154
+ end
155
+
156
+ let(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
157
+
158
+ before(:each) do
159
+ machine.event :activate do
160
+ transition parked: :idling
161
+ end
162
+ machine.state :idling, value: lambda { Time.now }
163
+ end
164
+
165
+ let!(:graph) { machine.draw }
166
+
167
+ it 'should_draw_all_states' do
168
+ expect(graph.node_count).to eq(3)
169
+ end
170
+
171
+ it 'should_draw_all_events' do
172
+ expect(graph.edge_count).to eq(2)
173
+ end
174
+
175
+ it 'should_draw_machine' do
176
+ expect(File.exist?("doc/state_machines/#{klass.name}_state.png")).to be_truthy
177
+ end
178
+
179
+ after(:each) do
180
+ FileUtils.rm Dir["doc/state_machines/#{klass.name}_state.png"]
181
+ end
182
+
183
+ end
184
+
185
+ context 'ClassDrawing' do
186
+ before(:each) do
187
+ klass = Class.new do
188
+ def self.name
189
+ @name ||= "Vehicle_#{rand(1_000_000)}"
190
+ end
191
+ end
192
+ machine = StateMachines::Machine.new(klass)
193
+ machine.event :ignite do
194
+ transition parked: :idling
195
+ end
196
+ end
197
+
198
+ it 'should_raise_exception_if_no_class_names_specified' do
199
+ expect { StateMachines::Machine.draw(nil) }.to raise_error(ArgumentError)
200
+ # FixMe
201
+ # assert_equal 'At least one class must be specified', exception.message
202
+ end
203
+
204
+ it 'should_load_files' do
205
+ StateMachines::Machine.draw('Switch', file: File.expand_path("#{File.dirname(__FILE__)}/files/switch.rb"))
206
+ expect(defined?(::Switch)).to be_truthy
207
+ end
208
+
209
+ it 'should_allow_path_and_format_to_be_customized' do
210
+ StateMachines::Machine.draw('Switch', file: File.expand_path("#{File.dirname(__FILE__)}/files/switch.rb"), path: "#{File.dirname(__FILE__)}/", format: 'jpg')
211
+ expect(File.exist?("#{File.dirname(__FILE__)}/#{Switch.name}_state.jpg")).to be_truthy
212
+ FileUtils.rm Dir["{.,#{File.dirname(__FILE__)}}/#{Switch.name}_state.{jpg,png}"]
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,19 @@
1
+ describe StateMachines::Machine do
2
+ before(:each) do
3
+ klass = Class.new do
4
+ def self.name
5
+ @name ||= "Vehicle_#{rand(1_000_000)}"
6
+ end
7
+ end
8
+
9
+ @machine = StateMachines::Machine.new(klass, initial: :parked)
10
+ @machine.event :ignite do
11
+ transition parked: :idling
12
+ end
13
+ end
14
+
15
+ it 'should not raise exception' do
16
+ expect { @machine.draw }.not_to raise_error
17
+ end
18
+
19
+ end
@@ -0,0 +1,2 @@
1
+ require 'state_machines'
2
+ require 'state_machines/graphviz'
@@ -0,0 +1,142 @@
1
+ context 'Drawing' do
2
+ before(:each) do
3
+ @machine = StateMachines::Machine.new(Class.new)
4
+ @machine.states << @state = StateMachines::State.new(@machine, :parked, :value => 1)
5
+ @machine.event :ignite do
6
+ transition :parked => :idling
7
+ end
8
+
9
+ graph = StateMachines::Graph.new('test')
10
+ @state.draw(graph)
11
+ @node = graph.get_node('parked')
12
+ end
13
+
14
+ it 'should_use_ellipse_shape' do
15
+ expect(@node['shape'].to_s.gsub('"', '')).to eq('ellipse')
16
+ end
17
+
18
+ it 'should_set_width_to_one' do
19
+ expect('1').to eq(@node['width'].to_s.gsub('"', ''))
20
+ end
21
+
22
+ it 'should_set_height_to_one' do
23
+ expect('1').to eq(@node['height'].to_s.gsub('"', ''))
24
+ end
25
+
26
+ it 'should_use_description_as_label' do
27
+ expect('parked (1)').to eq(@node['label'].to_s.gsub('"', ''))
28
+ end
29
+ end
30
+
31
+ context 'DrawingInitial' do
32
+ before(:each) do
33
+ @machine = StateMachines::Machine.new(Class.new)
34
+ @machine.states << @state = StateMachines::State.new(@machine, :parked, :initial => true)
35
+ @machine.event :ignite do
36
+ transition :parked => :idling
37
+ end
38
+
39
+ @graph = StateMachines::Graph.new('test')
40
+ @state.draw(@graph)
41
+ @node = @graph.get_node('parked')
42
+ end
43
+
44
+ it 'should_use_ellipse_as_shape' do
45
+ expect('ellipse').to eq(@node['shape'].to_s.gsub('"', ''))
46
+ end
47
+
48
+ it 'should_draw_edge_between_point_and_state' do
49
+ expect(2).to eq(@graph.node_count)
50
+ expect(1).to eq(@graph.edge_count)
51
+ end
52
+ end
53
+
54
+ context 'DrawingNilName' do
55
+ before(:each) do
56
+ @machine = StateMachines::Machine.new(Class.new)
57
+ @machine.states << @state = StateMachines::State.new(@machine, nil)
58
+
59
+ graph = StateMachines::Graph.new('test')
60
+ @state.draw(graph)
61
+ @node = graph.get_node('nil')
62
+ end
63
+
64
+ it 'should_have_a_node' do
65
+ expect(@node).to be_truthy
66
+ end
67
+
68
+ it 'should_use_description_as_label' do
69
+ expect('nil').to eq(@node['label'].to_s.gsub('"', ''))
70
+ end
71
+ end
72
+
73
+ context 'DrawingLambdaValue' do
74
+ before(:each) do
75
+ @machine = StateMachines::Machine.new(Class.new)
76
+ @machine.states << @state = StateMachines::State.new(@machine, :parked, :value => lambda {})
77
+
78
+ graph = StateMachines::Graph.new('test')
79
+ @state.draw(graph)
80
+ @node = graph.get_node('parked')
81
+ end
82
+
83
+ it 'should_have_a_node' do
84
+ expect(@node).to be_truthy
85
+ end
86
+
87
+ it 'should_use_description_as_label' do
88
+ expect('parked (*)').to eq(@node['label'].to_s.gsub('"', ''))
89
+ end
90
+ end
91
+
92
+ context 'DrawingNonFinal' do
93
+ before(:each) do
94
+ @machine = StateMachines::Machine.new(Class.new)
95
+ @machine.states << @state = StateMachines::State.new(@machine, :parked)
96
+ @machine.event :ignite do
97
+ transition :parked => :idling
98
+ end
99
+
100
+ graph = StateMachines::Graph.new('test')
101
+ @state.draw(graph)
102
+ @node = graph.get_node('parked')
103
+ end
104
+
105
+ it 'should_use_ellipse_as_shape' do
106
+ expect('ellipse').to eq(@node['shape'].to_s.gsub('"', ''))
107
+ end
108
+ end
109
+
110
+ context 'DrawingFinal' do
111
+ before(:each) do
112
+ @machine = StateMachines::Machine.new(Class.new)
113
+ @machine.states << @state = StateMachines::State.new(@machine, :parked)
114
+
115
+ graph = StateMachines::Graph.new('test')
116
+ @state.draw(graph)
117
+ @node = graph.get_node('parked')
118
+ end
119
+
120
+ it 'should_use_doublecircle_as_shape' do
121
+ expect('doublecircle').to eq(@node['shape'].to_s.gsub('"', ''))
122
+ end
123
+ end
124
+
125
+ context 'DrawingWithHumanName' do
126
+ before(:each) do
127
+ @machine = StateMachines::Machine.new(Class.new)
128
+ @machine.states << @state = StateMachines::State.new(@machine, :parked, :human_name => 'Parked')
129
+ @machine.event :ignite do
130
+ transition :parked => :idling
131
+ end
132
+
133
+ graph = StateMachines::Graph.new('test')
134
+ @state.draw(graph, :human_name => true)
135
+ @node = graph.get_node('parked')
136
+ end
137
+
138
+ it 'should_use_description_with_human_name_as_label' do
139
+ expect('Parked').to eq(@node['label'].to_s.gsub('"', ''))
140
+ end
141
+ end
142
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'state_machines/graphviz/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'state_machines-graphviz'
8
+ spec.version = StateMachines::Graphviz::VERSION
9
+ spec.authors = ['Abdelkader Boudih', 'Aaron Pfeifer']
10
+ spec.email = ['terminale@gmail.com']
11
+ spec.summary = %q(Drawing module for state machine2)
12
+ spec.description = %q(Graphviz module for state machine2)
13
+ spec.homepage = 'https://github.com/seuros/state_machines-graphviz'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r{/^spec\//})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'state_machines'
21
+ spec.add_dependency 'ruby-graphviz'
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec' , '3.0.0.beta2'
25
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: state_machines-graphviz
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Abdelkader Boudih
8
+ - Aaron Pfeifer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: state_machines
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: ruby-graphviz
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '='
75
+ - !ruby/object:Gem::Version
76
+ version: 3.0.0.beta2
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '='
82
+ - !ruby/object:Gem::Version
83
+ version: 3.0.0.beta2
84
+ description: Graphviz module for state machine2
85
+ email:
86
+ - terminale@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yaml"
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - lib/state_machines/graphviz.rb
99
+ - lib/state_machines/graphviz/graph.rb
100
+ - lib/state_machines/graphviz/monkeypatch.rb
101
+ - lib/state_machines/graphviz/version.rb
102
+ - spec/files/switch.rb
103
+ - spec/graph_spec.rb
104
+ - spec/machine_drawing_spec.rb
105
+ - spec/machine_spec.rb
106
+ - spec/spec_helper.rb
107
+ - spec/state_spec.rb
108
+ - state_machines-graphviz.gemspec
109
+ homepage: https://github.com/seuros/state_machines-graphviz
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.2.2
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Drawing module for state machine2
133
+ test_files: []
134
+ has_rdoc: