volute 0.1.0 → 0.1.1
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/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
|
+
|