namebox 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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