protocol 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cd98524da174341f1dc25b039f03b5ad05adfdc8
4
+ data.tar.gz: b94806b796960a8413af5b89dd0d6d150db9e174
5
+ SHA512:
6
+ metadata.gz: 61bc4d85d2fc9f9e08f66eaed176f890ba848cbdd5197708abc86d15992321dafe651d3c007e09e497b600dcfdf54f0585f44a33e22c71319c9aa5857a19592a
7
+ data.tar.gz: c78f6dde5b37c2fe123b19cc799c64492f50e9d889b370640bc60c1462c4fb5df49f815a9b015458b69c938d130870a3551fc28d42db7b6664be9c4de65f2781
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .*.sw[pon]
2
+ .AppleDouble
3
+ .rvmrc
4
+ Gemfile.lock
5
+ coverage
6
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - ruby-head
5
+ - rbx-19mode
6
+ - jruby-19mode
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: ruby-head
10
+ - rvm: jruby-19mode
11
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # vim: set filetype=ruby et sw=2 ts=2:
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'utils'
@@ -1,4 +1,4 @@
1
- == Protocol - Method Protocol Specifications in Ruby
1
+ == Protocol - Method Protocol Specifications in Ruby
2
2
 
3
3
  === Author
4
4
 
@@ -57,11 +57,11 @@ An example of a conforming class is the class +Ary+:
57
57
  def initialize
58
58
  @ary = [1, 2, 3]
59
59
  end
60
-
60
+
61
61
  def each(&block)
62
62
  @ary.each(&block)
63
63
  end
64
-
64
+
65
65
  conform_to Enumerating
66
66
  end
67
67
 
@@ -84,13 +84,13 @@ like this:
84
84
  Locking = Protocol do
85
85
  specification # not necessary, because Protocol defaults to specification
86
86
  # mode already
87
-
87
+
88
88
  def lock() end
89
-
89
+
90
90
  def unlock() end
91
-
91
+
92
92
  implementation
93
-
93
+
94
94
  def synchronize
95
95
  lock
96
96
  begin
@@ -134,7 +134,7 @@ implemtented methods, to make block based locking possbile:
134
134
  mutex.synchronize do
135
135
  puts "Synchronized with '#{file.path}'."
136
136
  end
137
-
137
+
138
138
  Now it's easy to swap the implementation to a memory based mutex
139
139
  implementation instead:
140
140
 
@@ -198,15 +198,15 @@ shows how:
198
198
  postcondition { top === x }
199
199
  postcondition { result === myself }
200
200
  end
201
-
201
+
202
202
  def top() end
203
-
203
+
204
204
  def size() end
205
-
205
+
206
206
  def empty?()
207
207
  postcondition { size === 0 ? result : !result }
208
208
  end
209
-
209
+
210
210
  def pop()
211
211
  s = size
212
212
  precondition { not empty? }
data/Rakefile CHANGED
@@ -1,101 +1,41 @@
1
- begin
2
- require 'rake/gempackagetask'
3
- rescue LoadError
4
- end
5
- require 'rake/clean'
6
- require 'rbconfig'
7
- include Config
8
-
9
- PKG_NAME = 'protocol'
10
- PKG_VERSION = File.read('VERSION').chomp
11
- PKG_FILES = FileList['**/*'].exclude(/(CVS|\.svn|pkg|coverage)/)
12
- CLEAN.include 'coverage', 'doc', Dir['benchmarks/data/*.*']
13
-
14
- desc "Installing library"
15
- task :install do
16
- ruby 'install.rb'
17
- end
18
-
19
- desc "Creating documentation"
20
- task :doc do
21
- ruby 'make_doc.rb'
22
- end
1
+ # vim: set filetype=ruby et sw=2 ts=2:
23
2
 
24
- desc "Testing library"
25
- task :test do
26
- ruby '-Ilib tests/test_protocol.rb'
27
- end
28
-
29
- desc "Testing library (coverage)"
30
- task :coverage do
31
- sh 'rcov -Ilib tests/test_protocol.rb'
32
- end
3
+ require 'gem_hadar'
33
4
 
34
- if defined? Gem
35
- spec_src =<<GEM
36
- # -*- encoding: utf-8 -*-
37
- Gem::Specification.new do |s|
38
- s.name = '#{PKG_NAME}'
39
- s.version = '#{PKG_VERSION}'
40
- s.files = #{PKG_FILES.to_a.sort.inspect}
41
- s.summary = 'Method Protocols for Ruby Classes'
42
- s.description = <<EOT
5
+ GemHadar do
6
+ name 'protocol'
7
+ author 'Florian Frank'
8
+ email 'flori@ping.de'
9
+ homepage "http://flori.github.com/#{name}"
10
+ summary 'Method Protocols for Ruby Classes'
11
+ description <<EOT
43
12
  This library offers an implementation of protocols against which you can check
