namebox 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/namebox.rb +298 -75
- metadata +2 -2
data/lib/namebox.rb
CHANGED
@@ -1,95 +1,160 @@
|
|
1
1
|
# Namebox for Ruby
|
2
2
|
#
|
3
|
-
# (c)
|
3
|
+
# (c) 2013 Sony Fermino dos Santos
|
4
4
|
# http://rubychallenger.blogspot.com.br/2013/01/namebox.html
|
5
5
|
#
|
6
6
|
# License: Public Domain
|
7
|
+
#
|
7
8
|
# This software is released "AS IS", without any warranty.
|
8
9
|
# The author is not responsible for the consequences of use of this software.
|
9
|
-
#
|
10
|
-
# This code is not intended to look professional,
|
11
|
-
# provided that it does what it is supposed to do.
|
12
10
|
|
13
11
|
class Namebox
|
14
12
|
|
15
|
-
|
13
|
+
# currently loaded top-level modules
|
14
|
+
CORE = (Module.constants - [:Config]).
|
15
|
+
map { |c| Object.const_get(c) }.
|
16
|
+
select { |m| m.is_a? Module }.uniq
|
16
17
|
|
17
18
|
class << self
|
18
19
|
|
20
|
+
# wrapper to create a namebox only to protect modules when requiring
|
21
|
+
def require resource, *modules_to_protect
|
22
|
+
|
23
|
+
new(*modules_to_protect) do
|
24
|
+
|
25
|
+
# need to refer to top-level binding, which is lost inside this def.
|
26
|
+
TOPLEVEL_BINDING.eval("require '#{resource}'")
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# set and get the default modules to protect, for the caller file.
|
33
|
+
def default_modules
|
34
|
+
(@default_modules ||= {})[caller_info[:file]] || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_modules= modules_to_protect
|
38
|
+
(@default_modules ||= {})[caller_info[:file]] = [modules_to_protect].flatten
|
39
|
+
end
|
40
|
+
|
19
41
|
# Get the caller info in a structured way (hash)
|
20
42
|
def caller_info
|
43
|
+
|
21
44
|
# search for last reference to this file in caller and take the next one
|
22
45
|
cr = caller.reverse
|
23
|
-
c = cr[cr.
|
46
|
+
c = cr[cr.index { |s| s.start_with? __FILE__ } - 1]
|
24
47
|
raise "Unable to find a valid caller file in #{caller.inspect}" unless c
|
25
48
|
|
49
|
+
# match the info
|
26
50
|
m = c.match(/^(.*?):(\d+)(:in `(.*)')?$/)
|
27
51
|
raise "Unexpected caller syntax in \"#{c}\"" unless m
|
28
52
|
|
53
|
+
# label it
|
29
54
|
{:file => m[1], :line => m[2].to_i, :method => m[4]}
|
55
|
+
|
30
56
|
end
|
31
57
|
|
32
|
-
|
33
|
-
|
58
|
+
# get the superclass of ancestor in the base's ancestors list
|
59
|
+
def super_ancestor(base, ancestor)
|
60
|
+
ba = base.ancestors
|
61
|
+
i = ba.index(ancestor)
|
62
|
+
ba[i + 1] if i
|
34
63
|
end
|
35
64
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
65
|
+
# get the instance method from superclass of ancestor
|
66
|
+
# in the base_class' ancestors list
|
67
|
+
#
|
68
|
+
def super_method(name, base_class, ancestor = base_class)
|
69
|
+
|
70
|
+
super_class = super_ancestor(base_class, ancestor)
|
71
|
+
|
72
|
+
# no super_class!
|
73
|
+
raise NameError unless super_class
|
74
|
+
|
75
|
+
# get the super method (can throw NameError if it doesn't exist)
|
76
|
+
super_class.instance_method(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def super_owner(name, base_class, ancestor)
|
80
|
+
begin
|
81
|
+
super_method(name, base_class, ancestor).owner
|
82
|
+
rescue NameError
|
42
83
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
84
|
+
end
|
85
|
+
|
86
|
+
###################################################
|
87
|
+
# CLASS METHODS HELPERS
|
88
|
+
###################################################
|
89
|
+
|
90
|
+
# build ancestors list for class methods
|
91
|
+
def class_ancestors(klass)
|
92
|
+
list = []
|
93
|
+
|
94
|
+
# in reverse order is easier to add modules in the correct order
|
95
|
+
# and to avoid redundancy thru Array#|
|
96
|
+
#
|
97
|
+
klass.ancestors.reverse.each do |a|
|
98
|
+
|
99
|
+
# get eigenclass ancestors
|
100
|
+
aa = class << a; self; end
|
101
|
+
|
102
|
+
# add new modules to list
|
103
|
+
list |= aa.ancestors.reverse
|
104
|
+
|
105
|
+
# add current ancestor to list
|
106
|
+
list << a
|
47
107
|
end
|
48
|
-
|
49
|
-
|
108
|
+
|
109
|
+
# reverse to the correct order
|
110
|
+
list.reverse
|
50
111
|
end
|
51
112
|
|
52
|
-
|
53
|
-
|
54
|
-
|
113
|
+
def class_owner(mtd)
|
114
|
+
owner = mtd.owner
|
115
|
+
return owner if owner.instance_of? Module
|
116
|
+
ObjectSpace.each_object(owner).to_a.last
|
55
117
|
end
|
56
118
|
|
57
|
-
|
58
|
-
|
119
|
+
# get the superclass of ancestor in the base's ancestors list
|
120
|
+
def class_super_ancestor(base, ancestor)
|
121
|
+
ca = class_ancestors(base)
|
122
|
+
i = ca.index(ancestor)
|
123
|
+
ca[i + 1] if i
|
59
124
|
end
|
60
125
|
|
61
|
-
|
126
|
+
# get the class method from superclass of ancestor
|
127
|
+
# in the base_class' ancestors list
|
128
|
+
#
|
129
|
+
def class_super_method(name, base_class, ancestor = base_class)
|
62
130
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
131
|
+
super_class = class_super_ancestor(base_class, ancestor)
|
132
|
+
|
133
|
+
# no super_class!
|
134
|
+
raise NameError unless super_class
|
135
|
+
|
136
|
+
# get the super method (can throw NameError if it doesn't exist)
|
137
|
+
super_class.method(name)
|
71
138
|
end
|
72
139
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@all_modules.push *new_mods
|
78
|
-
new_mods.each { |m| get_modules19(m) }
|
140
|
+
def class_super_owner(name, base_class, ancestor)
|
141
|
+
begin
|
142
|
+
class_owner(class_super_method(name, base_class, ancestor))
|
143
|
+
rescue NameError
|
79
144
|
end
|
80
145
|
end
|
81
146
|
|
82
147
|
end
|
83
148
|
|
84
|
-
# +
|
85
|
-
# Must be the classes themselves, e.g., String, Symbol,
|
149
|
+
# +modules_to_protect+ must be the classes themselves, e.g., String, Symbol,
|
86
150
|
# not their names ("String" or :Symbol).
|
87
151
|
# Special names are:
|
88
|
-
# :all => protect all known modules and submodules. It's safer but
|
152
|
+
# :all => protect all known modules and submodules. It's safer but slooow.
|
89
153
|
# :core => protect the known top-level modules.
|
90
|
-
# :default => protect the modules defined in Namebox.default_modules
|
154
|
+
# :default => protect the modules defined in Namebox.default_modules.
|
91
155
|
# Obs.: :core and :default can be used together and/or with other classes,
|
92
156
|
# but :all must be the only parameter if it is used.
|
157
|
+
#
|
93
158
|
def initialize *modules_to_protect, &blk
|
94
159
|
|
95
160
|
# initialize
|
@@ -98,32 +163,63 @@ class Namebox
|
|
98
163
|
|
99
164
|
# by default, get Namebox.default_modules
|
100
165
|
modules_to_protect = Namebox.default_modules if modules_to_protect.empty?
|
166
|
+
|
167
|
+
# check for :all modules
|
101
168
|
if modules_to_protect.include? :all
|
102
|
-
|
103
|
-
|
169
|
+
|
170
|
+
# :all must be the only parameter
|
171
|
+
if modules_to_protect.length > 1
|
172
|
+
raise "If :all is used in Namebox.new, :all must be the only parameter!"
|
173
|
+
end
|
174
|
+
|
175
|
+
# get all modules
|
176
|
+
@modules = ObjectSpace.each_object(Module).to_a
|
177
|
+
|
104
178
|
else
|
179
|
+
|
180
|
+
# take off the symbols
|
105
181
|
@modules = modules_to_protect.select { |m| m.is_a? Module }
|
182
|
+
|
183
|
+
# include default modules if wanted
|
106
184
|
@modules += Namebox.default_modules if modules_to_protect.include? :default
|
185
|
+
|
186
|
+
# include core modules if wanted
|
107
187
|
@modules += CORE if modules_to_protect.include? :core
|
188
|
+
|
189
|
+
# avoid redundancy
|
108
190
|
@modules.uniq!
|
191
|
+
|
109
192
|
end
|
110
193
|
|
111
|
-
|
194
|
+
# modules must be given or Namebox.default_modules must be set
|
195
|
+
if @modules.empty?
|
196
|
+
raise ("Modules to protect were not given and there's no " +
|
197
|
+
"Namebox.default_modules defined for file " +
|
198
|
+
Namebox.caller_info[:file])
|
199
|
+
end
|
112
200
|
|
113
|
-
#
|
201
|
+
# save preexisting methods
|
114
202
|
methods_before = get_methods
|
115
203
|
|
204
|
+
# ###############################################################
|
205
|
+
# RUN THE CODE, which can change the methods of protected modules
|
206
|
+
#
|
116
207
|
blk.call
|
208
|
+
#
|
209
|
+
# ###############################################################
|
117
210
|
|
118
|
-
#
|
211
|
+
# get methods after changes
|
119
212
|
methods_after = get_methods
|
120
213
|
|
121
|
-
#
|
214
|
+
# compare with preexisting data to discover affected methods
|
122
215
|
unless methods_after == methods_before
|
123
216
|
|
217
|
+
# thru closure, this namebox will be useful inside redefined methods
|
124
218
|
this_nb = self
|
219
|
+
|
125
220
|
methods_after.each do |fullname, info|
|
126
221
|
|
222
|
+
# get old method
|
127
223
|
info_old = methods_before[fullname] || {}
|
128
224
|
new_m = info[:method]
|
129
225
|
old_m = info_old[:method]
|
@@ -131,75 +227,187 @@ class Namebox
|
|
131
227
|
# don't touch unmodified methods
|
132
228
|
next if new_m == old_m
|
133
229
|
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
next if @protected_methods.include? protected_method
|
138
|
-
@protected_methods << protected_method
|
230
|
+
# method was modified! take some info
|
231
|
+
name = info[:name]
|
232
|
+
klass = info[:class]
|
139
233
|
|
140
|
-
|
141
|
-
|
234
|
+
# instance and class methods are managed differently
|
235
|
+
if info[:instance]
|
236
|
+
|
237
|
+
##########################################################
|
238
|
+
#
|
239
|
+
# PROTECT INSTANCE METHODS
|
240
|
+
#
|
241
|
+
##########################################################
|
242
|
+
|
243
|
+
# use closure to avoid recalculations inside method
|
244
|
+
old_owner = old_m && old_m.owner
|
245
|
+
|
246
|
+
# owners to skip
|
247
|
+
new_owners = []
|
248
|
+
o = new_m.owner
|
249
|
+
while o && o != old_owner
|
250
|
+
new_owners << o
|
251
|
+
o = Namebox.super_owner(name, klass, o)
|
252
|
+
end
|
253
|
+
|
254
|
+
## print "#{klass}##{name}: "; p new_owners;
|
255
|
+
|
256
|
+
# redefine the method, which will check namebox visibility dinamically
|
257
|
+
klass.send :define_method, name do |*args, &blk|
|
258
|
+
|
259
|
+
# check namebox visibility
|
142
260
|
if this_nb.open?
|
261
|
+
|
262
|
+
# call the protected (by namebox) method
|
263
|
+
# instance methods are unbound; must bind to self
|
143
264
|
new_m.bind(self).call(*args, &blk)
|
265
|
+
|
144
266
|
else
|
145
|
-
|
267
|
+
|
268
|
+
# search a different method to call
|
269
|
+
# must be dynamic because it can change anytime
|
270
|
+
# (someone can attach a new definition to some ancestor)
|
271
|
+
|
146
272
|
begin
|
147
|
-
|
148
|
-
|
273
|
+
# try before owner (after klass - this method)
|
274
|
+
m = Namebox.super_method(name, klass)
|
275
|
+
|
276
|
+
# skip new owners
|
277
|
+
while new_owners.include? m.owner
|
278
|
+
m = Namebox.super_method(name, klass, m.owner)
|
279
|
+
end
|
280
|
+
|
281
|
+
rescue NameError
|
282
|
+
|
283
|
+
# didn't find a different method; raise NoMethodError
|
284
|
+
|
285
|
+
# if inspect is too big, shorten it
|
149
286
|
obj = self.inspect.to_s
|
150
287
|
obj = obj[0..45] + '...' + obj[-1] if obj.length > 50
|
151
|
-
|
288
|
+
|
289
|
+
raise NoMethodError.new("Undefined instance method `#{name}' " +
|
290
|
+
"for #{obj}:#{self.class}")
|
152
291
|
end
|
292
|
+
|
293
|
+
# play as self
|
294
|
+
m.bind(self).call(*args, &blk)
|
153
295
|
end
|
154
296
|
end
|
297
|
+
|
155
298
|
else
|
156
|
-
|
157
|
-
|
299
|
+
|
300
|
+
##########################################################
|
301
|
+
#
|
302
|
+
# PROTECT CLASS METHODS
|
303
|
+
#
|
304
|
+
##########################################################
|
305
|
+
|
306
|
+
# use closure to avoid recalculations inside method
|
307
|
+
old_owner = old_m && Namebox.class_owner(old_m)
|
308
|
+
|
309
|
+
# owners to skip
|
310
|
+
new_owners = []
|
311
|
+
o = Namebox.class_owner(new_m)
|
312
|
+
while o && o != old_owner
|
313
|
+
new_owners << o
|
314
|
+
o = Namebox.class_super_owner(name, klass, o)
|
315
|
+
end
|
316
|
+
|
317
|
+
## print "#{klass.inspect}.#{name}: "; p new_owners;
|
318
|
+
|
319
|
+
# redefine class method thru eigenclass
|
320
|
+
class << klass; self; end.send :define_method, name do |*args, &blk|
|
321
|
+
|
322
|
+
# check namebox visibility
|
158
323
|
if this_nb.open?
|
324
|
+
|
325
|
+
# call the protected (by namebox) method
|
326
|
+
# new_m is already bound to self; just call it
|
159
327
|
new_m.call(*args, &blk)
|
328
|
+
|
160
329
|
else
|
161
|
-
|
330
|
+
|
331
|
+
# search a different method to call
|
332
|
+
# must be dynamic because it can change anytime
|
333
|
+
# (someone can extend an ancestor with some module)
|
334
|
+
|
162
335
|
begin
|
163
|
-
|
164
|
-
|
165
|
-
|
336
|
+
# try before owner (after klass - this method)
|
337
|
+
m = Namebox.class_super_method(name, klass)
|
338
|
+
|
339
|
+
# skip new owners
|
340
|
+
while new_owners.include?(m_owner = Namebox.class_owner(m))
|
341
|
+
m = Namebox.class_super_method(name, klass, m_owner)
|
342
|
+
end
|
343
|
+
|
344
|
+
# bind to self (can throw TypeError in 1.8.7)
|
345
|
+
m = m.unbind.bind(self)
|
346
|
+
|
347
|
+
rescue NameError
|
348
|
+
|
349
|
+
# didn't find a different method
|
350
|
+
raise NoMethodError.new("Undefined class method `#{name}' " +
|
351
|
+
"for #{self}:#{self.class}")
|
352
|
+
|
353
|
+
rescue TypeError
|
354
|
+
|
355
|
+
# Ruby 1.8.7 does not bind singleton methods to descendant class;
|
356
|
+
# use current m anyway
|
357
|
+
|
166
358
|
end
|
359
|
+
|
360
|
+
# run!
|
361
|
+
m.call(*args, &blk)
|
362
|
+
|
167
363
|
end
|
168
364
|
end
|
169
365
|
end
|
170
366
|
end
|
171
367
|
end
|
172
|
-
|
173
368
|
end
|
174
369
|
|
370
|
+
|
371
|
+
# open a namebox region for visibility in the caller file at caller line
|
175
372
|
def open
|
176
373
|
info = ranges_info
|
177
374
|
|
178
375
|
# there must be no open range
|
179
|
-
|
376
|
+
if info[:last]
|
377
|
+
raise "Namebox was already opened in #{info[:file]}:#{info[:last]}"
|
378
|
+
end
|
180
379
|
|
181
380
|
# range in progress
|
182
381
|
info[:ranges] << info[:line]
|
183
382
|
end
|
184
383
|
|
384
|
+
|
385
|
+
# close the namebox region in the caller file
|
185
386
|
def close
|
186
387
|
info = ranges_info
|
187
388
|
|
188
389
|
# there must be an open range in progress
|
189
|
-
|
390
|
+
unless info[:last]
|
391
|
+
raise "Namebox was not opened in #{info[:file]} before line #{info[:line]}"
|
392
|
+
end
|
190
393
|
|
191
394
|
# begin of range must be before end
|
192
395
|
r_beg = info[:last]
|
193
396
|
r_end = info[:line]
|
194
|
-
|
195
|
-
|
397
|
+
unless r_end >= r_beg
|
398
|
+
raise ("Namebox#close in #{info[:file]}:#{r_end} should be after " +
|
399
|
+
"Namebox#open (line #{r_beg})")
|
400
|
+
end
|
196
401
|
|
197
402
|
# replace the single initial line with the range, making sure it's unique
|
403
|
+
r = Range.new(r_beg, r_end)
|
198
404
|
info[:ranges].pop
|
199
405
|
info[:ranges] << r unless info[:ranges].include? r
|
200
406
|
end
|
201
407
|
|
408
|
+
# check namebox' visibility
|
202
409
|
def open?
|
410
|
+
|
203
411
|
# check file before checking ranges
|
204
412
|
ci = Namebox.caller_info
|
205
413
|
return true if @enabled_files.include? ci[:file]
|
@@ -209,14 +417,22 @@ class Namebox
|
|
209
417
|
info[:ranges].each do |r|
|
210
418
|
case r
|
211
419
|
when Range
|
420
|
+
|
421
|
+
# check if caller is in an open range for this namebox
|
212
422
|
return true if r.include?(info[:line])
|
423
|
+
|
213
424
|
when Integer
|
425
|
+
|
426
|
+
# check if caller is after an initied range (Namebox#open)
|
214
427
|
return true if info[:line] >= r
|
428
|
+
|
215
429
|
end
|
216
430
|
end
|
217
431
|
false
|
218
432
|
end
|
219
433
|
|
434
|
+
|
435
|
+
# open namebox in entire caller file (valid only after called!)
|
220
436
|
def file_wide
|
221
437
|
@enabled_files << Namebox.caller_info[:file]
|
222
438
|
end
|
@@ -225,6 +441,7 @@ class Namebox
|
|
225
441
|
|
226
442
|
# Get line ranges info for the caller file
|
227
443
|
def ranges_info ci = Namebox.caller_info
|
444
|
+
|
228
445
|
ranges = enabled_ranges[ci[:file]] ||= []
|
229
446
|
ci[:ranges] = ranges
|
230
447
|
|
@@ -240,19 +457,25 @@ class Namebox
|
|
240
457
|
@enabled_ranges ||= {}
|
241
458
|
end
|
242
459
|
|
243
|
-
# get methods in use
|
460
|
+
# get methods in use thru modules to protect
|
244
461
|
def get_methods
|
245
462
|
gm = {}
|
463
|
+
|
246
464
|
@modules.each do |c|
|
465
|
+
|
247
466
|
c.instance_methods.each do |m|
|
248
467
|
fullname = "#{c}##{m}"
|
249
|
-
gm[fullname] = {:
|
468
|
+
gm[fullname] = {:class => c, :instance => true, :name => m,
|
469
|
+
:method => c.instance_method(m) }
|
250
470
|
end
|
471
|
+
|
251
472
|
c.methods.each do |m|
|
252
473
|
fullname = "#{c}.#{m}"
|
253
|
-
gm[fullname] = {:
|
474
|
+
gm[fullname] = {:class => c, :name => m, :method => c.method(m) }
|
254
475
|
end
|
476
|
+
|
255
477
|
end
|
478
|
+
|
256
479
|
gm
|
257
480
|
end
|
258
481
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: namebox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-11 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: sony.fermino@gmail.com
|