switcher 0.0.1.beta
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +1 -0
- data/lib/switcher/facade.rb +21 -0
- data/lib/switcher/listener.rb +38 -0
- data/lib/switcher/machine.rb +43 -0
- data/lib/switcher/spec.rb +45 -0
- data/lib/switcher/version.rb +3 -0
- data/lib/switcher.rb +7 -0
- data/spec/fixtures/human.rb +92 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/switcher_spec.rb +54 -0
- data/switcher.gemspec +24 -0
- metadata +84 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Switcher
|
2
|
+
class Facade
|
3
|
+
def initialize(data, target_state=nil)
|
4
|
+
@args = data
|
5
|
+
@stopped = false
|
6
|
+
@target_state = target_state
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :args, :stopped, :target_state
|
10
|
+
|
11
|
+
def stop
|
12
|
+
@stopped = true
|
13
|
+
end
|
14
|
+
|
15
|
+
alias :restrict :stop
|
16
|
+
|
17
|
+
def switch_to(state)
|
18
|
+
@target_state = state.to_sym
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Switcher
|
2
|
+
class Listener
|
3
|
+
def initialize(name)
|
4
|
+
@events = {}
|
5
|
+
@event_names = []
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :events, :event_names, :name
|
10
|
+
|
11
|
+
def before(event, options={}, &block)
|
12
|
+
options[:allow_switch] = true
|
13
|
+
data = { callback: block, options: options }
|
14
|
+
@events["before_#{event}"] = data
|
15
|
+
end
|
16
|
+
|
17
|
+
def on(event, options={}, &block)
|
18
|
+
options[:allow_switch] = true
|
19
|
+
data = { callback: block, options: options }
|
20
|
+
@event_names << event.to_sym
|
21
|
+
@events[event.to_s] = data
|
22
|
+
end
|
23
|
+
|
24
|
+
def after(event, options={}, &block)
|
25
|
+
data = { callback: block, options: options }
|
26
|
+
@events["after_#{event}"] = data
|
27
|
+
end
|
28
|
+
|
29
|
+
def trigger(event, facade, instance, args)
|
30
|
+
if ev = @events[event]
|
31
|
+
instance.instance_exec(facade, *args, &ev[:callback]) if ev[:callback].respond_to?(:call)
|
32
|
+
if !facade.stopped && ev[:options][:allow_switch] && switch_to = ev[:options][:switch_to]
|
33
|
+
facade.switch_to(switch_to)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'switcher/spec'
|
2
|
+
|
3
|
+
module Switcher
|
4
|
+
module Machine
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def switcher(name, options={}, &block)
|
8
|
+
self.class_variable_defined?(:@@__specs__) or self.class_variable_set(:@@__specs__, [])
|
9
|
+
|
10
|
+
spec = Spec.new(name, options)
|
11
|
+
spec.instance_eval(&block)
|
12
|
+
|
13
|
+
self.class_variable_get(:@@__specs__) << spec # dup and destroy?
|
14
|
+
|
15
|
+
self.class_eval do
|
16
|
+
define_method(:"#{spec.name}_spec") { spec }
|
17
|
+
define_method(:"#{spec.name}_prev") { spec.state_prev }
|
18
|
+
|
19
|
+
define_method(:"#{spec.name}") { spec.current_state }
|
20
|
+
|
21
|
+
events = []
|
22
|
+
|
23
|
+
spec.states.each_pair do |state_name, state|
|
24
|
+
define_method(:"#{state_name}?") { state_name == self.send(:"#{spec.name}") }
|
25
|
+
|
26
|
+
events << state.event_names
|
27
|
+
end
|
28
|
+
|
29
|
+
events.flatten.each do |event_name|
|
30
|
+
define_method(:"can_#{event_name}?") { spec.states[self.send(:"#{spec.name}")].event_names.include?(event_name) }
|
31
|
+
define_method(:"#{event_name}!") do |*args|
|
32
|
+
spec.publish(self.send(:"#{spec.name}"), event_name, self, args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.included(base)
|
40
|
+
base.extend ClassMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'switcher/listener'
|
2
|
+
require 'switcher/facade'
|
3
|
+
|
4
|
+
module Switcher
|
5
|
+
class Spec
|
6
|
+
def initialize(name, options={})
|
7
|
+
@name = name.to_sym
|
8
|
+
@states = {}
|
9
|
+
@states_list = []
|
10
|
+
@state_prev = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name, :states, :state_prev
|
14
|
+
|
15
|
+
def state(name, &block)
|
16
|
+
listener = Listener.new(name)
|
17
|
+
listener.instance_eval(&block) if block_given?
|
18
|
+
|
19
|
+
@states_list << name.to_sym
|
20
|
+
@states[name.to_sym] = listener
|
21
|
+
end
|
22
|
+
|
23
|
+
def current_state
|
24
|
+
@current_state || @states_list.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def publish(current_state, event, instance, args)
|
28
|
+
facade = Facade.new(args)
|
29
|
+
["before_#{event}", event.to_s].each do |ev|
|
30
|
+
@states[current_state.to_sym].trigger(ev, facade, instance, args)
|
31
|
+
end
|
32
|
+
set_state(facade)
|
33
|
+
@states[current_state.to_sym].trigger("after_#{event}", facade, instance, args)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_state(facade)
|
39
|
+
unless facade.stopped && facade.target_state.nil?
|
40
|
+
@state_prev = @current_state
|
41
|
+
@current_state = facade.target_state.to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/switcher.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
class Human
|
2
|
+
include Switcher::Machine
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@easy_to_fight = false
|
6
|
+
@honor = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :easy_to_fight, :honor
|
10
|
+
|
11
|
+
switcher :state do
|
12
|
+
state :newborn do
|
13
|
+
on :go_to_school, switch_to: :scholar
|
14
|
+
end
|
15
|
+
|
16
|
+
state :scholar do
|
17
|
+
on :finish_school do |ev, exams|
|
18
|
+
if pass?(exams)
|
19
|
+
ev.switch_to :student
|
20
|
+
else
|
21
|
+
ev.switch_to :worker
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
state :student do
|
27
|
+
on :finish_university do |ev, exams|
|
28
|
+
if pass?(exams)
|
29
|
+
ev.switch_to :manager
|
30
|
+
else
|
31
|
+
ev.switch_to :soldier
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
state :worker do
|
37
|
+
on :full_age do |ev, money|
|
38
|
+
if enough?(money)
|
39
|
+
ev.switch_to :manager
|
40
|
+
else
|
41
|
+
ev.switch_to :soldier
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
state :soldier do
|
47
|
+
on :die, switch_to: :dead
|
48
|
+
|
49
|
+
before :fight do |ev, learn|
|
50
|
+
if hard_to?(learn)
|
51
|
+
@easy_to_fight = true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
on :fight do |ev|
|
56
|
+
if @easy_to_fight
|
57
|
+
ev.switch_to :civil
|
58
|
+
else
|
59
|
+
rand(2).even? ? ev.switch_to(:dead) : ev.switch_to(:civil)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
after :fight do
|
64
|
+
if self.state == :civil
|
65
|
+
@honor = 100
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
state :civil do
|
71
|
+
on :find_job, switch_to: :manager do |ev, cv|
|
72
|
+
ev.restrict unless cv
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
state :manager do
|
77
|
+
on :die, switch_to: :dead
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def pass?(score)
|
82
|
+
score > 3
|
83
|
+
end
|
84
|
+
|
85
|
+
def enough?(num)
|
86
|
+
num > 9000
|
87
|
+
end
|
88
|
+
|
89
|
+
def hard_to?(learn=false)
|
90
|
+
!!learn
|
91
|
+
end
|
92
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'switcher'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'fixtures/human'
|
4
|
+
|
5
|
+
describe Switcher do
|
6
|
+
|
7
|
+
describe "Basic functionality" do
|
8
|
+
people = Human.new
|
9
|
+
|
10
|
+
it "should switch to state with option" do
|
11
|
+
people.go_to_school!
|
12
|
+
people.state.should eq(:scholar)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should switch to state by internal condition" do
|
16
|
+
people.finish_school!(2)
|
17
|
+
people.state.should eq(:worker)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have before and after callbacks" do
|
21
|
+
people.full_age!(100)
|
22
|
+
people.fight!(true)
|
23
|
+
|
24
|
+
people.state.should eq(:civil)
|
25
|
+
people.easy_to_fight.should be_true
|
26
|
+
people.honor.should eq(100)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can prevent switch" do
|
30
|
+
people.find_job!(false)
|
31
|
+
people.state.should eq(:civil)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "and can not" do
|
35
|
+
people.find_job!(true)
|
36
|
+
people.state.should eq(:manager)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can detect possibility to switch" do
|
40
|
+
people.can_fight?.should be_false
|
41
|
+
people.can_die?.should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should remember previous state" do
|
45
|
+
people.state_prev.should eq(:civil)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have state predicate" do
|
49
|
+
people.manager?.should be_true
|
50
|
+
people.civil?.should be_false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/switcher.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "switcher/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "switcher"
|
7
|
+
s.version = Switcher::VERSION
|
8
|
+
s.authors = ["Andrey Savchenko"]
|
9
|
+
s.email = ["andrey@aejis.eu"]
|
10
|
+
s.homepage = "https://github.com/Ptico/switcher"
|
11
|
+
s.summary = %q{Switcher is simple, event-driven state machine}
|
12
|
+
s.description = %q{Switcher is simple, event-driven state machine}
|
13
|
+
|
14
|
+
s.rubyforge_project = "switcher"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
#s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rake"
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: switcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrey Savchenko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70269386935900 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70269386935900
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70269386935320 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70269386935320
|
36
|
+
description: Switcher is simple, event-driven state machine
|
37
|
+
email:
|
38
|
+
- andrey@aejis.eu
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- Gemfile
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- lib/switcher.rb
|
48
|
+
- lib/switcher/facade.rb
|
49
|
+
- lib/switcher/listener.rb
|
50
|
+
- lib/switcher/machine.rb
|
51
|
+
- lib/switcher/spec.rb
|
52
|
+
- lib/switcher/version.rb
|
53
|
+
- spec/fixtures/human.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
- spec/switcher_spec.rb
|
56
|
+
- switcher.gemspec
|
57
|
+
homepage: https://github.com/Ptico/switcher
|
58
|
+
licenses: []
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>'
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 1.3.1
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project: switcher
|
77
|
+
rubygems_version: 1.8.6
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Switcher is simple, event-driven state machine
|
81
|
+
test_files:
|
82
|
+
- spec/fixtures/human.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
- spec/switcher_spec.rb
|