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,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
+