hifsm 0.4.5 → 0.6.0

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: 58e6687ae4f9842846c1053888058f2a900472a2
4
- data.tar.gz: b8bd4da00022fca41f541224b09a717189395d58
3
+ metadata.gz: e8c840d5a3b6429146d55b87579db8d29ca6c6c6
4
+ data.tar.gz: 2130df37d77e426940c430a7b9a29abc4619838e
5
5
  SHA512:
6
- metadata.gz: 01b20d9770b5a2623ed3af913de677b28e3de8110580d20781ed601d4d42c8a70ec7178d44c6050a9889cf709fd8737b0f27a234ffaa0aa413f624b58cf03ee8
7
- data.tar.gz: 455a85c0392c5e64038f62e8b5025a282ba9657b1b0dec9f082a7e41b1ed2c685c94429ea4ed42d4f258e544f109cac954cc508f8fd52aa6b474af43643d8c47
6
+ metadata.gz: a182af19cfb5cc12291e395c4041934d9ae5c25997d865ab573807bbf18b27efcc7c131038b0f3bd18f8fae5d5d4440ce4d08bece8ea0b8efd8dd3c3bdb668bb
7
+ data.tar.gz: b9e9e923ea20dfbf4cb335a65c297b877397559cfc2bc2b12be2a7ea1d34b9837fec772e8c857431a863fd7a1dac48d35601d232741bb6f39f3b8e081e9597d6
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .DS_Store
1
2
  *.gem
2
3
  *.rbc
3
4
  .bundle
data/README.md CHANGED
@@ -23,8 +23,6 @@ Or install it yourself as:
23
23
 
24
24
  Written in Ruby 1.8-style (hashes, lambdas), but few non-essential 1.9 niceties used, tested in 2+.
25
25
 
26
- __This is in early development, so be careful.__
27
-
28
26
  ## Features
29
27
 
30
28
  * Easy to use
@@ -44,7 +42,7 @@ Here is how to use it to model a monster in a Quake-like game. It covers most Hi
44
42
  require 'hifsm'
45
43
 
46
44
  class Monster
47
- include Hifsm
45
+ extend Hifsm
48
46
 
49
47
  hifsm do
50
48
  state :idle, :initial => true
@@ -58,7 +56,7 @@ class Monster
58
56
  state :pursuing do
59
57
  before_enter do
60
58
  self.roar!
61
- true # since roar! returns nil it would stop processing
59
+ true # since it would stop processing if roar! returns false
62
60
  end
63
61
  action do
64
62
  step_towards target
@@ -162,6 +160,7 @@ ogre.act! # Acting @attacking.pursuing
162
160
  # step step player2
163
161
  ogre.reached
164
162
  puts ogre.state # attacking.fighting
163
+ # ogre.attacking_fighting? = true
165
164
  ogre.act! # Acting @attacking.fighting
166
165
  # 6: Attack!
167
166
  # ~~> player2
@@ -191,7 +190,7 @@ On event:
191
190
  * to_state.after_enter
192
191
  * event.after
193
192
 
194
- If any of `before...` callbacks returns `false` then no further processing is done, no exceptions raised, machine state is not changed.
193
+ If any of `before...` callbacks returns `false` (literally, `nil` equals to `true` here) then no further processing is done, no exceptions raised, machine state is not changed.
195
194
 
196
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.
197
196
 
@@ -221,6 +220,8 @@ order.start_processing.save # 'processing.packaging'
221
220
 
222
221
  # scopes defined automatically. parent scopes looked up via like "processing.%"
223
222
  Order.processing.first.start_delivery.save
223
+ Order.first.processing? # true
224
+ Order.first.processing_delivering? # true
224
225
  Order.processing_packaging.first # nil
225
226
  Order.processing_delivering.first.cancel!.save # save is never called inisde hifsm
226
227
 
@@ -41,6 +41,6 @@ module Hifsm
41
41
  end
42
42
 
43
43
  ActiveRecord::Base.class_eval do
44
- include Hifsm
44
+ extend Hifsm
45
45
  include Hifsm::Adapters::ActiveRecordAdapter
46
46
  end
@@ -1,22 +1,30 @@
1
- class Callbacks
1
+ module Hifsm
2
+ module Callbacks
2
3
 
