Class: Namebox
- Inherits:
-
Object
- Object
- Namebox
- Defined in:
- lib/namebox.rb
Overview
Namebox for Ruby
© 2013 Sony Fermino dos Santos rubychallenger.blogspot.com.br/2013/01/namebox.html
License: Public Domain
This software is released “AS IS”, without any warranty. The author is not responsible for the consequences of use of this software.
This version is only compatible with Ruby 1.9.2 or greater. For use with Ruby 1.8.7 or 1.9.1, get the version 0.1.8 or 0.1.9.
Constant Summary collapse
- CORE =
currently loaded top-level modules
(Module.constants - [:Config]). map { |c| Object.const_get(c) }. select { |m| m.is_a? Module }.uniq
Class Method Summary collapse
-
.caller_info ⇒ Object
Get the caller info in a structured way (hash).
-
.default_modules ⇒ Object
Array with default modules to protect.
-
.default_modules=(modules_to_protect) ⇒ Object
Set the default modules to protect (file-wide, for the caller file).
-
.no_method_error(obj, m_name) ⇒ Object
Raises NoMethodError, limiting the length of
obj.inspect
. -
.require(resource, *modules_to_protect) ⇒ Object
Wrapper to create a namebox only to protect modules when requiring.
Instance Method Summary collapse
-
#close ⇒ Object
Close the namebox visible region in the caller file.
-
#file_wide ⇒ Object
Open namebox in entire caller file (valid only after called!).
-
#initialize(*modules_to_protect, &blk) ⇒ Namebox
constructor
modules_to_protect
must be the classes themselves, e.g., String, Symbol, not their names (“String” or :Symbol).
Special names are:
:all
=> protect all known modules and submodules. -
#open ⇒ Object
Open a namebox region for visibility in the caller file at caller line.
-
#open? ⇒ Boolean
Check namebox visibility (openness).
Constructor Details
#initialize(*modules_to_protect, &blk) ⇒ Namebox
modules_to_protect
must be the classes themselves, e.g., String, Symbol, not their names (“String” or :Symbol).
Special names are:
:all
=> protect all known modules and submodules. It’s safer but slower.
:core
=> protect the known top-level modules.
:default
=> protect the modules defined in Namebox.default_modules
.
Obs.: :core
and :default
can be used together and/or with other classes, but :all
must be the only parameter if it is used.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/namebox.rb', line 92 def initialize *modules_to_protect, &blk # initialize @enabled_files = [] @protected_methods = [] # this_nb will be useful in a closure, where self will be different this_nb = self # by default, get Namebox.default_modules modules_to_protect = Namebox.default_modules if modules_to_protect.empty? # check for :all modules if modules_to_protect.include? :all # :all must be the only parameter if modules_to_protect.length > 1 raise "If :all is used in Namebox.new, :all must be the only parameter!" end # get all modules @modules = ObjectSpace.each_object(Module).to_a else # take off the symbols modules = modules_to_protect.select { |m| m.is_a? Module } @modules = modules # include default modules if wanted @modules += Namebox.default_modules if modules_to_protect.include? :default # include core modules if wanted @modules += CORE if modules_to_protect.include? :core # avoid redundancy @modules.uniq! # include all ancestors for modules and eigenclasses of classes modules.each do |m| @modules |= m.ancestors @modules |= class << m; self; end.ancestors if m.is_a? Class end end # modules must be given or Namebox.default_modules must be set if @modules.empty? raise ("Modules to protect were not given and there's no " + "Namebox.default_modules defined for file " + Namebox.caller_info[:file]) end # select classes to protect against included modules @classes = @modules.select { |m| m.is_a? Class } # get eigenclasses eigenclasses = @classes.map { |c| class << c; self; end } # include eigenclasses into @classes and @modules @classes |= eigenclasses @modules |= eigenclasses # save preexisting methods and included modules inc_mods_before = get_included_modules methods_before = get_methods # ############################################################### # RUN THE CODE, which can change the methods of protected modules # blk.call # # ############################################################### # get data after changes inc_mods_after = get_included_modules methods_after = get_methods # compare with preexisting data to discover affected methods and modules # compare included modules (before vs after) unless inc_mods_after == inc_mods_before inc_mods_after.each do |klass, inc_mods| old_modules = inc_mods_before[klass] new_modules = inc_mods - old_modules # there's no new included module for this class next if new_modules.empty? new_modules.each do |new_module| # Get a protector module for new_module; don't recreate it # if a protector was already created for new_module. # protector = protector_module(new_module) # reincludes the new_module with super_tunnel # to allow bind(self) inside protector code. # klass.send :include, new_module # finally, include the protector in the class klass.send :include, protector end end end # Compare changed methods (before vs after) unless methods_after == methods_before methods_after.each do |fullname, info| # get old method info_old = methods_before[fullname] || {} new_method = info[:method] old_method = info_old[:method] # don't touch unmodified methods next if new_method == old_method # method was modified! take some info method_name = info[:name] klass = info[:class] # redefine the method, which will check namebox visibility dinamically klass.send :define_method, method_name do |*args, &blk| # check namebox visibility if this_nb.open? # namebox method; bind instance method to self. new_method.bind(self).call(*args, &blk) else # old method or super if old_method old_method.bind(self).call(*args, &blk) else super(*args, &blk) end end end end end end |
Class Method Details
.caller_info ⇒ Object
Get the caller info in a structured way (hash).
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/namebox.rb', line 53 def caller_info # search for last reference to this file in caller and take the next one cr = caller.reverse c = cr[cr.index { |s| s.start_with? __FILE__ } - 1] raise "Unable to find a valid caller file in #{caller.inspect}" unless c # match the info m = c.match(/^(.*?):(\d+)(:in `(.*)')?$/) raise "Unexpected caller syntax in \"#{c}\"" unless m # label them {:file => m[1], :line => m[2].to_i, :method => m[4]} end |
.default_modules ⇒ Object
Array with default modules to protect. You may want redefine this, protecting Namebox itself inside another namebox, and assign it to a constant, which can be available thru other files in the project.
48 49 50 |
# File 'lib/namebox.rb', line 48 def default_modules (@default_modules ||= {})[caller_info[:file]] || [] end |
.default_modules=(modules_to_protect) ⇒ Object
Set the default modules to protect (file-wide, for the caller file). Each file you want to use #default_modules, you must define them again. This is to avoid conflicts when using other people libraries, which may want to protect different modules by default. See #default_modules
40 41 42 |
# File 'lib/namebox.rb', line 40 def default_modules= modules_to_protect (@default_modules ||= {})[caller_info[:file]] = [modules_to_protect].flatten end |
.no_method_error(obj, m_name) ⇒ Object
Raises NoMethodError, limiting the length of obj.inspect
.
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/namebox.rb', line 70 def no_method_error(obj, m_name) # if inspect is too big, shorten it obj_name = obj.inspect.to_s obj_name = obj_name[0..45] + '...' + obj_name[-1] if obj_name.length > 50 msg = "Undefined method `#{m_name}' for #{obj_name}:#{obj.class}" raise NoMethodError.new(msg) end |
.require(resource, *modules_to_protect) ⇒ Object
Wrapper to create a namebox only to protect modules when requiring.
24 25 26 27 28 29 30 31 32 33 |
# File 'lib/namebox.rb', line 24 def require resource, *modules_to_protect new(*modules_to_protect) do # need to refer to top-level binding, which is lost inside this def. TOPLEVEL_BINDING.eval("require '#{resource}'") end end |
Instance Method Details
#close ⇒ Object
Close the namebox visible region in the caller file.
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/namebox.rb', line 257 def close info = ranges_info # there must be an open range in progress unless info[:last] raise "Namebox was not opened in #{info[:file]} before line #{info[:line]}" end # begin of range must be before end r_beg = info[:last] r_end = info[:line] unless r_end >= r_beg raise ("Namebox#close in #{info[:file]}:#{r_end} should be after " + "Namebox#open (line #{r_beg})") end # replace the single initial line with the range, making sure it's unique r = Range.new(r_beg, r_end) info[:ranges].pop info[:ranges] << r unless info[:ranges].include? r end |
#file_wide ⇒ Object
Open namebox in entire caller file (valid only after called!).
306 307 308 |
# File 'lib/namebox.rb', line 306 def file_wide @enabled_files << Namebox.caller_info[:file] end |
#open ⇒ Object
Open a namebox region for visibility in the caller file at caller line.
243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/namebox.rb', line 243 def open info = ranges_info # there must be no open range if info[:last] raise "Namebox was already opened in #{info[:file]}:#{info[:last]}" end # range in progress info[:ranges] << info[:line] end |
#open? ⇒ Boolean
Check namebox visibility (openness).
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/namebox.rb', line 280 def open? # check file before checking ranges ci = Namebox.caller_info return true if @enabled_files.include? ci[:file] # check ranges for this file info = ranges_info ci info[:ranges].each do |r| case r when Range # check if caller is in an open range for this namebox return true if r.include?(info[:line]) when Integer # check if caller is after an initied range (Namebox#open) return true if info[:line] >= r end end false end |