must_be 1.0.0

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.
@@ -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