namebox 0.0.0
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 +258 -0
- metadata +47 -0
data/lib/namebox.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
# Namebox for Ruby
|
2
|
+
#
|
3
|
+
# (c) 2012 Sony Fermino dos Santos
|
4
|
+
# http://rubychallenger.blogspot.com.br/2013/01/namebox.html
|
5
|
+
#
|
6
|
+
# License: Public Domain
|
7
|
+
# This software is released "AS IS", without any warranty.
|
8
|
+
# 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
|
+
|
13
|
+
class Namebox
|
14
|
+
|
15
|
+
CORE = (Module.constants - [:Config]).map { |c| Object.const_get(c) }.select { |m| m.is_a? Module }.uniq
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
# Get the caller info in a structured way (hash)
|
20
|
+
def caller_info
|
21
|
+
# search for last reference to this file in caller and take the next one
|
22
|
+
cr = caller.reverse
|
23
|
+
c = cr[cr.find_index { |s| s.start_with? __FILE__ } - 1]
|
24
|
+
raise "Unable to find a valid caller file in #{caller.inspect}" unless c
|
25
|
+
|
26
|
+
m = c.match(/^(.*?):(\d+)(:in `(.*)')?$/)
|
27
|
+
raise "Unexpected caller syntax in \"#{c}\"" unless m
|
28
|
+
|
29
|
+
{:file => m[1], :line => m[2].to_i, :method => m[4]}
|
30
|
+
end
|
31
|
+
|
32
|
+
def require resource, *modules_to_protect
|
33
|
+
new(*modules_to_protect) { TOPLEVEL_BINDING.eval("require '#{resource}'") }
|
34
|
+
end
|
35
|
+
|
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
|
42
|
+
end
|
43
|
+
if RUBY_VERSION >= "1.9"
|
44
|
+
get_modules19 Object
|
45
|
+
else
|
46
|
+
get_modules18 Object
|
47
|
+
end
|
48
|
+
@all_modules.uniq!
|
49
|
+
@all_modules.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
# set and get the default modules to protect, for the caller file.
|
53
|
+
def default_modules
|
54
|
+
(@default_modules ||= {})[caller_info[:file]] || []
|
55
|
+
end
|
56
|
+
|
57
|
+
def default_modules= modules_to_protect
|
58
|
+
(@default_modules ||= {})[caller_info[:file]] = [modules_to_protect].flatten
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
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
|
71
|
+
end
|
72
|
+
|
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) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# +modules+ are modules to protect.
|
85
|
+
# Must be the classes themselves, e.g., String, Symbol,
|
86
|
+
# not their names ("String" or :Symbol).
|
87
|
+
# Special names are:
|
88
|
+
# :all => protect all known modules and submodules. It's safer but slower.
|
89
|
+
# :core => protect the known top-level modules.
|
90
|
+
# :default => protect the modules defined in Namebox.default_modules for the caller file.
|
91
|
+
# Obs.: :core and :default can be used together and/or with other classes,
|
92
|
+
# but :all must be the only parameter if it is used.
|
93
|
+
def initialize *modules_to_protect, &blk
|
94
|
+
|
95
|
+
# initialize
|
96
|
+
@enabled_files = []
|
97
|
+
@protected_methods = []
|
98
|
+
|
99
|
+
# by default, get Namebox.default_modules
|
100
|
+
modules_to_protect = Namebox.default_modules if modules_to_protect.empty?
|
101
|
+
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
|
104
|
+
else
|
105
|
+
@modules = modules_to_protect.select { |m| m.is_a? Module }
|
106
|
+
@modules += Namebox.default_modules if modules_to_protect.include? :default
|
107
|
+
@modules += CORE if modules_to_protect.include? :core
|
108
|
+
@modules.uniq!
|
109
|
+
end
|
110
|
+
|
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?
|
112
|
+
|
113
|
+
# update modules list and save preexisting data
|
114
|
+
methods_before = get_methods
|
115
|
+
|
116
|
+
blk.call
|
117
|
+
|
118
|
+
# compare with preexisting data to discover affected methods
|
119
|
+
methods_after = get_methods
|
120
|
+
|
121
|
+
# refine the new methods!
|
122
|
+
unless methods_after == methods_before
|
123
|
+
|
124
|
+
this_nb = self
|
125
|
+
methods_after.each do |fullname, info|
|
126
|
+
|
127
|
+
info_old = methods_before[fullname] || {}
|
128
|
+
new_m = info[:method]
|
129
|
+
old_m = info_old[:method]
|
130
|
+
|
131
|
+
# don't touch unmodified methods
|
132
|
+
next if new_m == old_m
|
133
|
+
|
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
|
139
|
+
|
140
|
+
if info[:type] == :instance
|
141
|
+
owner.send :define_method, info[:name] do |*args, &blk|
|
142
|
+
if this_nb.open?
|
143
|
+
new_m.bind(self).call(*args, &blk)
|
144
|
+
else
|
145
|
+
# try previous method or super
|
146
|
+
begin
|
147
|
+
old_m ? old_m.bind(self).call(*args, &blk) : super(*args, &blk)
|
148
|
+
rescue NoMethodError
|
149
|
+
obj = self.inspect.to_s
|
150
|
+
obj = obj[0..45] + '...' + obj[-1] if obj.length > 50
|
151
|
+
raise NoMethodError.new("Undefined instance method `#{info[:name]}' for #{obj}:#{self.class}")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
else
|
156
|
+
# class methods
|
157
|
+
owner.send :define_method, info[:name] do |*args, &blk|
|
158
|
+
if this_nb.open?
|
159
|
+
new_m.call(*args, &blk)
|
160
|
+
else
|
161
|
+
# try previous method or super
|
162
|
+
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}")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
def open
|
176
|
+
info = ranges_info
|
177
|
+
|
178
|
+
# there must be no open range
|
179
|
+
raise "Namebox was already opened in #{info[:file]}:#{info[:last]}" if info[:last]
|
180
|
+
|
181
|
+
# range in progress
|
182
|
+
info[:ranges] << info[:line]
|
183
|
+
end
|
184
|
+
|
185
|
+
def close
|
186
|
+
info = ranges_info
|
187
|
+
|
188
|
+
# there must be an open range in progress
|
189
|
+
raise "Namebox was not opened in #{info[:file]} before line #{info[:line]}" unless info[:last]
|
190
|
+
|
191
|
+
# begin of range must be before end
|
192
|
+
r_beg = info[:last]
|
193
|
+
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)
|
196
|
+
|
197
|
+
# replace the single initial line with the range, making sure it's unique
|
198
|
+
info[:ranges].pop
|
199
|
+
info[:ranges] << r unless info[:ranges].include? r
|
200
|
+
end
|
201
|
+
|
202
|
+
def open?
|
203
|
+
# check file before checking ranges
|
204
|
+
ci = Namebox.caller_info
|
205
|
+
return true if @enabled_files.include? ci[:file]
|
206
|
+
|
207
|
+
# check ranges for this file
|
208
|
+
info = ranges_info ci
|
209
|
+
info[:ranges].each do |r|
|
210
|
+
case r
|
211
|
+
when Range
|
212
|
+
return true if r.include?(info[:line])
|
213
|
+
when Integer
|
214
|
+
return true if info[:line] >= r
|
215
|
+
end
|
216
|
+
end
|
217
|
+
false
|
218
|
+
end
|
219
|
+
|
220
|
+
def file_wide
|
221
|
+
@enabled_files << Namebox.caller_info[:file]
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
# Get line ranges info for the caller file
|
227
|
+
def ranges_info ci = Namebox.caller_info
|
228
|
+
ranges = enabled_ranges[ci[:file]] ||= []
|
229
|
+
ci[:ranges] = ranges
|
230
|
+
|
231
|
+
# check whether there is an opened range in progress for the caller file
|
232
|
+
last = ranges[-1]
|
233
|
+
ci[:last] = last if last.is_a? Integer
|
234
|
+
|
235
|
+
ci
|
236
|
+
end
|
237
|
+
|
238
|
+
# Stores enabled line ranges of caller files for this module
|
239
|
+
def enabled_ranges
|
240
|
+
@enabled_ranges ||= {}
|
241
|
+
end
|
242
|
+
|
243
|
+
# get methods in use
|
244
|
+
def get_methods
|
245
|
+
gm = {}
|
246
|
+
@modules.each do |c|
|
247
|
+
c.instance_methods.each do |m|
|
248
|
+
fullname = "#{c}##{m}"
|
249
|
+
gm[fullname] = {:type => :instance, :name => m, :method => c.instance_method(m) }
|
250
|
+
end
|
251
|
+
c.methods.each do |m|
|
252
|
+
fullname = "#{c}.#{m}"
|
253
|
+
gm[fullname] = {:type => :class, :name => m, :method => c.method(m) }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
gm
|
257
|
+
end
|
258
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: namebox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sony Santos
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-05 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A simple hello world gem
|
15
|
+
email: sony.fermino@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/namebox.rb
|
21
|
+
homepage: http://rubychallenger.blogspot.com.br/2013/01/namebox.html
|
22
|
+
licenses:
|
23
|
+
- Public Domain
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.8.7
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 1.8.24
|
43
|
+
signing_key:
|
44
|
+
specification_version: 3
|
45
|
+
summary: Create namespace boxes to protect the core classes' methods from changes,
|
46
|
+
like refinements
|
47
|
+
test_files: []
|