state_machines-graphviz 0.0.1

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.
@@ -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: