statemachine 1.0.0 → 1.1.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.
data/CHANGES CHANGED
@@ -1,5 +1,16 @@
1
1
  = Statemachine Changelog
2
2
 
3
+ == Version 1.1.0
4
+
5
+ DotGraph
6
+ * DotGraph generator was added to generate graphs of statemachines using Omnigraffle.
7
+ * Fixed bug in Java generator where Statenames where not formated correctly.
8
+
9
+ == Version 1.0.0
10
+
11
+ Generator
12
+ * Java generator was added. Statemachines defined in the Ruby DSL can generate Java code.
13
+
3
14
  == Version 0.4.2
4
15
 
5
16
  Simple Fixes
data/README.rdoc CHANGED
@@ -21,7 +21,7 @@ at http://blog.8thlight.com/articles/2006/11/17/understanding-statemachines-part
21
21
 
22
22
  == License
23
23
 
24
- Copyright (C) 2006 Micah Martin
24
+ Copyright (C) 2006-2010 Micah Martin
25
25
 
26
26
  This library is free software; you can redistribute it and/or
27
27
  modify it under the terms of the GNU Lesser General Public
@@ -0,0 +1 @@
1
+ require 'statemachine/generate/dot_graph/dot_graph_statemachine'
@@ -0,0 +1,127 @@
1
+ require 'statemachine/generate/util'
2
+ require 'statemachine/generate/src_builder'
3
+
4
+ module Statemachine
5
+ class Statemachine
6
+
7
+ attr_reader :states
8
+
9
+ def to_dot(options = {})
10
+ generator = Generate::DotGraph::DotGraphStatemachine.new(self, options)
11
+ generator.generate!
12
+ end
13
+
14
+ end
15
+
16
+ module Generate
17
+ module DotGraph
18
+
19
+ class DotGraphStatemachine
20
+
21
+ include Generate::Util
22
+
23
+ def initialize(sm, options)
24
+ @sm = sm
25
+ @output_dir = options[:output]
26
+ raise "Please specify an output directory. (:output => 'where/you/want/your/code')" if @output_dir.nil?
27
+ raise "Output dir '#{@output_dir}' doesn't exist." if !File.exist?(@output_dir)
28
+ end
29
+
30
+ def generate!
31
+ explore_sm
32
+ save_output(src_file("main"), build_full_graph)
33
+ @sm.states.values.each do |state|
34
+ save_output(src_file("#{state.id}"), build_state_graph(state))
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def explore_sm
41
+ @nodes = []
42
+ @transitions = []
43
+ @sm.states.values.each { |state|
44
+ state.transitions.values.each { |transition|
45
+ @nodes << transition.origin_id
46
+ @nodes << transition.destination_id
47
+ @transitions << transition
48
+ }
49
+ }
50
+ @nodes = @nodes.uniq
51
+ end
52
+
53
+ def build_full_graph
54
+ builder = Generate::SrcBuilder.new
55
+
56
+ add_graph_header(builder, "main")
57
+
58
+ @nodes.each { |node| add_node(builder, node) }
59
+ builder << endl
60
+
61
+ @transitions.each do |transition|
62
+ add_transition(builder, transition)
63
+ end
64
+
65
+ add_graph_footer(builder)
66
+
67
+ return builder.to_s
68
+ end
69
+
70
+ def build_state_graph(state)
71
+ builder = Generate::SrcBuilder.new
72
+
73
+ add_graph_header(builder, state.id)
74
+
75
+ state.transitions.values.each do |transition|
76
+ add_transition(builder, transition)
77
+ end
78
+
79
+ add_graph_footer(builder)
80
+
81
+ return builder.to_s
82
+ end
83
+
84
+ def add_graph_header(builder, graph_name)
85
+ builder << "digraph #{graph_name} {" << endl
86
+ builder.indent!
87
+ end
88
+
89
+ def add_graph_footer(builder)
90
+ builder.undent!
91
+ builder << "}" << endl
92
+ end
93
+
94
+ def add_node(builder, node)
95
+ builder << node
96
+ builder << " [ href = \"#{node}.svg\"]"
97
+ builder << endl
98
+ end
99
+
100
+ def add_transition(builder, transition)
101
+ builder << transition.origin_id
102
+ builder << " -> "
103
+ builder << transition.destination_id
104
+ builder << " [ "
105
+ builder << "label = #{transition.event} "
106
+ builder << "]"
107
+ builder << endl
108
+ end
109
+
110
+ def src_file(name)
111
+ return name if @output_dir.nil?
112
+ path = @output_dir
113
+ answer = File.join(path, "#{name}.dot")
114
+ return answer
115
+ end
116
+
117
+ def save_output(filename, content)
118
+ if @output_dir.nil?
119
+ say "Writing to file: #{filename}"
120
+ else
121
+ create_file(filename, content)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -37,6 +37,7 @@ module Statemachine
37
37
  explore_sm
