Class: Protocol::ProtocolModule

Inherits:
Module
  • Object
show all
Includes:
Enumerable
Defined in:
lib/protocol/protocol_module.rb

Overview

A ProtocolModule object

Instance Method Summary collapse

Constructor Details

#initialize(&block) ⇒ ProtocolModule

Creates an new ProtocolModule instance.



5
6
7
8
9
# File 'lib/protocol/protocol_module.rb', line 5

def initialize(&block)
  @descriptor     = Descriptor.new(self)
  @implementation = false
  block and module_eval(&block)
end

Instance Method Details

#[](name) ⇒ Object

Return the Message object named name or nil, if it doesn’t exist.



77
78
79
80
# File 'lib/protocol/protocol_module.rb', line 77

def [](name)
  name = name.to_s
  find { |m| m.name == name }
end

#check(object) ⇒ Object

Check the conformity of object recursively and return true iff it does.



147
148
149
150
151
152
153
# File 'lib/protocol/protocol_module.rb', line 147

def check(object)
  check!(object)
rescue CheckFailed
  false
else
  true
end

#check!(object) ⇒ Object Also known as: =~

Check the conformity of object recursively and raise an exception if it doesn’t.



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/protocol/protocol_module.rb', line 130

def check!(object)
  checked = {}
  errors = CheckFailed.new
  each do |message|
    begin
      message.check(object, checked)
    rescue CheckError => e
      errors << e
    end
  end
  errors.empty? or raise errors
  true
end

#check_failures(object) ⇒ Object

Return all messages for whick a check failed.



156
157
158
159
160
# File 'lib/protocol/protocol_module.rb', line 156

def check_failures(object)
  check!(object)
rescue CheckFailed => e
  e.errors.map { |e| e.protocol_message }
end

#descriptorsObject

Returns all the protocol descriptions to check against as an Array.



12
13
14
15
16
17
18
# File 'lib/protocol/protocol_module.rb', line 12

def descriptors
  descriptors = []
  protocols.each do |a|
    descriptors << a.instance_variable_get(:@descriptor)
  end
  descriptors
end

#each_message(&block) ⇒ Object Also known as: each

Iterate over all messages and yield to all of them.



88
89
90
91
# File 'lib/protocol/protocol_module.rb', line 88

def each_message(&block) # :yields: message
  messages.each(&block)
  self
end

#extend_object(object) ⇒ Object



178
179
180
181
182
# File 'lib/protocol/protocol_module.rb', line 178

def extend_object(object)
  result = super
  check! object
  result
end

#grep(pattern) ⇒ Object

Return all message whose names matches pattern.



83
84
85
# File 'lib/protocol/protocol_module.rb', line 83

def grep(pattern)
  select { |m| pattern === m.name }
end

#implementationObject

Switch to implementation mode. Defined methods are added to the ProtocolModule as instance methods.



232
233
234
# File 'lib/protocol/protocol_module.rb', line 232

def implementation
  @implementation = true
end

#implementation?Boolean

Return true, if the ProtocolModule is currently in implementation mode. Otherwise return false.

Returns:

  • (Boolean)


238
239
240
# File 'lib/protocol/protocol_module.rb', line 238

def implementation?
  @implementation
end

#included(modul) ⇒ Object

