Skip to content

Commit

Permalink
added some examples/tests for the dot_graph generation. Made comments…
Browse files Browse the repository at this point in the history
… hidden during test runs
  • Loading branch information
slagyr committed Sep 29, 2010
2 parents f1094f3 + 100affe commit d14b57d
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 52 deletions.
11 changes: 11 additions & 0 deletions CHANGES
@@ -1,5 +1,16 @@
= Statemachine Changelog

== Version 1.1.0

DotGraph
* DotGraph generator was added to generate graphs of statemachines using Omnigraffle.
* Fixed bug in Java generator where Statenames where not formated correctly.

== Version 1.0.0

Generator
* Java generator was added. Statemachines defined in the Ruby DSL can generate Java code.

== Version 0.4.2

Simple Fixes
Expand Down
29 changes: 29 additions & 0 deletions generate_tests/dot_graph/turnstile.rb
@@ -0,0 +1,29 @@
$: << File.expand_path(File.dirname(__FILE__) + "/../../lib")
require 'statemachine'
require 'statemachine/generate/dot_graph'
@output = File.expand_path(File.dirname(__FILE__) + "/turnstile")

def clean
class_files = Dir.glob("#{@output}/*.dot")
class_files.each { |file| system "rm #{file}" }
end

def generate
@sm = Statemachine.build do
trans :locked, :coin, :unlocked, :unlock
trans :unlocked, :pass, :locked, :lock
trans :locked, :pass, :locked, :alarm
trans :unlocked, :coin, :locked, :thanks
end
@sm.to_dot(:output => @output)
end

def open
`open #{@output}/main.dot`
end

clean
generate
open


48 changes: 48 additions & 0 deletions generate_tests/dot_graph/turnstile2.rb
@@ -0,0 +1,48 @@
$: << File.expand_path(File.dirname(__FILE__) + "/../../lib")
require 'statemachine'
require 'statemachine/generate/dot_graph'
@output = File.expand_path(File.dirname(__FILE__) + "/turnstile2")

def clean
class_files = Dir.glob("#{@output}/*.dot")
class_files.each { |file| system "rm #{file}" }
end

def generate
@sm = Statemachine.build do
superstate :operational do
on_entry :operate
on_exit :beep
state :locked do
on_entry :lock
event :coin, :unlocked
event :pass, :locked, :alarm
end
state :unlocked do
on_entry :unlock
event :coin, :unlocked, :thanks
event :pass, :locked
end
event :diagnose, :diagnostics
end
state :diagnostics do
on_entry :disable
on_exit :beep
event :operate, :operational
end
stub_context :verbose => false
end

@sm.to_dot(:output => @output)
end

def open
`open #{@output}/main.dot`
end


clean
generate
open


1 change: 1 addition & 0 deletions lib/statemachine/generate/dot_graph.rb
@@ -0,0 +1 @@
require 'statemachine/generate/dot_graph/dot_graph_statemachine'
8 changes: 4 additions & 4 deletions lib/statemachine/generate/java/java_statemachine.rb
Expand Up @@ -37,7 +37,7 @@ def generate!
explore_sm
create_file(src_file(@classname), build_statemachine_src)
create_file(src_file(@context_classname), build_context_src)
puts "Statemachine generated."
say "Statemachine generated."
end

private ###########################################
Expand Down Expand Up @@ -194,7 +194,7 @@ def add_state_event_handler(transition, src)
src << "statemachine.getContext().#{transition.action.to_s.camalized(:lower)}();" << endl if transition.action
src << "statemachine.setState(statemachine.#{transition.destination_id.to_s.upcase});" << endl
entries.each do |entry|
src << "statemachine.getContext().#{entry.entry_action.to_s.camalized(:lower)}();" << endl if entry.entry_action
src << "statemachine.getContext().#{entry.entry_action.to_s.camalized(:lower)}();" << endl if entry.entry_action
end
end
end
Expand Down Expand Up @@ -245,7 +245,7 @@ def begin_src

def create_file(filename, content)
establish_directory(File.dirname(filename))
puts "Writing to file: #{filename}"
say "Writing to file: #{filename}"
File.open(filename, 'w') do |file|
file.write(content)
end
Expand All @@ -262,4 +262,4 @@ def src_file(name)
end
end
end
end
end
15 changes: 14 additions & 1 deletion lib/statemachine/generate/util.rb
Expand Up @@ -4,6 +4,13 @@ module Statemachine
module Generate
module Util

def create_file(filename, content)
establish_directory(File.dirname(filename))
File.open(filename, 'w') do |file|
file.write(content)
end
end

def establish_directory(path)
return if File.exist?(path)
establish_directory(File.dirname(path))
Expand All @@ -18,6 +25,12 @@ def endl
return :endl
end

def say(message)
if !defined?($IS_TEST)
puts message
end
end

end
end
end
Expand All @@ -34,4 +47,4 @@ class Symbol
def <=>(other)
return to_s <=> other.to_s
end
end
end
66 changes: 33 additions & 33 deletions lib/statemachine/statemachine.rb
@@ -1,52 +1,52 @@
module Statemachine

class StatemachineException < Exception
end
class TransitionMissingException < Exception

class TransitionMissingException < Exception
end

