volute 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +19 -1
- data/README.rdoc +333 -76
- data/Rakefile +4 -4
- data/TODO.txt +28 -14
- data/examples/diagnosis.rb +82 -0
- data/examples/equation.rb +1 -1
- data/examples/light.rb +27 -0
- data/examples/state_machine.rb +26 -24
- data/examples/state_machine_2.rb +64 -0
- data/examples/traffic.rb +115 -0
- data/lib/volute.rb +159 -32
- data/spec/apply_spec.rb +52 -0
- data/spec/filter_not_spec.rb +58 -0
- data/spec/{volute_guard_spec.rb → filter_spec.rb} +57 -29
- data/spec/filter_state_spec.rb +179 -0
- data/spec/filter_transitions_spec.rb +200 -0
- data/spec/over_spec.rb +36 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/volute_spec.rb +0 -32
- data/spec/volutes_spec.rb +91 -0
- data/volute.gemspec +31 -11
- metadata +60 -20
data/Rakefile
CHANGED
@@ -10,11 +10,11 @@ require 'rake'
|
|
10
10
|
#
|
11
11
|
# SPEC
|
12
12
|
|
13
|
-
task :
|
14
|
-
sh
|
13
|
+
task :rspec do
|
14
|
+
sh 'time rspec -cfs spec/'
|
15
15
|
end
|
16
16
|
|
17
|
-
task :default => :
|
17
|
+
task :default => :rspec
|
18
18
|
|
19
19
|
|
20
20
|
#
|
@@ -47,7 +47,7 @@ placing some [business] logic outside of classes
|
|
47
47
|
|
48
48
|
#gem.add_dependency 'mime-types', '>= 1.16'
|
49
49
|
gem.add_development_dependency 'rake'
|
50
|
-
gem.add_development_dependency 'rspec'
|
50
|
+
gem.add_development_dependency 'rspec', '>= 2.0.0'
|
51
51
|
gem.add_development_dependency 'jeweler'
|
52
52
|
|
53
53
|
# gemspec spec : http://www.rubygems.org/read/chapter/20
|
data/TODO.txt
CHANGED
@@ -1,24 +1,38 @@
|
|
1
1
|
|
2
2
|
[o] volute 'prev' => 'new' { ... }
|
3
3
|
[o] volute Module { ... }
|
4
|
-
[o] obj.
|
4
|
+
[o] obj.vset(:a => 0, :b => 1)
|
5
5
|
[o] volute { over }
|
6
|
+
[o] volute Class { volute :attr { } } --> Class AND attr
|
7
|
+
volute Class, :attr { } --> Class OR attr
|
8
|
+
[o] volute :not, Light { ... }
|
9
|
+
[o] volute /^att/ { ... }
|
10
|
+
[o] not including Volute, but triggering Volute.xxx on some changes
|
11
|
+
[o] volute :att => :not_nil { ... } (not a nil value)
|
12
|
+
[o] volute :att => [ a, b ] { ... } (a or b)
|
13
|
+
[o] volute previous_value => [ val0, val1 ] { ... } (OR)
|
14
|
+
[o] volute [ pval0, pval1 ] => value { ... } (OR)
|
15
|
+
[o] volute :att => /regex/ { ... }
|
16
|
+
[o] volute /regex/ => /regex/ { ... }
|
17
|
+
[o] rewrite README head
|
18
|
+
[o] readme : list alternatives (aspects/hooks/callbacks)
|
6
19
|
|
7
|
-
[ ] volute
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
if is(true)
|
15
|
-
object.comment = 'got paid'
|
16
|
-
elsif was(nil)
|
17
|
-
object.comment = 'still not paid'
|
18
|
-
end
|
20
|
+
[ ] volute Invoice do
|
21
|
+
volute :paid do
|
22
|
+
if is(true)
|
23
|
+
object.comment = 'got paid'
|
24
|
+
elsif was(nil)
|
25
|
+
object.comment = 'still not paid'
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
23
|
-
[
|
30
|
+
[x] multi-entity state machine example
|
31
|
+
not happy with the traffic light example
|
32
|
+
|
33
|
+
[ ] volutes
|
34
|
+
`-- volute x, y
|
35
|
+
`-- volute a, b
|
36
|
+
|
37
|
+
[ ] pass the object as block binding ?
|
24
38
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
# license is MIT
|
3
|
+
|
4
|
+
# this example is strongly inspired by the diagnosis example
|
5
|
+
# for Ruleby ( http://github.com/codeaspects/ruleby )
|
6
|
+
# but please remember that Ruleby is a Rete implementation and is thus
|
7
|
+
# vastly superior to what is implemented here
|
8
|
+
|
9
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
10
|
+
|
11
|
+
require 'volute'
|
12
|
+
|
13
|
+
|
14
|
+
#
|
15
|
+
# some classes
|
16
|
+
|
17
|
+
class Patient
|
18
|
+
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
attr_accessor :fever
|
22
|
+
attr_accessor :rash
|
23
|
+
attr_accessor :spots
|
24
|
+
attr_accessor :sore_throat
|
25
|
+
attr_accessor :innoculated
|
26
|
+
|
27
|
+
attr_accessor :diagnosis
|
28
|
+
|
29
|
+
def initialize(name)
|
30
|
+
|
31
|
+
@name = name
|
32
|
+
@symptoms = {}
|
33
|
+
@innoculated = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def diagnose!
|
37
|
+
|
38
|
+
Volute.apply(self)
|
39
|
+
|
40
|
+
@diagnosis
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# the volutes
|
46
|
+
|
47
|
+
volute Patient do
|
48
|
+
|
49
|
+
# These volutes are 'state' volutes, they trigger if all the attributes
|
50
|
+
# mentioned as keys yield the given value.
|
51
|
+
# unlike classical volutes that act as OR, these states volutes have an
|
52
|
+
# AND behaviour.
|
53
|
+
|
54
|
+
volute :fever => :high, :spots => :true, :innoculated => true do
|
55
|
+
object.diagnosis = 'measles'
|
56
|
+
over # prevent further evals
|
57
|
+
end
|
58
|
+
volute :spots => true do
|
59
|
+
object.diagnosis = 'allergy (spots but no measles)'
|
60
|
+
end
|
61
|
+
volute :rash => true do
|
62
|
+
object.diagnosis = 'allergy (rash)'
|
63
|
+
end
|
64
|
+
volute :sore_throat => true, :fever => :mild do
|
65
|
+
object.diagnosis = 'flu'
|
66
|
+
end
|
67
|
+
volute :sore_throat => true, :fever => :high do
|
68
|
+
object.diagnosis = 'flu'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
pat = Patient.new('alice')
|
73
|
+
pat.rash = true
|
74
|
+
|
75
|
+
puts "#{pat.name} : diagnosed with #{pat.diagnose!}"
|
76
|
+
|
77
|
+
pat = Patient.new('bob')
|
78
|
+
pat.sore_throat = true
|
79
|
+
pat.fever = :high
|
80
|
+
|
81
|
+
puts "#{pat.name} : diagnosed with #{pat.diagnose!}"
|
82
|
+
|
data/examples/equation.rb
CHANGED
data/examples/light.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
# license is MIT
|
3
|
+
|
4
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
|
6
|
+
require 'volute' # gem install volute
|
7
|
+
|
8
|
+
class Light
|
9
|
+
include Volute
|
10
|
+
|
11
|
+
attr_accessor :colour
|
12
|
+
attr_accessor :changed_at
|
13
|
+
end
|
14
|
+
|
15
|
+
volute Light do
|
16
|
+
volute :colour do
|
17
|
+
object.changed_at = Time.now
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
l = Light.new
|
22
|
+
p l # => #<Light:0x10014c480>
|
23
|
+
|
24
|
+
l.colour = :blue
|
25
|
+
l.colour = :red
|
26
|
+
p l # => #<Light:0x10014c480 @changed_at=Fri Oct 08 20:01:52 +0900 2010, @colour=:blue>
|
27
|
+
|
data/examples/state_machine.rb
CHANGED
@@ -9,50 +9,52 @@ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
9
9
|
require 'volute'
|
10
10
|
|
11
11
|
#
|
12
|
-
# our
|
12
|
+
# our classes
|
13
13
|
|
14
|
-
|
15
|
-
include Volute
|
14
|
+
module Bookshop
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
class Book
|
17
|
+
include Volute
|
19
18
|
|
20
|
-
|
19
|
+
attr_accessor :stock
|
20
|
+
attr_accessor :discontinued
|
21
|
+
attr_accessor :state
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def initialize (stock)
|
24
|
+
@stock = stock
|
25
|
+
@discontinued = false
|
26
|
+
@state = :in_stock
|
27
|
+
end
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
31
|
#
|
30
|
-
#
|
32
|
+
# volutes
|
31
33
|
|
32
|
-
volute
|
34
|
+
volute Bookshop do
|
33
35
|
|
34
|
-
|
35
|
-
# is equivalent to
|
36
|
-
# object.instance_variable_set(:@state, x)
|
36
|
+
volute :stock, :discontinued do
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
# anything in the module Bookshop that has an attribute :stock
|
39
|
+
# or :discontinued
|
40
|
+
|
41
|
+
if object.stock <= 0
|
42
|
+
object.state = object.discontinued ? :discontinued : :out_of_stock
|
43
|
+
else
|
44
|
+
object.state = :in_stock
|
45
|
+
end
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
47
49
|
#
|
48
50
|
# trying
|
49
51
|
|
50
|
-
emma = Book.new(10)
|
52
|
+
emma = Bookshop::Book.new(10)
|
51
53
|
|
52
|
-
emma.stock
|
54
|
+
emma.stock -= 8
|
53
55
|
p emma.state # => :in_stock
|
54
56
|
|
55
|
-
emma.stock
|
57
|
+
emma.stock -= 2
|
56
58
|
p emma.state # => :out_of_stock
|
57
59
|
|
58
60
|
emma.discontinued = true
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
# license is MIT
|
3
|
+
#
|
4
|
+
# a state-machine-ish example, inspired by the example at
|
5
|
+
# http://github.com/qoobaa/transitions
|
6
|
+
|
7
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
+
|
9
|
+
require 'volute'
|
10
|
+
|
11
|
+
#
|
12
|
+
# our classes
|
13
|
+
|
14
|
+
module Bookshop
|
15
|
+
|
16
|
+
class Book
|
17
|
+
include Volute
|
18
|
+
|
19
|
+
attr_accessor :stock
|
20
|
+
attr_accessor :discontinued
|
21
|
+
attr_accessor :state
|
22
|
+
|
23
|
+
def initialize (stock)
|
24
|
+
@stock = stock
|
25
|
+
@discontinued = false
|
26
|
+
@state = :in_stock
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# volutes
|
33
|
+
|
34
|
+
volute Bookshop do
|
35
|
+
|
36
|
+
volute :stock, :discontinued do
|
37
|
+
|
38
|
+
# anything in the module Bookshop that has an attribute :stock
|
39
|
+
# or :discontinued
|
40
|
+
|
41
|
+
volute :any => [ 0, true ] do
|
42
|
+
object.state = object.discontinued ? :discontinued : :out_of_stock
|
43
|
+
over
|
44
|
+
end
|
45
|
+
volute do
|
46
|
+
object.state = :in_stock
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# trying
|
53
|
+
|
54
|
+
emma = Bookshop::Book.new(10)
|
55
|
+
|
56
|
+
emma.stock -= 8
|
57
|
+
p emma.state # => :in_stock
|
58
|
+
|
59
|
+
emma.stock -= 2
|
60
|
+
p emma.state # => :out_of_stock
|
61
|
+
|
62
|
+
emma.discontinued = true
|
63
|
+
p emma.state # => :discontinued
|
64
|
+
|
data/examples/traffic.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
# license is MIT
|
3
|
+
|
4
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
|
6
|
+
require 'volute'
|
7
|
+
|
8
|
+
#
|
9
|
+
# our classes
|
10
|
+
|
11
|
+
class Light
|
12
|
+
include Volute
|
13
|
+
|
14
|
+
attr_accessor :colour
|
15
|
+
attr_accessor :changed_at
|
16
|
+
|
17
|
+
def initialize(colour)
|
18
|
+
self.colour = colour
|
19
|
+
end
|
20
|
+
|
21
|
+
def green?
|
22
|
+
self.colour == :green
|
23
|
+
end
|
24
|
+
|
25
|
+
def red!
|
26
|
+
self.colour = :red
|
27
|
+
end
|
28
|
+
def green!
|
29
|
+
self.colour = :green
|
30
|
+
end
|
31
|
+
|
32
|
+
def seconds
|
33
|
+
Time.now - @changed_at
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Line
|
38
|
+
include Volute
|
39
|
+
|
40
|
+
attr_accessor :name
|
41
|
+
attr_accessor :cars
|
42
|
+
attr_accessor :light
|
43
|
+
attr_accessor :complement
|
44
|
+
|
45
|
+
def initialize(name, colour)
|
46
|
+
@name = name
|
47
|
+
@cars = 0
|
48
|
+
@light = Light.new(colour)
|
49
|
+
end
|
50
|
+
|
51
|
+
def one_car
|
52
|
+
self.cars = self.cars + 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def log(message)
|
56
|
+
puts "#{@name} : #{message} (#{@cars} cars)"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
#
|
62
|
+
# 'business logic'
|
63
|
+
|
64
|
+
volute Light do
|
65
|
+
|
66
|
+
volute :colour do
|
67
|
+
object.changed_at = Time.now
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
volute Line do
|
72
|
+
|
73
|
+
volute :cars do
|
74
|
+
|
75
|
+
delta = value - object.complement.cars
|
76
|
+
|
77
|
+
if object.light.green?
|
78
|
+
object.vset(:cars, value - 1)
|
79
|
+
object.log('cars pass')
|
80
|
+
else
|
81
|
+
if delta > 4 || object.light.seconds > 5
|
82
|
+
object.complement.light.red!
|
83
|
+
object.light.green!
|
84
|
+
object.log('now green')
|
85
|
+
object.vset(:cars, 0)
|
86
|
+
else
|
87
|
+
object.log('car stops')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# initialize system
|
95
|
+
|
96
|
+
north_south = Line.new('north-south', :green)
|
97
|
+
east_west = Line.new('east-west', :red)
|
98
|
+
|
99
|
+
north_south.complement = east_west
|
100
|
+
east_west.complement = north_south
|
101
|
+
|
102
|
+
#
|
103
|
+
# play
|
104
|
+
|
105
|
+
north_south.one_car
|
106
|
+
east_west.one_car
|
107
|
+
east_west.one_car
|
108
|
+
north_south.one_car
|
109
|
+
east_west.one_car
|
110
|
+
east_west.one_car
|
111
|
+
north_south.one_car
|
112
|
+
east_west.one_car
|
113
|
+
east_west.one_car
|
114
|
+
north_south.one_car
|
115
|
+
|