38
38
  create_file(src_file(@classname), build_statemachine_src)
39
39
  create_file(src_file(@context_classname), build_context_src)
40
+ say "Statemachine generated."
40
41
  end
41
42
 
42
43
  private ###########################################
@@ -88,8 +89,8 @@ module Statemachine
88
89
  src << "// Instance variables" << endl
89
90
  concrete_states = @sm.states.values.reject { |state| state.id.nil? || !state.concrete? }.sort { |a, b| a.id <=> b.id }
90
91
  concrete_states.each do |state|
91
- name = state.id.to_s.camalized
92
- src << "public final State #{name.upcase} = new #{name}State(this);" << endl
92
+ name = state.id.to_s
93
+ src << "public final State #{name.upcase} = new #{name.camalized}State(this);" << endl
93
94
  end
94
95
  superstates = @sm.states.values.reject { |state| state.concrete? }.sort { |a, b| a.id <=> b.id }
95
96
  superstates.each do |superstate|
@@ -193,7 +194,7 @@ module Statemachine
193
194
  src << "statemachine.getContext().#{transition.action.to_s.camalized(:lower)}();" << endl if transition.action
194
195
  src << "statemachine.setState(statemachine.#{transition.destination_id.to_s.upcase});" << endl
195
196
  entries.each do |entry|
196
- src << "statemachine.getContext().#{entry.entry_action.to_s.camalized(:lower)}();" << endl if entry.entry_action
197
+ src << "statemachine.getContext().#{entry.entry_action.to_s.camalized(:lower)}();" << endl if entry.entry_action
197
198
  end
198
199
  end
199
200
  end
@@ -244,6 +245,7 @@ module Statemachine
244
245
 
245
246
  def create_file(filename, content)
246
247
  establish_directory(File.dirname(filename))
248
+ say "Writing to file: #{filename}"
247
249
  File.open(filename, 'w') do |file|
248
250
  file.write(content)
249
251
  end
@@ -260,4 +262,4 @@ module Statemachine
260
262
  end
261
263
  end
262
264
  end
263
- end
265
+ end
@@ -4,6 +4,13 @@ module Statemachine
4
4
  module Generate
5
5
  module Util
6
6
 
7
+ def create_file(filename, content)
8
+ establish_directory(File.dirname(filename))
9
+ File.open(filename, 'w') do |file|
10
+ file.write(content)
11
+ end
12
+ end
13
+
7
14
  def establish_directory(path)
8
15
  return if File.exist?(path)
9
16
  establish_directory(File.dirname(path))
@@ -18,6 +25,12 @@ module Statemachine
18
25
  return :endl
19
26
  end
20
27
 
28
+ def say(message)
29
+ if !defined?($IS_TEST)
30
+ puts message
31
+ end
32
+ end
33
+
21
34
  end
22
35
  end
23
36
  end
@@ -34,4 +47,4 @@ class Symbol
34
47
  def <=>(other)
35
48
  return to_s <=> other.to_s
36
49
  end
37
- end
50
+ end
@@ -1,52 +1,52 @@
1
1
  module Statemachine
2
-
2
+
3
3
  class StatemachineException < Exception
