protocol 0.8.0
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/Rakefile +75 -0
- data/VERSION +1 -0
- data/examples/comparing.rb +37 -0
- data/examples/enumerating.rb +47 -0
- data/examples/game.rb +81 -0
- data/examples/hello_world_patternitis.rb +77 -0
- data/examples/indexing.rb +27 -0
- data/examples/locking.rb +111 -0
- data/examples/queue.rb +154 -0
- data/examples/stack.rb +75 -0
- data/install.rb +18 -0
- data/lib/protocol.rb +1070 -0
- data/lib/protocol/core.rb +22 -0
- data/make_doc.rb +9 -0
- data/tests/test_protocol.rb +620 -0
- metadata +74 -0
data/Rakefile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# vim: set et sw=2 ts=2:
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
require 'rbconfig'
|
8
|
+
|
9
|
+
include Config
|
10
|
+
|
11
|
+
PKG_NAME = 'protocol'
|
12
|
+
PKG_VERSION = File.read('VERSION').chomp
|
13
|
+
PKG_FILES = FileList['**/*'].exclude(/(CVS|\.svn|pkg|coverage)/)
|
14
|
+
|
15
|
+
desc "Installing library"
|
16
|
+
task :install do
|
17
|
+
ruby 'install.rb'
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Creating documentation"
|
21
|
+
task :doc do
|
22
|
+
ruby 'make_doc.rb'
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Testing library"
|
26
|
+
task :test do
|
27
|
+
ruby '-Ilib tests/test_protocol.rb'
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Testing library (coverage)"
|
31
|
+
task :coverage do
|
32
|
+
sh 'rcov -Ilib tests/test_protocol.rb'
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Clean created files"
|
36
|
+
task :clean do
|
37
|
+
rm_rf %w[doc coverage]
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Prepare a release"
|
41
|
+
task :release => [ :test, :clean, :package ]
|
42
|
+
|
43
|
+
if defined? Gem
|
44
|
+
spec = Gem::Specification.new do |s|
|
45
|
+
s.name = PKG_NAME
|
46
|
+
s.version = PKG_VERSION
|
47
|
+
s.files = PKG_FILES
|
48
|
+
s.summary = 'Method Protocols for Ruby Classes'
|
49
|
+
s.description = <<EOT
|
50
|
+
This library offers an implementation of protocols against which you can check
|
51
|
+
the conformity of your classes or instances of your classes. They are a bit
|
52
|
+
like Java Interfaces, but as mixin modules they can also contain already
|
53
|
+
implemented methods. Additionaly you can define preconditions/postconditions
|
54
|
+
for methods specified in a protocol.
|
55
|
+
EOT
|
56
|
+
|
57
|
+
s.require_path = 'lib'
|
58
|
+
s.add_dependency 'ParseTree', '>= 2.0.2'
|
59
|
+
|
60
|
+
s.has_rdoc = true
|
61
|
+
s.rdoc_options << '--title' << s.summary <<
|
62
|
+
'-S'
|
63
|
+
s.test_files << 'tests/test_protocol.rb'
|
64
|
+
|
65
|
+
s.author = "Florian Frank"
|
66
|
+
s.email = "flori@ping.de"
|
67
|
+
s.homepage = "http://protocol.rubyforge.org"
|
68
|
+
s.rubyforge_project = "protocol"
|
69
|
+
end
|
70
|
+
|
71
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
72
|
+
pkg.need_tar = true
|
73
|
+
pkg.package_files += PKG_FILES
|
74
|
+
end
|
75
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.8.0
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'protocol/core'
|
2
|
+
|
3
|
+
class Person
|
4
|
+
def initialize(name, size)
|
5
|
+
@name, @size = name, size
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :name, :size
|
9
|
+
|
10
|
+
def <=>(other)
|
11
|
+
size <=> other.size
|
12
|
+
end
|
13
|
+
|
14
|
+
conform_to Comparing
|
15
|
+
end
|
16
|
+
|
17
|
+
goliath = Person.new 'Goliath', 2_60
|
18
|
+
david = Person.new 'David', 1_40
|
19
|
+
symbol = Person.new 'The artist formerly known as Prince', 1_40
|
20
|
+
|
21
|
+
david < goliath # => true
|
22
|
+
david >= goliath # => false
|
23
|
+
david == symbol # => true
|
24
|
+
|
25
|
+
begin
|
26
|
+
class NoPerson
|
27
|
+
def initialize(name, size)
|
28
|
+
@name, @size = name, size
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :name, :size
|
32
|
+
|
33
|
+
conform_to Comparing
|
34
|
+
end
|
35
|
+
rescue Protocol::CheckFailed => e
|
36
|
+
e.to_s # => "Comparing#<=>(1): method '<=>' not implemented in NoPerson"
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'protocol/core'
|
2
|
+
|
3
|
+
begin
|
4
|
+
class FailAry
|
5
|
+
def initialize
|
6
|
+
@ary = [1, 2, 3]
|
7
|
+
end
|
8
|
+
|
9
|
+
def each()
|
10
|
+
end
|
11
|
+
|
12
|
+
conform_to Enumerating
|
13
|
+
end
|
14
|
+
rescue Protocol::CheckFailed => e
|
15
|
+
e.to_s # => "Enumerating#each(0&): expected a block argument for FailAry"
|
16
|
+
end
|
17
|
+
|
18
|
+
class Ary
|
19
|
+
def initialize
|
20
|
+
@ary = [1, 2, 3]
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
@ary.each(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
conform_to Enumerating
|
28
|
+
end
|
29
|
+
|
30
|
+
Ary.new.map { |x| x * x } # => [1, 4, 9]
|
31
|
+
Ary.conform_to?(Enumerating) # => true
|
32
|
+
Ary.new.conform_to?(Enumerating) # => true
|
33
|
+
|
34
|
+
Enumerating.check_failure :none
|
35
|
+
|
36
|
+
class FailAry2
|
37
|
+
def initialize
|
38
|
+
@ary = [1, 2, 3]
|
39
|
+
end
|
40
|
+
|
41
|
+
def each() end
|
42
|
+
|
43
|
+
conform_to Enumerating
|
44
|
+
end
|
45
|
+
|
46
|
+
FailAry2.conform_to?(Enumerating) # => false
|
47
|
+
FailAry2.new.conform_to?(Enumerating) # => false
|
data/examples/game.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'protocol'
|
2
|
+
|
3
|
+
# Small example of the template method pattern with Protocol. (I think, it was
|
4
|
+
# inspired by this wikipedia article:
|
5
|
+
# http://en.wikipedia.org/wiki/Template_method_pattern
|
6
|
+
Gaming = Protocol do
|
7
|
+
# defaults to specification mode
|
8
|
+
|
9
|
+
# Initialize the game before entering the game loop.
|
10
|
+
def initialize_game()
|
11
|
+
end
|
12
|
+
|
13
|
+
implementation # switch to implemetation mode
|
14
|
+
|
15
|
+
attr_accessor :player_count
|
16
|
+
|
17
|
+
attr_accessor :current_player
|
18
|
+
|
19
|
+
specification # switch back to specification mode
|
20
|
+
|
21
|
+
# Make the next move with player +player+.
|
22
|
+
def make_move(player)
|
23
|
+
player.is_a? Fixnum and player >= 0 or
|
24
|
+
raise TypeError, "player #{player.class} is not a Fixnum >= 0"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return true if the game is over.
|
28
|
+
def game_over?()
|
29
|
+
end
|
30
|
+
|
31
|
+
# Output the winner of the game after +game_over?+ returned true.
|
32
|
+
def output_winner()
|
33
|
+
end
|
34
|
+
|
35
|
+
implementation # switch to implemetation mode again
|
36
|
+
|
37
|
+
# Play the game with +player_count+ players.
|
38
|
+
def play(player_count)
|
39
|
+
self.player_count = player_count
|
40
|
+
self.current_player = 0
|
41
|
+
initialize_game
|
42
|
+
loop do
|
43
|
+
make_move current_player
|
44
|
+
game_over? and break
|
45
|
+
self.current_player = (current_player + 1) % player_count
|
46
|
+
end
|
47
|
+
output_winner
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class GuessGame
|
52
|
+
def initialize_game
|
53
|
+
@winner = nil
|
54
|
+
@move = 0
|
55
|
+
@secret_number = 1 + rand(10)
|
56
|
+
end
|
57
|
+
|
58
|
+
def make_move(player)
|
59
|
+
@move += 1
|
60
|
+
@guess = 1 + rand(10)
|
61
|
+
puts "#@move. Player ##{player}'s move: number = #{@guess}?"
|
62
|
+
end
|
63
|
+
|
64
|
+
def game_over?
|
65
|
+
if @guess == @secret_number
|
66
|
+
@winner = current_player
|
67
|
+
true
|
68
|
+
else
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def output_winner
|
74
|
+
puts "The winner is player ##@winner, the secret number was #@secret_number!"
|
75
|
+
end
|
76
|
+
|
77
|
+
conform_to Gaming
|
78
|
+
end
|
79
|
+
game = GuessGame.new
|
80
|
+
game.play 2
|
81
|
+
game.play 3
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This example is loosely based on a joke posted by MillionthMonkey on
|
3
|
+
# slashdot: http://ask.slashdot.org/comments.pl?sid=250311&cid=19862863
|
4
|
+
# Nonetheless, it demonstrates some features of the protocol library pretty
|
5
|
+
# nicely.
|
6
|
+
|
7
|
+
require 'protocol'
|
8
|
+
require 'singleton'
|
9
|
+
|
10
|
+
MessageStrategyProtocol = Protocol do
|
11
|
+
def send_message() end
|
12
|
+
end
|
13
|
+
|
14
|
+
MessageBodyProtocol = Protocol do
|
15
|
+
attr_reader :payload
|
16
|
+
|
17
|
+
def configure(obj)
|
18
|
+
precondition { obj.respond_to? :to_str }
|
19
|
+
end
|
20
|
+
|
21
|
+
def send(message_strategy)
|
22
|
+
MessageStrategyProtocol =~ message_strategy
|
23
|
+
precondition { payload.respond_to? :to_str }
|
24
|
+
postcondition { result == :done }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class MessageBody
|
29
|
+
attr_reader :payload
|
30
|
+
|
31
|
+
def configure(obj)
|
32
|
+
@payload = obj
|
33
|
+
end
|
34
|
+
|
35
|
+
def send(message_strategy)
|
36
|
+
message_strategy.send_message
|
37
|
+
:done
|
38
|
+
end
|
39
|
+
|
40
|
+
conform_to MessageBodyProtocol
|
41
|
+
end
|
42
|
+
|
43
|
+
StrategyFactoryProtocol = Protocol do
|
44
|
+
def create_strategy(message_body)
|
45
|
+
MessageBodyProtocol =~ message_body
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class DefaultFactory
|
50
|
+
include Singleton
|
51
|
+
|
52
|
+
def create_strategy(message_body)
|
53
|
+
Class.new do
|
54
|
+
define_method(:send_message) do ||
|
55
|
+
puts message_body.payload
|
56
|
+
end
|
57
|
+
|
58
|
+
conform_to MessageStrategyProtocol
|
59
|
+
end.new
|
60
|
+
end
|
61
|
+
|
62
|
+
conform_to StrategyFactoryProtocol
|
63
|
+
end
|
64
|
+
|
65
|
+
class HelloWorld
|
66
|
+
def self.main(*args)
|
67
|
+
message_body = MessageBody.new
|
68
|
+
message_body.configure "Hello World!"
|
69
|
+
factory = DefaultFactory.instance
|
70
|
+
strategy = factory.create_strategy message_body
|
71
|
+
message_body.send strategy
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if $0 == __FILE__
|
76
|
+
HelloWorld.main(*ARGV)
|
77
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'protocol'
|
2
|
+
|
3
|
+
Indexing = Protocol {
|
4
|
+
check_failure :error
|
5
|
+
|
6
|
+
understand :[]
|
7
|
+
|
8
|
+
understand :[]=
|
9
|
+
}
|
10
|
+
|
11
|
+
if $0 == __FILE__
|
12
|
+
class Array
|
13
|
+
conform_to Indexing
|
14
|
+
end
|
15
|
+
|
16
|
+
class Hash
|
17
|
+
conform_to Indexing
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
class Proc
|
22
|
+
conform_to Indexing
|
23
|
+
end
|
24
|
+
rescue Protocol::CheckFailed => e
|
25
|
+
e.to_s # => "Indexing#[]=(): method '[]=' not implemented in Proc"
|
26
|
+
end
|
27
|
+
end
|
data/examples/locking.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'protocol'
|
2
|
+
|
3
|
+
Locking = Protocol do
|
4
|
+
specification # not necessary, because Protocol defaults to specification
|
5
|
+
# mode already
|
6
|
+
|
7
|
+
def lock() end
|
8
|
+
|
9
|
+
def unlock() end
|
10
|
+
|
11
|
+
implementation
|
12
|
+
|
13
|
+
def synchronize
|
14
|
+
lock
|
15
|
+
begin
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
unlock
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if $0 == __FILE__
|
24
|
+
require 'thread'
|
25
|
+
require 'tempfile'
|
26
|
+
|
27
|
+
class FileMutex
|
28
|
+
def initialize
|
29
|
+
@tempfile = Tempfile.new 'file-mutex'
|
30
|
+
end
|
31
|
+
|
32
|
+
def path
|
33
|
+
@tempfile.path
|
34
|
+
end
|
35
|
+
|
36
|
+
def lock
|
37
|
+
puts "Locking '#{path}'."
|
38
|
+
@tempfile.flock File::LOCK_EX
|
39
|
+
end
|
40
|
+
|
41
|
+
def unlock
|
42
|
+
puts "Unlocking '#{path}'."
|
43
|
+
@tempfile.flock File::LOCK_UN
|
44
|
+
end
|
45
|
+
|
46
|
+
conform_to Locking
|
47
|
+
end
|
48
|
+
|
49
|
+
FileMutex.conform_to? Locking # => true
|
50
|
+
FileMutex.new.conform_to? Locking # => true
|
51
|
+
|
52
|
+
# Outputs something like:
|
53
|
+
# Locking '...'.
|
54
|
+
# Synchronized with '...'..
|
55
|
+
# Unlocking '...'.
|
56
|
+
mutex = FileMutex.new
|
57
|
+
mutex.synchronize do
|
58
|
+
puts "Synchronized with '#{mutex.path}'."
|
59
|
+
end
|
60
|
+
|
61
|
+
class MemoryMutex
|
62
|
+
def initialize
|
63
|
+
@mutex = Mutex.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def lock
|
67
|
+
@mutex.lock
|
68
|
+
end
|
69
|
+
|
70
|
+
def unlock
|
71
|
+
@mutex.unlock
|
72
|
+
end
|
73
|
+
|
74
|
+
conform_to Locking # actually Mutex itself would conform as well ;)
|
75
|
+
end
|
76
|
+
|
77
|
+
mutex = MemoryMutex.new
|
78
|
+
mutex.synchronize do
|
79
|
+
puts "Synchronized in memory."
|
80
|
+
end
|
81
|
+
|
82
|
+
MemoryMutex.conform_to? Locking # => true
|
83
|
+
MemoryMutex.new.conform_to? Locking # => true
|
84
|
+
|
85
|
+
class MyClass
|
86
|
+
def initialize
|
87
|
+
@mutex = FileMutex.new
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :mutex
|
91
|
+
|
92
|
+
def mutex=(mutex)
|
93
|
+
Locking.check mutex
|
94
|
+
@mutex = mutex
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
obj = MyClass.new
|
99
|
+
obj.mutex # => #<FileMutex:0xb788f9ac @tempfile=#<File:/tmp/file-mutex.26553.2>>
|
100
|
+
begin
|
101
|
+
obj.mutex = Object.new
|
102
|
+
rescue Protocol::CheckFailed => e
|
103
|
+
e # => #<Protocol::CheckFailed: #<Protocol::NotImplementedErrorCheckError: Locking#lock(0): method 'lock' not responding in #<Object:0xb788f59c>>|#<Protocol::NotImplementedErrorCheckError: Locking#unlock(0): method 'unlock' not responding in #<Object:0xb788f59c>>
|
104
|
+
end
|
105
|
+
obj.mutex = MemoryMutex.new # => #<MemoryMutex:0xb788f038 @mutex=#<Mutex:0xb788eea8>>
|
106
|
+
# This works as well:
|
107
|
+
obj.mutex = Mutex.new # => #<Mutex:0xb788ecc8>
|
108
|
+
Locking.check Mutex # => true
|
109
|
+
Mutex.conform_to? Locking # => true
|
110
|
+
end
|
111
|
+
# >> "Locking '/tmp/file-mutex.26553.1'.\nSynchronized with '/tmp/file-mutex.26553.1'.\nUnlocking '/tmp/file-mutex.26553.1'.\nSynchronized in memory.\n"
|