44
13
  the conformity of your classes or instances of your classes. They are a bit
45
14
  like Java Interfaces, but as mixin modules they can also contain already
46
15
  implemented methods. Additionaly you can define preconditions/postconditions
47
16
  for methods specified in a protocol.
48
17
  EOT
49
-
50
- s.require_path = 'lib'
51
- s.add_dependency 'ParseTree', '~> 3.0'
52
- s.add_dependency 'ruby_parser', '~> 2.0'
53
-
54
- s.has_rdoc = true
55
- s.rdoc_options << '--main' << 'doc-main.txt'
56
- s.extra_rdoc_files << 'doc-main.txt'
57
- s.test_files << 'tests/test_protocol.rb'
58
-
59
- s.author = "Florian Frank"
60
- s.email = "flori@ping.de"
61
- s.homepage = "http://#{PKG_NAME}.rubyforge.org"
62
- s.rubyforge_project = "#{PKG_NAME}"
63
- end
64
- GEM
65
-
66
- desc 'Create a gemspec file'
67
- task :gemspec do
68
- File.open("#{PKG_NAME}.gemspec", 'w') do |f|
69
- f.puts spec_src
18
+ test_dir 'tests'
19
+ ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc', '.AppleDouble'
20
+ readme 'README.rdoc'
21
+ dependency 'ruby_parser', '~> 3.0'
22
+ development_dependency 'simplecov'
23
+
24
+ install_library do
25
+ file = 'lib/protocol.rb'
26
+ dest = CONFIG["sitelibdir"]
27
+ install(file, dest)
28
+
29
+ dest = File.join(CONFIG["sitelibdir"], 'protocol')
30
+ mkdir_p dest
31
+ for file in Dir['lib/protocol/*.rb']
32
+ install(file, dest)
70
33
  end
71
- end
72
-
73
- spec = eval(spec_src)
74
- Rake::GemPackageTask.new(spec) do |pkg|
75
- pkg.need_tar = true
76
- pkg.package_files += PKG_FILES
77
- end
78
- end
79
34
 
80
- desc m = "Writing version information for #{PKG_VERSION}"
81
- task :version do
82
- puts m
83
- File.open(File.join('lib', 'protocol', 'version.rb'), 'w') do |v|
84
- v.puts <<EOT
85
- module Protocol
86
- # Protocol version
87
- VERSION = '#{PKG_VERSION}'
88
- VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
89
- VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
90
- VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
91
- VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
92
- end
93
- EOT
35
+ dest = File.join(CONFIG["sitelibdir"], 'protocol', 'method_parser')
36
+ mkdir_p dest
37
+ for file in Dir['lib/protocol/method_parser/*.rb']
38
+ install(file, dest)
39
+ end
94
40
  end
95
41
  end
96
-
97
- desc "Default task"
98
- task :default => [ :version, :gemspec, :test ]
99
-
100
- desc "Prepare a release"
101
- task :release => [ :clean, :version, :gemspec, :package ]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.0
1
+ 1.0.0
File without changes
@@ -8,6 +8,9 @@ class MethodParserBenchmark < Bullshit::RepeatCase
8
8
 
9
9
  class Foo
10
10
  def foo(a, b = nil, *c)
11
+ if false then ; end
12
+ if false then ; end
13
+ if false then ; end
11
14
  yield
12
15
  end
13
16
  end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'protocol'
4
+
5
+ Assignable = Protocol do
6
+ def assign_to(assignee)
7
+ Assignee =~ assignee
8
+ end
9
+ end
10
+
11
+ Assignee = Protocol do
12
+ end
13
+
14
+ Assignments = Protocol do
15
+ implementation
16
+
17
+ def assignments
18
+ @assignable ||= []
19
+ end
20
+
21
+ def add(assignable)
22
+ Assignable =~ assignable
23
+ assignments << assignable
24
+ self
25
+ end
26
+
27
+ def assign(assignable, assignee)
28
+ Assignable =~ assignable
29
+ Assignee =~ assignee
30
+ assignable.assign_to(assignee)
31
+ add(assignable)
32
+ end
33
+ end
34
+
35
+ class Task
36
+ def assign_to(assignee)
37
+ @assignee = assignee
38
+ end
39
+
40
+ conform_to Assignable
41
+ end
42
+
43
+ class Project
44
+ conform_to Assignments
45
+ conform_to Assignee
46
+ end
47
+
48
+ class User
49
+ conform_to Assignments
50
+ conform_to Assignee
51
+ end
52
+
53
+ p Project.new.assign(Task.new, User.new)
54
+ p Project.new.assign(Task.new, Object.new)
55
+ begin
56
+ Project.new.assign(Object.new, Task.new)
57
+ rescue Protocol::CheckError => e
58
+ p e
59
+ end
data/examples/game.rb CHANGED
@@ -2,7 +2,7 @@ require 'protocol'
2
2
 
