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 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