protocol 0.9.0 → 1.0.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.
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