statemachine 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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