namebox 0.0.1 → 0.0.2
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/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
|