3
3
  # Small example of the template method pattern with Protocol. (I think, it was
4
4
  # inspired by this wikipedia article:
5
- # http://en.wikipedia.org/wiki/Template_method_pattern
5
+ # http://en.wikipedia.org/wiki/Template_method_pattern
6
6
  Gaming = Protocol do
7
7
  # defaults to specification mode
8
8
 
@@ -17,7 +17,7 @@ MessageBodyProtocol = Protocol do
17
17
  def configure(obj)
18
18
  precondition { obj.respond_to? :to_str }
19
19
  end
20
-
20
+
21
21
  def send(message_strategy)
22
22
  MessageStrategyProtocol =~ message_strategy
23
23
  precondition { payload.respond_to? :to_str }
data/examples/locking.rb CHANGED
@@ -5,7 +5,7 @@ Locking = Protocol do
5
5
  # mode already
6
6
 
7
7
  def lock() end
8
-
8
+
9
9
  def unlock() end
10
10
 
11
11
  implementation
@@ -0,0 +1,34 @@
1
+ module Protocol
2
+ # This class encapsulates the protocol description, to check the classes
3
+ # against, if the Class#conform_to method is called with the protocol constant
4
+ # as an argument.
5
+ class Descriptor
6
+ # Creates a new Protocol::Descriptor object.
7
+ def initialize(protocol)
8
+ @protocol = protocol
9
+ @messages = {}
10
+ end
11
+
12
+ # Addes a new Protocol::Message instance to this Protocol::Descriptor
13
+ # object.
14
+ def add_message(message)
15
+ @messages.key?(message.name) and raise SpecificationError,
16
+ "A message named #{message.name} was already defined in #@protocol"
17
+ @messages[message.name] = message
18
+ end
19
+
20
+ # Return all the messages stored in this Descriptor instance.
21
+ def messages
22
+ @messages.values
23
+ end
24
+
25
+ # Returns a string representation of this Protocol::Descriptor object.
26
+ def inspect
27
+ "#<#{self.class}(#@protocol)>"
28
+ end
29
+
30
+ def to_s
31
+ messages * ', '
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,95 @@
1
+ module Protocol
2
+ # The base class for protocol errors.
3
+ class ProtocolError < StandardError; end
4
+
5
+ # This exception is raise if an error was encountered while defining a
6
+ # protocol specification.
7
+ class SpecificationError < ProtocolError; end
8
+
9
+ # Marker module for all exceptions related to check errors.
10
+ module CheckError; end
11
+
12
+ # If a protocol check failes this exception is raised.
13
+ class BaseCheckError < ProtocolError
14
+ include CheckError
15
+
16
+ def initialize(protocol_message, message)
17
+ super(message)
18
+ @protocol_message = protocol_message
19
+ end
20
+
21
+ # Returns the Protocol::Message object, that caused this CheckError to be
22
+ # raised.
23
+ attr_reader :protocol_message
24
+
25
+ def to_s
26
+ "#{protocol_message}: #{super}"
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class.name}: #{to_s}>"
31
+ end
32
+ end
33
+
34
+ # This exception is raised if a method was not implented in a class, that was
35
+ # required to conform to the checked protocol.
36
+ class NotImplementedErrorCheckError < BaseCheckError; end
37
+
38
+ # This exception is raised if a method implented in a class didn't have the
39
+ # required arity, that was required to conform to the checked protocol.
40
+ class ArgumentErrorCheckError < BaseCheckError; end
41
+
42
+ # This exception is raised if a method implented in a class didn't have the
43
+ # expected block argument, that was required to conform to the checked
44
+ # protocol.
45
+ class BlockCheckError < BaseCheckError; end
46
+
47
+ # This exception is raised if a precondition check failed (the yielded
48
+ # block returned a non-true value) in a protocol description.
49
+ class PreconditionCheckError < BaseCheckError; end
50
+
51
+ # This exception is raised if a postcondition check failed (the yielded block
52
+ # returned a non-true value) in a protocol description.
53
+ class PostconditionCheckError < BaseCheckError; end
54
+
55
+ # This exception collects CheckError exceptions and mixes in Enumerable for
56
+ # further processing of them.
57
+ class CheckFailed < ProtocolError
58
+ include CheckError
59
+
60
+ def initialize(*errors)
61
+ @errors = errors
62
+ end
63
+
64
+ attr_reader :errors
65
+
66
+ # Return true, if this CheckFailed doesn't contain any errors (yet).
67
+ # Otherwise false is returned.
68
+ def empty?
69
+ errors.empty?
70
+ end
71
+
72
+ # Add +check_error+ to this CheckFailed instance.
73
+ def <<(check_error)
74
+ @errors << check_error
75
+ self
76
+ end
77
+
78
+ # Iterate over all errors of this CheckFailed instance and pass each one to
79
+ # +block+.
80
+ def each_error(&block)
81
+ errors.each(&block)
82
+ end
83
+
84
+ alias each each_error
85
+ include Enumerable
86
+
87
+ def to_s
88
+ errors * "|"
89
+ end
90
+
91
+ def inspect
92
+ "#<#{self.class.name}: #{errors.map { |e| e.inspect} * '|'}"
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,219 @@
1
+ module Protocol
2
+ # A Message consists of the name of the message (=method name), and the
3
+ # method argument's arity.
4
+ class Message
5
+ include Comparable
6
+
7
+ # Creates a Message instance named +name+, with the arity +arity+.
8
+ # If +arity+ is nil, the arity isn't checked during conformity tests.
9
+ def initialize(protocol, name, arity = nil, block_expected = false)
10
+ name = name.to_s
11
+ @protocol, @name, @arity, @block_expected =
12
+ protocol, name, arity, !!block_expected
13
+ end
14
+
15
+ # The protocol this message was defined in.
16
+ attr_reader :protocol
17
+
18
+ # Name of this message.
19
+ attr_reader :name
20
+
21
+ # Arity of this message = the number of arguments.
22
+ attr_accessor :arity
23
+
24
+ # Set to true if this message should expect a block.
25
+ def block_expected=(block_expected)
26
+ @block_expected = !!block_expected
27
+ end
28
+
29
+ # Returns true if this message is expected to include a block argument.
30
+ def block_expected?
31
+ @block_expected
32
+ end
33
+
34
+ # Message order is alphabetic name order.
35
+ def <=>(other)
36
+ name <=> other.name
37
+ end
38
+
39
+ # Returns true if this message equals the message +other+.
40
+ def ==(other)
41
+ name == other.name && arity == other.arity
42
+ end
43
+
44
+ # Returns the shortcut for this message of the form "methodname(arity)".
45
+ def shortcut
46
+ "#{name}(#{arity}#{block_expected? ? '&' : ''})"
47
+ end
48
+
49
+ # Return a string representation of this message, in the form
50
+ # Protocol#name(arity).
51
+ def to_s
52
+ "#{protocol.name}##{shortcut}"
53
+ end
54
+
55
+ # Concatenates a method signature as ruby code to the +result+ string and
56
+ # returns it.
57
+ def to_ruby(result = '')
58
+ if arity
59
+ result << " def #{name}("
60
+ args = if arity >= 0
61
+ (1..arity).map { |i| "x#{i}" }
62
+ else
63
+ (1..~arity).map { |i| "x#{i}" } << '*rest'
64
+ end
65
+ if block_expected?
66
+ args << '&block'
67
+ end
68
+ result << args * ', '
69
+ result << ") end\n"
70
+ else
71
+ result << " understand :#{name}\n"
72
+ end
73
+ end
74
+
75
+ # The class +klass+ is checked against this Message instance. A CheckError
76
+ # exception will called, if either a required method isn't found in the
77
+ # +klass+, or it doesn't have the required arity (if a fixed arity was
78
+ # demanded).
79
+ def check(object, checked)
80
+ check_message = object.is_a?(Class) ? :check_class : :check_object
81
+ if checked.key?(name)
82
+ true
83
+ else
84
+ checked[name] = __send__(check_message, object)
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Check class +klass+ against this Message instance, and raise a CheckError
91
+ # exception if necessary.
92
+ def check_class(klass)
93
+ unless klass.method_defined?(name)
94
+ raise NotImplementedErrorCheckError.new(self,
95
+ "method '#{name}' not implemented in #{klass}")
96
+ end
97
+ check_method = klass.instance_method(name)
98
+ if arity and (check_arity = check_method.arity) != arity
99
+ raise ArgumentErrorCheckError.new(self,
100
+ "wrong number of arguments for protocol"\
101
+ " in method '#{name}' (#{check_arity} for #{arity}) of #{klass}")
102
+ end
103
+ if block_expected?
104
+ modul = Utilities.find_method_module(name, klass.ancestors)
105
+ parser = MethodParser.new(modul, name)
106
+ parser.block_arg? or raise BlockCheckError.new(self,
107
+ "expected a block argument for #{klass}")
108
+ end
109
+ arity and wrap_method(klass)
110
+ true
111
+ end
112
+
113
+ # :stopdoc:
114
+ MyArray = Array.dup # Hack to make checking against Array possible.
115
+ # :startdoc:
116
+
117
+ def wrap_method(klass)
118
+ check_name = "__protocol_check_#{name}"
119
+ if klass.method_defined?(check_name)
120
+ inner_name = "__protocol_inner_#{name}"
121
+ unless klass.method_defined?(inner_name)
122
+ args =
123
+ if arity >= 0
124
+ (1..arity).map { |i| "x#{i}," }
125
+ else
126
+ (1..~arity).map { |i| "x#{i}," } << '*rest,'
127
+ end.join
128
+ wrapped_call = %{
129
+ alias_method :'#{inner_name}', :'#{name}'
130
+
131
+ def precondition
132
+ yield or
133
+ raise Protocol::PreconditionCheckError.new(
134
+ ObjectSpace._id2ref(#{__id__}),
135
+ "precondition failed for \#{self.class}")
136
+ end unless method_defined?(:precondition)
137
+
138
+ def postcondition(&block)
139
+ post_name = "__protocol_#{klass.__id__.abs}_postcondition__"
140
+ (Thread.current[post_name][-1] ||= Protocol::Postcondition.new(
141
+ self)).__add__ block
142
+ end unless method_defined?(:postcondition)
143
+
144
+ def #{name}(#{args} &block)
145
+ result = nil
146
+ post_name = "__protocol_#{klass.__id__.abs}_postcondition__"
147
+ (Thread.current[post_name] ||= MyArray.new) << nil
148
+ __send__('#{check_name}', #{args} &block)
149
+ if postcondition = Thread.current[post_name].last
150
+ begin
151
+ reraised = false
152
+ result = __send__('#{inner_name}', #{args} &block)
153
+ postcondition.__result__= result
154
+ rescue Protocol::PostconditionCheckError => e
155
+ reraised = true
156
+ raise e
157
+ ensure
158
+ unless reraised
159
+ postcondition.__check__ or
160
+ raise Protocol::PostconditionCheckError.new(
161
+ ObjectSpace._id2ref(#{__id__}),
162
+ "postcondition failed for \#{self.class}, result = " +
163
+ result.inspect)
164
+ end
165
+ end
166
+ else
167
+ result = __send__('#{inner_name}', #{args} &block)
168
+ end
169
+ result
170
+ rescue Protocol::CheckError => e
171
+ case ObjectSpace._id2ref(#{__id__}).protocol.mode
172
+ when :error
173
+ raise e
174
+ when :warning
175
+ warn e
176
+ end
177
+ ensure
178
+ Thread.current[post_name].pop
179
+ Thread.current[post_name].empty? and
180
+ Thread.current[post_name] = nil
181
+ end
182
+ }
183
+ klass.class_eval wrapped_call
184
+ end
185
+ end
186
+ end
187
+
188
+ # Check object +object+ against this Message instance, and raise a
189
+ # CheckError exception if necessary.
190
+ def check_object(object)
191
+ if !object.respond_to?(name)
192
+ raise NotImplementedErrorCheckError.new(self,
193
+ "method '#{name}' not responding in #{object}")
194
+ end
195
+ check_method = object.method(name)
196
+ if arity and (check_arity = check_method.arity) != arity
197
+ raise ArgumentErrorCheckError.new(self,
198
+ "wrong number of arguments for protocol"\
199
+ " in method '#{name}' (#{check_arity} for #{arity}) of #{object}")
200
+ end
201
+ if block_expected?
202
+ if object.singleton_methods(false).map { |m| m.to_s } .include?(name)
203
+ parser = MethodParser.new(object, name, true)
204
+ else
205
+ ancestors = object.class.ancestors
206
+ modul = Utilities.find_method_module(name, ancestors)
207
+ parser = MethodParser.new(modul, name)
208
+ end
209
+ parser.block_arg? or raise BlockCheckError.new(self,
210
+ "expected a block argument for #{object}:#{object.class}")
211
+ end
212
+ if arity and not protocol === object
213
+ object.extend protocol
214
+ wrap_method(class << object ; self ; end)
215
+ end
216
+ true
217
+ end
218
+ end
219
+ end