3
- def initialize(listeners = [])
4
- @listeners = listeners
5
- end
4
+ def set_callbacks(key, listeners)
5
+ @__callbacks ||= {}
6
+ @__callbacks[key] = listeners
7
+ end
6
8
 
7
- def trigger(target, *args)
8
- @listeners.map do |cb|
9
- if cb.nil?
10
- # raise something maybe? :)
11
- elsif cb.is_a? Symbol
12
- if target.method(cb).arity.zero?
13
- target.send(cb)
9
+ def trigger(key, target, *args)
10
+ return [] unless @__callbacks[key]
11
+ @__callbacks[key].map do |cb|
12
+ if cb.nil?
13
+ # raise something maybe? :)
14
+ elsif cb.is_a? Symbol
15
+ if target.method(cb).arity.zero?
16
+ target.send(cb)
17
+ else
18
+ target.send(cb, *args)
19
+ end
14
20
  else
15
- target.send(cb, *args)
21
+ target.instance_exec(*args, &cb)
16
22
  end
17
- else
18
- target.instance_exec(*args, &cb)
19
23
  end
20
24
  end
25
+
26
+ def trigger?(key, target, *args)
27
+ !trigger(key, target, *args).any? {|v| v == false }
28
+ end
21
29
  end
22
30
  end
data/lib/hifsm/event.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Hifsm
2
2
  class Event
3
+ include Hifsm::Callbacks
4
+
3
5
  CALLBACKS = [:before, :after, :guard].freeze
4
6
 
5
7
  attr_reader :name, :to
@@ -7,18 +9,13 @@ module Hifsm
7
9
  def initialize(name, to, callbacks_options)
8
10
  @name = name
9
11
  @to = to
10
- @callbacks = {}
11
12
  CALLBACKS.each do |cb|
12
- @callbacks[cb] = Callbacks.new(callbacks_options[cb])
13
+ set_callbacks cb, callbacks_options[cb]
13
14
  end
14
15
  end
15
16
 
16
- def trigger(target, cb, *args)
17
- @callbacks[cb].trigger(target, *args)
18
- end
19
-
20
17
  def guard?(target, *args)
21
- trigger(target, :guard, *args).all?
18
+ trigger?(:guard, target, *args)
22
19
  end
23
20
  end
24
21
  end
data/lib/hifsm/fsm.rb CHANGED
@@ -94,6 +94,15 @@ module Hifsm
94
94
  fsm.all_events.each do |event_name, event|
95
95
  define_method(event_name) {|*args| send(machine_name).fire(event_name, *args) }
96
96
  end
97
+
98
+ # <state.to_s.underscore>? = is machine currently in state <state>?
99
+ fsm.all_states.each do |st|
100
+ query = st.to_s
101
+ define_method("#{query.gsub('.', '_')}?") do
102
+ current = send(machine_name).to_s
103
+ current == query || current.start_with?("#{query}.")
104
+ end
105
+ end
97
106
  end
98
107
  end
99
108
 
data/lib/hifsm/state.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Hifsm
2
2
  class State
3
+ include Hifsm::Callbacks
4
+
3
5
  CALLBACKS = [:before_enter, :before_exit, :after_enter, :after_exit, :action].freeze
4
6
 
5
7
  attr_reader :name, :sub_fsm
@@ -7,9 +9,8 @@ module Hifsm
7
9
  def initialize(name, parent = nil, options)
8
10
  @name = name.to_s
9
11
  @parent = parent
10
- @callbacks = {}
11
12
  CALLBACKS.each do |cb|
12
- @callbacks[cb] = Callbacks.new(options[cb])
13
+ set_callbacks cb, options[cb]
13
14
  end
14
15
  @transitions = Hash.new {|h, key| h[key] = Array.new }
15
16
 
@@ -24,7 +25,7 @@ module Hifsm
24
25
 
25
26
  def act!(target, *args)
26
27
  @parent.act!(target, *args) if @parent
27
- trigger(target, :action, *args).last
28
+ trigger(:action, target, *args).last
28
29
  end
29
30
 
30
31
  def add_transition(ev)
@@ -55,13 +56,13 @@ module Hifsm
55
56
  if ev.guard?(target, *args)
