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,247 @@
1
+ module MustBe
2
+
3
+ ### Short Inspect ###
4
+
5
+ SHORT_INSPECT_CUTOFF_LENGTH = 200
6
+ SHORT_INSPECT_WORD_BREAK_LENGTH = 20
7
+ SHORT_INSPECT_ELLIPSES = "..."
8
+
9
+ def self.short_inspect(obj)
10
+ s = obj.inspect
11
+ if s.bytesize > SHORT_INSPECT_CUTOFF_LENGTH
12
+ real_cutoff = SHORT_INSPECT_CUTOFF_LENGTH - SHORT_INSPECT_ELLIPSES.length
13
+ left_side = (real_cutoff + 1) / 2
14
+ right_side = -(real_cutoff / 2)
15
+
16
+ left_start = left_side - SHORT_INSPECT_WORD_BREAK_LENGTH
17
+ left_word_break_area = s[left_start, SHORT_INSPECT_WORD_BREAK_LENGTH]
18
+ left_word_break = left_word_break_area.rindex(/\b\s/)
19
+ start = left_word_break ? left_start + left_word_break + 1 : left_side
20
+
21
+ right_start = right_side
22
+ right_word_break_area = s[right_start, SHORT_INSPECT_WORD_BREAK_LENGTH]
23
+ right_word_break = right_word_break_area.index(/\s\b/)
24
+ stop = right_word_break ? right_start + right_word_break : right_side
25
+
26
+ s[start...stop] = SHORT_INSPECT_ELLIPSES
27
+ end
28
+ s
29
+ end
30
+
31
+ ### Enable ###
32
+
33
+ @@disabled_method_for_method = Hash.new(:must_just_return)
34
+ @@disabled_handlers = []
35
+
36
+ class <<self
37
+ def disable
38
+ return unless enabled?
39
+
40
+ @disabled_methods = instance_methods.map do |method_name|
41
+ method_name = method_name.to_sym
42
+ method = instance_method(method_name)
43
+ disabled_method_name = @@disabled_method_for_method[method_name]
44
+ alias_method method_name, disabled_method_name
45
+ [method_name, method]
46
+ end
47
+ invoke_disabled_handlers
48
+ end
49
+
50
+ def enable
51
+ return if enabled?
52
+
53
+ @disabled_methods.each do |method_record|
54
+ define_method(*method_record)
55
+ end
56
+ @disabled_methods = nil
57
+ invoke_disabled_handlers
58
+ end
59
+
60
+ def enabled?
61
+ @disabled_methods.nil?
62
+ end
63
+
64
+ def register_disabled_method(method_name,
65
+ disabled_method_name = method_name)
66
+ @@disabled_method_for_method[method_name.to_sym] =
67
+ disabled_method_name.to_sym
68
+ end
69
+
70
+ def register_disabled_handler(&handler)
71
+ @@disabled_handlers << handler
72
+ handler[enabled?] unless enabled?
73
+ end
74
+
75
+ private
76
+
77
+ def invoke_disabled_handlers
78
+ @@disabled_handlers.each {|handler| handler[enabled?] }
79
+ end
80
+ end
81
+
82
+ def must_just_return(*args)
83
+ self
84
+ end
85
+
86
+ register_disabled_method(:must_just_return)
87
+
88
+ def must_just_yield(*args)
89
+ yield
90
+ end
91
+
92
+ register_disabled_method(:must_just_yield)
93
+
94
+ ### Notifiers ###
95
+
96
+ NOTIFIERS = {}
97
+
98
+ class <<self
99
+ attr_accessor :notifier # should respond_to? :call with Note argument.
100
+
101
+ def def_notifier(constant_name, key = nil, &notifier)
102
+ const_set(constant_name, notifier)
103
+ NOTIFIERS[key] = constant_name if key
104
+ end
105
+
106
+ def set_notifier_from_env(key = ENV['MUST_BE__NOTIFIER'])
107
+ key = key.to_sym
108
+
109
+ if key == :disable
110
+ disable
111
+ return
112
+ end
113
+
114
+ constant_name = NOTIFIERS[key]
115
+ unless constant_name
116
+ raise ArgumentError, "no MustBe::NOTIFIERS called #{key.inspect}"
117
+ end
118
+ self.notifier = const_get(constant_name)
119
+ end
120
+ end
121
+
122
+ def_notifier(:RaiseNotifier, :raise) {|note| true }
123
+
124
+ def_notifier(:LogNotifier, :log) do |note|
125
+ begin
126
+ raise note
127
+ rescue Note
128
+ puts [note.message, *note.backtrace].join("\n\t")
129
+ end
130
+ false
131
+ end
132
+
133
+ def_notifier(:DebugNotifier, :debug) do |note|
134
+ $must_be__note = note
135
+ puts note.message
136
+ puts "Starting debugger ($must_be__note stores the note)..."
137
+ require 'ruby-debug'
138
+ debugger
139
+ false
140
+ end
141
+
142
+ set_notifier_from_env(ENV['MUST_BE__NOTIFIER'] || :raise)
143
+
144
+ ### Note ###
145
+
146
+ class Note < StandardError
147
+ attr_accessor :receiver, :assertion, :args, :block, :additional_message,
148
+ :prefix
149
+
150
+ def initialize(receiver, assertion = nil, args = nil, block = nil,
151
+ additional_message = nil)
152
+ if assertion
153
+ @receiver = receiver
154
+ @assertion = assertion
155
+ @args = args
156
+ @block = block
157
+ @additional_message = additional_message
158
+ else
159
+ super(receiver)
160
+ end
161
+ end
162
+
163
+ def to_s
164
+ if assertion
165
+ "#{prefix}#{MustBe.short_inspect(receiver)}."\
166
+ "#{assertion}#{format_args_and_block}#{additional_message}"
167
+ else
168
+ super
169
+ end
170
+ end
171
+
172
+ alias complete_backtrace backtrace
173
+
174
+ def backtrace
175
+ complete_backtrace and complete_backtrace.drop_while do |line|
176
+ line =~ %r{lib/must_be.*\.rb:}
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ def format_args_and_block
183
+ if args.nil? or args.empty?
184
+ if block.nil?
185
+ ""
186
+ else
187
+ " {}"
188
+ end
189
+ else
190
+ args_format = "(#{args.map{|v| MustBe.short_inspect(v) }.join(", ")})"
191
+ if block.nil?
192
+ args_format
193
+ else
194
+ args_format+" {}"
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ def must_notify(receiver = nil, assertion= nil, args = nil, block = nil,
201
+ additional_message = nil)
202
+ note = Note === receiver ? receiver :
203
+ Note.new(receiver, assertion, args, block, additional_message)
204
+ if Thread.current[:must_check__is_checking]
205
+ Thread.current[:must_check__found_note] = note
206
+ else
207
+ raise note if MustBe.notifier.call(note)
208
+ end
209
+ note
210
+ end
211
+
212
+ def must_check(check_block = nil, &block)
213
+ if check_block
214
+ result = nil
215
+ note = must_check do |obj|
216
+ result = check_block.arity.zero? ? check_block[] : check_block[obj]
217
+ end
218
+ if note
219
+ must_notify(block[note])
220
+ end
221
+ return result
222
+ end
223
+
224
+ begin
225
+ was_checking = Thread.current[:must_check__is_checking]
226
+ Thread.current[:must_check__is_checking] = true
227
+
228
+ already_found = Thread.current[:must_check__found_note]
229
+ Thread.current[:must_check__found_note] = nil
230
+
231
+ yield(self)
232
+
233
+ Thread.current[:must_check__found_note]
234
+ ensure
235
+ Thread.current[:must_check__is_checking] = was_checking
236
+ Thread.current[:must_check__found_note] = already_found
237
+ end
238
+ end
239
+ end
240
+
241
+ ### Automatically Include in Object ###
242
+
243
+ unless ENV['MUST_BE__DO_NOT_AUTOMATICALLY_INCLUDE_IN_OBJECT']
244
+ class Object
245
+ include MustBe
246
+ end
247
+ end
@@ -0,0 +1,159 @@
1
+ module MustBe
2
+
3
+ private
4
+
5
+ def must_raise__body(method, args, &block)
6
+ if args.size > 2
7
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
8
+ elsif args.size == 2
9
+ expected_exception = args[0]
10
+ expected_message = args[1]
11
+ else
12
+ case args[0]
13
+ when nil, String, Regexp
14
+ expected_exception = Exception
15
+ expected_message = args[0]
16
+ else
17
+ expected_exception = args[0]
18
+ end
19
+ end
20
+
21
+ unless expected_exception.is_a?(Class) and
22
+ expected_exception.ancestors.include?(Exception)
23
+ raise TypeError, "exception class expected"
24
+ end
25
+
26
+ case expected_message
27
+ when nil, String, Regexp
28
+ else
29
+ raise TypeError, "nil, string, or regexp required"
30
+ end
31
+
32
+ begin
33
+ result = yield
34
+ rescue expected_exception => actual_exception
35
+
36
+ is_match = expected_message.nil? ||
37
+ expected_message === actual_exception.message
38
+
39
+ if is_match
40
+ if method == :must_not_raise
41
+ must_notify(self, :must_not_raise, args, block,
42
+ ", but raised #{actual_exception.class}"+(
43
+ expected_message ?
44
+ " with message #{actual_exception.message.inspect}" : ""))
45
+ end
46
+ else
47
+ if method == :must_raise
48
+ must_notify(self, :must_raise, args, block,
49
+ ", but #{actual_exception.class} with"\
50
+ " message #{actual_exception.message.inspect} was raised")
51
+ end
52
+ end
53
+
54
+ raise
55
+ rescue Exception => actual_exception
56
+ if method == :must_raise
57
+ must_notify(self, :must_raise, args, block,
58
+ ", but #{actual_exception.class} was raised")
59
+ end
60
+ raise
61
+ end
62
+
63
+ if method == :must_raise
64
+ must_notify(self, :must_raise, args, block, ", but nothing was raised")
65
+ end
66
+
67
+ result
68
+ end
69
+
70
+ public
71
+
72
+ def must_raise(*args, &block)
73
+ must_raise__body(:must_raise, args, &block)
74
+ end
75
+
76
+ def must_not_raise(*args, &block)
77
+ must_raise__body(:must_not_raise, args, &block)
78
+ end
79
+
80
+ register_disabled_method(:must_raise, :must_just_yield)
81
+ register_disabled_method(:must_not_raise, :must_just_yield)
82
+
83
+ private
84
+
85
+ @@must_throw__installed = false
86
+
87
+ def must_throw__body(method, args, &block)
88
+ if args.size > 2
89
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
90
+ end
91
+ tag = args[0]
92
+ obj = args[1]
93
+
94
+ unless @@must_throw__installed
95
+ original_throw = Kernel.instance_method(:throw)
96
+ Kernel.send(:define_method, :throw) do |*arguments|
97
+ Thread.current[:must_throw__args] = arguments
98
+ original_throw.bind(self)[*arguments]
99
+ end
100
+ @@must_throw__installed = true
101
+ end
102
+
103
+ begin
104
+ raised = false
105
+ returned_normaly = false
106
+ result = yield
107
+ returned_normaly = true
108
+ result
109
+ rescue Exception => ex
110
+ raised = true
111
+ if method == :must_throw
112
+ must_notify(self, :must_throw, args, block,
113
+ ", but raised #{ex.class}")
114
+ end
115
+ raise
116
+ ensure
117
+ if raised
118
+ elsif returned_normaly
119
+ if method == :must_throw
120
+ must_notify(self, :must_throw, args, block, ", but did not throw")
121
+ end
122
+ else
123
+ thrown = Thread.current[:must_throw__args]
124
+ thrown_tag = thrown[0]
125
+ thrown_obj = thrown[1]
126
+
127
+ if method == :must_throw
128
+ if args.size >= 1 and tag != thrown_tag
129
+ must_notify(self, :must_throw, args, block,
130
+ ", but threw #{thrown.map(&:inspect).join(", ")}")
131
+ elsif args.size == 2 and thrown.size < 2 || obj != thrown_obj
132
+ must_notify(self, :must_throw, args, block,
133
+ ", but threw #{thrown.map(&:inspect).join(", ")}")
134
+ end
135
+ elsif method == :must_not_throw
136
+ if args.size == 0 or args.size == 1 && tag == thrown_tag or
137
+ args.size == 2 && tag == thrown_tag && thrown.size == 2 &&
138
+ obj == thrown_obj
139
+ must_notify(self, :must_not_throw, args, block,
140
+ ", but threw #{thrown.map(&:inspect).join(", ")}")
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ public
148
+
149
+ def must_throw(*args, &block)
150
+ must_throw__body(:must_throw, args, &block)
151
+ end
152
+
153
+ def must_not_throw(*args, &block)
154
+ must_throw__body(:must_not_throw, args, &block)
155
+ end
156
+
157
+ register_disabled_method(:must_throw, :must_just_yield)
158
+ register_disabled_method(:must_not_throw, :must_just_yield)
159
+ end
@@ -0,0 +1,62 @@
1
+ module MustBe
2
+ class Proxy
3
+ mandatory_methods = [:__id__, :object_id, :__send__]
4
+
5
+ if RUBY_VERSION < "1.9"
6
+ mandatory_methods.map! &:to_s
7
+ end
8
+
9
+ (instance_methods - mandatory_methods).each do |method|
10
+ undef_method(method)
11
+ end
12
+
13
+ def initialize(delegate, assertion = :must)
14
+ unless assertion == :must or assertion == :must_not
15
+ raise ArgumentError,
16
+ "assertion (#{assertion.inspect}) must be :must or :must_not"
17
+ end
18
+ @delegate = delegate
19
+ @assertion = assertion
20
+ end
21
+
22
+ def method_missing(symbol, *args, &block)
23
+ result = @delegate.send(symbol, *args, &block)
24
+ assertion = result ? true : false
25
+ unless assertion == (@assertion == :must)
26
+ @delegate.must_notify(@delegate, "#{@assertion}.#{symbol}", args,
27
+ block)
28
+ end
29
+ result
30
+ end
31
+ end
32
+
33
+ def must(message = nil, &block)
34
+ if block_given?
35
+ unless block.arity > 1 ? yield(self, message) : yield(self)
36
+ if message
37
+ must_notify(message)
38
+ else
39
+ must_notify(self, __method__, nil, block)
40
+ end
41
+ end
42
+ self
43
+ else
44
+ Proxy.new(self, :must)
45
+ end
46
+ end
47
+
48
+ def must_not(message = nil, &block)
49
+ if block_given?
50
+ if block.arity > 1 ? yield(self, message) : yield(self)
51
+ if message
52
+ must_notify(message)
53
+ else
54
+ must_notify(self, __method__, nil, block)
55
+ end
56
+ end
57
+ self
58
+ else
59
+ Proxy.new(self, :must_not)
60
+ end
61
+ end
62
+ end
data/lib/must_be.rb ADDED
@@ -0,0 +1,9 @@
1
+ here = File.expand_path(File.dirname(__FILE__))
2
+
3
+ require here+'/must_be/core'
4
+ require here+'/must_be/basic'
5
+ require here+'/must_be/proxy'
6
+ require here+'/must_be/containers'
7
+ require here+'/must_be/containers_registered_classes'
8
+ require here+'/must_be/attr_typed'
9
+ require here+'/must_be/nonstandard_control_flow'
data/must_be.gemspec ADDED
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{must_be}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["William Taysom"]
12
+ s.date = %q{2010-09-12}
13
+ s.description = %q{must_be provides runtime assertions which can easily be disabled in production environments. Likewise, the notifier can be customized to raise errors, log failure, enter the debugger, or anything else.}
14
+ s.email = %q{wtaysom@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.md",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "doc/readme/examples.rb",
24
+ "doc/readme/run_examples.rb",
25
+ "lib/must_be.rb",
26
+ "lib/must_be/attr_typed.rb",
27
+ "lib/must_be/basic.rb",
28
+ "lib/must_be/containers.rb",
29
+ "lib/must_be/containers_registered_classes.rb",
30
+ "lib/must_be/core.rb",
31
+ "lib/must_be/nonstandard_control_flow.rb",
32
+ "lib/must_be/proxy.rb",
33
+ "must_be.gemspec",
34
+ "spec/must_be/attr_typed_spec.rb",
35
+ "spec/must_be/basic_spec.rb",
36
+ "spec/must_be/containers_spec.rb",
37
+ "spec/must_be/core_spec.rb",
38
+ "spec/must_be/nonstandard_control_flow_spec.rb",
39
+ "spec/must_be/proxy_spec.rb",
40
+ "spec/notify_matcher_spec.rb",
41
+ "spec/spec_helper.rb",
42
+ "spec/typical_usage_spec.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/wtaysom/must_be}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.7}
48
+ s.summary = %q{must_be Runtime Assertions}
49
+ s.test_files = [
50
+ "spec/must_be/attr_typed_spec.rb",
51
+ "spec/must_be/basic_spec.rb",
52
+ "spec/must_be/containers_spec.rb",
53
+ "spec/must_be/core_spec.rb",
54
+ "spec/must_be/nonstandard_control_flow_spec.rb",
55
+ "spec/must_be/proxy_spec.rb",
56
+ "spec/notify_matcher_spec.rb",
57
+ "spec/spec_helper.rb",
58
+ "spec/typical_usage_spec.rb"
59
+ ]
60
+
61
+ if s.respond_to? :specification_version then
62
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
63
+ s.specification_version = 3
64
+
65
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
66
+ else
67
+ end
68
+ else
69
+ end
70
+ end
71
+