ruby_contracts 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -42,7 +42,8 @@ Without the environment variable `ENABLE_ASSERTION` you have zero overhead and z
42
42
 
43
43
  When you inherit a class that contains contracts, all contracts must be satified by the subclass.
44
44
 
45
- If you override a method, you still have to satisfy the existing contracts for this method and you can add some others.
45
+ If you override a method, you still have to satisfy the existing postconditions for this method and you can add some others.
46
+ This means that postconditions groups are AND-ed and preconditions groups are OR-ed by inheritance.
46
47
 
47
48
  See the [issue #1](https://github.com/nicoolas25/ruby_contracts/issues/1) for an example.
48
49
 
@@ -1,3 +1,3 @@
1
1
  module RubyContracts
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -3,6 +3,13 @@ require "ruby_contracts/version"
3
3
  module Contracts
4
4
  class Error < Exception ; end
5
5
 
6
+ # When it is used with @__contracts_for:
7
+ # before key must contain a disjunction of conjunction with preconditions
8
+ # after key must contain a conjunction with postconditions
9
+ #
10
+ # When it is used with @__contracts:
11
+ # before key must contain a conjunction
12
+ # after key must contain a conjunction
6
13
  def self.empty_contracts
7
14
  {:before => [], :after => []}
8
15
  end
@@ -27,16 +34,17 @@ module Contracts
27
34
  def __contracts_for(name, current_contracts=nil)
28
35
  inherited_contracts = ancestors[1..-1].reduce(Contracts.empty_contracts) do |c, klass|
29
36
  ancestor_hash = klass.instance_variable_get('@__contracts_for') || {}
30
- c[:before] += ancestor_hash.has_key?(name) ? ancestor_hash[name][:before] : []
31
- c[:after] += ancestor_hash.has_key?(name) ? ancestor_hash[name][:after] : []
37
+ c[:before] << ancestor_hash[name][:before] if ancestor_hash.has_key?(name)
38
+ c[:after] += ancestor_hash[name][:after] if ancestor_hash.has_key?(name)
32
39
  c
33
40
  end
34
- current_contracts = @__contracts_for[name] || current_contracts || Contracts.empty_contracts
35
41
 
36
- contracts = Contracts.empty_contracts
37
- contracts[:before] = current_contracts[:before] + inherited_contracts[:before]
38
- contracts[:after] = current_contracts[:after] + inherited_contracts[:after]
39
- contracts
42
+ if current_contracts
43
+ inherited_contracts[:before] << current_contracts[:before] unless current_contracts[:before].empty?
44
+ inherited_contracts[:after] += current_contracts[:after] unless current_contracts[:after].empty?
45
+ end
46
+
47
+ inherited_contracts
40
48
  end
41
49
 
42
50
  def __contract_failure!(name, message, result, *args)
@@ -61,45 +69,55 @@ module Contracts
61
69
  super
62
70
 
63
71
  return unless ENV['ENABLE_ASSERTION']
72
+ return if @__skip_other_contracts_definitions
64
73
  return if @__contracts_for.has_key?(name)
65
74
 
66
75
  __contracts = @__contracts_for[name] ||= __contracts_for(name, @__contracts)
67
76
  @__contracts = Contracts.empty_contracts
68
77
 
69
78
  if !__contracts[:before].empty? || !__contracts[:after].empty?
79
+ @__skip_other_contracts_definitions = true
70
80
  original_method_name = "#{name}__with_contracts"
71
81
  define_method(original_method_name, instance_method(name))
72
82
 
73
83
  count = 0
74
- before_contracts = __contracts[:before].reduce("") do |code, contract|
75
- type, *args = contract
76
- case type
77
- when :type
78
- classes = args[0]
79
- code << "if __args.size < #{classes.size} then\n"
80
- code << " self.class.__contract_failure!('#{name}', \"need at least #{classes.size} arguments (%i given)\" % [__args.size], nil, *args)\n"
81
- code << "else\n"
82
- conditions = []
83
- classes.each_with_index{ |klass, i| conditions << "__args[#{i}].kind_of?(#{klass})" }
84
- code << " if !(#{conditions.join(' && ')}) then\n"
85
- code << " self.class.__contract_failure!('#{name}', 'input type error', nil, *__args)\n"
86
- code << " end\n"
87
- code << "end\n"
88
- code
89
-
90
- when :params
91
- # Define a method that verify the assertion
92
- contract_method_name = "__verify_contract_#{name}_in_#{count = count + 1}"
93
- define_method(contract_method_name) { |*params| self.instance_exec(*params, &args[1]) }
94
-
95
- code << "if !#{contract_method_name}(*__args) then\n"
96
- code << " self.class.__contract_failure!('#{name}', \"invalid precondition: #{args[0]}\", nil, *__args)\n"
97
- code << "end\n"
98
- code
99
- else
100
- code
84
+ before_contracts = __contracts[:before].reduce("__before_contracts_disjunction = []\n") do |code, contracts_disjunction|
85
+ contracts_conjunction = contracts_disjunction.reduce("__before_contracts_conjunction = []\n") do |code, contract|
86
+ type, *args = contract
87
+ case type
88
+ when :type
89
+ classes = args[0]
90
+ code << "if __args.size < #{classes.size} then\n"
91
+ code << " __before_contracts_conjunction << ['#{name}', \"need at least #{classes.size} arguments (%i given)\" % [__args.size], nil, *args]\n"
92
+ code << "else\n"
93
+ conditions = []
94
+ classes.each_with_index{ |klass, i| conditions << "__args[#{i}].kind_of?(#{klass})" }
95
+ code << " if !(#{conditions.join(' && ')}) then\n"
96
+ code << " __before_contracts_conjunction << ['#{name}', 'input type error', nil, *__args]\n"
97
+ code << " end\n"
98
+ code << "end\n"
99
+ code
100
+
101
+ when :params
102
+ # Define a method that verify the assertion
103
+ contract_method_name = "__verify_contract_#{name}_in_#{count = count + 1}"
104
+ define_method(contract_method_name) { |*params| self.instance_exec(*params, &args[1]) }
105
+
106
+ code << "if !#{contract_method_name}(*__args) then\n"
107
+ code << " __before_contracts_conjunction << ['#{name}', \"invalid precondition: #{args[0]}\", nil, *__args]\n"
108
+ code << "end\n"
109
+ code
110
+ else
111
+ code
112
+ end
101
113
  end
114
+ code << contracts_conjunction
115
+ code << "__before_contracts_disjunction << __before_contracts_conjunction\n"
116
+ code
102
117
  end
118
+ before_contracts << "if __before_contracts_disjunction.any?{|conj| !conj.empty?} then\n"
119
+ before_contracts << " self.class.__contract_failure!(*__before_contracts_disjunction.first.first)\n"
120
+ before_contracts << "end\n"
103
121
 
104
122
  after_contracts = __contracts[:after].reduce("") do |code, contract|
105
123
  type, *args = contract
@@ -134,6 +152,8 @@ module Contracts
134
152
  EOM
135
153
 
136
154
  class_eval method
155
+
156
+ @__skip_other_contracts_definitions = false
137
157
  end
138
158
  end
139
159
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_contracts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-11 00:00:00.000000000 Z
12
+ date: 2013-06-13 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Micro DSL to add pre & post condition to methods. It try to bring some
15
15
  design by contract in the Ruby world.