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.
Files changed (2) hide show
  1. data/lib/namebox.rb +298 -75
  2. metadata +2 -2
data/lib/namebox.rb CHANGED
@@ -1,95 +1,160 @@
1
1
  # Namebox for Ruby
2
2
  #
3
- # (c) 2012 Sony Fermino dos Santos
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
- CORE = (Module.constants - [:Config]).map { |c| Object.const_get(c) }.select { |m| m.is_a? Module }.uniq
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.find_index { |s| s.start_with? __FILE__ } - 1]
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
- def require resource, *modules_to_protect
33
- new(*modules_to_protect) { TOPLEVEL_BINDING.eval("require '#{resource}'") }
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
- def all_modules
37
- unless @all_modules
38
- @all_modules = [Object, Module, Class]
39
- @all_modules << BasicObject if RUBY_VERSION >= '1.9'
40
- # must run twice to get all modules correctly
41
- all_modules
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
- if RUBY_VERSION >= "1.9"
44
- get_modules19 Object
45
- else
46
- get_modules18 Object
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
- @all_modules.uniq!
49
- @all_modules.dup
108
+
109
+ # reverse to the correct order
110
+ list.reverse
50
111
  end
51
112
 
52
- # set and get the default modules to protect, for the caller file.
53
- def default_modules
54
- (@default_modules ||= {})[caller_info[:file]] || []
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
- def default_modules= modules_to_protect
58
- (@default_modules ||= {})[caller_info[:file]] = [modules_to_protect].flatten
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
- private
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
- # get modules in use (differs from 1.8 to 1.9)
64
- def get_modules18 base
65
- new_mods = base.constants.map { |m| base.const_get(m) }.
66
- select { |m| m.is_a?(Module) && !@all_modules.include?(m)}
67
- unless new_mods.length == 0
68
- @all_modules.push *new_mods
69
- new_mods.each { |m| get_modules18(m) }
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 get_modules19 base
74
- new_mods = (base.constants - [:Config]).map { |m| base.const_get(m) }.
75
- select { |m| m.is_a?(Module) && !@all_modules.include?(m)}
76
- unless new_mods.length == 0
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
- # +modules+ are modules to protect.
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 slower.
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 for the caller file.
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
- raise "If :all is used in Namebox.new, :all must be the only parameter!" if modules_to_protect.length > 1
103
- @modules = Namebox.all_modules
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
- raise "Modules to protect were not given and there's no Namebox.default_modules defined for file #{Namebox.caller_info[:file]}" if @modules.empty?
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
- # update modules list and save preexisting data
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
- # compare with preexisting data to discover affected methods
211
+ # get methods after changes
119
212
  methods_after = get_methods
120
213
 
121
- # refine the new methods!
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
- # avoid reprotecting a method
135
- owner = new_m.owner
136
- protected_method = "#{owner}.#{info[:name]}"
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
- if info[:type] == :instance
141
- owner.send :define_method, info[:name] do |*args, &blk|
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
- # try previous method or super
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
- old_m ? old_m.bind(self).call(*args, &blk) : super(*args, &blk)
148
- rescue NoMethodError
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
- raise NoMethodError.new("Undefined instance method `#{info[:name]}' for #{obj}:#{self.class}")
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
- # class methods
157
- owner.send :define_method, info[:name] do |*args, &blk|
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
- # try previous method or super
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
- old_m ? old_m.call(*args, &blk) : super(*args, &blk)
164
- rescue NoMethodError
165
- raise NoMethodError.new("Undefined class method `#{info[:name]}' for #{self}:#{self.class}")
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
- raise "Namebox was already opened in #{info[:file]}:#{info[:last]}" if info[:last]
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
- raise "Namebox was not opened in #{info[:file]} before line #{info[:line]}" unless info[:last]
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
- raise "Namebox#close in #{info[:file]}:#{r_end} should be after Namebox#open (line #{r_beg})" unless r_end >= r_beg
195
- r = Range.new(r_beg, r_end)
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] = {:type => :instance, :name => m, :method => c.instance_method(m) }
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] = {:type => :class, :name => m, :method => c.method(m) }
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.1
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-05 00:00:00.000000000 Z
12
+ date: 2013-01-11 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: sony.fermino@gmail.com