4
4
  end
5
-
6
- class TransitionMissingException < Exception
5
+
6
+ class TransitionMissingException < Exception
7
7
  end
8
-
8
+
9
9
  # Used at runtime to execute the behavior of the statemachine.
10
10
  # Should be created by using the Statemachine.build method.
11
- #
11
+ #
12
12
  # sm = Statemachine.build do
13
13
  # trans :locked, :coin, :unlocked
14
14
  # trans :unlocked, :pass, :locked:
15
15
  # end
16
- #
16
+ #
17
17
  # sm.coin
18
18
  # sm.state
19
- #
19
+ #
20
20
  # This class will accept any method that corresponds to an event. If the
21
21
  # current state respons to the event, the appropriate transtion will be invoked.
22
22
  # Otherwise an exception will be raised.
23
23
  class Statemachine
24
24
  include ActionInvokation
25
-
26
- # The tracer is an IO object. The statemachine will write run time execution
25
+
26
+ # The tracer is an IO object. The statemachine will write run time execution
27
27
  # information to the +tracer+. Can be helpful in debugging. Defaults to nil.
28
28
  attr_accessor :tracer
29
-
29
+
30
30
  # Provides access to the +context+ of the statemachine. The context is a object
31
31
  # where all actions will be invoked. This provides a way to separate logic from
32
32
  # behavior. The statemachine is responsible for all the logic and the context
33
- # is responsible for all the behavior.
33
+ # is responsible for all the behavior.
34
34
  attr_accessor :context
35
-
35
+
36
36
  attr_reader :root #:nodoc:
37
-
37
+
38
38
  # Should not be called directly. Instances of Statemachine::Statemachine are created
39
39
  # through the Statemachine.build method.
40
40
  def initialize(root = Superstate.new(:root, nil, self))
41
41
  @root = root
42
42
  @states = {}
43
43
  end
44
-
44
+
45
45
  # Returns the id of the startstate of the statemachine.
46
46
  def startstate
47
47
  return @root.startstate_id
48
48
  end
49
-
49
+
50
50
  # Resets the statemachine back to its starting state.
51
51
  def reset
52
52
  @state = get_state(@root.startstate_id)
@@ -55,15 +55,15 @@ module Statemachine
55
55
  end
56
56
  raise StatemachineException.new("The state machine doesn't know where to start. Try setting the startstate.") if @state == nil
57
57
  @state.enter
58
-
58
+
59
59
  @states.values.each { |state| state.reset }
60
60
  end
61
-
61
+
62
62
  # Return the id of the current state of the statemachine.
63
63
  def state
64
64
  return @state.id
65
65
  end
66
-
66
+
67
67
  # You may change the state of the statemachine by using this method. The parameter should be
68
68
  # the id of the desired state.
69
69
  def state= value
@@ -75,10 +75,10 @@ module Statemachine
75
75
  @state = @states[value.to_sym]
76
76
  end
77
77
  end
78
-
78
+
79
79
  # The key method to exercise the statemachine. Any extra arguments supplied will be passed into
80
80
  # any actions associated with the transition.
81
- #
81
+ #
82
82
  # Alternatively to this method, you may invoke methods, names the same as the event, on the statemachine.
83
83
  # The advantage of using +process_event+ is that errors messages are more informative.
84
84
  def process_event(event, *args)
@@ -95,11 +95,11 @@ module Statemachine
95
95
  raise StatemachineException.new("The state machine isn't in any state while processing the '#{event}' event.")
96
96
  end
97
97
  end
98
-
98
+
99
99
  def trace(message) #:nodoc:
100
100
  @tracer.puts message if @tracer
101
101
  end
102
-
102
+
103
103
  def get_state(id) #:nodoc:
104
104
  if @states.has_key? id
105
105
  return @states[id]
@@ -114,11 +114,11 @@ module Statemachine
114
114
  return state
115
115
  end
116
116
  end
117
-
117
+
118
118
  def add_state(state) #:nodoc:
119
119
  @states[state.id] = state
120
120
  end
