hifsm 0.4.0 → 0.4.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.
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