56
57
  from_state = self
57
58
  to_state = ev.to
58
- if ev.trigger(target, :before, *args).all? &&
59
- to_state.trigger(target, :before_enter, *args).all? &&
60
- from_state.trigger(target, :before_exit, *args).all?
59
+ if ev.trigger?(:before, target, *args) &&
60
+ to_state.trigger?(:before_enter, target, *args) &&
61
+ from_state.trigger?(:before_exit, target, *args)
61
62
  new_state_callback.call(to_state.enter!)
62
- from_state.trigger(target, :after_exit, *args)
63
- to_state.trigger(target, :after_enter, *args)
64
- ev.trigger(target, :after, *args)
63
+ from_state.trigger(:after_exit, target, *args)
64
+ to_state.trigger(:after_enter, target, *args)
65
+ ev.trigger(:after, target, *args)
65
66
  end
66
67
  return target
67
68
  end
@@ -72,10 +73,6 @@ module Hifsm
72
73
  raise Hifsm::MissingTransition.new(to_s, event_name)
73
74
  end
74
75
 
75
- def trigger(target, cb, *args)
76
- @callbacks[cb].trigger(target, *args)
77
- end
78
-
79
76
  def to_s
80
77
  if @parent
81
78
  "#{@parent.to_s}.#{@name}"
data/lib/hifsm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hifsm
2
- VERSION = "0.4.5"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/hifsm.rb CHANGED
@@ -22,13 +22,11 @@ module Hifsm
22
22
  end
23
23
 
24
24
  def self.included(base)
25
- base.send(:extend, ClassMethods) unless base.respond_to?(:hifsm)
25
+ raise 'use extend Hifsm instead of include'
26
26
  end
27
27
 
28
- module ClassMethods
29
- def hifsm(name = :state, &block)
30
- include FSM::new(name, &block).to_module
31
- end
28
+ def hifsm(name = :state, &block)
29
+ include FSM::new(name, &block).to_module
32
30
  end
33
31
  end
34
32
 
data/test/monster.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'hifsm'
2
2
 
3
3
  class Monster
4
- include Hifsm
4
+ extend Hifsm
5
5
 
6
6
  hifsm do
7
7
  state :idle, :initial => true
@@ -15,7 +15,7 @@ class Monster
15
15
  state :pursuing do
16
16
  before_enter do
17
17
  self.roar!
18
- true # since roar! returns nil it would stop processing
18
+ true # since it would stop processing if roar! returns false
19
19
  end
20
20
  action do
21
21
  step_towards target
@@ -120,6 +120,7 @@ if $0 == __FILE__
120
120
  # step step player2
121
121
  ogre.reached
122
122
  puts ogre.state # attacking.fighting
123
+ # ogre.attacking_fighting? = true
123
124
  ogre.act! # Acting @attacking.fighting
124
125
  # 6: Attack!
125
126
  # ~~> player2
@@ -16,6 +16,11 @@ class TestBasicFSM < Minitest::Test
16
16
  assert_equal 'off', @machine.state.to_s
17
17
  end
18
18
 
19
+ def test_state_question_methods
20
+ refute @machine.on?, "Machine .on? should be false"
21
+ assert @machine.off?, "Machine .off? should be true"
22
+ end
23
+
19
24
  def test_toggle_switches_state_to_on
20
25
  @machine.toggle
21
26
  assert_equal 'on', @machine.state.to_s
@@ -2,13 +2,17 @@ require 'setup_tests'
2
2
 
3
3
  class TestBeforeReturningFalse < Minitest::Test
4
4
  class Door
5
- include Hifsm
5
+ extend Hifsm
6
6
 
7
7
  hifsm do
8
8
  state :open
9
9
  state :closed, :initial => true do
10
10
  state :unlocked, :initial => true
11
- state :locked
11
+ state :locked do
12
+ before_enter do
13
+ nil # nil equals true in this case, like in Rails
14
+ end
15
+ end
12
16
 
13
17
  event :lock, :from => :unlocked, :to => :locked
14
18
  end
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestDynamicInitialState < Minitest::Test
4
4
  class Value < Struct.new(:value)
5
- include Hifsm
5
+ extend Hifsm
6
6
  hifsm :group do
7
7
  state :few do
