ruby-contract 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +56 -0
- data/Manifest +85 -0
- data/README +32 -0
- data/TODO +83 -0
- data/doc/classes/Contract.html +599 -0
- data/doc/classes/Contract/Check.html +229 -0
- data/doc/classes/Contract/Check/All.html +172 -0
- data/doc/classes/Contract/Check/Any.html +172 -0
- data/doc/classes/Contract/Check/Block.html +172 -0
- data/doc/classes/Contract/Check/None.html +173 -0
- data/doc/classes/Contract/Check/Quack.html +172 -0
- data/doc/classes/Contract/ContractError.html +151 -0
- data/doc/classes/Contract/ContractException.html +162 -0
- data/doc/classes/Contract/ContractMismatch.html +134 -0
- data/doc/classes/Kernel.html +256 -0
- data/doc/classes/Method.html +135 -0
- data/doc/classes/MethodSignatureMixin.html +208 -0
- data/doc/classes/Module.html +526 -0
- data/doc/created.rid +1 -0
- data/doc/dot/f_0.dot +14 -0
- data/doc/dot/f_0.png +0 -0
- data/doc/dot/f_1.dot +14 -0
- data/doc/dot/f_1.png +0 -0
- data/doc/dot/f_2.dot +14 -0
- data/doc/dot/f_2.png +0 -0
- data/doc/dot/f_3.dot +112 -0
- data/doc/dot/f_3.png +0 -0
- data/doc/dot/f_4.dot +62 -0
- data/doc/dot/f_4.png +0 -0
- data/doc/dot/f_5.dot +62 -0
- data/doc/dot/f_5.png +0 -0
- data/doc/dot/f_6.dot +224 -0
- data/doc/dot/f_6.png +0 -0
- data/doc/dot/f_6_0.dot +24 -0
- data/doc/dot/f_6_0.png +0 -0
- data/doc/dot/f_6_1.dot +24 -0
- data/doc/dot/f_6_1.png +0 -0
- data/doc/dot/f_7.dot +62 -0
- data/doc/dot/f_7.png +0 -0
- data/doc/files/COPYING.html +168 -0
- data/doc/files/README.html +146 -0
- data/doc/files/TODO.html +240 -0
- data/doc/files/lib/contract/assertions_rb.html +118 -0
- data/doc/files/lib/contract/exception_rb.html +125 -0
- data/doc/files/lib/contract/integration_rb.html +130 -0
- data/doc/files/lib/contract/overrides_rb.html +118 -0
- data/doc/files/lib/contract_rb.html +127 -0
- data/doc/fr_class_index.html +40 -0
- data/doc/fr_file_index.html +34 -0
- data/doc/fr_method_index.html +45 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/contract.rb +146 -0
- data/lib/contract/assertions.rb +42 -0
- data/lib/contract/exception.rb +95 -0
- data/lib/contract/integration.rb +664 -0
- data/lib/contract/overrides.rb +41 -0
- data/setup.rb +1360 -0
- data/test/coverage/_-lib-contract-assertions_rb.html +526 -0
- data/test/coverage/_-lib-contract-exception_rb.html +632 -0
- data/test/coverage/_-lib-contract-integration_rb.html +1450 -0
- data/test/coverage/_-lib-contract-overrides_rb.html +524 -0
- data/test/coverage/_-lib-contract_rb.html +724 -0
- data/test/coverage/__-lib-contract-assertions_rb.html +484 -0
- data/test/coverage/__-lib-contract-exception_rb.html +537 -0
- data/test/coverage/__-lib-contract-integration_rb.html +946 -0
- data/test/coverage/__-lib-contract-overrides_rb.html +483 -0
- data/test/coverage/__-lib-contract_rb.html +583 -0
- data/test/coverage/index.html +93 -0
- data/test/tc_all.rb +8 -0
- data/test/tc_contract.rb +109 -0
- data/test/tc_exception.rb +43 -0
- data/test/tc_integration.rb +357 -0
- metadata +136 -0
data/lib/contract.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# Contains the main implementation of the Contract class.
|
2
|
+
# :main:Contract
|
3
|
+
|
4
|
+
require 'test/unit/testcase'
|
5
|
+
require 'test/unit/testresult'
|
6
|
+
require 'test/unit/testsuite'
|
7
|
+
|
8
|
+
require 'contract/exception'
|
9
|
+
require 'contract/overrides'
|
10
|
+
require 'contract/assertions'
|
11
|
+
require 'contract/integration'
|
12
|
+
|
13
|
+
# Represents a contract between Objects as a collection of test cases.
|
14
|
+
# Objects are said to fulfill a contract if all test cases suceed. This
|
15
|
+
# is useful for ensuring that Objects your code is getting behave in a
|
16
|
+
# way that you expect them to behave so you can fail early or execute
|
17
|
+
# different logic for Objects with different interfaces.
|
18
|
+
#
|
19
|
+
# The tests of the test suite will be run on a copy of the tested Object
|
20
|
+
# so you can safely test its behavior without having to fear data loss.
|
21
|
+
# By default Contracts obtain deep copies of Objects by serializing and
|
22
|
+
# unserializing them with Ruby's +Marshal+ functionality. This will work
|
23
|
+
# in most cases but can fail for Objects containing unserializable parts
|
24
|
+
# like Procs, Files or Sockets. In those cases it is currently of your
|
25
|
+
# responsibility to provide a fitting implementation by overwriting the
|
26
|
+
# Contract.deep_copy method. In the future the contract library might
|
27
|
+
# provide different implementations of it via Ruby's mixin mechanism.
|
28
|
+
class Contract < Test::Unit::TestCase
|
29
|
+
id = %q$Id: contract.rb 120 2005-02-11 20:39:14Z flgr $
|
30
|
+
current_version = id.split(" ")[2]
|
31
|
+
unless defined?(Version)
|
32
|
+
# The Version of the contract library you are using as String of the
|
33
|
+
# 1.2.3 form where the digits stand for release, major and minor
|
34
|
+
# version respectively.
|
35
|
+
Version = "0.1.1"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if the given object fulfills this contract.
|
39
|
+
# This is useful for implementing dispatching mechanisms where you
|
40
|
+
# want to hit different code branches based on whether an Object has
|
41
|
+
# one or another interface.
|
42
|
+
def self.fulfilled_by?(object)
|
43
|
+
self.test(object).nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
# You can use contracts in +case+ ... +when+ statements or in
|
48
|
+
# Module#signature checks. For example:
|
49
|
+
# case obj
|
50
|
+
# when Numeric then obj + 1
|
51
|
+
# when ListContract then obj + [1]
|
52
|
+
# end
|
53
|
+
alias :=== :fulfilled_by?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Enforces that object implements this contract. If it does not an
|
57
|
+
# Exception will be raised. This is useful for example useful when you
|
58
|
+
# need to ensure that the arguments given to a method fulfill a given
|
59
|
+
# contract.
|
60
|
+
#
|
61
|
+
# Note that using Module#enforce is a higher-level way of checking
|
62
|
+
# arguments and return values for the conformance of a given type. You
|
63
|
+
# might however still want to use Contract.enforce directly when you
|
64
|
+
# need more flexibility.
|
65
|
+
def self.enforce(object)
|
66
|
+
reason = self.test(object)
|
67
|
+
raise reason if reason
|
68
|
+
end
|
69
|
+
|
70
|
+
# Tests whether the given Object fulfils this contract.
|
71
|
+
#
|
72
|
+
# Note: This will return the first reason for the Object not fulfilling
|
73
|
+
# the contract or +nil+ in case it fulfills it.
|
74
|
+
def self.test(object, return_all = false)
|
75
|
+
reasons = []
|
76
|
+
|
77
|
+
result = Test::Unit::TestResult.new
|
78
|
+
result.add_listener(Test::Unit::TestResult::FAULT) do |fault|
|
79
|
+
reason = Contract.fault_to_exception(fault, object, self)
|
80
|
+
return reason unless return_all
|
81
|
+
reasons << reason
|
82
|
+
end
|
83
|
+
|
84
|
+
self.suite.run(result, deep_copy(object))
|
85
|
+
|
86
|
+
return reasons unless result.passed?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Same as Contract.test, but will return all reasons for the Object not
|
90
|
+
# fulfilling the contract in an Array or nil in case of fulfillment.
|
91
|
+
# (as an Array of Exceptions) or +nil+ in the case it does fulfill it.
|
92
|
+
def self.test_all(object)
|
93
|
+
test(object, true)
|
94
|
+
end
|
95
|
+
|
96
|
+
# This method is used internally for getting a copy of Objects that
|
97
|
+
# the contract is checked against. By default it uses Ruby's +Marshal+
|
98
|
+
# functionality for obtaining a copy, but this can fail if the Object
|
99
|
+
# contains unserializable parts like Procs, Files or Sockets. It is
|
100
|
+
# currently your responsibility to provide a fitting implementation
|
101
|
+
# of this by overwriting the method in case the default implementation
|
102
|
+
# does not work for you. In the future the contract library might offer
|
103
|
+
# different implementations for this via Ruby's mixin mechanism.
|
104
|
+
def self.deep_copy(object)
|
105
|
+
Marshal.load(Marshal.dump(object))
|
106
|
+
end
|
107
|
+
|
108
|
+
# Fulfilling this Contract (via Module#fulfills) implies that the Object
|
109
|
+
# is automatically compatible with the specified mixins which will then
|
110
|
+
# be included automatically. For example the Enumerable relationship
|
111
|
+
# could be expressed like this:
|
112
|
+
#
|
113
|
+
# class EnumerableContract < Contract
|
114
|
+
# provides :each
|
115
|
+
# implies Enumerable
|
116
|
+
# end
|
117
|
+
def self.implies(*mixins)
|
118
|
+
mixins.each do |mixin|
|
119
|
+
if not mixin.is_a?(Module) then
|
120
|
+
raise(TypeError, "wrong argument type #{mixin.class} for " +
|
121
|
+
"#{mixin.inspect} (expected Module)")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
@implications ||= Array.new
|
126
|
+
@implications += mixins
|
127
|
+
end
|
128
|
+
|
129
|
+
class << self
|
130
|
+
# Returns all implications of a given contract that were stated via
|
131
|
+
# calling Contract.implies.
|
132
|
+
attr_reader :implications
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.implications() # :nodoc:
|
136
|
+
@implications ||= Array.new
|
137
|
+
|
138
|
+
ancestors[1 .. -1].inject(@implications) do |result, ancestor|
|
139
|
+
if ancestor.respond_to?(:implications) then
|
140
|
+
ancestor.implications + result
|
141
|
+
else
|
142
|
+
result
|
143
|
+
end
|
144
|
+
end.uniq
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# This file provides a few new assertions and class methods for expressing
|
2
|
+
# contracts in a comfortable way that does not require manually writing test
|
3
|
+
# methods.
|
4
|
+
#
|
5
|
+
# See Contract.provides.
|
6
|
+
|
7
|
+
|
8
|
+
class Contract < Test::Unit::TestCase
|
9
|
+
# Tests that the tested Object provides the specified methods with the
|
10
|
+
# specified behavior.
|
11
|
+
#
|
12
|
+
# If a block is supplied it will be evaluated in the context of the
|
13
|
+
# contract so <code>@object</code> will refer to the object being tested.
|
14
|
+
#
|
15
|
+
# This can be used like this:
|
16
|
+
# class ListContract < Contract
|
17
|
+
# provides :size do
|
18
|
+
# assert(@object.size >= 0, "#size should never be negative.")
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# provides :include?
|
22
|
+
#
|
23
|
+
# provides :each do
|
24
|
+
# count = 0
|
25
|
+
# @object.each do |item|
|
26
|
+
# assert(@object.include?(item),
|
27
|
+
# "#each should only yield items that the list includes.")
|
28
|
+
# count += 1
|
29
|
+
# end
|
30
|
+
# assert_equal(@object.size, count,
|
31
|
+
# "#each should yield #size items.")
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
def self.provides(*symbols, &block) # :yields:
|
35
|
+
symbols.each do |symbol|
|
36
|
+
define_method("test_provides_#{symbol}".intern) do
|
37
|
+
assert_respond_to(@object, symbol)
|
38
|
+
instance_eval(&block) if block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# This file contains things needed to map Test::Unit status objects to
|
2
|
+
# Exceptions, Exception related infrastructure and similar things.
|
3
|
+
#
|
4
|
+
# See Contract::ContractException, Contract::ContractMismatch and
|
5
|
+
# Contract::ContractError.
|
6
|
+
|
7
|
+
|
8
|
+
class Contract < Test::Unit::TestCase
|
9
|
+
# Exceptions raised by Contract contain some useful meta-information.
|
10
|
+
# This module is mixed into Exceptions that provide such information.
|
11
|
+
module ContractException
|
12
|
+
def ce_initialize(original_message, backtrace, test_object,
|
13
|
+
test_method, test_contract, *more) # :nodoc:
|
14
|
+
@original_message = original_message
|
15
|
+
message = original_message.dup
|
16
|
+
detail = " for #{test_object.inspect} in " +
|
17
|
+
"#{test_contract.inspect}"
|
18
|
+
message[/(\s*)[.!?]?\s*\Z/, 1] = detail
|
19
|
+
Exception.instance_method(:initialize).bind(self).call(message)
|
20
|
+
|
21
|
+
set_backtrace(backtrace)
|
22
|
+
@test_object = test_object
|
23
|
+
@test_method = test_method
|
24
|
+
@test_contract = test_contract
|
25
|
+
end
|
26
|
+
private :ce_initialize
|
27
|
+
|
28
|
+
# What object was tested when this Exception was raised?
|
29
|
+
attr_reader :test_object
|
30
|
+
# What method implemented that test?
|
31
|
+
attr_reader :test_method
|
32
|
+
# What contract was that method part of?
|
33
|
+
attr_reader :test_contract
|
34
|
+
# The original, unfiltered exception message.
|
35
|
+
attr_reader :original_message
|
36
|
+
end
|
37
|
+
|
38
|
+
# Represents a failed test in a contract. This means that an object
|
39
|
+
# simply does not fulfill one of the parts of a contract. Subclass of
|
40
|
+
# TypeError.
|
41
|
+
class ContractMismatch < TypeError
|
42
|
+
def initialize(*args) # :nodoc:
|
43
|
+
ce_initialize(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
include ContractException
|
47
|
+
end
|
48
|
+
|
49
|
+
# Represents an unexpected failure while processing a contract test.
|
50
|
+
# This is more critical than ContractMismatch and usually means that
|
51
|
+
# something is wrong with the test itself.
|
52
|
+
class ContractError < StandardError
|
53
|
+
def initialize(*args) # :nodoc:
|
54
|
+
@type = args.pop
|
55
|
+
ce_initialize(*args)
|
56
|
+
end
|
57
|
+
|
58
|
+
# The type of the original Exception that triggered the unexpected
|
59
|
+
# failure.
|
60
|
+
attr_reader :type
|
61
|
+
|
62
|
+
include ContractException
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Maps a Test::Unit::Failure instance to an actual Exception with the
|
67
|
+
# specified meta data.
|
68
|
+
def self.failure_to_exception(failure, object, contract) # :nodoc:
|
69
|
+
ContractMismatch.new(failure.message, failure.location, object,
|
70
|
+
extract_method_name(failure.test_name), contract)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maps a Test::Unit::Error instance to an actual Exception with the
|
74
|
+
# specified meta data.
|
75
|
+
def self.error_to_exception(error, object, contract) # :nodoc:
|
76
|
+
original = error.exception
|
77
|
+
ContractError.new(original.message, original.backtrace, object,
|
78
|
+
extract_method_name(error.test_name), contract, original.class)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Maps a Test::Unit fault (either a Failure or Error) to an actual
|
82
|
+
# exception with the specified meta data.
|
83
|
+
def self.fault_to_exception(fault, *args) # :nodoc:
|
84
|
+
if fault.is_a?(Test::Unit::Failure) then
|
85
|
+
failure_to_exception(fault, *args)
|
86
|
+
else
|
87
|
+
error_to_exception(fault, *args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Extracts the method name from a Test::Unit test_name style String.
|
92
|
+
def self.extract_method_name(test_name) # :nodoc:
|
93
|
+
test_name[/\A(.+?)\(.+?\)\Z/, 1]
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,664 @@
|
|
1
|
+
# This file contains code for integrating the contract library with built-in
|
2
|
+
# classes and methods.
|
3
|
+
#
|
4
|
+
# See Contract::Check, Module#signature and Module#fulfills.
|
5
|
+
|
6
|
+
|
7
|
+
class Contract < Test::Unit::TestCase
|
8
|
+
# Implements checks that can for example be used in Module#signature
|
9
|
+
# specifications. They are implemented simply by overriding the === case
|
10
|
+
# equality operator. They can also be nested like this:
|
11
|
+
# # Matches something that is an Enumerable and that responds to
|
12
|
+
# # either :to_ary or :to_a.
|
13
|
+
# signature :x, Contract::Check::All[
|
14
|
+
# Enumerable,
|
15
|
+
# Contract::Check::Any[
|
16
|
+
# Contract::Check::Quack[:to_a],
|
17
|
+
# Contract::Check::Quack[:to_ary]
|
18
|
+
# ]
|
19
|
+
# ]
|
20
|
+
module Check
|
21
|
+
# An abstract Base class for Contract::Check classes.
|
22
|
+
# Contains logic for instantation.
|
23
|
+
class Base # :nodoc:
|
24
|
+
class << self
|
25
|
+
alias :[] :new
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(*args, &block)
|
29
|
+
@args, @block = args, block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Checks that the specified block matches.
|
34
|
+
# Example:
|
35
|
+
# signature :x, Contract::Check.block { |arg| arg > 0 }
|
36
|
+
class Block < Base
|
37
|
+
def ===(other)
|
38
|
+
@block.call(other)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Short-cut for creating a Contract::Check::Block.
|
42
|
+
def self.block(&block) # :yields: arg
|
43
|
+
Block.new(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Checks that all the specified methods are answered.
|
47
|
+
# Example:
|
48
|
+
# signature :x, Contract::Check::Quack[:to_sym]
|
49
|
+
class Quack < Base
|
50
|
+
def ===(other)
|
51
|
+
@args.all? { |arg| other.respond_to?(arg) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks that all the specified conditions match.
|
56
|
+
# Example:
|
57
|
+
# signature :x, Contract::Check::All[Array, Enumerable]
|
58
|
+
class All < Base
|
59
|
+
def ===(other)
|
60
|
+
@args.all? { |arg| arg === other }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
# Alias for Contract::Check::All
|
64
|
+
And = All unless defined?(And)
|
65
|
+
|
66
|
+
# Checks that at least one of the specified conditions match.
|
67
|
+
# Example:
|
68
|
+
# signature :x, Contract::Check::Any[String, Symbol]
|
69
|
+
class Any < Base
|
70
|
+
def ===(other)
|
71
|
+
@args.any? { |arg| arg === other }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# Alias for Contract::Check::Any
|
75
|
+
Or = Any unless defined?(Or)
|
76
|
+
|
77
|
+
# Checks that none of the specified conditions match.
|
78
|
+
# Example:
|
79
|
+
# signature :x, Contract::Check::None[Numeric, Symbol]
|
80
|
+
# signature :x, Contract::Check::Not[Comparable]
|
81
|
+
class None < Base
|
82
|
+
def ===(other)
|
83
|
+
not @args.any? { |arg| arg === other }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
# Alias for Contract::Check::None
|
87
|
+
Not = None unless defined?(Not)
|
88
|
+
end
|
89
|
+
|
90
|
+
class << self
|
91
|
+
# Whether signatures should be checked. By default signatures are checked
|
92
|
+
# only when the application is run in $DEBUG mode. (By specifying the -d
|
93
|
+
# switch on the invocation of Ruby.)
|
94
|
+
#
|
95
|
+
# Note: If you want to change this you need to do so before doing any
|
96
|
+
# Module#signature calls or it will not be applied. It's probably best
|
97
|
+
# set right after requiring the contract library.
|
98
|
+
attr_accessor :check_signatures
|
99
|
+
alias :check_signatures? :check_signatures
|
100
|
+
|
101
|
+
# Whether fulfills should be checked. This is enabled by default.
|
102
|
+
#
|
103
|
+
# Note: If you want to change this you need to do so before doing any
|
104
|
+
# Module#fulfills calls or it will not be applied. It's probably best
|
105
|
+
# set right after requiring the contract library.
|
106
|
+
attr_accessor :check_fulfills
|
107
|
+
alias :check_fulfills? :check_fulfills
|
108
|
+
|
109
|
+
# All adaption routes.
|
110
|
+
attr_accessor :adaptions # :nodoc:
|
111
|
+
end
|
112
|
+
self.check_signatures = $DEBUG if self.check_signatures.nil?
|
113
|
+
self.check_fulfills = true if self.check_fulfills.nil?
|
114
|
+
if self.adaptions.nil? then
|
115
|
+
self.adaptions = Hash.new { |hash, key| hash[key] = Array.new }
|
116
|
+
end
|
117
|
+
|
118
|
+
# Tries to adapt the specified object to the specified type.
|
119
|
+
# Returns the old object if no suitable adaption route was found or if
|
120
|
+
# it already is of the specified type.
|
121
|
+
#
|
122
|
+
# This will only use adaptions where the :to part is equal to the specified
|
123
|
+
# type. No multi-step conversion will be performed.
|
124
|
+
def self.adapt(object, type)
|
125
|
+
return object if type === object
|
126
|
+
|
127
|
+
@adaptions[type].each do |adaption|
|
128
|
+
if adaption[:from] === object and
|
129
|
+
(adaption[:if].nil? or adaption[:if] === object)
|
130
|
+
then
|
131
|
+
result = adaption[:via].call(object)
|
132
|
+
return result if type === result
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
return object
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
class Module
|
142
|
+
# Checks that the arguments and return value of a method match the specified
|
143
|
+
# signature. Checks are only actually done when Contract.check_signatures is
|
144
|
+
# set to true or if the <code>:no_adaption</code> option is +false+. The
|
145
|
+
# method will return +true+ in case it actually inserted the signature check
|
146
|
+
# logic and +nil+ in case it didn't.
|
147
|
+
#
|
148
|
+
# You will usually specify one type specifier (<code>:any</code> which will
|
149
|
+
# allow anything to appear at that position of the argument list or something
|
150
|
+
# that implements the === case equality operator -- samples are Contracts,
|
151
|
+
# Ranges, Classes, Modules, Regexps, Contract::Checks and so on) per argument.
|
152
|
+
# You can also use objects that implement the +call+ method as type specifiers
|
153
|
+
# which includes Methods and Procs.
|
154
|
+
#
|
155
|
+
# If you don't use the <code>:repeated</code> or <code>:allow_trailing</code>
|
156
|
+
# options the method will take exactly as many arguments as there are type
|
157
|
+
# specifiers which means that <code>signature :a_method</code> enforces
|
158
|
+
# +a_method+ having exactly zero arguments.
|
159
|
+
#
|
160
|
+
# The checks are done by wrapping the type checks around the method.
|
161
|
+
# ArgumentError exceptions will be raised in case the signature contract is
|
162
|
+
# not adhered to by your caller.
|
163
|
+
#
|
164
|
+
# An ArgumentError exception will be raised in case the methods natural
|
165
|
+
# argument list size and the signature you specified via Module.signature are
|
166
|
+
# incompatible. (Note that they don't have to be completely equivalent, you
|
167
|
+
# can still have a method taking zero or more arguments and apply a signature
|
168
|
+
# that limits the actual argument count to three arguments.)
|
169
|
+
#
|
170
|
+
# This method can take quite a few options. Here's a complete list:
|
171
|
+
#
|
172
|
+
# <code>:return</code>::
|
173
|
+
# A return type that the method must comply to. Note that this check (if
|
174
|
+
# failed) will actually raise a StandardError instead of an ArgumentError
|
175
|
+
# because the failure likely lies in the method itself and not in what the
|
176
|
+
# caller did.
|
177
|
+
#
|
178
|
+
# <code>:block</code>::
|
179
|
+
# +true+ or +false+ -- whether the method must take a block or not. So
|
180
|
+
# specifying <code>:block => false</code> enforces that the method is not
|
181
|
+
# allowed to have a block supplied.
|
182
|
+
#
|
183
|
+
# <code>:allow_trailing</code>::
|
184
|
+
# +true+ or +false+ -- whether the argument list may contain trailing,
|
185
|
+
# unchecked arguments.
|
186
|
+
#
|
187
|
+
# <code>:optional</code>::
|
188
|
+
# An Array specifying optional arguments. These arguments are assumed to
|
189
|
+
# be after regular arguments, but *before* repeated ones. They will be
|
190
|
+
# checked if they are present, but don't actually have to be present.
|
191
|
+
#
|
192
|
+
# This could for example be useful for <code>File.open(name, mode)</code>
|
193
|
+
# where mode is optional, but has to be either an Integer or String.
|
194
|
+
#
|
195
|
+
# Note that all optional arguments will have to be specified if you want
|
196
|
+
# to use optional and repeated arguments.
|
197
|
+
#
|
198
|
+
# Specifying an empty Array is like not supplying the option at all.
|
199
|
+
#
|
200
|
+
# <code>:repeated</code>::
|
201
|
+
# An Array that specifies arguments of a method that will be repeated over
|
202
|
+
# and over again at the end of the argument list.
|
203
|
+
#
|
204
|
+
# A good sample of this are Array#values_at which takes zero or or more
|
205
|
+
# Numeric arguments and Enumerable#zip which takes zero or more other
|
206
|
+
# Enumerable arguments.
|
207
|
+
#
|
208
|
+
# Note that the Array that was associated with the <code>:repeated</code>
|
209
|
+
# option must not be empty or an ArgumentError exception will be raised.
|
210
|
+
# If there's just one repeated type you can omit the Array and directly
|
211
|
+
# specify the type identifier.
|
212
|
+
#
|
213
|
+
# The <code>:repeated</code> option overrides the
|
214
|
+
# <code>:allow_trailing</code> option. Combining them is thus quite
|
215
|
+
# meaningless.
|
216
|
+
#
|
217
|
+
# <code>:no_adaption</code>::
|
218
|
+
# +true+ or +false+ -- whether no type adaption should be performed.
|
219
|
+
#
|
220
|
+
# Usage:
|
221
|
+
# signature(:to_s) # no arguments
|
222
|
+
# signature(:+, :any) # one argument, type unchecked
|
223
|
+
# signature(:+, Fixnum) # one argument, type Fixnum
|
224
|
+
# signature(:+, NumericContract)
|
225
|
+
# signature(:+, 1 .. 10)
|
226
|
+
# signature(:sqrt, lambda { |arg| arg > 0 })
|
227
|
+
#
|
228
|
+
# signature(:each, :block => true) # has to have block
|
229
|
+
# signature(:to_i, :block => false) # not allowed to have block
|
230
|
+
# signature(:to_i, :result => Fixnum) # return value must be Fixnum
|
231
|
+
# signature(:zip, :allow_trailing => true) # unchecked trailing args
|
232
|
+
# signature(:zip, :repeated => [Enumerable]) # repeated trailing args
|
233
|
+
# signature(:zip, :repeated => Enumerable)
|
234
|
+
# # foo(3, 6, 4, 7) works; foo(5), foo(3, 2) etc. don't
|
235
|
+
# signature(:foo, :repeated => [1..4, 5..9])
|
236
|
+
# signature(:foo, :optional => [Numeric, String]) # two optional args
|
237
|
+
def signature(method, *args)
|
238
|
+
options = {}
|
239
|
+
signature = args.dup
|
240
|
+
options.update(signature.pop) if signature.last.is_a?(Hash)
|
241
|
+
method = method.to_sym
|
242
|
+
|
243
|
+
return if not Contract.check_signatures? and options[:no_adaption]
|
244
|
+
|
245
|
+
old_method = instance_method(method)
|
246
|
+
remove_method(method) if instance_methods(false).include?(method.to_s)
|
247
|
+
|
248
|
+
arity = old_method.arity
|
249
|
+
if arity != signature.size and
|
250
|
+
(arity >= 0 or signature.size < ~arity) then
|
251
|
+
raise(ArgumentError, "signature isn't compatible with arity")
|
252
|
+
end
|
253
|
+
|
254
|
+
# Normalizes specifiers to Objects that respond to === so that the run-time
|
255
|
+
# checks only have to deal with that case. Also checks that a specifier is
|
256
|
+
# actually valid.
|
257
|
+
convert_specifier = lambda do |item|
|
258
|
+
# Procs, Methods etc.
|
259
|
+
if item.respond_to?(:call) then
|
260
|
+
Contract::Check.block { |arg| item.call(arg) }
|
261
|
+
# Already okay
|
262
|
+
elsif item.respond_to?(:===) or item == :any then
|
263
|
+
item
|
264
|
+
# Unknown specifier
|
265
|
+
else
|
266
|
+
raise(ArgumentError, "unsupported argument specifier #{item.inspect}")
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
signature.map!(&convert_specifier)
|
271
|
+
|
272
|
+
if options.include?(:optional) then
|
273
|
+
options[:optional] = Array(options[:optional])
|
274
|
+
options[:optional].map!(&convert_specifier)
|
275
|
+
options.delete(:optional) if options[:optional].empty?
|
276
|
+
end
|
277
|
+
|
278
|
+
if options.include?(:repeated) then
|
279
|
+
options[:repeated] = Array(options[:repeated])
|
280
|
+
if options[:repeated].size == 0 then
|
281
|
+
raise(ArgumentError, "repeated arguments may not be an empty Array")
|
282
|
+
else
|
283
|
+
options[:repeated].map!(&convert_specifier)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
if options.include?(:return) then
|
288
|
+
options[:return] = convert_specifier.call(options[:return])
|
289
|
+
end
|
290
|
+
|
291
|
+
# We need to keep around references to our arguments because we will
|
292
|
+
# need to access them via ObjectSpace._id2ref so that they do not
|
293
|
+
# get garbage collected.
|
294
|
+
@signatures ||= Hash.new { |hash, key| hash[key] = Array.new }
|
295
|
+
@signatures[method] << [signature, options, old_method]
|
296
|
+
|
297
|
+
adapted = Proc.new do |obj, type, assign_to|
|
298
|
+
if options[:no_adaption] then
|
299
|
+
obj
|
300
|
+
elsif assign_to then
|
301
|
+
%{(#{assign_to} = Contract.adapt(#{obj}, #{type}))}
|
302
|
+
else
|
303
|
+
%{Contract.adapt(#{obj}, #{type})}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# We have to use class_eval so that signatures can be specified for
|
308
|
+
# methods taking blocks in Ruby 1.8. (This will be obsolete in 1.9)
|
309
|
+
# We also make the checks as efficient as we can.
|
310
|
+
code = %{
|
311
|
+
def #{method}(*args, &block)
|
312
|
+
old_args = args.dup
|
313
|
+
|
314
|
+
#{if options.include?(:block) then
|
315
|
+
if options[:block] then
|
316
|
+
%{raise(ArgumentError, "no block given") unless block}
|
317
|
+
else
|
318
|
+
%{raise(ArgumentError, "block given") if block}
|
319
|
+
end
|
320
|
+
end
|
321
|
+
}
|
322
|
+
|
323
|
+
#{if not (options[:allow_trailing] or
|
324
|
+
options.include?(:repeated) or options.include?(:optional))
|
325
|
+
then
|
326
|
+
msg = "wrong number of arguments (\#{args.size} for " +
|
327
|
+
"#{signature.size})"
|
328
|
+
%{if args.size != #{signature.size} then
|
329
|
+
raise(ArgumentError, "#{msg}")
|
330
|
+
end
|
331
|
+
}
|
332
|
+
elsif options.include?(:optional) and
|
333
|
+
not options.include?(:allow_trailing) and
|
334
|
+
not options.include?(:repeated)
|
335
|
+
then
|
336
|
+
min = signature.size
|
337
|
+
max = signature.size + options[:optional].size
|
338
|
+
msg = "wrong number of arguments (\#{args.size} for " +
|
339
|
+
"#{min} upto #{max})"
|
340
|
+
%{unless args.size.between?(#{min}, #{max})
|
341
|
+
raise(ArgumentError, "#{msg}")
|
342
|
+
end
|
343
|
+
}
|
344
|
+
elsif signature.size > 0 then
|
345
|
+
msg = "wrong number of arguments (\#{args.size} for " +
|
346
|
+
"at least #{signature.size}"
|
347
|
+
%{if args.size < #{signature.size} then
|
348
|
+
raise(ArgumentError, "#{msg}")
|
349
|
+
end
|
350
|
+
}
|
351
|
+
end
|
352
|
+
}
|
353
|
+
|
354
|
+
#{index = 0
|
355
|
+
signature.map do |part|
|
356
|
+
next if part == :any
|
357
|
+
index += 1
|
358
|
+
msg = "argument #{index} (\#{arg.inspect}) does not match " +
|
359
|
+
"#{part.inspect}"
|
360
|
+
%{type = ObjectSpace._id2ref(#{part.object_id})
|
361
|
+
arg = args.shift
|
362
|
+
unless type === #{adapted[%{arg}, %{type}, %{old_args[#{index - 1}]}]}
|
363
|
+
raise(ArgumentError, "#{msg}")
|
364
|
+
end
|
365
|
+
}
|
366
|
+
end
|
367
|
+
}
|
368
|
+
|
369
|
+
#{%{catch(:args_exhausted) do} if options.include?(:optional)}
|
370
|
+
#{if optional = options[:optional] then
|
371
|
+
index = 0
|
372
|
+
optional.map do |part|
|
373
|
+
next if part == :any
|
374
|
+
index += 1
|
375
|
+
msg = "argument #{index + signature.size} " +
|
376
|
+
"(\#{arg.inspect}) does not match #{part.inspect}"
|
377
|
+
oa_index = index + signature.size - 1
|
378
|
+
|
379
|
+
%{throw(:args_exhausted) if args.empty?
|
380
|
+
type = ObjectSpace._id2ref(#{part.object_id})
|
381
|
+
arg = args.shift
|
382
|
+
unless type === #{adapted[%{arg}, %{type}, %{old_args[#{oa_index}]}]}
|
383
|
+
raise(ArgumentError, "#{msg}")
|
384
|
+
end
|
385
|
+
}
|
386
|
+
end
|
387
|
+
end
|
388
|
+
}
|
389
|
+
|
390
|
+
#{if repeated = options[:repeated] then
|
391
|
+
arg_off = 1 + signature.size
|
392
|
+
arg_off += options[:optional].size if options.include?(:optional)
|
393
|
+
msg = "argument \#{idx + #{arg_off}} " +
|
394
|
+
"(\#{arg.inspect}) does not match \#{part.inspect}"
|
395
|
+
%{parts = ObjectSpace._id2ref(#{repeated.object_id})
|
396
|
+
args.each_with_index do |arg, idx|
|
397
|
+
part = parts[idx % #{repeated.size}]
|
398
|
+
if part != :any and
|
399
|
+
not part === (#{adapted[%{arg}, %{part}, %{old_args[idx]}]})
|
400
|
+
then
|
401
|
+
raise(ArgumentError, "#{msg}")
|
402
|
+
end
|
403
|
+
end
|
404
|
+
}
|
405
|
+
end
|
406
|
+
}
|
407
|
+
#{%{end} if options.include?(:optional)}
|
408
|
+
|
409
|
+
result = ObjectSpace._id2ref(#{old_method.object_id}).bind(self).
|
410
|
+
call(*old_args, &block)
|
411
|
+
#{if rt = options[:return] and rt != :any then
|
412
|
+
msg = "return value (\#{result.inspect}) does not match #{rt.inspect}"
|
413
|
+
%{type = ObjectSpace._id2ref(#{rt.object_id})
|
414
|
+
unless type === #{adapted[%{result}, %{type}]}
|
415
|
+
raise(StandardError, "#{msg}")
|
416
|
+
end
|
417
|
+
}
|
418
|
+
end
|
419
|
+
}
|
420
|
+
end
|
421
|
+
}
|
422
|
+
class_eval code, "(signature check for #{old_method.inspect[/: (.+?)>\Z/, 1]})"
|
423
|
+
|
424
|
+
return true
|
425
|
+
end
|
426
|
+
|
427
|
+
# Specifies that this Module/Class fulfills one or more contracts. The contracts
|
428
|
+
# will automatically be verified after an instance has been successfully created.
|
429
|
+
# This only actually does the checks when Contract.check_fulfills is enabled.
|
430
|
+
# The method will return +true+ in case it actually inserted the check logic and
|
431
|
+
# +nil+ in case it didn't.
|
432
|
+
#
|
433
|
+
# Note that this works by overriding the #initialize method which means that you
|
434
|
+
# should either add the fulfills statements after your initialize method or call
|
435
|
+
# the previously defined initialize method from your new one.
|
436
|
+
def fulfills(*contracts)
|
437
|
+
return unless Contract.check_fulfills?
|
438
|
+
|
439
|
+
contracts.each do |contract|
|
440
|
+
contract.implications.each do |implication|
|
441
|
+
include implication
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
old_method = instance_method(:initialize)
|
446
|
+
remove_method(:initialize) if instance_methods(false).include?("initialize")
|
447
|
+
|
448
|
+
# Keep visible references around so that the GC will not eat these up.
|
449
|
+
@fulfills ||= Array.new
|
450
|
+
@fulfills << [contracts, old_method]
|
451
|
+
|
452
|
+
# Have to use class_eval because define_method does not allow methods to take
|
453
|
+
# blocks. This can be cleaned up when Ruby 1.9 has become current.
|
454
|
+
class_eval %{
|
455
|
+
def initialize(*args, &block)
|
456
|
+
ObjectSpace._id2ref(#{old_method.object_id}).bind(self).call(*args, &block)
|
457
|
+
ObjectSpace._id2ref(#{contracts.object_id}).each do |contract|
|
458
|
+
contract.enforce self
|
459
|
+
end
|
460
|
+
end
|
461
|
+
}, "(post initialization contract check for #{self.inspect})"
|
462
|
+
|
463
|
+
return true
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
|
468
|
+
module Kernel
|
469
|
+
# Adds an adaption route from the specified type to the specified type.
|
470
|
+
# Basic usage looks like this:
|
471
|
+
# adaption :from => StringIO, :to => String, :via => :read
|
472
|
+
#
|
473
|
+
# This method takes various options. Here's a complete list:
|
474
|
+
#
|
475
|
+
# <code>:from</code>::
|
476
|
+
# The type that can be converted from. Defaults to +self+ meaning you
|
477
|
+
# can safely omit it in Class, Module or Contract context.
|
478
|
+
#
|
479
|
+
# <code>:to</code>::
|
480
|
+
# The type that can be converted to. Defaults to +self+ meaning you
|
481
|
+
# can safely omit it in Class, Module or Contract context.
|
482
|
+
#
|
483
|
+
# Note that you need to specify either <code>:from</code> or
|
484
|
+
# <code>:to</code>.
|
485
|
+
#
|
486
|
+
# <code>:via</code>::
|
487
|
+
# How the <code>:from</code> type will be converted to the
|
488
|
+
# <code>:to</code> type. If this is a Symbol the conversion will be
|
489
|
+
# done by invoking the method identified by that Symbol on the
|
490
|
+
# source object. Otherwise this should be something that responds to
|
491
|
+
# the +call+ method (for example Methods and Procs) which will get
|
492
|
+
# the source object as its argument and which should return the
|
493
|
+
# target object.
|
494
|
+
#
|
495
|
+
# <code>:if</code>::
|
496
|
+
# The conversion can only be performed if this condition is met.
|
497
|
+
# This can either be something that implements the === case
|
498
|
+
# equivalence operator or something that implements the +call+
|
499
|
+
# method. So Methods, Procs, Modules, Classes and Contracts all
|
500
|
+
# make sense in this context. You can also specify a Symbol in
|
501
|
+
# which case the conversion can only be performed if the source
|
502
|
+
# object responds to the method identified by that Symbol.
|
503
|
+
#
|
504
|
+
# Note that the <code>:if</code> option will default to the same
|
505
|
+
# value as the <code>:via</code> option if the <code>:via</code>
|
506
|
+
# option is a Symbol.
|
507
|
+
#
|
508
|
+
# If you invoke this method with a block it will be used instead of
|
509
|
+
# the <code>:via</code> option.
|
510
|
+
#
|
511
|
+
# See Contract.adapt for how conversion look-ups are performed.
|
512
|
+
def adaption(options = {}, &block) # :yield: source_object
|
513
|
+
options = {
|
514
|
+
:from => self,
|
515
|
+
:to => self
|
516
|
+
}.merge(options)
|
517
|
+
|
518
|
+
if block then
|
519
|
+
if options.include?(:via) then
|
520
|
+
raise(ArgumentError, "Can't use both block and :via")
|
521
|
+
else
|
522
|
+
options[:via] = block
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
if options[:via].respond_to?(:to_sym) then
|
527
|
+
options[:via] = options[:via].to_sym
|
528
|
+
end
|
529
|
+
|
530
|
+
options[:if] ||= options[:via] if options[:via].is_a?(Symbol)
|
531
|
+
|
532
|
+
if options[:via].is_a?(Symbol) then
|
533
|
+
symbol = options[:via]
|
534
|
+
options[:via] = lambda { |obj| obj.send(symbol) }
|
535
|
+
end
|
536
|
+
|
537
|
+
if options[:if].respond_to?(:to_sym) then
|
538
|
+
options[:if] = options[:if].to_sym
|
539
|
+
end
|
540
|
+
|
541
|
+
if options[:if].is_a?(Symbol) then
|
542
|
+
options[:if] = Contract::Check::Quack[options[:if]]
|
543
|
+
elsif options[:if].respond_to?(:call) then
|
544
|
+
callable = options[:if]
|
545
|
+
options[:if] = Contract::Check.block { |obj| callable.call(obj) }
|
546
|
+
end
|
547
|
+
|
548
|
+
if options[:from] == self and options[:to] == self then
|
549
|
+
raise(ArgumentError, "Need to specify either :from or :to")
|
550
|
+
elsif options[:from] == options[:to] then
|
551
|
+
raise(ArgumentError, "Self-adaption: :from and :to both are " +
|
552
|
+
options[:to].inspect)
|
553
|
+
end
|
554
|
+
|
555
|
+
unless options[:via]
|
556
|
+
raise(ArgumentError, "Need to specify how to adapt (use :via or block)")
|
557
|
+
end
|
558
|
+
|
559
|
+
Contract.adaptions[options[:to]] << options
|
560
|
+
end
|
561
|
+
|
562
|
+
# Built-in adaption routes that Ruby already uses in its C code.
|
563
|
+
adaption :to => Symbol, :via => :to_sym
|
564
|
+
adaption :to => String, :via => :to_str
|
565
|
+
adaption :to => Array, :via => :to_ary
|
566
|
+
adaption :to => Integer, :via => :to_int
|
567
|
+
end
|
568
|
+
|
569
|
+
|
570
|
+
# Modifies Method and UnboundMethod so that signatures set by
|
571
|
+
# Module.signatures can be retrieved.
|
572
|
+
#
|
573
|
+
# Note that this can only work when the method origin and definition name
|
574
|
+
# are known which is the reason for ruby-contract currently overloading
|
575
|
+
# all methods that return a method.
|
576
|
+
#
|
577
|
+
# This could be greatly simplified it http://www.rcrchive.net/rcr/show/292
|
578
|
+
# were to be accepted. You can help the development of ruby-contract by
|
579
|
+
# voting for that RCR.
|
580
|
+
module MethodSignatureMixin
|
581
|
+
attr_reader :origin # :nodoc:
|
582
|
+
attr_reader :name # :nodoc:
|
583
|
+
|
584
|
+
def initialize(origin = nil, name = nil) # :nodoc:
|
585
|
+
@origin, @name = origin, name
|
586
|
+
@signature = nil
|
587
|
+
@has_signature = false
|
588
|
+
signatures = origin.instance_variable_get(:@signatures)
|
589
|
+
@signature = if signatures and signatures.include?(name) then
|
590
|
+
@has_signature = true
|
591
|
+
signatures[name].last[0, 2]
|
592
|
+
elsif self.arity >= 0 then
|
593
|
+
[[:any] * self.arity, {}]
|
594
|
+
else
|
595
|
+
[[:any] * ~self.arity, { :allow_trailing => true }]
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
# Returns the signature of this method in the form of
|
600
|
+
# <code>[fixed types, options]</code>. If no signature was specified via
|
601
|
+
# Module#signature it will still return something useful.
|
602
|
+
#
|
603
|
+
# This information can be useful in meta programming.
|
604
|
+
def signature() @signature end
|
605
|
+
|
606
|
+
# Returns whether a signatue for this method was defined via
|
607
|
+
# Module#signature.
|
608
|
+
def has_signature?() @has_signature end
|
609
|
+
end
|
610
|
+
|
611
|
+
class Method; include MethodSignatureMixin; end # :nodoc:
|
612
|
+
class UnboundMethod; include MethodSignatureMixin; end # :nodoc:
|
613
|
+
|
614
|
+
# Wrap all places where (Unbound)Methods can be created so that they
|
615
|
+
# carry around the origin and name meta data.
|
616
|
+
orig_instance_method = Module.instance_method(:instance_method)
|
617
|
+
class UnboundMethod # :nodoc:
|
618
|
+
alias :old_bind :bind # :nodoc:
|
619
|
+
end
|
620
|
+
{ UnboundMethod => [:forward, [:bind, :clone, :dup]],
|
621
|
+
Method => [:forward, [:unbind, :clone, :dup]],
|
622
|
+
Object => [:create_obj, [:method]],
|
623
|
+
Module => [:create_mod, [:instance_method]]
|
624
|
+
}.each do |mod, (type, methods)|
|
625
|
+
methods.each do |method|
|
626
|
+
old_method = orig_instance_method.old_bind(mod).call(method)
|
627
|
+
|
628
|
+
case type
|
629
|
+
when :forward then
|
630
|
+
mod.send(:define_method, method) do |*args|
|
631
|
+
result = old_method.old_bind(self).call(*args)
|
632
|
+
result.send(:initialize, @origin, @name)
|
633
|
+
result
|
634
|
+
end
|
635
|
+
|
636
|
+
when :create_mod then
|
637
|
+
mod.send(:define_method, method) do |name|
|
638
|
+
result = old_method.old_bind(self).call(name)
|
639
|
+
result.send(:initialize, self, name)
|
640
|
+
result
|
641
|
+
end
|
642
|
+
|
643
|
+
when :create_obj then
|
644
|
+
mod.send(:define_method, method) do |name|
|
645
|
+
result = old_method.old_bind(self).call(name)
|
646
|
+
meta_origin = result.inspect["."]
|
647
|
+
|
648
|
+
origin = if meta_origin then
|
649
|
+
class << self; self; end
|
650
|
+
else
|
651
|
+
origin_str = result.inspect[/[( ](.+?)\)?#/, 1]
|
652
|
+
self.class.ancestors.find do |mod|
|
653
|
+
mod.inspect == origin_str
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
result.send(:initialize, origin, name)
|
658
|
+
result
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
mod.send(:public, method)
|
663
|
+
end
|
664
|
+
end
|