# Used at runtime to execute the behavior of the statemachine.
# Should be created by using the Statemachine.build method.
#
#
# sm = Statemachine.build do
# trans :locked, :coin, :unlocked
# trans :unlocked, :pass, :locked:
# end
#
#
# sm.coin
# sm.state
#
#
# This class will accept any method that corresponds to an event. If the
# current state respons to the event, the appropriate transtion will be invoked.
# Otherwise an exception will be raised.
class Statemachine
include ActionInvokation
# The tracer is an IO object. The statemachine will write run time execution

# The tracer is an IO object. The statemachine will write run time execution
# information to the +tracer+. Can be helpful in debugging. Defaults to nil.
attr_accessor :tracer

# Provides access to the +context+ of the statemachine. The context is a object
# where all actions will be invoked. This provides a way to separate logic from
# behavior. The statemachine is responsible for all the logic and the context
# is responsible for all the behavior.
# is responsible for all the behavior.
attr_accessor :context

attr_reader :root #:nodoc:

# Should not be called directly. Instances of Statemachine::Statemachine are created
# through the Statemachine.build method.
def initialize(root = Superstate.new(:root, nil, self))
@root = root
@states = {}
end

# Returns the id of the startstate of the statemachine.
def startstate
return @root.startstate_id
end

# Resets the statemachine back to its starting state.
def reset
@state = get_state(@root.startstate_id)
Expand All @@ -55,15 +55,15 @@ def reset
end
raise StatemachineException.new("The state machine doesn't know where to start. Try setting the startstate.") if @state == nil
@state.enter

@states.values.each { |state| state.reset }
end

# Return the id of the current state of the statemachine.
def state
return @state.id
end

# You may change the state of the statemachine by using this method. The parameter should be
# the id of the desired state.
def state= value
Expand All @@ -75,10 +75,10 @@ def state= value
@state = @states[value.to_sym]
end
end

# The key method to exercise the statemachine. Any extra arguments supplied will be passed into
# any actions associated with the transition.
#
#
# Alternatively to this method, you may invoke methods, names the same as the event, on the statemachine.
# The advantage of using +process_event+ is that errors messages are more informative.
def process_event(event, *args)
Expand All @@ -95,11 +95,11 @@ def process_event(event, *args)
raise StatemachineException.new("The state machine isn't in any state while processing the '#{event}' event.")
end
end

def trace(message) #:nodoc:
@tracer.puts message if @tracer
end

def get_state(id) #:nodoc:
if @states.has_key? id
return @states[id]
Expand All @@ -114,25 +114,25 @@ def get_state(id) #:nodoc:
return state
end
end

def add_state(state) #:nodoc:
@states[state.id] = state
end

def has_state(id) #:nodoc:
if(is_history_state_id?(id))
return @states.has_key?(base_id(id))
else
return @states.has_key?(id)
end
end

def respond_to?(message)
return true if super(message)
return true if @state and @state.transition_for(message)
return false
end

def method_missing(message, *args) #:nodoc:
if @state and @state.transition_for(message)
process_event(message.to_sym, *args)
Expand All @@ -147,17 +147,17 @@ def method_missing(message, *args) #:nodoc:
end
end
end
private

private

def is_history_state_id?(id)
return id.to_s[-2..-1] == "_H"
end

def base_id(history_id)
return history_id.to_s[0...-2].to_sym
end

def load_history(superstate)
100.times do
history = superstate.history_id ? get_state(superstate.history_id) : nil
Expand All @@ -170,6 +170,6 @@ def load_history(superstate)
end
raise StatemachineException.new("No history found within 100 levels of nested superstates.")
end

end
end
end
4 changes: 3 additions & 1 deletion lib/statemachine/stub_context.rb
Expand Up @@ -16,7 +16,9 @@ def method(name)
end

def __generic_method(name, *args)
puts "action invoked: #{name}(#{args.join(", ")}) #{block_given? ? "with block" : ""}" if @verbose
if !defined?($IS_TEST)
puts "action invoked: #{name}(#{args.join(", ")}) #{block_given? ? "with block" : ""}" if @verbose
end
end

end
Expand Down
2 changes: 1 addition & 1 deletion lib/statemachine/version.rb
Expand Up @@ -2,7 +2,7 @@ module Statemachine
module VERSION #:nodoc:
unless defined? MAJOR
MAJOR = 1
MINOR = 0
MINOR = 1
TINY = 0

STRING = [MAJOR, MINOR, TINY].join('.')
Expand Down
12 changes: 6 additions & 6 deletions spec/generate/java/java_statemachine_spec.rb
Expand Up @@ -48,11 +48,11 @@ def generate_complex_turnstile_sm
@sm.to_java(:output => @output, :name => "JavaTurnstile", :package => "test.turnstile")
end

def load_lines(*segs)
filename = File.join(*segs)
File.should exist( filename)
return IO.read(filename).split("\n")
end
# def load_lines(*segs)
# filename = File.join(*segs)
# File.should exist( filename)
# return IO.read(filename).split("\n")
# end

def empty_sm_lines
@sm.to_java(:name => "JavaTest", :output => @output, :package => "test.blank")
Expand Down Expand Up @@ -345,4 +345,4 @@ def find_lines_after(lines, goose)
lines.shift.should == " }"
end

end
end

0 comments on commit d14b57d

Please sign in to comment.