hifsm 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d654a06d28adc762a19f31cacf281568503e4de
4
- data.tar.gz: 2d8e83ee31acc329efcce082d33d43d65574e71c
3
+ metadata.gz: 58e7c452cb3f0ebfce493c75f0130b191807daae
4
+ data.tar.gz: 973378be201d7957f7fc22231d0d86e34896825a
5
5
  SHA512:
6
- metadata.gz: 5ea67c4a15aaee373a2b672184781e975ba67b2ef2681ce461a66c9c062d7bce9509d5bc01e1d8ce82848391d0a63c1df21e9530bc177251d6fb798f2a5560f5
7
- data.tar.gz: 7eb5c56002cc5a98c74a55255e465fa280e91b9ba038b3d99355c23884f960040938b97931d65ea0406c63f84e73afe0e978ed37d490b2f6e3d485a7369bc92a
6
+ metadata.gz: 2093aab1fc0253c749fd3b352560ad517a64f4b6a6d14a437197b61e77b7aa8364cf38a269b6455ed2b7eb4c5bb130442ca938fd21a2bfcc86e6d98ae359b30a
7
+ data.tar.gz: 4730e63e2f71fa653e7c487953da10b0415c00a26a2a005578f92d24178882d9e09b271ac8f1f9715e848ee5078f44ae4428ff99420b84cdf700a92ccf1e0ee9
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
3
+ - rbx
4
4
  - 2.1.0
5
5
  gemfile:
6
6
  - Gemfile
data/README.md CHANGED
@@ -192,7 +192,7 @@ On event:
192
192
 
193
193
  If any of `before...` callbacks returns `false` then no further processing is done, no exceptions raised, machine state is not changed.
194
194
 
195
- On `act!` state's actions called from top state to nested. If [several FSMs defined]](https://github.com/stiff/hifsm/blob/master/test/test_two_machines.rb), object's `act!` invokes them all in order as they were defined and returns value from last action.
195
+ On `act!` state's actions called from top state to nested. If [several FSMs defined](https://github.com/stiff/hifsm/blob/master/test/test_two_machines.rb), object's `act!` invokes them all in order as they were defined and returns value from last action.
196
196
 
197
197
  ## ActiveRecord integration
198
198
 
data/hifsm.gemspec CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.required_ruby_version = '>= 1.9'
22
+
21
23
  spec.add_development_dependency "bundler", "~> 1.3"
22
24
  spec.add_development_dependency "rake"
23
25
  spec.add_development_dependency "minitest"
@@ -1,8 +1,13 @@
1
1
  class Callbacks
2
2
 
3
- class <<self
4
- def invoke(target, cb, *args)
3
+ def initialize(listeners = [])
4
+ @listeners = listeners
5
+ end
6
+
7
+ def trigger(target, *args)
8
+ @listeners.map do |cb|
5
9
  if cb.nil?
10
+ # raise something maybe? :)
6
11
  elsif cb.is_a? Symbol
7
12
  if target.method(cb).arity.zero?
8
13
  target.send(cb)
@@ -14,18 +19,4 @@ class Callbacks
14
19
  end
15
20
  end
16
21
  end
17
-
18
- def initialize
19
- @listeners = []
20
- end
21
-
22
- def add(symbol = nil, &callback)
23
- @listeners.push symbol || callback
24
- end
25
-
26
- def trigger(target, *args)
27
- @listeners.map do |callback|
28
- Callbacks.invoke target, callback, *args
29
- end
30
- end
31
22
  end