121
-
121
+
122
122
  def has_state(id) #:nodoc:
123
123
  if(is_history_state_id?(id))
124
124
  return @states.has_key?(base_id(id))
@@ -126,13 +126,13 @@ module Statemachine
126
126
  return @states.has_key?(id)
127
127
  end
128
128
  end
129
-
129
+
130
130
  def respond_to?(message)
131
131
  return true if super(message)
132
132
  return true if @state and @state.transition_for(message)
133
133
  return false
134
134
  end
135
-
135
+
136
136
  def method_missing(message, *args) #:nodoc:
137
137
  if @state and @state.transition_for(message)
138
138
  process_event(message.to_sym, *args)
@@ -147,17 +147,17 @@ module Statemachine
147
147
  end
148
148
  end
149
149
  end
150
-
151
- private
152
-
150
+
151
+ private
152
+
153
153
  def is_history_state_id?(id)
154
154
  return id.to_s[-2..-1] == "_H"
155
155
  end
156
-
156
+
157
157
  def base_id(history_id)
158
158
  return history_id.to_s[0...-2].to_sym
159
159
  end
160
-
160
+
161
161
  def load_history(superstate)
162
162
  100.times do
163
163
  history = superstate.history_id ? get_state(superstate.history_id) : nil
@@ -170,6 +170,6 @@ module Statemachine
170
170
  end
171
171
  raise StatemachineException.new("No history found within 100 levels of nested superstates.")
172
172
  end
173
-
173
+
174
174
  end
175
- end
175
+ end
@@ -16,7 +16,9 @@ module Statemachine
16
16
  end
17
17
 
18
18
  def __generic_method(name, *args)
19
- puts "action invoked: #{name}(#{args.join(", ")}) #{block_given? ? "with block" : ""}" if @verbose
19
+ if !defined?($IS_TEST)
20
+ puts "action invoked: #{name}(#{args.join(", ")}) #{block_given? ? "with block" : ""}" if @verbose
21
+ end
20
22
  end
21
23
 
22
24
  end
@@ -2,8 +2,8 @@ module Statemachine
2
2
  module VERSION #:nodoc:
3
3
  unless defined? MAJOR
4
4
  MAJOR = 1
5
- MINOR = 0
6
- TINY = 0
5
+ MINOR = 1
6
+ TINY = 1
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
9
9
  TAG = "REL_" + [MAJOR, MINOR, TINY].join('_')
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require 'statemachine/generate/dot_graph/dot_graph_statemachine'
3
+
4
+ describe Statemachine::Statemachine, "(Turn Stile)" do
5
+ include TurnstileStatemachine
6
+
7
+ before(:each) do
8
+ remove_test_dir("dot")
9
+ @output = test_dir("dot")
10
+ create_turnstile
11
+ end
12
+
13
+ # it "should output to console when no output dir provided" do
14
+ # # Note - this test doesn't verify output to the console, but it does
15
+ # # ensure that the to_dot call does not fail if not output is provided.
16
+ # @sm.to_dot
17
+ # end
18
+
19
+ it "should generate a basic graph declaration" do
20
+ @sm.to_dot(:output => @output)
21
+
22
+ dot = load_lines(@output, "main.dot")
23
+
24
+ dot.should_not equal(nil)
25
+ dot[0].include?("digraph").should == true
26
+ end
27
+ end
@@ -48,11 +48,11 @@ describe Statemachine::Statemachine, "(Java)" do
48
48
  @sm.to_java(:output => @output, :name => "JavaTurnstile", :package => "test.turnstile")
49
49
  end
50
50
 
51
- def load_lines(*segs)
52
- filename = File.join(*segs)
53
- File.should exist( filename)
54
- return IO.read(filename).split("\n")
55
- end
51
+ # def load_lines(*segs)
52
+ # filename = File.join(*segs)
53
+ # File.should exist( filename)
54
+ # return IO.read(filename).split("\n")
55
+ # end
56
56
 
57
57
  def empty_sm_lines
