state-fu 0.11.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/LICENSE +40 -0
- data/README.textile +293 -0
- data/Rakefile +114 -0
- data/lib/binding.rb +292 -0
- data/lib/event.rb +192 -0
- data/lib/executioner.rb +120 -0
- data/lib/hooks.rb +39 -0
- data/lib/interface.rb +132 -0
- data/lib/lathe.rb +538 -0
- data/lib/machine.rb +184 -0
- data/lib/method_factory.rb +243 -0
- data/lib/persistence.rb +116 -0
- data/lib/persistence/active_record.rb +34 -0
- data/lib/persistence/attribute.rb +47 -0
- data/lib/persistence/base.rb +100 -0
- data/lib/persistence/relaxdb.rb +23 -0
- data/lib/persistence/session.rb +7 -0
- data/lib/sprocket.rb +58 -0
- data/lib/state-fu.rb +56 -0
- data/lib/state.rb +48 -0
- data/lib/support/active_support_lite/array.rb +9 -0
- data/lib/support/active_support_lite/array/access.rb +60 -0
- data/lib/support/active_support_lite/array/conversions.rb +202 -0
- data/lib/support/active_support_lite/array/extract_options.rb +21 -0
- data/lib/support/active_support_lite/array/grouping.rb +109 -0
- data/lib/support/active_support_lite/array/random_access.rb +13 -0
- data/lib/support/active_support_lite/array/wrapper.rb +25 -0
- data/lib/support/active_support_lite/blank.rb +67 -0
- data/lib/support/active_support_lite/cattr_reader.rb +57 -0
- data/lib/support/active_support_lite/keys.rb +57 -0
- data/lib/support/active_support_lite/misc.rb +59 -0
- data/lib/support/active_support_lite/module.rb +1 -0
- data/lib/support/active_support_lite/module/delegation.rb +130 -0
- data/lib/support/active_support_lite/object.rb +9 -0
- data/lib/support/active_support_lite/string.rb +38 -0
- data/lib/support/active_support_lite/symbol.rb +16 -0
- data/lib/support/applicable.rb +41 -0
- data/lib/support/arrays.rb +197 -0
- data/lib/support/core_ext.rb +90 -0
- data/lib/support/exceptions.rb +106 -0
- data/lib/support/has_options.rb +16 -0
- data/lib/support/logger.rb +165 -0
- data/lib/support/methodical.rb +17 -0
- data/lib/support/no_stdout.rb +55 -0
- data/lib/support/plotter.rb +62 -0
- data/lib/support/vizier.rb +300 -0
- data/lib/tasks/spec_last.rake +55 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/transition.rb +338 -0
- data/lib/transition_query.rb +224 -0
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +118 -0
- data/spec/features/plotter_spec.rb +97 -0
- data/spec/features/shared_log_spec.rb +7 -0
- data/spec/features/singleton_machine_spec.rb +39 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison_spec.rb +101 -0
- data/spec/helper.rb +13 -0
- data/spec/integration/active_record_persistence_spec.rb +202 -0
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/class_accessor_spec.rb +117 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/example_01_document_spec.rb +133 -0
- data/spec/integration/example_02_string_spec.rb +88 -0
- data/spec/integration/instance_accessor_spec.rb +97 -0
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/machine_duplication_spec.rb +101 -0
- data/spec/integration/relaxdb_persistence_spec.rb +97 -0
- data/spec/integration/requirement_reflection_spec.rb +270 -0
- data/spec/integration/state_definition_spec.rb +163 -0
- data/spec/integration/transition_spec.rb +1033 -0
- data/spec/spec.opts +9 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +948 -0
- data/spec/units/binding_spec.rb +192 -0
- data/spec/units/event_spec.rb +214 -0
- data/spec/units/exceptions_spec.rb +82 -0
- data/spec/units/lathe_spec.rb +570 -0
- data/spec/units/machine_spec.rb +229 -0
- data/spec/units/method_factory_spec.rb +366 -0
- data/spec/units/sprocket_spec.rb +69 -0
- data/spec/units/state_spec.rb +59 -0
- metadata +171 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
# if ActiveSupport is absent, install a very small subset of it for
|
2
|
+
# some convenience methods
|
3
|
+
unless Object.const_defined?('ActiveSupport') #:nodoc
|
4
|
+
Dir[File.join(File.dirname( __FILE__), 'active_support_lite','**' )].sort.each do |lib|
|
5
|
+
lib = File.expand_path lib
|
6
|
+
next unless File.file?( lib )
|
7
|
+
require lib
|
8
|
+
end
|
9
|
+
|
10
|
+
class Hash #:nodoc
|
11
|
+
include ActiveSupport::CoreExtensions::Hash::Keys
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# ruby1.9 style symbol comparability for ruby1.8
|
16
|
+
if RUBY_VERSION < "1.9"
|
17
|
+
class Symbol #:nodoc
|
18
|
+
unless instance_methods.include?(:'<=>')
|
19
|
+
def <=> other
|
20
|
+
self.to_s <=> other.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ArrayToHash
|
27
|
+
def to_h
|
28
|
+
if RUBY_VERSION >= '1.8.7'
|
29
|
+
Hash[self]
|
30
|
+
else
|
31
|
+
inject({}) { |h, a| h[a.first] = a.last; h }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Array
|
37
|
+
include ArrayToHash unless instance_methods.include?(:to_h)
|
38
|
+
end
|
39
|
+
|
40
|
+
class Object
|
41
|
+
|
42
|
+
def self.__define_method( method_name, &block )
|
43
|
+
self.class.class_eval do
|
44
|
+
define_method method_name, &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def __define_singleton_method( method_name, &block )
|
49
|
+
(class << self; self; end).class_eval do
|
50
|
+
define_method method_name, &block
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def with_methods_on(other)
|
56
|
+
(class << self; self; end).class_eval do
|
57
|
+
# we need some accounting to ensure that everything behaves itself when
|
58
|
+
# .with_methods_on is called more than once.
|
59
|
+
@_with_methods_on ||= []
|
60
|
+
if !@_with_methods_on.include?("method_missing_before_#{other.__id__}")
|
61
|
+
alias_method "method_missing_before_#{other.__id__}", :method_missing
|
62
|
+
end
|
63
|
+
@_with_methods_on << "method_missing_before_#{other.__id__}"
|
64
|
+
|
65
|
+
define_method :method_missing do |method_name, *args|
|
66
|
+
if _other.respond_to?(method_name, true)
|
67
|
+
_other.__send__( method_name, *args )
|
68
|
+
else
|
69
|
+
send "method_missing_before_#{other.__id__}", method_name, *args
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
result = yield
|
75
|
+
|
76
|
+
(class << self; self; end).class_eval do
|
77
|
+
# heal the damage
|
78
|
+
if @_with_methods_on.pop != "method_missing_before_#{other.__id__}"
|
79
|
+
raise "there is no god"
|
80
|
+
end
|
81
|
+
if !@_with_methods_on.include?("method_missing_before_#{other.__id__}")
|
82
|
+
alias_method :method_missing, "method_missing_before_#{other.__id__}"
|
83
|
+
undef_method "method_missing_before_#{other.__id__}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
result
|
88
|
+
end # with_methods_on
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module StateFu
|
2
|
+
|
3
|
+
class MagicMethodError < NoMethodError
|
4
|
+
end
|
5
|
+
|
6
|
+
class Error < ::StandardError
|
7
|
+
attr_reader :binding, :options
|
8
|
+
|
9
|
+
def initialize binding, message=nil, options={}
|
10
|
+
@binding = binding
|
11
|
+
@options = options
|
12
|
+
super message
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class TransitionNotFound < Error
|
18
|
+
attr_reader :valid_transitions
|
19
|
+
attr_reader :valid_destinations
|
20
|
+
DEFAULT_MESSAGE = "Transition could not be determined"
|
21
|
+
|
22
|
+
def initialize(binding, valid_transitions, message=DEFAULT_MESSAGE, options={})
|
23
|
+
@valid_transitions = valid_transitions
|
24
|
+
@valid_destinations = valid_transitions.map(&:destination)
|
25
|
+
super(binding, message, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"<#{self.class.to_s} #{message} available=[#{valid_destinations.inspect}]>"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class TransitionError < Error
|
35
|
+
# TODO default message
|
36
|
+
attr_reader :transition
|
37
|
+
|
38
|
+
def initialize transition, message=nil, options={}
|
39
|
+
raise caller.inspect unless transition.is_a?(Transition)
|
40
|
+
@transition = transition
|
41
|
+
super transition.binding, message, options
|
42
|
+
end
|
43
|
+
|
44
|
+
delegate :origin, :to => :transition
|
45
|
+
delegate :target, :to => :transition
|
46
|
+
delegate :event, :to => :transition
|
47
|
+
delegate :args, :to => :transition
|
48
|
+
|
49
|
+
# TODO capture these on initialization
|
50
|
+
delegate :unmet_requirements, :to => :transition
|
51
|
+
delegate :unmet_requirement_messages, :to => :transition
|
52
|
+
delegate :requirement_errors, :to => :transition
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
origin_name = origin && origin.name
|
56
|
+
target_name = target && target.name
|
57
|
+
event_name = event && event.name
|
58
|
+
"<#{self.class.to_s} #{message} #{origin_name.inspect}=[#{event_name.inspect}]=>#{target_name.inspect}>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class UnknownTarget < TransitionError
|
63
|
+
end
|
64
|
+
|
65
|
+
class TransitionAlreadyFired < TransitionError
|
66
|
+
end
|
67
|
+
|
68
|
+
class RequirementError < TransitionError
|
69
|
+
include Enumerable
|
70
|
+
|
71
|
+
delegate :each, :to => :to_h
|
72
|
+
delegate :length, :to => :to_h
|
73
|
+
delegate :empty?, :to => :to_h
|
74
|
+
|
75
|
+
def to_a
|
76
|
+
unmet_requirement_messages
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_h
|
80
|
+
requirement_errors
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
inspect
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
"<#{self.class.to_s}::#{__id__} :#{transition.origin.to_sym}-[#{transition.event.to_sym}]->:#{transition.target.to_sym} unmet_requirements=#{to_a.inspect}>"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class TransitionHalted < TransitionError
|
93
|
+
end
|
94
|
+
|
95
|
+
# deprecated?
|
96
|
+
# class Invalid Transition < TransitionError
|
97
|
+
|
98
|
+
class IllegalTransition < TransitionError
|
99
|
+
attr_reader :legal_transitions
|
100
|
+
|
101
|
+
def initialize transition, message=nil, valid_transitions=nil, options={}
|
102
|
+
@valid_transitions = valid_transitions
|
103
|
+
super transition, message, options
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
|
2
|
+
require 'logger'
|
3
|
+
module StateFu
|
4
|
+
#
|
5
|
+
# TODO - spec coverage
|
6
|
+
#
|
7
|
+
# Provide logging facilities, including the ability to use a shared logger.
|
8
|
+
# Use Rails' log if running as a rails plugin; allow independent control of
|
9
|
+
# StateFu log level.
|
10
|
+
|
11
|
+
class Logger
|
12
|
+
cattr_accessor :prefix # prefix for log messages
|
13
|
+
cattr_accessor :suppress # set true to send messages to /dev/null
|
14
|
+
cattr_accessor :shared
|
15
|
+
cattr_accessor :suppress
|
16
|
+
|
17
|
+
DEBUG = 0
|
18
|
+
INFO = 1
|
19
|
+
WARN = 2
|
20
|
+
ERROR = 3
|
21
|
+
FATAL = 4
|
22
|
+
UNKNOWN = 5
|
23
|
+
|
24
|
+
ENV_LOG_LEVEL = 'STATEFU_LOGLEVEL'
|
25
|
+
DEFAULT_LEVEL = INFO
|
26
|
+
|
27
|
+
DEFAULT_SHARED_LOG_PREFIX = '[StateFu] '
|
28
|
+
|
29
|
+
@@prefix = DEFAULT_SHARED_LOG_PREFIX
|
30
|
+
@@logger = nil
|
31
|
+
@@suppress = false
|
32
|
+
@@shared = false
|
33
|
+
@@log_level = nil
|
34
|
+
|
35
|
+
def self.new( logger = $stdout, options={} )
|
36
|
+
self.suppress = false
|
37
|
+
self.logger = logger, options
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.initial_log_level
|
42
|
+
if env_level = ENV[ENV_LOG_LEVEL]
|
43
|
+
parse_log_level( env_level )
|
44
|
+
else
|
45
|
+
DEFAULT_LEVEL
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.level
|
50
|
+
@@log_level ||= initial_log_level
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.level=( new_level )
|
54
|
+
@@log_level = parse_log_level(new_level)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.shared?
|
58
|
+
!! @@shared
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.prefix
|
62
|
+
shared? ? @@prefix : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.logger= logger
|
66
|
+
set_logger logger
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.instance
|
70
|
+
@@logger ||= set_logger(Logger.default_logger)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.suppress!
|
74
|
+
self.suppress = true
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.suppressed?(severity = DEBUG)
|
78
|
+
suppress == true || severity < level
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.add(severity, message = nil, progname = nil, &block)
|
82
|
+
severity = parse_log_level( severity )
|
83
|
+
return if suppressed?( severity )
|
84
|
+
message = [prefix, (message || (block && block.call) || progname).to_s].compact.join
|
85
|
+
# message = "#{message}\n" unless message[-1] == ?\n
|
86
|
+
instance.add( severity, message )
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.debug progname = nil, █ add DEBUG, progname, █ end
|
90
|
+
def self.info progname = nil, █ add INFO, progname, █ end
|
91
|
+
def self.warn progname = nil, █ add WARN, progname, █ end
|
92
|
+
def self.error progname = nil, █ add ERROR, progname, █ end
|
93
|
+
def self.fatal progname = nil, █ add FATAL, progname, █ end
|
94
|
+
def self.unknown progname = nil, █ add UNKNOWN, progname, █ end
|
95
|
+
|
96
|
+
#
|
97
|
+
# TODO fix these crappy methods
|
98
|
+
#
|
99
|
+
|
100
|
+
# setter for logger instance
|
101
|
+
def self.set_logger( logger, options = { :shared => false } )
|
102
|
+
case logger
|
103
|
+
when String
|
104
|
+
file = File.open(logger, File::WRONLY | File::APPEND)
|
105
|
+
@@logger = Logger.activesupport_logger_available? ? ActiveSupport::BufferedLogger.new(file) : ::Logger.new(file)
|
106
|
+
when ::Logger
|
107
|
+
@@logger = logger
|
108
|
+
when Logger.activesupport_logger_available? && ActiveSupport::BufferedLogger
|
109
|
+
@@logger = logger
|
110
|
+
else
|
111
|
+
raise ArgumentError.new
|
112
|
+
end
|
113
|
+
self.shared = !!options.symbolize_keys![:shared]
|
114
|
+
if shared?
|
115
|
+
@@prefix = options[:prefix] || DEFAULT_SHARED_LOG_PREFIX
|
116
|
+
puts "shared :: #{@@prefix} #{prefix}"
|
117
|
+
end
|
118
|
+
if lvl = options[:level] || options[:log_level]
|
119
|
+
self.level = lvl
|
120
|
+
end
|
121
|
+
instance
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def self.get_logger( logr = $stdout )
|
127
|
+
if Object.const_defined?( "RAILS_DEFAULT_LOGGER" )
|
128
|
+
set_logger RAILS_DEFAULT_LOGGER, :shared => true
|
129
|
+
else
|
130
|
+
if Object.const_defined?( 'ActiveSupport' ) && ActiveSupport.const_defined?('BufferedLogger')
|
131
|
+
set_logger( ActiveSupport::BufferedLogger.new( logr ))
|
132
|
+
else
|
133
|
+
set_logger ::Logger.new( logr )
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.activesupport_logger_available?
|
139
|
+
Object.const_defined?( 'ActiveSupport' ) && ActiveSupport.const_defined?('BufferedLogger')
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.default_logger
|
143
|
+
if Object.const_defined?( "RAILS_DEFAULT_LOGGER" )
|
144
|
+
RAILS_DEFAULT_LOGGER
|
145
|
+
else
|
146
|
+
$stdout
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.parse_log_level(input)
|
151
|
+
case input
|
152
|
+
when String, Symbol
|
153
|
+
const_get( input )
|
154
|
+
when 0,1,2,3,4,5
|
155
|
+
input
|
156
|
+
when nil
|
157
|
+
level
|
158
|
+
else
|
159
|
+
raise ArgumentError
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module StateFu
|
2
|
+
module Methodical
|
3
|
+
|
4
|
+
def self.__define_method( method_name, &block )
|
5
|
+
self.class.class_eval do
|
6
|
+
define_method method_name, &block
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def __define_singleton_method( method_name, &block )
|
11
|
+
(class << object; self; end).class_eval do
|
12
|
+
define_method method_name, &block
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
# a module for suppressing or capturing STDOUT or STDERR.
|
4
|
+
# useful when shelling out to "noisy" applications or to suppress
|
5
|
+
# output during tests.
|
6
|
+
module NoStdout #:nodoc:all
|
7
|
+
module InstanceMethods
|
8
|
+
|
9
|
+
# Suppresses or redirects STDOUT inside the given block.
|
10
|
+
# supply an IO of your own to capture STDOUT, otherwise it's put
|
11
|
+
# in a new StringIO object.
|
12
|
+
def no_stdout ( to = StringIO.new('','r+'), &block )
|
13
|
+
orig_stdout = $stdout
|
14
|
+
$stdout = @alt_stdout = to
|
15
|
+
result = yield
|
16
|
+
$stdout = orig_stdout
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns the contents of STDOUT from the previous usage of
|
21
|
+
# no_stdout, or nil
|
22
|
+
def last_stdout
|
23
|
+
return nil unless @alt_stdout
|
24
|
+
@alt_stdout.rewind
|
25
|
+
@alt_stdout.read
|
26
|
+
end
|
27
|
+
|
28
|
+
## COPIED FROM ABOVE ####
|
29
|
+
|
30
|
+
# Suppresses or redirects STDERR inside the given block.
|
31
|
+
# supply an IO of your own to capture STDERR, otherwise it's put
|
32
|
+
# in a new StringIO object.
|
33
|
+
def no_stderr ( to = StringIO.new('','r+'), &block )
|
34
|
+
orig_stderr = $stderr
|
35
|
+
$stderr = @alt_stderr = to
|
36
|
+
result = yield
|
37
|
+
$stderr = orig_stderr
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns the contents of STDERR from the previous usage of
|
42
|
+
# no_stderr, or nil
|
43
|
+
def last_stderr
|
44
|
+
return nil unless @alt_stderr
|
45
|
+
@alt_stderr.rewind
|
46
|
+
@alt_stderr.read
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.included klass
|
51
|
+
klass.class_eval do
|
52
|
+
include InstanceMethods
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|