This callback is called, when a module, that was extended with Protocol, is included (via Modul#include/via Class#conform_to) into some other module/class. If modul is a Class, all protocol descriptions of the inheritance tree are collected and the given class is checked for conformance to the protocol. modul isn’t a Class and only a Module, it is extended with the Protocol module.



170
171
172
173
174
175
176
# File 'lib/protocol/protocol_module.rb', line 170

def included(modul)
  result = super
  if modul.is_a? Class
    check! modul
  end
  result
end

#infer(modul, methodname = modul.public_instance_methods(false), block_expected = nil) ⇒ Object

Infer a method signature from an instance method named methodname of modul. This means that this protocol should understand these instance methods with their arity and block expectation. Note that automatic detection of blocks does not work for Ruby methods defined in C. You can set the block_expected argument if you want to do this manually.



216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/protocol/protocol_module.rb', line 216

def infer(modul, methodname = modul.public_instance_methods(false), block_expected = nil)
  Module === modul or
    raise TypeError, "expected Module not #{modul.class} as modul argument"
  methodnames = methodname.respond_to?(:to_ary) ?
    methodname.to_ary :
    [ methodname ]
  methodnames.each do |methodname|
    m = parse_instance_method_signature(modul, methodname)
    block_expected and m.block_expected = block_expected
    @descriptor.add_message m
  end
  self
end

#inspectObject

Returns a short string representation of this protocol, that consists of the understood messages. This protocol

FooProtocol = Protocol do
  def bar(x, y, &b) end
  def baz(x, y, z) end
  def foo(*rest) end
end

returns this string:

#<FooProtocol: bar(2&), baz(3), foo(-1)>


124
125
126
# File 'lib/protocol/protocol_module.rb', line 124

def inspect
  "#<#{name}: #{messages.map { |m| m.shortcut } * ', '}>"
end

#messagesObject Also known as: to_a

Returns all messages this protocol (plus the included protocols) consists of in alphabetic order. This method caches the computed result array. You have to call #reset_messages, if you want to recompute the array in the next call to #messages.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/protocol/protocol_module.rb', line 45

def messages
  result = []
  seen = {}
  descriptors.each do |d|
    dm = d.messages
    dm.delete_if do |m|
      delete = seen[m.name]
      seen[m.name] = true
      delete
    end
    result.concat dm
  end
  result.sort!
end

#method_added(methodname) ⇒ Object

Capture all added methods and either leave the implementation in place or add them to the protocol description.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/protocol/protocol_module.rb', line 256

def method_added(methodname)
  methodname = methodname.to_s
  if specification? and methodname !~ /^__protocol_check_/
    protocol_check = instance_method(methodname)
    parser = MethodParser.new(self, methodname)
    if parser.complex?
      define_method("__protocol_check_#{methodname}", protocol_check)
      understand methodname, protocol_check.arity, parser.block_arg?
    else
      understand methodname, protocol_check.arity, parser.block_arg?
    end
    remove_method methodname
  else
    super
  end
end

#protocolsObject

Return self and all protocols included into self.



21
22
23
# File 'lib/protocol/protocol_module.rb', line 21

def protocols
  ancestors.select { |modul| modul.is_a? ProtocolModule }
end

#reset_messagesObject

Reset the cached message array. Call this if you want to change the protocol dynamically after it was already used (= the #messages method was called).



65
66
67
68
# File 'lib/protocol/protocol_module.rb', line 65

def reset_messages
  @messages = nil
  self
end

#specificationObject

Switch to specification mode. Defined methods are added to the protocol description in order to be checked against in later conformance tests.



244
245
246
# File 'lib/protocol/protocol_module.rb', line 244

def specification
  @implementation = false
end

#specification?Boolean

Return true, if the ProtocolModule is currently in specification mode. Otherwise return false.

Returns:

  • (Boolean)


250
251
252
# File 'lib/protocol/protocol_module.rb', line 250

def specification?
  !@implementation
end

#to_ruby(result = '') ⇒ Object

Concatenates the protocol as Ruby code to the result string and return it. At the moment this method only supports method signatures with generic argument names.



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/protocol/protocol_module.rb', line 28

def to_ruby(result = '')
  result << "#{name} = Protocol do"
  if messages.empty?
    result << "\n"
  else
    messages.each do |m|
      result << "\n"
      m.to_ruby(result)
    end
  end
  result << "end\n"
end

#to_sObject

Returns a string representation of this protocol, that consists of the understood messages. This protocol

FooProtocol = Protocol do
  def bar(x, y, &b) end
  def baz(x, y, z) end
  def foo(*rest) end
end

returns this string:

FooProtocol#bar(2&), FooProtocol#baz(3), FooProtocol#foo(-1)


108
109
110
# File 'lib/protocol/protocol_module.rb', line 108

def to_s
  messages * ', '
end

#understand(methodname, arity = nil, block_expected = false) ⇒ Object

This method defines one of the messages, the protocol in question consists of: The messages which the class, that conforms to this protocol, should understand and respond to. An example shows best which messagedescriptions_ are allowed:

MyProtocol = Protocol do
  understand :bar            # conforming class must respond to :bar
  understand :baz, 3         # c. c. must respond to :baz with 3 args.
  understand :foo, -1        # c. c. must respond to :foo, any number of args.
  understand :quux, 0, true  # c. c. must respond to :quux, no args + block.
  understand :quux1, 1, true # c. c. must respond to :quux, 1 arg + block.
end


196
197
198
199
200
# File 'lib/protocol/protocol_module.rb', line 196

def understand(methodname, arity = nil, block_expected = false)
  m = Message.new(self, methodname.to_s, arity, block_expected)
  @descriptor.add_message(m)
  self
end

#understand?(name, arity = nil) ⇒ Boolean

Returns true if it is required to understand the

Returns:

  • (Boolean)


71
72
73
74
# File 'lib/protocol/protocol_module.rb', line 71

def understand?(name, arity = nil)
  name = name.to_s
  !!find { |m| m.name == name && (!arity || m.arity == arity) }
end