ruby-contract 0.1.1
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.
- 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
|