contracts 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,9 +10,7 @@ module Contracts
10
10
 
11
11
  module EigenclassWithOwner
12
12
  def self.lift(eigenclass)
13
- unless with_owner?(eigenclass)
14
- raise Contracts::ContractsNotIncluded
15
- end
13
+ fail Contracts::ContractsNotIncluded unless with_owner?(eigenclass)
16
14
 
17
15
  eigenclass
18
16
  end
@@ -51,18 +49,14 @@ module Contracts
51
49
  decorators = fetch_decorators
52
50
  return if decorators.empty?
53
51
 
54
- @decorated_methods ||= {:class_methods => {}, :instance_methods => {}}
52
+ @decorated_methods ||= { :class_methods => {}, :instance_methods => {} }
55
53
 
56
54
  if is_class_method
57
55
  method_reference = SingletonMethodReference.new(name, method(name))
58
56
  method_type = :class_methods
59
- # private_methods is an array of strings on 1.8 and an array of symbols on 1.9
60
- is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s)
61
57
  else
62
58
  method_reference = MethodReference.new(name, instance_method(name))
63
59
  method_type = :instance_methods
64
- # private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9
65
- is_private = self.private_instance_methods.include?(name) || self.private_instance_methods.include?(name.to_s)
66
60
  end
67
61
 
68
62
  @decorated_methods[method_type][name] ||= []
@@ -79,9 +73,7 @@ module Contracts
79
73
  end
80
74
 
81
75
  if @decorated_methods[method_type][name].any? { |x| x.method != method_reference }
82
- @decorated_methods[method_type][name].each do |decorator|
83
- decorator.pattern_match!
84
- end
76
+ @decorated_methods[method_type][name].each(&:pattern_match!)
85
77
 
86
78
  pattern_matching = true
87
79
  end
@@ -95,42 +87,40 @@ module Contracts
95
87
  # The decorator in turn has a reference to the actual method, so it can call it
96
88
  # on its own, after doing it's decorating of course.
97
89
 