8
8
  state :very
@@ -2,19 +2,27 @@ require 'setup_tests'
2
2
 
3
3
  class TestEventGuard < Minitest::Test
4
4
  class Wall < Struct.new(:stones)
5
- include Hifsm
5
+ extend Hifsm
6
6
 
7
7
  hifsm do
8
8
  state :constructed, :initial => true
9
9
  state :broken
10
10
 
11
- # guards can be inline proc, or symbols
12
- event :break, :from => :constructed, :to => :broken, :guard => proc { stones < 5 }
11
+ event :break do
12
+ # guards can be inline proc, or symbols
13
+ from :constructed, :to => :broken, :guard => proc { stones < 5 }
13
14
 
14
- # event parameters are passed to guards only if arity > 0
15
- event :break, :from => :constructed, :to => :broken, :guard => :breakable?
15
+ # event parameters are passed to guards only if arity > 0
16
+ from :constructed, :to => :broken, :guard => :breakable?
17
+
18
+ before do |strength|
19
+ @last_hit_strength = strength.to_s
20
+ end
21
+ end
16
22
  end
17
23
 
24
+ attr_reader :last_hit_strength
25
+
18
26
  def breakable?(hits = 1)
19
27
  hits * 5 > stones
20
28
  end
@@ -24,6 +32,8 @@ class TestEventGuard < Minitest::Test
24
32
  wall = Wall.new(3)
25
33
  wall.break
26
34
  assert_equal 'broken', wall.state
35
+ # before callback invoked
36
+ assert_equal '', wall.last_hit_strength
27
37
  end
28
38
 
29
39
  def test_cant_break_wall_10_stones_thick
@@ -31,12 +41,16 @@ class TestEventGuard < Minitest::Test
31
41
  assert_raises(Hifsm::MissingTransition) do
32
42
  wall.break
33
43
  end
44
+ # before callback not called
45
+ assert_equal nil, wall.last_hit_strength
34
46
  end
35
47
 
36
48
  def test_can_break_thick_wall_if_hit_3_times
37
49
  wall = Wall.new(10)
38
50
  wall.break 3
39
51
  assert_equal 'broken', wall.state
52
+ # before callback invoked
53
+ assert_equal '3', wall.last_hit_strength
40
54
  end
41
55
 
42
56
  end
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestGrouppedTransitions < Minitest::Test
4
4
  class Button
5
- include Hifsm
5
+ extend Hifsm
6
6
 
7
7
  hifsm do
8
8
  state :active, :initial => true
@@ -25,6 +25,7 @@ class TestGrouppedTransitions < Minitest::Test
25
25
  attr_accessor :disable_on_click, :log
26
26
 
27
27
  def initialize
28
+ self.disable_on_click = false # nil = true, same as for callbacks
28
29
  self.log = []
29
30
  end
30
31
  end
@@ -51,6 +51,14 @@ class TestHierarchical < Minitest::Test
51
51
  assert_equal 'off.sync.third_level', machine.state.to_s
52
52
  end
53
53
 
54
+ def test_state_question_methods
55
+ machine = @fsm.instantiate
56
+ refute machine.on?, "Machine .on? should be false"
57
+ assert machine.off?, "Machine .off? should be true"
58
+ refute machine.off_sync?, "Machine .off_sync? should be false"
59
+ assert machine.off_pending?, "Machine .off_pending? should be true"
60
+ end
61
+
54
62
  def test_toggle_from_off_sync_to_on_pending
55
63
  machine = @fsm.instantiate
56
64
  machine.sync
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestIflessFactorial < Minitest::Test
4
4
  class Value < Struct.new(:value)
5
- include Hifsm
5
+ extend Hifsm
6
6
  hifsm do
7
7
  state :idle, :initial => true
8
8
  state :computing
@@ -2,7 +2,7 @@ require 'setup_tests'
2
2
 
3
3
  class TestTwoMachines < Minitest::Test
4
4
  class ColorPrinter < Struct.new(:log)
5
- include Hifsm
5
+ extend Hifsm
6
6
 
7
7
  hifsm :color do
8
8
  state :red, :initial => true
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.5
4
+ version: 0.6.0
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-07 00:00:00.000000000 Z
11
+ date: 2014-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler