must_be 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ class Module
2
+ def attr_typed(symbol, *types, &test)
3
+ raise TypeError, "#{symbol} is not a symbol" if symbol.is_a? Fixnum
4
+
5
+ types.each do |type|
6
+ raise TypeError, "class or module required" unless type.is_a? Module
7
+ end
8
+
9
+ attr_reader symbol
10
+ name = symbol.to_sym.id2name
11
+ check_method_name = "attr_typed__check_#{name}"
12
+
13
+ unless types.empty?
14
+ types_message = types.size == 1 ? types[0] :
15
+ types.size == 2 ? "#{types[0]} or #{types[1]}" :
16
+ "one of #{types.inspect}"
17
+
18
+ type_check = lambda do |value|
19
+ if types.none?{|type| value.is_a? type }
20
+ must_notify("attribute `#{name}' must be a #{types_message},"\
21
+ " but value #{value.inspect} is a #{value.class}")
22
+ end
23
+ end
24
+ end
25
+
26
+ if test
27
+ test_check = lambda do |value|
28
+ unless test[value]
29
+ must_notify("attribute `#{name}' cannot be #{value.inspect}")
30
+ end
31
+ end
32
+ end
33
+
34
+ define_method(check_method_name, &(
35
+ if types.empty?
36
+ if test
37
+ test_check
38
+ else
39
+ lambda do |value|
40
+ if value.nil?
41
+ must_notify("attribute `#{name}' cannot be nil")
42
+ end
43
+ end
44
+ end
45
+ else
46
+ if test
47
+ lambda do |value|
48
+ type_check[value]
49
+ test_check[value]
50
+ end
51
+ else
52
+ type_check
53
+ end
54
+ end
55
+ ))
56
+
57
+ module_eval %Q{
58
+ def #{name}=(value)
59
+ #{check_method_name}(value)
60
+ @#{name} = value
61
+ end
62
+ }
63
+ end
64
+
65
+ MustBe.register_disabled_handler do |enabled|
66
+ if enabled
67
+ if method(:attr_typed__original)
68
+ alias attr_typed attr_typed__original
69
+ remove_method(:attr_typed__original)
70
+ end
71
+ else
72
+ alias attr_typed__original attr_typed
73
+ define_method(:attr_typed) do |symbol, *types|
74
+ attr_accessor symbol
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,120 @@
1
+ module MustBe
2
+ def self.match_any_case?(v, cases)
3
+ cases = [cases] unless cases.is_a? Array
4
+ cases.any? {|c| c === v }
5
+ end
6
+
7
+ def must_be(*cases)
8
+ unless cases.empty? ? self : MustBe.match_any_case?(self, cases)
9
+ must_notify(self, __method__, cases, nil, ", but matches #{self.class}")
10
+ end
11
+ self
12
+ end
13
+
14
+ def must_not_be(*cases)
15
+ if cases.empty? ? self : MustBe.match_any_case?(self, cases)
16
+ must_notify(self, __method__, cases, nil, ", but matches #{self.class}")
17
+ end
18
+ self
19
+ end
20
+
21
+ private
22
+
23
+ def must_be_a__body(modules, test_method, method)
24
+ if modules.size.zero?
25
+ raise ArgumentError, "wrong number of arguments (0 for 1)"
26
+ end
27
+ ms = modules.last.is_a?(Module) ? modules : modules[0..-2]
28
+ if ms.size.zero?
29
+ raise TypeError, "class or module required"
30
+ end
31
+ ms.each do |mod|
32
+ raise TypeError, "class or module required" unless mod.is_a? Module
33
+ end
34
+
35
+ if ms.send(test_method) {|mod| is_a? mod }
36
+ must_notify(self, method, modules, nil, ", but is a #{self.class}")
37
+ end
38
+ self
39
+ end
40
+
41
+ public
42
+
43
+ def must_be_a(*modules)
44
+ must_be_a__body(modules, :none?, __method__)
45
+ end
46
+
47
+ def must_not_be_a(*modules)
48
+ must_be_a__body(modules, :any?, __method__)
49
+ end
50
+
51
+ def must_be_in(*collection)
52
+ cs = collection.size == 1 ? collection[0] : collection
53
+ unless cs.include? self
54
+ must_notify(self, __method__, collection)
55
+ end
56
+ self
57
+ end
58
+
59
+ def must_not_be_in(*collection)
60
+ cs = collection.size == 1 ? collection[0] : collection
61
+ if cs.include? self
62
+ must_notify(self, __method__, collection)
63
+ end
64
+ self
65
+ end
66
+
67
+ def must_be_nil
68
+ must_notify(self, __method__) unless nil?
69
+ self
70
+ end
71
+
72
+ def must_not_be_nil
73
+ must_notify(self, __method__) if nil?
74
+ self
75
+ end
76
+
77
+ def must_be_true
78
+ must_notify(self, __method__) unless self == true
79
+ self
80
+ end
81
+
82
+ def must_be_false
83
+ must_notify(self, __method__) unless self == false
84
+ self
85
+ end
86
+
87
+ def must_be_boolean
88
+ unless self == true or self == false
89
+ must_notify(self, __method__)
90
+ end
91
+ self
92
+ end
93
+
94
+ def must_be_close(expected, delta = 0.1)
95
+ difference = (self - expected).abs
96
+ unless difference < delta
97
+ must_notify(self, __method__, [expected, delta], nil,
98
+ ", difference is #{difference}")
99
+ end
100
+ self
101
+ end
102
+
103
+ def must_not_be_close(expected, delta = 0.1)
104
+ if (self - expected).abs < delta
105
+ must_notify(self, __method__, [expected, delta])
106
+ end
107
+ self
108
+ end
109
+ end
110
+
111
+ ### Proc Case Equality Patch ###
112
+ #
113
+ # Semantics of case equality `===' for Proc changed between Ruby 1.8 (useless)
114
+ # and Ruby 1.9 (awesome). So let's fix 'er up.
115
+ #
116
+ if RUBY_VERSION < "1.9"
117
+ class Proc
118
+ alias === call
119
+ end
120
+ end
@@ -0,0 +1,291 @@
1
+ require 'forwardable'
2
+
3
+ module MustBe
4
+ class ContainerNote < Note
5
+ extend Forwardable
6
+
7
+ attr_accessor :original_note, :container
8
+
9
+ def_delegators :@original_note,
10
+ :receiver, :receiver=,
11
+ :assertion, :assertion=,
12
+ :args, :args=,
13
+ :block, :block=,
14
+ :additional_message, :additional_message=,
15
+ :prefix, :prefix=
16
+
17
+ def initialize(original_note, container = nil)
18
+ @original_note = original_note
19
+ @container = container
20
+ end
21
+
22
+ def to_s
23
+ if assertion
24
+ @original_note.to_s+" in container #{MustBe.short_inspect(container)}"
25
+ else
26
+ @original_note.to_s
27
+ end
28
+ end
29
+
30
+ alias regular_backtrace backtrace
31
+
32
+ def backtrace
33
+ return unless regular_backtrace
34
+
35
+ if container.respond_to?(:must_only_ever_contain_backtrace) and
36
+ container.must_only_ever_contain_backtrace
37
+ regular_backtrace+["=== caused by container ==="]+
38
+ container.must_only_ever_contain_backtrace
39
+ else
40
+ regular_backtrace
41
+ end
42
+ end
43
+ end
44
+
45
+ class PairNote < ContainerNote
46
+ attr_accessor :key, :value, :cases, :negate
47
+
48
+ def initialize(key, value, cases, container, negate)
49
+ super(Note.new(""), container)
50
+ @key = key
51
+ @value = value
52
+ @cases = cases
53
+ @negate = negate
54
+ end
55
+
56
+ def to_s
57
+ match = negate ? "matches" : "does not match"
58
+ "#{prefix}pair {#{MustBe.short_inspect(key)}=>"\
59
+ "#{MustBe.short_inspect(value)}} #{match}"\
60
+ " #{MustBe.short_inspect(cases)} in"\
61
+ " container #{MustBe.short_inspect(container)}"
62
+ end
63
+ end
64
+
65
+ def self.check_pair_against_hash_cases(key, value, cases, negate = false)
66
+ if negate
67
+ if cases.empty?
68
+ !key and !value
69
+ else
70
+ cases.all? do |c|
71
+ c.all? do |k, v|
72
+ not (match_any_case?(key, k) and match_any_case?(value, v))
73
+ end
74
+ end
75
+ end
76
+ else
77
+ if cases.empty?
78
+ key and value
79
+ else
80
+ cases.any? do |c|
81
+ c.any? do |k, v|
82
+ match_any_case?(key, k) and match_any_case?(value, v)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.must_check_member_against_cases(container, member, cases,
90
+ negate = false)
91
+ member.must_check(lambda do
92
+ if negate
93
+ member.must_not_be(*cases)
94
+ else
95
+ member.must_be(*cases)
96
+ end
97
+ end) do |note|
98
+ note = ContainerNote.new(note, container)
99
+ block_given? ? yield(note) : note
100
+ end
101
+ end
102
+
103
+ def self.must_check_pair_against_hash_cases(container, key, value, cases,
104
+ negate = false)
105
+ unless MustBe.check_pair_against_hash_cases(key, value, cases, negate)
106
+ note = PairNote.new(key, value, cases, container, negate)
107
+ must_notify(block_given? ? yield(note) : note)
108
+ end
109
+ end
110
+
111
+ def self.must_only_contain(container, cases, negate = false)
112
+ prefix = negate ? "must_not_contain: " : "must_only_contain: "
113
+
114
+ advice = MustOnlyEverContain.registered_class(container)
115
+ if advice and advice.respond_to? :must_only_contain_check
116
+ advice.must_only_contain_check(container, cases, negate)
117
+ elsif container.respond_to? :each_pair
118
+ container.each_pair do |key, value|
119
+ MustBe.must_check_pair_against_hash_cases(container, key, value,
120
+ cases, negate) do |note|
121
+ note.prefix = prefix
122
+ note
123
+ end
124
+ end
125
+ else
126
+ container.each do |member|
127
+ MustBe.must_check_member_against_cases(container, member, cases,
128
+ negate) do |note|
129
+ note.prefix = prefix
130
+ note
131
+ end
132
+ end
133
+ end
134
+ container
135
+ end
136
+
137
+ def must_only_contain(*cases)
138
+ MustBe.must_only_contain(self, cases)
139
+ end
140
+
141
+ def must_not_contain(*cases)
142
+ MustBe.must_only_contain(self, cases, true)
143
+ end
144
+
145
+ module MustOnlyEverContain
146
+ REGISTERED_CLASSES = {}
147
+
148
+ module Base
149
+ attr_accessor :must_only_ever_contain_cases,
150
+ :must_only_ever_contain_backtrace, :must_only_ever_contain_negate
151
+
152
+ module ClassMethods
153
+ def must_check_contents_after(*methods)
154
+ methods.each do |method|
155
+ define_method(method) do |*args, &block|
156
+ begin
157
+ super(*args, &block)
158
+ ensure
159
+ must_check_contents
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def self.included(base)
167
+ base.extend(ClassMethods)
168
+ end
169
+
170
+ def must_only_ever_contain_prefix
171
+ must_only_ever_contain_negate ? "must_never_ever_contain: " :
172
+ "must_only_ever_contain: "
173
+ end
174
+
175
+ def must_only_ever_contain_cases=(cases)
176
+ cases = [cases] unless cases.is_a? Array
177
+ @must_only_ever_contain_cases = cases
178
+
179
+ must_check(lambda { must_check_contents }) do |note|
180
+ note.prefix = must_only_ever_contain_prefix
181
+ note
182
+ end
183
+ end
184
+
185
+ protected
186
+
187
+ def must_check_member(member)
188
+ MustBe.must_check_member_against_cases(self, member,
189
+ must_only_ever_contain_cases, must_only_ever_contain_negate)
190
+ end
191
+
192
+ def must_check_pair(key, value)
193
+ MustBe.must_check_pair_against_hash_cases(self, key, value,
194
+ must_only_ever_contain_cases, must_only_ever_contain_negate)
195
+ end
196
+
197
+ def must_check_contents(members = self)
198
+ MustBe.must_only_contain(members, must_only_ever_contain_cases,
199
+ must_only_ever_contain_negate)
200
+ end
201
+ end
202
+
203
+ public
204
+
205
+ ##
206
+ # Creates a module from `body' which includes MustOnlyEverContain::Base.
207
+ # The module will be mixed into an objects of type `klass' when
208
+ # `must_only_ever_contain' is called. The module should override methods
209
+ # of`klass' which modify the contents of the object.
210
+ #
211
+ # If the module has a class method
212
+ # `must_only_contain_check(object, cases, negate = false)',
213
+ # then this method is used by `MustBe.must_only_contain'
214
+ # to check the contents of `object' against `cases'.
215
+ # `must_only_contain_check' should call `MustBe#must_notify' for any
216
+ # contents which do not match `cases'. (Or if `negate' is true, then
217
+ # `MustBe#must_notify' should be called for any contents that do match
218
+ # `cases'.)
219
+ #
220
+ def self.register(klass, &body)
221
+ unless klass.is_a? Class
222
+ raise ArgumentError, "invalid value for Class: #{klass.inspect}"
223
+ end
224
+ if REGISTERED_CLASSES[klass]
225
+ raise ArgumentError, "handler for #{klass} previously provided"
226
+ end
227
+
228
+ REGISTERED_CLASSES[klass] = mod = Module.new
229
+ mod.send(:include, Base)
230
+ mod.class_eval &body
231
+
232
+ mutator_advice = Module.new
233
+ mod.instance_methods(false).each do |method_name|
234
+ mutator_advice.send(:define_method, method_name) do |*args, &block|
235
+ must_check(lambda { super(*args, &block) }) do |note|
236
+ note.prefix = nil
237
+ call_s = Note.new(self.class, method_name, args, block).message
238
+ call_s.sub!(".", "#")
239
+ note.prefix = "#{must_only_ever_contain_prefix}#{call_s}: "
240
+ note
241
+ end
242
+ end
243
+ end
244
+ mod.const_set(:MutatorAdvice, mutator_advice)
245
+ mod.instance_eval do
246
+ def extended(base)
247
+ base.extend(const_get(:MutatorAdvice))
248
+ end
249
+ end
250
+
251
+ mod
252
+ end
253
+
254
+ def self.registered_class(object)
255
+ REGISTERED_CLASSES[object.class]
256
+ end
257
+
258
+ def self.unregister(klass)
259
+ REGISTERED_CLASSES.delete(klass)
260
+ end
261
+ end
262
+
263
+ def self.must_only_ever_contain(container, cases, negate = false)
264
+ unless container.singleton_methods.empty?
265
+ method_name = "must_#{negate ? "never" : "only"}_ever_contain"
266
+ raise ArgumentError, "#{method_name} adds singleton methods but"\
267
+ " receiver #{MustBe.short_inspect(container)} already"\
268
+ " has singleton methods #{container.singleton_methods.inspect}"
269
+ end
270
+
271
+ advice = MustOnlyEverContain.registered_class(container)
272
+ if advice
273
+ container.extend advice
274
+ container.must_only_ever_contain_backtrace = caller
275
+ container.must_only_ever_contain_negate = negate
276
+ container.must_only_ever_contain_cases = cases
277
+ else
278
+ raise TypeError,
279
+ "No MustOnlyEverContain.registered_class for #{container.class}"
280
+ end
281
+ container
282
+ end
283
+
284
+ def must_only_ever_contain(*cases)
285
+ MustBe.must_only_ever_contain(self, cases)
286
+ end
287
+
288
+ def must_never_ever_contain(*cases)
289
+ MustBe.must_only_ever_contain(self, cases, true)
290
+ end
291
+ end
@@ -0,0 +1,83 @@
1
+ module MustBe::MustOnlyEverContain
2
+
3
+ ### Array ###
4
+
5
+ register Array do
6
+ must_check_contents_after :collect!, :map!, :flatten!
7
+
8
+ def <<(obj)
9
+ must_check_member(obj)
10
+ super
11
+ end
12
+
13
+ def []=(*args)
14
+ if args.size == 3 or args[0].is_a? Range
15
+ value = args.last
16
+ if value.nil?
17
+ # No check needed.
18
+ elsif value.is_a? Array
19
+ value.map {|v| must_check_member(v) }
20
+ else
21
+ must_check_member(value)
22
+ end
23
+ else
24
+ must_check_member(args[1])
25
+ end
26
+ super
27
+ end
28
+
29
+ def concat(other_array)
30
+ must_check_contents(other_array)
31
+ super
32
+ end
33
+
34
+ def fill(*args)
35
+ if block_given?
36
+ begin
37
+ super
38
+ ensure
39
+ must_check_contents
40
+ end
41
+ else
42
+ must_check_member(args[0])
43
+ super
44
+ end
45
+ end
46
+
47
+ def insert(index, *objs)
48
+ must_check_contents(objs)
49
+ super
50
+ end
51
+
52
+ def push(*objs)
53
+ must_check_contents(objs)
54
+ super
55
+ end
56
+
57
+ def replace(other_array)
58
+ must_check_contents(other_array)
59
+ super
60
+ end
61
+
62
+ def unshift(*objs)
63
+ must_check_contents(objs)
64
+ super
65
+ end
66
+ end
67
+
68
+ ### Hash ###
69
+
70
+ register Hash do
71
+ must_check_contents_after :replace, :merge!, :update
72
+
73
+ def []=(key, value)
74
+ must_check_pair(key, value)
75
+ super
76
+ end
77
+
78
+ def store(key, value)
79
+ must_check_pair(key, value)
80
+ super
81
+ end
82
+ end
83
+ end