@@ -0,0 +1,36 @@
1
+ module Hifsm
2
+ module DSL
3
+ class AbstractBuilder
4
+
5
+ def self.define_dsl_callback(cb)
6
+ define_method(cb) do |symbol = nil, &block|
7
+ @defs.each do |ev_def|
8
+ ev_def[cb].push symbol || block
9
+ end
10
+ end
11
+ end
12
+
13
+ def initialize
14
+ @defs = []
15
+ end
16
+
17
+ def each(&block)
18
+ @defs.each(&block)
19
+ end
20
+
21
+ protected
22
+ def defs
23
+ @defs
24
+ end
25
+
26
+ def slice_callbacks(options, names)
27
+ Hash[names.map {|n| [n, array_wrap(options[n])]}]
28
+ end
29
+
30
+ # like in ActiveSupport
31
+ def array_wrap(anything)
32
+ anything.is_a?(Array) ? anything : [anything].compact
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ module Hifsm
2
+ module DSL
3
+ # Normalizes event definitions to form
4
+ # {:from => [], :to => ..., :guard => ..., before, after}
5
+ class EventBuilder < AbstractBuilder
6
+
7
+ def initialize(options, &block)
8
+ super()
9
+ build_def(options) if options[:to]
10
+ instance_eval(&block) if block
11
+ end
12
+
13
+ Hifsm::Event::CALLBACKS.each do |cb|
14
+ define_dsl_callback(cb)
15
+ end
16
+
17
+ # from :state, :to => :state
18
+ def from(state, options)
19
+ build_def(options.dup.merge(:from => state))
20
+ end
21
+
22
+ private
23
+ def build_def(options)
24
+ defs << slice_callbacks(options, Hifsm::Event::CALLBACKS).merge(
25
+ :from => array_wrap(options[:from]),
26
+ :to => options[:to]
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ module Hifsm
2
+ module DSL
3
+ # Normalizes state definitions
4
+ class StateBuilder < AbstractBuilder
5
+
6
+ def initialize(options = {}, &block)
7
+ super()
8
+ build_def(options)
9
+ instance_eval(&block) if block
10
+ end
11
+
12
+ Hifsm::State::CALLBACKS.each do |cb|
13
+ define_dsl_callback(cb)
14
+ end
15
+
16
+ def state(*args, &block)
17
+ defs.first[:sub_states].push [args, block]
18
+ end
19
+
20
+ def event(*args, &block)
21
+ defs.first[:sub_events].push [args, block]
22
+ end
23
+
24
+ private
25
+ def build_def(options)
26
+ defs << slice_callbacks(options, Hifsm::State::CALLBACKS).merge(
27
+ :initial => options[:initial],
28
+ :sub_states => [],
29
+ :sub_events => []
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/hifsm/event.rb CHANGED
@@ -4,20 +4,15 @@ module Hifsm
4
4
 
5
5
  attr_reader :name, :to
6
6
 
7
- def initialize(name, to, guards)
7
+ def initialize(name, to, callbacks_options)
8
8
  @name = name
9
9
  @to = to
10
- @callbacks = Hash.new {|h, key| h[key] = Callbacks.new }
11
-
12
- guards.each do |g|
13
- @callbacks[:guard].add g
10
+ @callbacks = {}
11
+ CALLBACKS.each do |cb|
12
+ @callbacks[cb] = Callbacks.new(callbacks_options[cb])
14
13
  end
15
14
  end
16
15
 
17
- CALLBACKS.each do |cb|
18
- define_method(cb) { |&block| @callbacks[cb].add(&block) }
19
- end
20
-
21
16
  def trigger(target, cb, *args)
22
17
  @callbacks[cb].trigger(target, *args)
23
18
  end
data/lib/hifsm/fsm.rb CHANGED
@@ -12,33 +12,42 @@ module Hifsm
12
12
 
13
13
  instance_eval(&block) if block
14
14
 
15
- @fsm_module = fsm_module = initialize_module
16
- @machine_class = Class.new(Hifsm::Machine) do
17
- include fsm_module
18
- define_method("#{name}_machine") { self }
19
- end
15
+ @fsm_module = nil
16
+ @machine_class = nil
20
17
  end
21
18
 
22
19
  def instantiate(target = nil, initial_state = nil)
20
+ fsm_module = get_fsm_module
21
+ machine_name = "#{name}_machine"
22
+ @machine_class ||= Class.new(Hifsm::Machine) do
23
+ include fsm_module
24
+ define_method(machine_name) { self }
25
+ end
23
26
  @machine_class.new(self, target, initial_state)
24
27
  end
25
28
 
26
29
  #DSL
27
- def event(name, options, &block)
28
- ev = Hifsm::Event.new(name, get_state!(options[:to]), array_wrap(options[:guard]))
29
- from_states = array_wrap(options[:from])
30
- from_states = @states.keys if from_states.empty?
31
- from_states.each do |from|
32
- st = get_state!(from)
33
- st.add_transition(ev)
30
+ def event(name, options = {}, &block)
31
+ Hifsm::DSL::EventBuilder.new(options, &block).each do |ev_def|
32
+ ev = Hifsm::Event.new name,
33
+ get_state!(ev_def[:to]),
34
+ ev_def
35
+
36
+ from_states = ev_def[:from]
37
+ from_states = @states.keys if from_states.empty?
38
+ from_states.each do |from|
39
+ st = get_state!(from)
40
+ st.add_transition(ev)
41
+ end
42
+
34
43
  end
35
- ev.instance_eval(&block) if block
36
44
  end
37
45
 
38
46
  def state(name, options = {}, &block)
39
- st = @states[name.to_s] = Hifsm::State.new(self, name, @parent)
40
- @initial_state = st if options[:initial]
41
- st.instance_eval(&block) if block
47
+ Hifsm::DSL::StateBuilder.new(options, &block).each do |st_def|
48
+ st = @states[name.to_s] = Hifsm::State.new(name, @parent, st_def)
49
+ @initial_state = st if options[:initial]
50
+ end
42
51
  end
43
52
 
44
53
  # internals
@@ -68,41 +77,38 @@ module Hifsm
68
77
  end
69
78
 
70
79
  def to_module
71
- @fsm_module
80
+ get_fsm_module
72
81
  end
73
82
 
74
83
  private
75
- # like in ActiveSupport
76
- def array_wrap(anything)
77
- anything.is_a?(Array) ? anything : [anything].compact
78
- end
79
-
80
- def initialize_module
81
- fsm = self # capture self
82
- machine_var = "@#{name}_machine"
83
- machine_name = "#{name}_machine"
84
-
85
- Module.new do
86
- define_singleton_method :included do |base|
87
- base.send(:define_singleton_method, "#{machine_name}_definition") { fsm }
88
- end
84
+ def get_fsm_module
85
+ @fsm_module ||= begin
86
+ fsm = self # capture self
87
+ machine_var = "@#{name}_machine"
88
+ machine_name = "#{name}_machine"
89
+
90
+ Module.new do
91
+ define_singleton_method :included do |base|
92
+ base.send(:define_singleton_method, "#{machine_name}_definition") { fsm }
93
+ end
89
94
 
90
- # <state>_machine returns machine instance
91
- define_method(machine_name) do
92
- if instance_variable_defined?(machine_var)
93
- instance_variable_get(machine_var)
94
- else
95
- machine = fsm.instantiate(self)
96
- instance_variable_set(machine_var, machine)
95
+ # <state>_machine returns machine instance
96
+ define_method(machine_name) do
97
+ if instance_variable_defined?(machine_var)
98
+ instance_variable_get(machine_var)
99
+ else
100
+ machine = fsm.instantiate(self)
101
+ instance_variable_set(machine_var, machine)
102
+ end
97
103
  end
98
- end
99
104
 
100
- # <state> returns string representation of the current state
101
- define_method(fsm.name) { send(machine_name).to_s }
105
+ # <state> returns string representation of the current state
106
+ define_method(fsm.name) { send(machine_name).to_s }
102
107
 
103
- # <event> fires event
104
- fsm.all_events.each do |event_name, event|
105
- define_method(event_name) {|*args| send(machine_name).fire(event_name, *args) }
108
+ # <event> fires event
109
+ fsm.all_events.each do |event_name, event|
110
+ define_method(event_name) {|*args| send(machine_name).fire(event_name, *args) }
111
+ end
106
112
  end
107
113
  end
108
114
  end
data/lib/hifsm/state.rb CHANGED
@@ -1,41 +1,30 @@
1
1
  module Hifsm
2
2
  class State
3
- CALLBACKS = [:before_enter, :before_exit, :after_enter, :after_exit].freeze
3
+ CALLBACKS = [:before_enter, :before_exit, :after_enter, :after_exit, :action].freeze
4
4
 
5
5
  attr_reader :sub_fsm
6
6
 
7
- def initialize(fsm, name, parent = nil)
8
- @fsm = fsm
7
+ def initialize(name, parent = nil, options)
9
8
  @name = name
10
9
  @parent = parent
11
- @action = nil
12
- @sub_fsm = nil
13
-
14
- @callbacks = Hash.new {|h, key| h[key] = Callbacks.new }
10
+ @callbacks = {}
11
+ CALLBACKS.each do |cb|
12
+ @callbacks[cb] = Callbacks.new(options[cb])
13
+ end
15
14
  @transitions = Hash.new {|h, key| h[key] = Array.new }
16
- end
17
-
18
- # DSL
19
- def action(&block)
20
- @action = block
21
- end
22
-
23
- def state(*args, &block)
24
- sub_fsm!.state(*args, &block)
25
- end
26
15
 
27
- def event(*args, &block)
28
- sub_fsm!.event(*args, &block)
29
- end
30
-
31
- CALLBACKS.each do |cb|
32
- define_method(cb) { |&block| @callbacks[cb].add(&block) }
16
+ if options[:sub_states].empty?
17
+ @sub_fsm = nil
18
+ else
19
+ @sub_fsm = Hifsm::FSM.new(nil, self)
20
+ options[:sub_states].each {|args, block| @sub_fsm.state(*args, &block) }
21
+ options[:sub_events].each {|args, block| @sub_fsm.event(*args, &block) }
22
+ end
33
23
  end
34
24
 
35
- # internals
36
25
  def act!(target, *args)
37
26
  @parent.act!(target, *args) if @parent
38
- @action && Callbacks.invoke(target, @action, *args)
27
+ trigger(target, :action, *args).last
39
28
  end
40
29
 
41
30
  def add_transition(ev)
@@ -94,11 +83,5 @@ module Hifsm
94
83
  @name.to_s
95
84
  end
96
85
  end
97
-
98
- private
99
- def sub_fsm!
100
- # FIXME .name = too much coupling
101
- @sub_fsm ||= Hifsm::FSM.new(@fsm.name, self)
102
- end
103
86
  end
104
87
  end
data/lib/hifsm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hifsm
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
data/lib/hifsm.rb CHANGED
@@ -3,6 +3,9 @@ require "hifsm/fsm"
3
3
  require "hifsm/event"
4
4
  require "hifsm/machine"
5
5
  require "hifsm/state"
6
+ require "hifsm/dsl/abstract_builder"
7
+ require "hifsm/dsl/event_builder"
8
+ require "hifsm/dsl/state_builder"
6
9
  require "hifsm/version"
7
10
 
8
11
  module Hifsm
@@ -0,0 +1,58 @@
1
+ require 'setup_tests'
2
+
3
+ class TestGrouppedTransitions < Minitest::Test
4
+ class Button
5
+ include Hifsm
6
+
7
+ hifsm do
8
+ state :active, :initial => true
9
+ state :alternative
10
+ state :disabled
11
+
12
+ # each :to generates new event
13
+ event :click, :to => :disabled, :guard => :disable_on_click
14
+ event :click, :from => :disabled, :to => :disabled
15
+ event :click do
16
+ from :active, :to => :alternative
17
+ from :alternative, :to => :active
18
+
19
+ after do
20
+ log << "alternated"
21
+ end
22
+ end
23
+ end
24
+
25
+ attr_accessor :disable_on_click, :log
26
+
27
+ def initialize
28
+ self.log = []
29
+ end
30
+ end
31
+
32
+ def setup
33
+ @button = Button.new
34
+ end
35
+
36
+ def test_click_from_active
37
+ @button.click
38
+ assert_equal 'alternative', @button.state
39
+ assert_equal ['alternated'], @button.log
40
+ end
41
+
42
+ def test_click_with_disable_on_click
43
+ @button.disable_on_click = true
44
+ @button.click
45
+ assert_equal 'disabled', @button.state
46
+ assert_equal [], @button.log
47
+ end
48
+
49
+ def test_click_from_disabled
50
+ @button.disable_on_click = true
51
+ @button.click
52
+ @button.disable_on_click = false
53
+ @button.click
54
+ assert_equal 'disabled', @button.state
55
+ assert_equal [], @button.log
56
+ end
57
+
58
+ end
@@ -13,9 +13,11 @@ class TestTwoMachines < Minitest::Test
13
13
  end
14
14
  end
15
15
 
16
- event :cycle_color!, :from => :red, :to => :green
17
- event :cycle_color!, :from => :green, :to => :blue
18
- event :cycle_color!, :from => :blue, :to => :red
16
+ event :cycle_color! do
17
+ from :red, :to => :green
18
+ from :green, :to => :blue
19
+ from :blue, :to => :red
20
+ end
19
21
  end
20
22
 
21
23
  hifsm :working_state do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hifsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Meremyanin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-04 00:00:00.000000000 Z
11
+ date: 2014-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -111,6 +111,9 @@ files:
111
111
  - lib/hifsm.rb
112
112
  - lib/hifsm/adapters/active_record_adapter.rb
113
113
  - lib/hifsm/callbacks.rb
114
+ - lib/hifsm/dsl/abstract_builder.rb
115
+ - lib/hifsm/dsl/event_builder.rb
116
+ - lib/hifsm/dsl/state_builder.rb
114
117
  - lib/hifsm/event.rb
115
118
  - lib/hifsm/fsm.rb
116
119
  - lib/hifsm/machine.rb
@@ -124,6 +127,7 @@ files:
124
127
  - test/test_basic_fsm.rb
125
128
  - test/test_dynamic_initial_state.rb
126
129
  - test/test_event_guard.rb
130
+ - test/test_groupped_transitions.rb
127
131
  - test/test_hierarchical.rb
128
132
  - test/test_ifless_factorial.rb
129
133
  - test/test_many_states.rb
@@ -141,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
145
  requirements:
142
146
  - - ">="
143
147
  - !ruby/object:Gem::Version
144
- version: '0'
148
+ version: '1.9'
145
149
  required_rubygems_version: !ruby/object:Gem::Requirement
146
150
  requirements:
147
151
  - - ">="
@@ -162,6 +166,7 @@ test_files:
162
166
  - test/test_basic_fsm.rb
163
167
  - test/test_dynamic_initial_state.rb
164
168
  - test/test_event_guard.rb
169
+ - test/test_groupped_transitions.rb
165
170
  - test/test_hierarchical.rb
166
171
  - test/test_ifless_factorial.rb
167
172
  - test/test_many_states.rb