switcher 0.0.1.beta

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in switcher.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ TODO - write README
2
+
3
+ See specs
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
@@ -0,0 +1,3 @@
1
+ module Switcher
2
+ VERSION = "0.0.1.beta"
3
+ end
data/lib/switcher.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "switcher/version"
2
+
3
+ module Switcher
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'switcher/machine'
@@ -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
@@ -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