BMorearty-looksee 0.1.1
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.
- data/.autotest +9 -0
- data/History.txt +18 -0
- data/Manifest.txt +21 -0
- data/README.rdoc +129 -0
- data/Rakefile +37 -0
- data/ext/looksee/extconf.rb +6 -0
- data/ext/looksee/looksee.c +144 -0
- data/ext/looksee/node-1.9.h +35 -0
- data/lib/looksee/shortcuts.rb +63 -0
- data/lib/looksee/version.rb +3 -0
- data/lib/looksee/wirble_compatibility.rb +86 -0
- data/lib/looksee.rb +403 -0
- data/looksee.gemspec +44 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/looksee_spec.rb +425 -0
- data/spec/spec_helper.rb +139 -0
- data/spec/wirble_compatibility_spec.rb +119 -0
- data/tasks/extconf/looksee.rake +43 -0
- data/tasks/extconf.rake +13 -0
- metadata +116 -0
@@ -0,0 +1,425 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Looksee do
|
4
|
+
include TemporaryClasses
|
5
|
+
|
6
|
+
describe ".lookup_modules" do
|
7
|
+
#
|
8
|
+
# Wrapper for the method under test.
|
9
|
+
#
|
10
|
+
# Filter out modules which are hard to test against, and returns
|
11
|
+
# the list of module names. #inspect strings are used for names
|
12
|
+
# of singleton classes, since they have no name.
|
13
|
+
#
|
14
|
+
def filtered_lookup_modules(object)
|
15
|
+
result = Looksee.lookup_modules(object)
|
16
|
+
# Singleton classes have no name ('' in <1.9, nil in 1.9+). Use
|
17
|
+
# the inspect string instead.
|
18
|
+
names = result.map{|mod| mod.name.to_s.empty? ? mod.inspect : mod.name}
|
19
|
+
names.select{|name| deterministic_module_name?(name)}
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Return true if the given module name is of a module we can test
|
24
|
+
# for.
|
25
|
+
#
|
26
|
+
# This excludes ruby version dependent modules, and modules tossed
|
27
|
+
# into the hierarchy by testing frameworks.
|
28
|
+
#
|
29
|
+
def deterministic_module_name?(name)
|
30
|
+
junk_patterns = [
|
31
|
+
# pollution from testing libraries
|
32
|
+
'Mocha', 'Spec',
|
33
|
+
# RSpec adds this under ruby 1.8.6
|
34
|
+
'InstanceExecHelper',
|
35
|
+
# only in ruby 1.9
|
36
|
+
'BasicObject',
|
37
|
+
# something pulls this in under ruby 1.9
|
38
|
+
'PP',
|
39
|
+
]
|
40
|
+
|
41
|
+
# Singleton classes of junk are junk.
|
42
|
+
while name =~ /\A#<Class:(.*)>\z/
|
43
|
+
name = $1
|
44
|
+
end
|
45
|
+
|
46
|
+
name !~ /\A(#{junk_patterns.join('|')})/
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should contain an entry for each module in the object's lookup path" do
|
50
|
+
temporary_module :Mod1
|
51
|
+
temporary_module :Mod2
|
52
|
+
temporary_class :Base
|
53
|
+
temporary_class :Derived, :superclass => Base do
|
54
|
+
include Mod1
|
55
|
+
include Mod2
|
56
|
+
end
|
57
|
+
filtered_lookup_modules(Derived.new) == %w'Derived Mod2 Mod1 Base Object Kernel'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "contain an entry for the object's singleton class if it exists" do
|
61
|
+
object = Object.new
|
62
|
+
object.singleton_class
|
63
|
+
|
64
|
+
result = filtered_lookup_modules(object)
|
65
|
+
result.shift.should =~ /\A#<Class:\#<Object:0x[\da-f]+>>\z/
|
66
|
+
result.should == %w"Object Kernel"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should contain entries for singleton classes of all ancestors for class objects" do
|
70
|
+
temporary_class :C
|
71
|
+
result = filtered_lookup_modules(C)
|
72
|
+
result.should == %w'#<Class:C> #<Class:Object> Class Module Object Kernel'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should work for immediate objects" do
|
76
|
+
filtered_lookup_modules(1).first.should == 'Fixnum'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".lookup_path" do
|
81
|
+
it "should return a LookupPath object" do
|
82
|
+
object = Object.new
|
83
|
+
lookup_path = Looksee.lookup_path(object)
|
84
|
+
lookup_path.should be_a(Looksee::LookupPath)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return a LookupPath object for the given object" do
|
88
|
+
object = Object.new
|
89
|
+
Looksee.stubs(:default_lookup_path_options).returns({})
|
90
|
+
Looksee::LookupPath.expects(:for).with(object, {})
|
91
|
+
lookup_path = Looksee.lookup_path(object)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should allow symbol arguments as shortcuts for true options" do
|
95
|
+
object = Object.new
|
96
|
+
Looksee.stubs(:default_lookup_path_options).returns({})
|
97
|
+
Looksee::LookupPath.expects(:for).with(object, {:public => true, :overridden => true})
|
98
|
+
Looksee.lookup_path(object, :public, :overridden)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should merge the default options, with the symbols, and the options hash" do
|
102
|
+
object = Object.new
|
103
|
+
Looksee.stubs(:default_lookup_path_options).returns({:public => false, :protected => false, :private => false})
|
104
|
+
Looksee::LookupPath.expects(:for).with(object, {:public => false, :protected => true, :private => false})
|
105
|
+
Looksee.lookup_path(object, :protected, :private, :private => false)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "internal instance methods:" do
|
110
|
+
def self.target_method(name)
|
111
|
+
define_method(:target_method){name}
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.it_should_list_methods_with_visibility(visibility)
|
115
|
+
it "should return the list of #{visibility} instance methods defined directly on a class" do
|
116
|
+
temporary_class :C
|
117
|
+
replace_methods C, visibility => [:one, :two]
|
118
|
+
Looksee.send(target_method, C).to_set.should == Set[:one, :two]
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should return the list of #{visibility} instance methods defined directly on a module" do
|
122
|
+
temporary_module :M
|
123
|
+
replace_methods M, visibility => [:one, :two]
|
124
|
+
Looksee.send(target_method, M).to_set.should == Set[:one, :two]
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return the list of #{visibility} instance methods defined directly on a singleton class" do
|
128
|
+
temporary_class :C
|
129
|
+
c = C.new
|
130
|
+
replace_methods c.singleton_class, visibility => [:one, :two]
|
131
|
+
Looksee.send(target_method, c.singleton_class).to_set.should == Set[:one, :two]
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should return the list of #{visibility} instance methods defined directly on a class' singleton class" do
|
135
|
+
temporary_class :C
|
136
|
+
replace_methods C.singleton_class, visibility => [:one, :two], :class_singleton => true
|
137
|
+
Looksee.send(target_method, C.singleton_class).to_set.should == Set[:one, :two]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Worth checking as ruby keeps undef'd methods in method tables.
|
141
|
+
it "should not return undefined methods" do
|
142
|
+
temporary_class :C
|
143
|
+
replace_methods C, visibility => [:removed]
|
144
|
+
C.send(:undef_method, :removed)
|
145
|
+
Looksee.send(target_method, C).to_set.should == Set[]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.it_should_not_list_methods_with_visibility(visibility1, visibility2)
|
150
|
+
it "should not return any #{visibility1} or #{visibility2} instance methods" do
|
151
|
+
temporary_class :C
|
152
|
+
replace_methods C, {visibility1 => [:a], visibility2 => [:b]}
|
153
|
+
Looksee.send(target_method, C).to_set.should == Set[]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe ".internal_public_instance_methods" do
|
158
|
+
target_method :internal_public_instance_methods
|
159
|
+
it_should_list_methods_with_visibility :public
|
160
|
+
it_should_not_list_methods_with_visibility :private, :protected
|
161
|
+
end
|
162
|
+
|
163
|
+
describe ".internal_protected_instance_methods" do
|
164
|
+
target_method :internal_protected_instance_methods
|
165
|
+
it_should_list_methods_with_visibility :protected
|
166
|
+
it_should_not_list_methods_with_visibility :public, :private
|
167
|
+
end
|
168
|
+
|
169
|
+
describe ".internal_private_instance_methods" do
|
170
|
+
target_method :internal_private_instance_methods
|
171
|
+
it_should_list_methods_with_visibility :private
|
172
|
+
it_should_not_list_methods_with_visibility :public, :protected
|
173
|
+
end
|
174
|
+
|
175
|
+
describe ".internal_undefined_instance_methods" do
|
176
|
+
it "should return the list of undefined instance methods directly on a class" do
|
177
|
+
temporary_class :C
|
178
|
+
C.send(:define_method, :f){}
|
179
|
+
C.send(:undef_method, :f)
|
180
|
+
Looksee.internal_undefined_instance_methods(C).should == [:f]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should return the list of undefined instance methods directly on a module" do
|
184
|
+
temporary_module :M
|
185
|
+
M.send(:define_method, :f){}
|
186
|
+
M.send(:undef_method, :f)
|
187
|
+
Looksee.internal_undefined_instance_methods(M).should == [:f]
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should return the list of undefined instance methods directly on a singleton class" do
|
191
|
+
temporary_class :C
|
192
|
+
c = C.new
|
193
|
+
c.singleton_class.send(:define_method, :f){}
|
194
|
+
c.singleton_class.send(:undef_method, :f)
|
195
|
+
Looksee.internal_undefined_instance_methods(c.singleton_class).should == [:f]
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should return the list of undefined instance methods directly on a class' singleton class" do
|
199
|
+
temporary_class :C
|
200
|
+
C.singleton_class.send(:define_method, :f){}
|
201
|
+
C.singleton_class.send(:undef_method, :f)
|
202
|
+
Looksee.internal_undefined_instance_methods(C.singleton_class).should == [:f]
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should not return defined methods" do
|
206
|
+
temporary_class :C
|
207
|
+
C.send(:define_method, :f){}
|
208
|
+
Looksee.internal_undefined_instance_methods(C).should == []
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should not return removed methods" do
|
212
|
+
temporary_class :C
|
213
|
+
C.send(:define_method, :f){}
|
214
|
+
C.send(:remove_method, :f)
|
215
|
+
Looksee.internal_undefined_instance_methods(C).should == []
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe Looksee::LookupPath do
|
222
|
+
include TemporaryClasses
|
223
|
+
|
224
|
+
def stub_methods(mod, public, protected, private, undefined)
|
225
|
+
Looksee.stubs(:internal_public_instance_methods ).with(mod).returns(public)
|
226
|
+
Looksee.stubs(:internal_protected_instance_methods).with(mod).returns(protected)
|
227
|
+
Looksee.stubs(:internal_private_instance_methods ).with(mod).returns(private)
|
228
|
+
Looksee.stubs(:internal_undefined_instance_methods).with(mod).returns(undefined)
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "#entries" do
|
232
|
+
it "should contain an entry for each module in the object's lookup path" do
|
233
|
+
object = Object.new
|
234
|
+
temporary_class :C
|
235
|
+
temporary_class :D
|
236
|
+
Looksee.stubs(:lookup_modules).with(object).returns([C, D])
|
237
|
+
Looksee::LookupPath.for(object).entries.map{|entry| entry.module_name}.should == %w'C D'
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe "grep" do
|
242
|
+
it "should only include methods matching the given regexp" do
|
243
|
+
temporary_class :C
|
244
|
+
temporary_class :D
|
245
|
+
stub_methods(C, ['axbyc', 'xy'], [], [], [])
|
246
|
+
stub_methods(D, ['axbyc', 'xdy'], [], [], [])
|
247
|
+
object = Object.new
|
248
|
+
Looksee.stubs(:lookup_modules).with(object).returns([C, D])
|
249
|
+
lookup_path = Looksee::LookupPath.for(object, :public => true, :overridden => true).grep(/x.y/)
|
250
|
+
lookup_path.entries.map{|entry| entry.module_name}.should == %w'C D'
|
251
|
+
lookup_path.entries[0].methods.to_set.should == Set['axbyc']
|
252
|
+
lookup_path.entries[1].methods.to_set.should == Set['axbyc', 'xdy']
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should only include methods including the given string" do
|
256
|
+
temporary_class :C
|
257
|
+
temporary_class :D
|
258
|
+
stub_methods(C, ['axxa', 'axa'], [], [], [])
|
259
|
+
stub_methods(D, ['bxxb', 'axxa'], [], [], [])
|
260
|
+
object = Object.new
|
261
|
+
Looksee.stubs(:lookup_modules).with(object).returns([C, D])
|
262
|
+
lookup_path = Looksee::LookupPath.for(object, :public => true, :overridden => true).grep('xx')
|
263
|
+
lookup_path.entries.map{|entry| entry.module_name}.should == %w'C D'
|
264
|
+
lookup_path.entries[0].methods.to_set.should == Set['axxa']
|
265
|
+
lookup_path.entries[1].methods.to_set.should == Set['axxa', 'bxxb']
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
describe "#inspect" do
|
270
|
+
before do
|
271
|
+
Looksee.stubs(:default_lookup_path_options).returns({})
|
272
|
+
end
|
273
|
+
|
274
|
+
before do
|
275
|
+
Looksee.stubs(:styles).returns(Hash.new{'%s'})
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "contents" do
|
279
|
+
before do
|
280
|
+
temporary_module :M
|
281
|
+
temporary_class :C do
|
282
|
+
include M
|
283
|
+
end
|
284
|
+
@object = Object.new
|
285
|
+
Looksee.stubs(:lookup_modules).with(@object).returns([C, M])
|
286
|
+
stub_methods(C, ['public1', 'public2'], ['protected1', 'protected2'], ['private1', 'private2'], ['undefined1', 'undefined2'])
|
287
|
+
stub_methods(M, ['public1', 'public2'], ['protected1', 'protected2'], ['private1', 'private2'], ['undefined1', 'undefined2'])
|
288
|
+
end
|
289
|
+
|
290
|
+
it "should show only public instance methods when only public methods are requested" do
|
291
|
+
lookup_path = Looksee::LookupPath.for(@object, :public => true, :overridden => true)
|
292
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
293
|
+
|C
|
294
|
+
| public1 public2
|
295
|
+
|M
|
296
|
+
| public1 public2
|
297
|
+
EOS
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should show modules and protected instance methods when only protected methods are requested" do
|
301
|
+
lookup_path = Looksee::LookupPath.for(@object, :protected => true, :overridden => true)
|
302
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
303
|
+
|C
|
304
|
+
| protected1 protected2
|
305
|
+
|M
|
306
|
+
| protected1 protected2
|
307
|
+
EOS
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should show modules and private instance methods when only private methods are requested" do
|
311
|
+
lookup_path = Looksee::LookupPath.for(@object, :private => true, :overridden => true)
|
312
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
313
|
+
|C
|
314
|
+
| private1 private2
|
315
|
+
|M
|
316
|
+
| private1 private2
|
317
|
+
EOS
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should show modules and undefined instance methods when only undefined methods are requested" do
|
321
|
+
lookup_path = Looksee::LookupPath.for(@object, :undefined => true, :overridden => true)
|
322
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
323
|
+
|C
|
324
|
+
| undefined1 undefined2
|
325
|
+
|M
|
326
|
+
| undefined1 undefined2
|
327
|
+
EOS
|
328
|
+
end
|
329
|
+
|
330
|
+
it "should show modules with public and private instance methods when only public and private methods are requested" do
|
331
|
+
lookup_path = Looksee::LookupPath.for(@object, :public => true, :private => true, :overridden => true)
|
332
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
333
|
+
|C
|
334
|
+
| private1 private2 public1 public2
|
335
|
+
|M
|
336
|
+
| private1 private2 public1 public2
|
337
|
+
EOS
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should show singleton classes as class names in brackets" do
|
341
|
+
Looksee.stubs(:lookup_modules).with(C).returns([C.singleton_class])
|
342
|
+
stub_methods(C.singleton_class, ['public1', 'public2'], [], [], [])
|
343
|
+
lookup_path = Looksee::LookupPath.for(C, :public => true)
|
344
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
345
|
+
|[C]
|
346
|
+
| public1 public2
|
347
|
+
EOS
|
348
|
+
end
|
349
|
+
|
350
|
+
it "should handle singleton classes of singleton classes correctly" do
|
351
|
+
Looksee.stubs(:lookup_modules).with(C.singleton_class).returns([C.singleton_class.singleton_class])
|
352
|
+
stub_methods(C.singleton_class.singleton_class, ['public1', 'public2'], [], [], [])
|
353
|
+
lookup_path = Looksee::LookupPath.for(C.singleton_class, :public => true)
|
354
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
355
|
+
|[[C]]
|
356
|
+
| public1 public2
|
357
|
+
EOS
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should not show any blank lines if a module has no methods" do
|
361
|
+
stub_methods(C, [], [], [], [])
|
362
|
+
lookup_path = Looksee::LookupPath.for(@object, :public => true, :overridden => true)
|
363
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
364
|
+
|C
|
365
|
+
|M
|
366
|
+
| public1 public2
|
367
|
+
EOS
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
describe "styles" do
|
372
|
+
before do
|
373
|
+
styles = {
|
374
|
+
:module => "`%s'",
|
375
|
+
:public => "{%s}",
|
376
|
+
:protected => "[%s]",
|
377
|
+
:private => "<%s>",
|
378
|
+
:undefined => "~%s~",
|
379
|
+
:overridden => "(%s)",
|
380
|
+
}
|
381
|
+
Looksee.stubs(:styles).returns(styles)
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should delimit each word with the configured delimiters" do
|
385
|
+
temporary_class :C
|
386
|
+
Looksee.stubs(:lookup_modules).returns([C])
|
387
|
+
stub_methods(C, ['public'], ['protected'], ['private'], ['undefined'])
|
388
|
+
lookup_path = Looksee::LookupPath.for(Object.new, :public => true, :protected => true, :private => true, :undefined => true, :overridden => true)
|
389
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
390
|
+
|\`C\'
|
391
|
+
| <private> [protected] {public} ~undefined~
|
392
|
+
EOS
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "layout" do
|
397
|
+
it "should wrap method lists at the configured number of columns, sorting vertically first, and aligning into a grid" do
|
398
|
+
temporary_class :C
|
399
|
+
Looksee.stubs(:lookup_modules).returns([C])
|
400
|
+
stub_methods(C, %w'aa b c dd ee f g hh i', [], [], [])
|
401
|
+
lookup_path = Looksee::LookupPath.for(Object.new, :public => true)
|
402
|
+
lookup_path.inspect(:width => 20).should == <<-EOS.demargin.chomp
|
403
|
+
|C
|
404
|
+
| aa c ee g i
|
405
|
+
| b dd f hh
|
406
|
+
EOS
|
407
|
+
end
|
408
|
+
|
409
|
+
it "should lay the methods of each module out independently" do
|
410
|
+
temporary_class :A
|
411
|
+
temporary_class :B
|
412
|
+
Looksee.stubs(:lookup_modules).returns([A, B])
|
413
|
+
stub_methods(A, ['a', 'long_long_long_long_name'], [], [], [])
|
414
|
+
stub_methods(B, ['long_long_long', 'short'], [], [], [])
|
415
|
+
lookup_path = Looksee::LookupPath.for(Object.new, :public => true)
|
416
|
+
lookup_path.inspect.should == <<-EOS.demargin.chomp
|
417
|
+
|A
|
418
|
+
| a long_long_long_long_name
|
419
|
+
|B
|
420
|
+
| long_long_long short
|
421
|
+
EOS
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'mocha'
|
3
|
+
require 'looksee'
|
4
|
+
|
5
|
+
require 'rbconfig'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
Spec::Runner.configure do |config|
|
9
|
+
config.mock_with :mocha
|
10
|
+
end
|
11
|
+
|
12
|
+
class Object
|
13
|
+
#
|
14
|
+
# Return this object's singleton class.
|
15
|
+
#
|
16
|
+
def singleton_class
|
17
|
+
class << self; self; end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Return true if the given object include?-s this object.
|
22
|
+
#
|
23
|
+
def in?(object)
|
24
|
+
object.include?(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class String
|
29
|
+
#
|
30
|
+
# Remove a left margin delimited by '|'-characters. Useful for
|
31
|
+
# heredocs:
|
32
|
+
#
|
33
|
+
def demargin
|
34
|
+
gsub(/^ *\|/, '')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Include these in example groups to add facilities to create
|
40
|
+
# temporary classes and modules, which are swept up at the end of each
|
41
|
+
# example.
|
42
|
+
#
|
43
|
+
# Call make_class('ClassName') or make_module('ModuleName') to create
|
44
|
+
# a temporary class, then access them with plain constants (ClassName,
|
45
|
+
# ModuleName).
|
46
|
+
#
|
47
|
+
module TemporaryClasses
|
48
|
+
def self.included(mod)
|
49
|
+
mod.before do
|
50
|
+
@temporary_modules = []
|
51
|
+
end
|
52
|
+
|
53
|
+
mod.after do
|
54
|
+
@temporary_modules.each do |mod|
|
55
|
+
Object.send :remove_const, mod.name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Create a temporary class with the given name and superclass.
|
62
|
+
#
|
63
|
+
def temporary_class(name, options={}, &block)
|
64
|
+
klass = Class.new(options[:superclass] || Object)
|
65
|
+
Object.const_set(name, klass)
|
66
|
+
klass.class_eval(&block) if block
|
67
|
+
@temporary_modules << klass
|
68
|
+
klass
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Create a temporary module with the given name.
|
73
|
+
#
|
74
|
+
def temporary_module(name, &block)
|
75
|
+
mod = Module.new
|
76
|
+
Object.const_set(name, mod)
|
77
|
+
mod.class_eval(&block) if block
|
78
|
+
@temporary_modules << mod
|
79
|
+
mod
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Remove all methods defined exactly on the given module.
|
84
|
+
#
|
85
|
+
# As Ruby's reflection on singleton classes of classes isn't quite
|
86
|
+
# adequate, you need to provide a :class_singleton option when such
|
87
|
+
# a class is given.
|
88
|
+
#
|
89
|
+
def remove_methods(mod, opts={})
|
90
|
+
names = all_instance_methods(mod)
|
91
|
+
|
92
|
+
# all_instance_methods can't get just the methods on a class
|
93
|
+
# singleton class. Filter out superclass methods here.
|
94
|
+
if opts[:class_singleton]
|
95
|
+
klass = ObjectSpace.each_object(mod){|klass| break klass}
|
96
|
+
names -= all_instance_methods(klass.superclass.singleton_class)
|
97
|
+
end
|
98
|
+
|
99
|
+
names.sort_by{|name| name.in?([:remove_method, :send]) ? 1 : 0}.flatten
|
100
|
+
names.each do |name|
|
101
|
+
mod.send :remove_method, name
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Replace the methods of the given module with those named.
|
107
|
+
#
|
108
|
+
# +methods+ is a hash of visibilities to names.
|
109
|
+
#
|
110
|
+
# As Ruby's reflection on singleton classes of classes isn't quite
|
111
|
+
# adequate, you need to provide a :class_singleton option when such
|
112
|
+
# a class is given.
|
113
|
+
#
|
114
|
+
# e.g.:
|
115
|
+
#
|
116
|
+
# replace_methods MyClass, :public => [:a, :b]
|
117
|
+
#
|
118
|
+
def replace_methods(mod, options={})
|
119
|
+
remove_methods(mod, options)
|
120
|
+
mod.module_eval do
|
121
|
+
[:public, :protected, :private].each do |visibility|
|
122
|
+
Array(options[visibility]).each do |name|
|
123
|
+
define_method(name){}
|
124
|
+
send visibility, name
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private # ---------------------------------------------------------
|
131
|
+
|
132
|
+
def all_instance_methods(mod)
|
133
|
+
names =
|
134
|
+
mod.public_instance_methods(false) +
|
135
|
+
mod.protected_instance_methods(false) +
|
136
|
+
mod.private_instance_methods(false)
|
137
|
+
names.map{|name| name.to_sym} # they're strings in ruby <1.9
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
gem 'wirble' # die if wirble unavailable
|
3
|
+
|
4
|
+
describe Looksee::WirbleCompatibility do
|
5
|
+
describe "when looksee is loaded" do
|
6
|
+
#
|
7
|
+
# Run the given ruby string, and return the standard output.
|
8
|
+
#
|
9
|
+
def init_irb_with(code)
|
10
|
+
code = <<-EOS.demargin.gsub(/\n/, ';')
|
11
|
+
|#{code}
|
12
|
+
|#{stubbing_code}
|
13
|
+
|lp Object.new
|
14
|
+
EOS
|
15
|
+
irb = File.join Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'].sub(/ruby/, 'irb')
|
16
|
+
lib_dir = File.expand_path('lib')
|
17
|
+
# irb hangs when using readline without a tty
|
18
|
+
output = IO.popen("#{irb} -f --noreadline --noprompt --noverbose -I#{lib_dir} 2>&1", 'r+') do |io|
|
19
|
+
io.puts code
|
20
|
+
io.flush
|
21
|
+
io.close_write
|
22
|
+
io.read
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def stubbing_code
|
27
|
+
<<-EOS.demargin
|
28
|
+
|C = Class.new
|
29
|
+
|
|
30
|
+
|Looksee.styles = Hash.new{'%s'}
|
31
|
+
|Looksee.styles[:public] = "\\e[1;32m%s\\e[0m"
|
32
|
+
|
|
33
|
+
|def Looksee.lookup_modules(object)
|
34
|
+
| [C]
|
35
|
+
|end
|
36
|
+
|def Looksee.internal_public_instance_methods(mod)
|
37
|
+
| [:a]
|
38
|
+
|end
|
39
|
+
|def Looksee.internal_protected_instance_methods(mod)
|
40
|
+
| []
|
41
|
+
|end
|
42
|
+
|def Looksee.internal_private_instance_methods(mod)
|
43
|
+
| []
|
44
|
+
|end
|
45
|
+
EOS
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should work if wirble is not loaded" do
|
49
|
+
output = init_irb_with(<<-EOS.demargin)
|
50
|
+
|require 'irb'
|
51
|
+
|require 'looksee/shortcuts'
|
52
|
+
|require 'wirble'
|
53
|
+
|Wirble.init
|
54
|
+
|Wirble.colorize
|
55
|
+
EOS
|
56
|
+
output.should == <<-EOS.demargin
|
57
|
+
|C
|
58
|
+
| \e[1;32ma\e[0m
|
59
|
+
EOS
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should work if wirble is loaded, but not initialized" do
|
63
|
+
output = init_irb_with(<<-EOS.demargin)
|
64
|
+
|require 'irb'
|
65
|
+
|require 'wirble'
|
66
|
+
|require 'looksee/shortcuts'
|
67
|
+
|Wirble.init
|
68
|
+
|Wirble.colorize
|
69
|
+
EOS
|
70
|
+
output.should == <<-EOS.demargin
|
71
|
+
|C
|
72
|
+
| \e[1;32ma\e[0m
|
73
|
+
EOS
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should work if wirble is loaded and initialized, but colorizing is off" do
|
77
|
+
output = init_irb_with(<<-EOS.demargin)
|
78
|
+
|require 'irb'
|
79
|
+
|require 'wirble'
|
80
|
+
|Wirble.init
|
81
|
+
|require 'looksee/shortcuts'
|
82
|
+
|Wirble.colorize
|
83
|
+
EOS
|
84
|
+
output.should == <<-EOS.demargin
|
85
|
+
|C
|
86
|
+
| \e[1;32ma\e[0m
|
87
|
+
EOS
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should work if wirble is loaded, initialized, and colorizing is on" do
|
91
|
+
output = init_irb_with(<<-EOS.demargin)
|
92
|
+
|require 'irb'
|
93
|
+
|require 'wirble'
|
94
|
+
|Wirble.init
|
95
|
+
|Wirble.colorize
|
96
|
+
|require 'looksee/shortcuts'
|
97
|
+
EOS
|
98
|
+
output.should == <<-EOS.demargin
|
99
|
+
|C
|
100
|
+
| \e[1;32ma\e[0m
|
101
|
+
EOS
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should work if wirble colorizing is enabled twice" do
|
105
|
+
output = init_irb_with(<<-EOS.demargin)
|
106
|
+
|require 'irb'
|
107
|
+
|require 'looksee/shortcuts'
|
108
|
+
|require 'wirble'
|
109
|
+
|Wirble.init
|
110
|
+
|Wirble.colorize
|
111
|
+
|Wirble.colorize
|
112
|
+
EOS
|
113
|
+
output.should == <<-EOS.demargin
|
114
|
+
|C
|
115
|
+
| \e[1;32ma\e[0m
|
116
|
+
EOS
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|