58
58
  @sm.to_java(:name => "JavaTest", :output => @output, :package => "test.blank")
@@ -345,4 +345,4 @@ describe Statemachine::Statemachine, "(Java)" do
345
345
  lines.shift.should == " }"
346
346
  end
347
347
 
348
- end
348
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,8 @@ require 'rubygems'
3
3
  require 'spec'
4
4
  require 'statemachine'
5
5
 
6
+ $IS_TEST = true
7
+
6
8
  def check_transition(transition, origin_id, destination_id, event, action)
7
9
  transition.should_not equal(nil)
8
10
  transition.event.should equal(event)
@@ -12,20 +14,20 @@ def check_transition(transition, origin_id, destination_id, event, action)
12
14
  end
13
15
 
14
16
  module SwitchStatemachine
15
-
17
+
16
18
  def create_switch
17
19
  @status = "off"
18
20
  @sm = Statemachine.build do
19
- trans :off, :toggle, :on, Proc.new { @status = "on" }
21
+ trans :off, :toggle, :on, Proc.new { @status = "on" }
20
22
  trans :on, :toggle, :off, Proc.new { @status = "off" }
21
23
  end
22
24
  @sm.context = self
23
25
  end
24
-
26
+
25
27
  end
26
28
 
27
29
  module TurnstileStatemachine
28
-
30
+
29
31
  def create_turnstile
30
32
  @locked = true
31
33
  @alarm_status = false
@@ -34,7 +36,7 @@ module TurnstileStatemachine
34
36
  @unlock = "@locked = false"
35
37
  @alarm = "@alarm_status = true"
36
38
  @thankyou = "@thankyou_status = true"
37
-
39
+
38
40
  @sm = Statemachine.build do
39
41
  trans :locked, :coin, :unlocked, "@locked = false"
40
42
  trans :unlocked, :pass, :locked, "@locked = true"
@@ -58,4 +60,10 @@ end
58
60
 
59
61
  def remove_test_dir(name)
60
62
  system "rm -rf #{test_dir(name)}" if File.exist?(test_dir(name))
61
- end
63
+ end
64
+
65
+ def load_lines(*segs)
66
+ filename = File.join(*segs)
67
+ File.should exist( filename)
68
+ return IO.read(filename).split("\n")
69
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 0
8
- - 0
9
- version: 1.0.0
7
+ - 1
8
+ - 1
9
+ version: 1.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Micah Martin
@@ -14,7 +14,7 @@ autorequire: statemachine
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-25 00:00:00 -05:00
17
+ date: 2010-09-29 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -34,6 +34,8 @@ files:
34
34
  - TODO
35
35
  - lib/statemachine/action_invokation.rb
36
36
  - lib/statemachine/builder.rb
37
+ - lib/statemachine/generate/dot_graph/dot_graph_statemachine.rb
38
+ - lib/statemachine/generate/dot_graph.rb
37
39
  - lib/statemachine/generate/java/java_statemachine.rb
38
40
  - lib/statemachine/generate/java.rb
39
41
  - lib/statemachine/generate/src_builder.rb
@@ -48,6 +50,7 @@ files:
48
50
  - spec/action_invokation_spec.rb
49
51
  - spec/builder_spec.rb
50
52
  - spec/default_transition_spec.rb
53
+ - spec/generate/dot_graph/dot_graph_stagemachine_spec.rb
51
54
  - spec/generate/java/java_statemachine_spec.rb
52
55
  - spec/history_spec.rb
53
56
  - spec/sm_action_parameterization_spec.rb
@@ -87,7 +90,7 @@ rubyforge_project: statemachine
87
90
  rubygems_version: 1.3.6
88
91
  signing_key:
89
92
  specification_version: 3
90
- summary: Statemachine-1.0.0 - Statemachine Library for Ruby http://slagyr.github.com/statemachine
93
+ summary: Statemachine-1.1.1 - Statemachine Library for Ruby http://slagyr.github.com/statemachine
91
94
  test_files:
92
95
  - spec/action_invokation_spec.rb
93
96
  - spec/builder_spec.rb