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,74 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
##
|
4
|
+
##
|
5
|
+
##
|
6
|
+
|
7
|
+
describe "Adding events to a Machine outside a state block" do
|
8
|
+
|
9
|
+
include MySpecHelper
|
10
|
+
|
11
|
+
describe "When there is an empty machine" do
|
12
|
+
before do
|
13
|
+
reset!
|
14
|
+
make_pristine_class 'Klass'
|
15
|
+
Klass.state_fu_machine() { }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "calling Klass.state_fu_machine().events" do
|
19
|
+
it "should return []" do
|
20
|
+
Klass.state_fu_machine().events.should == []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "calling event(:die){ from :dead, :to => :alive } in a Klass.state_fu_machine()" do
|
25
|
+
before do
|
26
|
+
Klass.state_fu_machine do
|
27
|
+
event :die do # arity == 0
|
28
|
+
from :dead, :to => :alive
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should require a name when calling machine.event()" do
|
34
|
+
lambda { Klass.state_fu_machine(){ event {} } }.should raise_error(ArgumentError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should add 2 states to the machine called: [:dead, :alive] " do
|
38
|
+
Klass.state_fu_machine.state_names.should == [:dead, :alive]
|
39
|
+
Klass.state_fu_machine.states.length.should == 2
|
40
|
+
Klass.state_fu_machine.states.each { |s| s.should be_kind_of(StateFu::State) }
|
41
|
+
Klass.state_fu_machine.states.map(&:name).sort.should == [:alive, :dead]
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "the <StateFu::Event> created" do
|
45
|
+
it "should be accessible through Klass.state_fu_machine.events" do
|
46
|
+
Klass.state_fu_machine.events.should be_kind_of(Array)
|
47
|
+
Klass.state_fu_machine.events.length.should == 1
|
48
|
+
Klass.state_fu_machine.events.first.should be_kind_of( StateFu::Event )
|
49
|
+
Klass.state_fu_machine.events.first.name.should == :die
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
# arity of blocks is optional, thanks to magic fairy dust ;)
|
56
|
+
describe "calling event(:die){ |s| s.from :dead, :to => :alive } in a Klass.state_fu_machine()" do
|
57
|
+
before do
|
58
|
+
Klass.state_fu_machine do
|
59
|
+
event :die do |s|
|
60
|
+
s.from :dead, :to => :alive
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should add 2 states to the machine called [:dead, :alive] " do
|
66
|
+
Klass.state_fu_machine.state_names.should == [:dead, :alive]
|
67
|
+
Klass.state_fu_machine.states.length.should == 2
|
68
|
+
Klass.state_fu_machine.states.each { |s| s.should be_kind_of( StateFu::State ) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
# require 'activesupport'
|
4
|
+
# require 'activerecord'
|
5
|
+
|
6
|
+
describe "Document" do
|
7
|
+
include MySpecHelper
|
8
|
+
before do
|
9
|
+
reset!
|
10
|
+
prepare_active_record do
|
11
|
+
def self.up
|
12
|
+
create_table :documents do |t|
|
13
|
+
t.string :name
|
14
|
+
t.string :author
|
15
|
+
t.string :status_field
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
make_pristine_class('Document', ActiveRecord::Base )
|
20
|
+
Document.class_eval do
|
21
|
+
include StateFu
|
22
|
+
|
23
|
+
attr_accessor :author
|
24
|
+
|
25
|
+
def update_rss
|
26
|
+
# puts "new feed!"
|
27
|
+
end
|
28
|
+
|
29
|
+
state_fu_machine( :status ) do
|
30
|
+
state :draft do
|
31
|
+
event :publish, :to => :published
|
32
|
+
end
|
33
|
+
|
34
|
+
state :published do
|
35
|
+
on_entry :update_rss
|
36
|
+
requires :author
|
37
|
+
end
|
38
|
+
|
39
|
+
event :delete, :from => :ALL, :to => :deleted do
|
40
|
+
execute :destroy
|
41
|
+
end
|
42
|
+
|
43
|
+
states do
|
44
|
+
accepted :save!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
@doc = Document.new
|
50
|
+
# @doc.status
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "a new document with no attributes" do
|
54
|
+
|
55
|
+
it "should have a status.name of :draft" do
|
56
|
+
@doc.status.name.should == :draft
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should have no author" do
|
60
|
+
@doc.author.should be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise a RequirementError when publish! is called" do
|
64
|
+
@doc.status.name.should == :draft
|
65
|
+
lambda { @doc.status.publish! }.should raise_error( StateFu::RequirementError )
|
66
|
+
begin
|
67
|
+
@doc.status.publish!
|
68
|
+
rescue StateFu::RequirementError => e
|
69
|
+
e.message.should =~ /[:author]/
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "a new document with an author" do
|
75
|
+
before do
|
76
|
+
@doc.author = "Susan"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should have a status.name of :draft" do
|
80
|
+
@doc.status.name.should == :draft
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should have an author" do
|
84
|
+
@doc.author.should_not be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should not raise an error when publish! is called" do
|
88
|
+
lambda { @doc.status.publish! }.should_not raise_error( )
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should call update_rss when publish! is called" do
|
92
|
+
mock( @doc ).update_rss(anything) {}
|
93
|
+
@doc.status.publish!
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should have the state name :published after .publish! is called" do
|
97
|
+
@doc.status.publish!
|
98
|
+
@doc.status.current_state_name.should == :published
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "status_field attribute" do
|
102
|
+
|
103
|
+
# need to think about this
|
104
|
+
#
|
105
|
+
# it "should be private in ruby 1.8 and 1.9" do
|
106
|
+
# lambda { @doc.status_field }.should raise_error()
|
107
|
+
# end
|
108
|
+
|
109
|
+
it "should be defined before state_fu is called" do
|
110
|
+
@doc.send( :status_field ).should == 'draft'
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should have an initial value of 'draft'" do
|
114
|
+
@doc.instance_eval { status_field }.should == "draft"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should be set to 'published' after publish! is called successfully" do
|
118
|
+
@doc.status.publish!
|
119
|
+
@doc.instance_eval { status_field }.should == "published"
|
120
|
+
end
|
121
|
+
end # status_field
|
122
|
+
end # with author
|
123
|
+
|
124
|
+
describe "delete!" do
|
125
|
+
|
126
|
+
it "should execute destroy()" do
|
127
|
+
mock( @doc ).destroy(anything) {}
|
128
|
+
@doc.status.delete!
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
describe String do
|
4
|
+
include MySpecHelper
|
5
|
+
before do
|
6
|
+
reset!
|
7
|
+
String.class_eval do
|
8
|
+
include StateFu
|
9
|
+
|
10
|
+
def sanitize_for_shell!
|
11
|
+
gsub!(/([\\\t\| &`<>)('"])/) { |s| '\\' << s }
|
12
|
+
end
|
13
|
+
|
14
|
+
def dirty?
|
15
|
+
shell.name == :dirty
|
16
|
+
end
|
17
|
+
|
18
|
+
def clean?
|
19
|
+
shell.name == :clean
|
20
|
+
end
|
21
|
+
|
22
|
+
def shell_escape!
|
23
|
+
shell.escape!
|
24
|
+
end
|
25
|
+
|
26
|
+
def shell_escape
|
27
|
+
#
|
28
|
+
klone = clone
|
29
|
+
begin
|
30
|
+
klone.shell.escape!
|
31
|
+
rescue StateFu::IllegalTransition
|
32
|
+
end
|
33
|
+
klone
|
34
|
+
end
|
35
|
+
|
36
|
+
state_fu_machine (:shell) do
|
37
|
+
event(:escape, :from => {:dirty => :clean}) do
|
38
|
+
execute :sanitize_for_shell!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end # String
|
42
|
+
@str = "; nohup 'rm -rf /opt' &"
|
43
|
+
end # before
|
44
|
+
|
45
|
+
it "should initially be dirty" do
|
46
|
+
@str.dirty?.should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should call sanitize_for_shell! when shell.escape! is called, and be clean afterwards " do
|
50
|
+
@str.should be_dirty
|
51
|
+
@str.should_not be_clean
|
52
|
+
mock( @str ).sanitize_for_shell!(anything) {}
|
53
|
+
@str.shell.escape!
|
54
|
+
@str.should_not be_dirty
|
55
|
+
@str.should be_clean
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise an IllegalTransition if shell.escape! is called more than once" do
|
59
|
+
@str.shell.escape!
|
60
|
+
@str.shell.state_name.should == :clean
|
61
|
+
|
62
|
+
lambda { @str.shell.escape! }.should raise_error( StateFu::IllegalTransition )
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should modify the string when shell.escape is called" do
|
66
|
+
original = @str.dup
|
67
|
+
original.should == @str
|
68
|
+
@str.shell.escape!
|
69
|
+
original.should_not == @str
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should modify the string when shell.escape! is called" do
|
73
|
+
original = @str.dup
|
74
|
+
original.should == @str
|
75
|
+
@str.shell.escape!
|
76
|
+
original.should_not == @str
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should not modify the original string when shell_escape() is called" do
|
80
|
+
original = @str.dup
|
81
|
+
original.should == @str
|
82
|
+
clean_copy = @str.shell_escape()
|
83
|
+
clean_copy.should be_clean
|
84
|
+
@str.should be_dirty
|
85
|
+
original.should == @str
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
##
|
4
|
+
##
|
5
|
+
##
|
6
|
+
|
7
|
+
describe "An instance of Klass with StateFu included:" do
|
8
|
+
include MySpecHelper
|
9
|
+
before(:each) do
|
10
|
+
make_pristine_class 'Klass'
|
11
|
+
@k = Klass.new()
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "when no machine is defined" do
|
15
|
+
it "should raise an ArgumentError given .state_fu()" do
|
16
|
+
lambda { @k.state_fu() }.should raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return {} given .bindings()" do
|
20
|
+
@k.state_fu_bindings.should == {}
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return [] given .state_fu!()" do
|
24
|
+
@k.state_fu!.should == []
|
25
|
+
end
|
26
|
+
end # no machine
|
27
|
+
|
28
|
+
describe "when an empty machine is defined for the class with the default name:" do
|
29
|
+
before(:each) do
|
30
|
+
Klass.state_fu_machine() {}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return a StateFu::Binding given .state_fu()" do
|
34
|
+
@k.state_fu_binding.should be_kind_of( StateFu::Binding )
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "before a binding is instantiated by calling .state_fu() or .state_fu!" do
|
38
|
+
it "should return {} given .bindings()" do
|
39
|
+
@k.bindings.should == {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "after a binding is instantiated with .state_fu()" do
|
44
|
+
before do
|
45
|
+
@k.state_fu()
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return { :state_fu => <StateFu::Binding>} given .bindings()" do
|
49
|
+
@k.bindings().length.should == 1
|
50
|
+
@k.bindings().keys.should == [StateFu::DEFAULT]
|
51
|
+
@k.bindings().values.first.should be_kind_of( StateFu::Binding )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "after .state_fu!()" do
|
56
|
+
it "should return { :state_fu => <StateFu::Binding>} given .bindings()" do
|
57
|
+
@k.state_fu!()
|
58
|
+
@k.bindings().length.should == 1
|
59
|
+
@k.bindings().keys.should == [StateFu::DEFAULT]
|
60
|
+
@k.bindings().values.first.should be_kind_of( StateFu::Binding )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return [<StateFu::Binding>] given .state_fu!()" do
|
65
|
+
@k.state_fu!.length.should == 1
|
66
|
+
@k.state_fu!.first.should be_kind_of( StateFu::Binding )
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "when there is an empty machine called :two for the class" do
|
70
|
+
before(:each) do
|
71
|
+
Klass.state_fu_machine(:two) {}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return the same Binding given .state_fu() and .state_fu(:state_fu)" do
|
75
|
+
@k.state_fu().should be_kind_of( StateFu::Binding )
|
76
|
+
@k.state_fu().should == @k.state_fu(StateFu::DEFAULT)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should return a StateFu::Binding for the machine called :two given .state_fu(:two)" do
|
80
|
+
@k.state_fu(:two).should be_kind_of( StateFu::Binding )
|
81
|
+
@k.state_fu(:two).should_not == @k.state_fu(StateFu::DEFAULT)
|
82
|
+
@k.state_fu(:two).machine.should == Klass.state_fu_machine(:two)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should raise an ArgumentError when .state_fu() is called with the name of a machine which doesn't exist" do
|
86
|
+
lambda { @k.state_fu(:hibiscus) }.should raise_error(ArgumentError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should return an array of the two StateFu::Bindings given .state_fu!" do
|
90
|
+
@k.state_fu!.should be_kind_of( Array )
|
91
|
+
@k.state_fu!.length.should == 2
|
92
|
+
@k.state_fu!.each { |m| m.should be_kind_of( StateFu::Binding ) }
|
93
|
+
@k.state_fu!.map(&:method_name).sort_by(&:to_s).should == [StateFu::DEFAULT, :two]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
module BenchGrinder
|
4
|
+
def snark
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "extending StateFu::Lathe" do
|
9
|
+
include MySpecHelper
|
10
|
+
|
11
|
+
describe "helpers" do
|
12
|
+
before do
|
13
|
+
reset!
|
14
|
+
make_pristine_class('Klass')
|
15
|
+
@machine = Klass.state_fu_machine() do
|
16
|
+
state :init
|
17
|
+
end
|
18
|
+
end # before
|
19
|
+
|
20
|
+
describe "lathe.tool" do
|
21
|
+
|
22
|
+
it "should add the arguments to the machine's collection of tools" do
|
23
|
+
@machine.should respond_to(:tools)
|
24
|
+
@machine.tools.should be_empty
|
25
|
+
@machine.lathe do
|
26
|
+
tool :bench_grinder
|
27
|
+
end
|
28
|
+
@machine.tools.should_not be_empty
|
29
|
+
@machine.tools.should include(:bench_grinder)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should extend the machine's lathe" do
|
33
|
+
@machine.lathe do
|
34
|
+
tool :bench_grinder
|
35
|
+
snark()
|
36
|
+
end
|
37
|
+
@machine.lathe.should respond_to( :snark )
|
38
|
+
@machine.lathe.snark
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should extend the machine's lathe for state and events" do
|
42
|
+
@machine.lathe do
|
43
|
+
tool :bench_grinder
|
44
|
+
snark()
|
45
|
+
state :grinding do
|
46
|
+
snark()
|
47
|
+
event :grind do
|
48
|
+
snark()
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not extend another machine's lathe" do
|
55
|
+
@machine.lathe do
|
56
|
+
tool :bench_grinder
|
57
|
+
snark()
|
58
|
+
end
|
59
|
+
m2 = Klass.state_fu_machine(:two) do
|
60
|
+
end
|
61
|
+
lambda { m2.lathe.snark }.should raise_error( NoMethodError )
|
62
|
+
end
|
63
|
+
|
64
|
+
end # tool
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|