Class: Protocol::ProtocolModule

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

Overview

A ProtocolModule object

Instance Attribute Summary collapse

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)
  @mode = :error
  module_eval(&block)
end

Instance Attribute Details

#modeObject (readonly)

The current check mode :none, :warning, or :error (the default).



12
13
14
# File 'lib/protocol/protocol_module.rb', line 12

def mode
  @mode
end

Instance Method Details

#[](name) ⇒ Object

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



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

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

#check(object, mode = @mode) ⇒ Object Also known as: =~

Check the conformity of object recursively. This method returns either false OR true, if mode is :none or :warning, or raises an CheckFailed, if mode was :error.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/protocol/protocol_module.rb', line 135

def check(object, mode = @mode)
  checked = {}
  result = true
  errors = CheckFailed.new
  each do |message|
    begin
      message.check(object, checked)
    rescue CheckError => e
      case mode
      when :error
        errors << e
      when :warning
        warn e.to_s
        result = false
      when :none
        result = false
      end
    end
  end
  raise errors unless errors.empty?
  result
end

#check_failure(mode) ⇒ Object

Sets the check mode to id. id should be one of :none, :warning, or :error. The mode to use while doing a conformity check is always the root module, that is, the modes of the included modules aren’t important for the check.



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

def check_failure(mode)
  CHECK_MODES.include?(mode) or
    raise ArgumentError, "illegal check mode #{mode}"
  @mode = mode
end

#check_failures(object) ⇒ Object

Return all messages for whick a check failed.



161
162
163
164
165
# File 'lib/protocol/protocol_module.rb', line 161

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

#descriptorsObject

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



15
16
17
18
19
20
21
# File 'lib/protocol/protocol_module.rb', line 15

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.



92
93
94
95
# File 'lib/protocol/protocol_module.rb', line 92

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

#extend_object(object) ⇒ Object



183
184
185
186
187
188
189
190
# File 'lib/protocol/protocol_module.rb', line 183

def extend_object(object)
  result = super
  if @mode == :error or @mode == :warning
    $DEBUG and warn "#{name} is checking class #{object}"
    check object
  end
  result
end

#grep(pattern) ⇒ Object

Return all message whose names matches pattern.



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

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

#implementationObject

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



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

def implementation
  @implementation = true
end

#implementation?Boolean

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

Returns:

  • (Boolean)


256
257
258
# File 'lib/protocol/protocol_module.rb', line 256

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.



175
176
177
178
179
180
181
# File 'lib/protocol/protocol_module.rb', line 175

def included(modul)
  super
  if modul.is_a? Class and @mode == :error or @mode == :warning
    $DEBUG and warn "#{name} is checking class #{modul}"
    check modul
  end
end

#inherit(modul, methodname, block_expected = nil) ⇒ Object

Inherit 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.



234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/protocol/protocol_module.rb', line 234

def inherit(modul, methodname, 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)>


128
129
130
# File 'lib/protocol/protocol_module.rb', line 128

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.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/protocol/protocol_module.rb', line 49

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.



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/protocol/protocol_module.rb', line 274

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.



24
25
26
# File 'lib/protocol/protocol_module.rb', line 24

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).



69
70
71
72
# File 'lib/protocol/protocol_module.rb', line 69

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.



262
263
264
# File 'lib/protocol/protocol_module.rb', line 262

def specification
  @implementation = false
end

#specification?Boolean

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

Returns:

  • (Boolean)


268
269
270
# File 'lib/protocol/protocol_module.rb', line 268

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.



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/protocol/protocol_module.rb', line 31

def to_ruby(result = '')
  result << "#{name} = Protocol do"
  first = true
  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)


112
113
114
# File 'lib/protocol/protocol_module.rb', line 112

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


214
215
216
217
218
# File 'lib/protocol/protocol_module.rb', line 214

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)


75
76
77
78
# File 'lib/protocol/protocol_module.rb', line 75

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