98
- =begin
99
- Very important: THe line `current = #{self}` in the start is crucial.
100
- Not having it means that any method that used contracts could NOT use `super`
101
- (see this issue for example: https://github.com/egonSchiele/contracts.ruby/issues/27).
102
- Here's why: Suppose you have this code:
103
-
104
- class Foo
105
- Contract nil => String
106
- def to_s
107
- "Foo"
108
- end
109
- end
110
-
111
- class Bar < Foo
112
- Contract nil => String
113
- def to_s
114
- super + "Bar"
115
- end
116
- end
117
-
118
- b = Bar.new
119
- p b.to_s
120
-
121
- `to_s` in Bar calls `super`. So you expect this to call `Foo`'s to_s. However,
122
- we have overwritten the function (that's what this next defn is). So it gets a
123
- reference to the function to call by looking at `decorated_methods`.
124
-
125
- Now, this line used to read something like:
126
-
127
- current = self#{is_class_method ? "" : ".class"}
128
-
129
- In that case, `self` would always be `Bar`, regardless of whether you were calling
130
- Foo's to_s or Bar's to_s. So you would keep getting Bar's decorated_methods, which
131
- means you would always call Bar's to_s...infinite recursion! Instead, you want to
132
- call Foo's version of decorated_methods. So the line needs to be `current = #{self}`.
133
- =end
90
+ # Very important: THe line `current = #{self}` in the start is crucial.
91
+ # Not having it means that any method that used contracts could NOT use `super`
92
+ # (see this issue for example: https://github.com/egonSchiele/contracts.ruby/issues/27).
93
+ # Here's why: Suppose you have this code:
94
+ #
95
+ # class Foo
96
+ # Contract nil => String
97
+ # def to_s
98
+ # "Foo"
99
+ # end
100
+ # end
101
+ #
102
+ # class Bar < Foo
103
+ # Contract nil => String
104
+ # def to_s
105
+ # super + "Bar"
106
+ # end
107
+ # end
108
+ #
109
+ # b = Bar.new
110
+ # p b.to_s
111
+ #
112
+ # `to_s` in Bar calls `super`. So you expect this to call `Foo`'s to_s. However,
113
+ # we have overwritten the function (that's what this next defn is). So it gets a
114
+ # reference to the function to call by looking at `decorated_methods`.
115
+ #
116
+ # Now, this line used to read something like:
117
+ #
118
+ # current = self#{is_class_method ? "" : ".class"}
119
+ #
120
+ # In that case, `self` would always be `Bar`, regardless of whether you were calling
121
+ # Foo's to_s or Bar's to_s. So you would keep getting Bar's decorated_methods, which
122
+ # means you would always call Bar's to_s...infinite recursion! Instead, you want to
123
+ # call Foo's version of decorated_methods. So the line needs to be `current = #{self}`.
134
124
 
135
125
  current = self
136
126
  method_reference.make_definition(self) do |*args, &blk|
@@ -140,7 +130,7 @@ Here's why: Suppose you have this code:
140
130
  current = ancestors.shift
141
131
  end
142
132
  if !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
143
- raise "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
133
+ fail "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
144
134
  end
145
135
  methods = current.decorated_methods[method_type][name]
146
136
 
@@ -151,7 +141,7 @@ Here's why: Suppose you have this code:
151
141
  i = 0
152
142
  result = nil
153
143
  expected_error = methods[0].failure_exception
154
- while !success
144
+ until success
155
145
  method = methods[i]
156
146
  i += 1
157
147
  begin
@@ -170,12 +160,10 @@ Here's why: Suppose you have this code:
170
160
  end
171
161
  result
172
162
  end
173
-
174
- method_reference.make_private(self) if is_private
175
163
  end
176
164
 
177
165
  def decorate(klass, *args)
178
- if self.singleton_class?
166
+ if Support.eigenclass? self
179
167
  return EigenclassWithOwner.lift(self).owner_class.decorate(klass, *args)
180
168
  end
181
169
 
@@ -189,7 +177,7 @@ Here's why: Suppose you have this code:
189
177
  class << self; attr_accessor :decorators; end
190
178
 
191
179
  def self.inherited(klass)
192
- name = klass.name.gsub(/^./) {|m| m.downcase}
180
+ name = klass.name.gsub(/^./) { |m| m.downcase }
193
181
 
194
182
  return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
195
183
 
@@ -1,6 +1,5 @@
1
1
  module Contracts
2
2
  module Eigenclass
3
-
4
3
  def self.extended(eigenclass)
5
4
  return if eigenclass.respond_to?(:owner_class=)
6
5
 
@@ -10,13 +9,11 @@ module Contracts
10
9
  end
11
10
 
12
11
  def self.lift(base)
13
- return NullEigenclass if base.singleton_class?
12
+ return NullEigenclass if Support.eigenclass? base
14
13
 
15
- eigenclass = base.singleton_class
14
+ eigenclass = Support.eigenclass_of base
16
15
 
17
- unless eigenclass.respond_to?(:owner_class=)
18
- eigenclass.extend(Eigenclass)
19
- end
16
+ eigenclass.extend(Eigenclass) unless eigenclass.respond_to?(:owner_class=)
20
17
 
21
18
  unless eigenclass.respond_to?(:pop_decorators)
22
19
  eigenclass.extend(MethodDecorators)
@@ -37,6 +34,5 @@ module Contracts
37
34
  []
38
35
  end
39
36
  end
40
-
41
37
  end
42
38
  end
@@ -58,7 +58,7 @@ module Contracts
58
58
  attr_reader :message
59
59
  alias_method :to_s, :message
60
60
 
61
- def initialize(message=DEFAULT_MESSAGE)
61
+ def initialize(message = DEFAULT_MESSAGE)
62
62
  @message = message
63
63
  end
64
64
  end
@@ -0,0 +1,134 @@
1
+ module Contracts
2
+ # A namespace for classes related to formatting.
3
+ module Formatters
4
+ # Used to format contracts for the `Expected:` field of error output.
5
+ class Expected
6
+ # @param full [Boolean] if false only unique `to_s` values will be output,
7
+ # non unique values become empty string.
8
+ def initialize(contract, full = true)
9
+ @contract, @full = contract, full
10
+ end
11
+
12
+ # Formats any type of Contract.
13
+ def contract(contract = @contract)
14
+ if contract.is_a?(Hash)
15
+ hash_contract(contract)
16
+ elsif contract.is_a?(Array)
17
+ array_contract(contract)
18
+ else
19
+ InspectWrapper.create(contract, @full)
20
+ end
21
+ end
22
+
23
+ # Formats Hash contracts.
24
+ def hash_contract(hash)
25
+ @full = true # Complex values output completely, overriding @full
26
+ hash.inject({}) do |repr, (k, v)|
27
+ repr.merge(k => InspectWrapper.create(contract(v), @full))
28
+ end.inspect
29
+ end
30
+
31
+ # Formats Array contracts.
32
+ def array_contract(array)
33
+ @full = true
34
+ array.map { |v| InspectWrapper.create(contract(v), @full) }.inspect
35
+ end
36
+ end
37
+
38
+ # A wrapper class to produce correct inspect behaviour for different
39
+ # contract values - constants, Class contracts, instance contracts etc.
40
+ module InspectWrapper
41
+ # InspectWrapper is a factory, will never be an instance
42
+ # @return [ClassInspectWrapper, ObjectInspectWrapper]
43
+ def self.create(value, full = true)
44
+ if value.class == Class
45
+ ClassInspectWrapper
46
+ else
47
+ ObjectInspectWrapper
48
+ end.new(value, full)
49
+ end
50
+
51
+ # @param full [Boolean] if false only unique `to_s` values will be output,
52
+ # non unique values become empty string.
53
+ def initialize(value, full)
54
+ @value, @full = value, full
55
+ end
56
+
57
+ # Inspect different types of contract values.
58
+ # Contracts module prefix will be removed from classes.
59
+ # Custom to_s messages will be wrapped in round brackets to differentiate
60
+ # from standard Strings.
61
+ # Primitive values e.g. 42, true, nil will be left alone.
62
+ def inspect
63
+ return "" unless full?
64
+ return @value.inspect if empty_val?
65
+ return @value.to_s if plain?
66
+ return delim(@value.to_s) if useful_to_s?
67
+ useful_inspect
68
+ end
69
+
70
+ def delim(value)
71
+ @full ? "(#{value})" : "#{value}"
72
+ end
73
+
74
+ # Eliminates eronious quotes in output that plain inspect includes.
75
+ def to_s
76
+ inspect
77
+ end
78
+
79
+ private
80
+
81
+ def empty_val?
82
+ @value.nil? || @value == ""
83
+ end
84
+
85
+ def full?
86
+ @full ||
87
+ @value.is_a?(Hash) || @value.is_a?(Array) ||
88
+ (!plain? && useful_to_s?)
89
+ end
90
+
91
+ def plain?
92
+ # Not a type of contract that can have a custom to_s defined
93
+ !@value.is_a?(CallableClass) && @value.class != Class
94
+ end
95
+
96
+ def useful_to_s?
97
+ # Useless to_s value or no custom to_s behavious defined
98
+ !empty_to_s? && custom_to_s?
99
+ end
100
+
101
+ def empty_to_s?
102
+ @value.to_s.empty?
103
+ end
104
+
105
+ def strip_prefix(val)
106
+ val.gsub(/^Contracts::/, "")
107
+ end
108
+ end
109
+
110
+ class ClassInspectWrapper
111
+ include InspectWrapper
112
+
113
+ def custom_to_s?
114
+ @value.to_s != @value.name
115
+ end
116
+
117
+ def useful_inspect
118
+ strip_prefix(empty_to_s? ? @value.name : @value.inspect)
119
+ end
120
+ end
121
+
122
+ class ObjectInspectWrapper
123
+ include InspectWrapper
124
+
125
+ def custom_to_s?
126
+ !@value.to_s.match(/#\<\w+:.+\>/)
127
+ end
128
+
129
+ def useful_inspect
130
+ strip_prefix(empty_to_s? ? @value.class.name : @value.inspect)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -23,7 +23,7 @@ module Contracts
23
23
  end
24
24
 
25
25
  module InvariantExtension
26
- def Invariant(name, &condition)
26
+ def invariant(name, &condition)
27
27
  return if ENV["NO_CONTRACTS"]
28
28
 
29
29
  invariants << Invariant.new(self, name, &condition)
@@ -53,17 +53,16 @@ module Contracts
53
53
  end
54
54
 
55
55
  def self.failure_callback(data)
56
- raise InvariantError, failure_msg(data)
56
+ fail InvariantError, failure_msg(data)
57
57
  end
58
58
 
59
59
  def self.failure_msg(data)
60
- %{Invariant violation:
61
- Expected: #{data[:expected]}
62
- Actual: #{data[:actual]}
63
- Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
64
- At: #{Support.method_position(data[:method])}}
60
+ %{Invariant violation:
61
+ Expected: #{data[:expected]}
62
+ Actual: #{data[:actual]}
63
+ Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
64
+ At: #{Support.method_position(data[:method])}}
65
65
  end
66
66
  end
67
-
68
67
  end
69
68
  end
@@ -2,7 +2,6 @@ module Contracts
2
2
  # MethodReference represents original method reference that was
3
3
  # decorated by contracts.ruby. Used for instance methods.
4
4
  class MethodReference
5
-
6
5
  attr_reader :name
7
6
 
8
7
  # name - name of the method
@@ -19,13 +18,11 @@ module Contracts
19
18
 
20
19
  # Makes a method re-definition in proper way
21
20
  def make_definition(this, &blk)
21
+ is_private = private?(this)
22
+ is_protected = protected?(this)
22
23
  alias_target(this).send(:define_method, name, &blk)
23
- end
24
-
25
- # Makes a method private
26
- def make_private(this)
27
- original_name = name
28
- alias_target(this).class_eval { private original_name }
24
+ make_private(this) if is_private
25
+ make_protected(this) if is_protected
29
26
  end
30
27
 
31
28
  # Aliases original method to a special unique name, which is known
@@ -48,6 +45,26 @@ module Contracts
48
45
 
49
46
  private
50
47
 
48
+ # Makes a method private
49
+ def make_private(this)
50
+ original_name = name
51
+ alias_target(this).class_eval { private original_name }
52
+ end
53
+
54
+ def private?(this)
55
+ this.private_instance_methods.map(&:to_sym).include?(name)
56
+ end
57
+
58
+ def protected?(this)
59
+ this.protected_instance_methods.map(&:to_sym).include?(name)
60
+ end
61
+
62
+ # Makes a method protected
63
+ def make_protected(this)
64
+ original_name = name
65
+ alias_target(this).class_eval { protected original_name }
66
+ end
67
+
51
68
  # Returns alias target for instance methods, subject to be
52
69
  # overriden in subclasses.
53
70
  def alias_target(this)
@@ -61,16 +78,23 @@ module Contracts
61
78
  def construct_unique_name
62
79
  :"__contracts_ruby_original_#{name}_#{Support.unique_id}"
63
80
  end
64
-
65
81
  end
66
82
 
67
83
  # The same as MethodReference, but used for singleton methods.
68
84
  class SingletonMethodReference < MethodReference
69
85
  private
70
86
 
87
+ def private?(this)
88
+ this.private_methods.map(&:to_sym).include?(name)
89
+ end
90
+
91
+ def protected?(this)
92
+ this.protected_methods.map(&:to_sym).include?(name)
93
+ end
94
+
71
95
  # Return alias target for singleton methods.
72
96
  def alias_target(this)
73
- this.singleton_class
97
+ Support.eigenclass_of this
74
98
  end
75
99
  end
76
100
  end
@@ -1,8 +1,7 @@
1
1
  module Contracts
2
2
  module Support
3
-
4
3
  def self.method_position(method)
5
- return method.method_position if MethodReference === method
4
+ return method.method_position if method.is_a?(MethodReference)
6
5
 
7
6
  if RUBY_VERSION =~ /^1\.8/
8
7
  if method.respond_to?(:__file__)
@@ -26,7 +25,7 @@ module Contracts
26
25
  # Contracts::Support.unique_id # => "i53u6tiw5hbo"
27
26
  def self.unique_id
28
27
  # Consider using SecureRandom.hex here, and benchmark which one is better
29
- (Time.now.to_f * 1000).to_i.to_s(36) + rand(1000000).to_s(36)
28
+ (Time.now.to_f * 1000).to_i.to_s(36) + rand(1_000_000).to_s(36)
30
29
  end
31
30
 
32
31
  def self.eigenclass_hierarchy_supported?
@@ -34,5 +33,12 @@ module Contracts
34
33
  RUBY_VERSION.to_f > 1.8
35
34
  end
36
35
 
36
+ def self.eigenclass_of(target)
37
+ class << target; self; end
38
+ end
39
+
40
+ def self.eigenclass?(target)
41
+ target <= eigenclass_of(Object)
42
+ end
37
43
  end
38
44
  end