namebox 0.0.4 → 0.1.8

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 +226 -208
  2. metadata +43 -21
@@ -7,6 +7,12 @@
7
7
  #
8
8
  # This software is released "AS IS", without any warranty.
9
9
  # The author is not responsible for the consequences of use of this software.
10
+ #
11
+ # Version 0.1.8 - This is the last version compatible with Ruby 1.8.7.
12
+ # It's slow, memory-consuming and it has some issues about loosing 'self'
13
+ # when in class methods changed by extending modules in subclasses
14
+ # (self will be the class instead of subclass when namebox is closed).
15
+ # Next versions will be only 1.9+-compatible.
10
16
 
11
17
  class Namebox
12
18
 
@@ -15,9 +21,12 @@ class Namebox
15
21
  map { |c| Object.const_get(c) }.
16
22
  select { |m| m.is_a? Module }.uniq
17
23
 
24
+ ### FOR RUBY 1.8.7 USE ###
25
+ CLASS_EIGENCLASS = class << Class; self; end
26
+
18
27
  class << self
19
28
 
20
- # wrapper to create a namebox only to protect modules when requiring
29
+ # Wrapper to create a namebox only to protect modules when requiring.
21
30
  def require resource, *modules_to_protect
22
31
 
23
32
  new(*modules_to_protect) do
@@ -29,16 +38,24 @@ class Namebox
29
38
 
30
39
  end
31
40
 
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
-
41
+ # Set the default modules to protect (file-wide, for the caller file).
42
+ # Each file you want to use #default_modules, you must define them again.
43
+ # This is to avoid conflicts when using other people libraries, which
44
+ # may want to protect different modules by default. See #default_modules
45
+ #
37
46
  def default_modules= modules_to_protect
38
47
  (@default_modules ||= {})[caller_info[:file]] = [modules_to_protect].flatten
39
48
  end
40
49
 
41
- # Get the caller info in a structured way (hash)
50
+ # Array with default modules to protect. You may want redefine this,
51
+ # protecting Namebox itself inside another namebox, and assign it to
52
+ # a constant, which can be available thru other files in the project.
53
+ #
54
+ def default_modules
55
+ (@default_modules ||= {})[caller_info[:file]] || []
56
+ end
57
+
58
+ # Get the caller info in a structured way (hash).
42
59
  def caller_info
43
60
 
44
61
  # search for last reference to this file in caller and take the next one
@@ -50,105 +67,33 @@ class Namebox
50
67
  m = c.match(/^(.*?):(\d+)(:in `(.*)')?$/)
51
68
  raise "Unexpected caller syntax in \"#{c}\"" unless m
52
69
 
53
- # label it
70
+ # label them
54
71
  {:file => m[1], :line => m[2].to_i, :method => m[4]}
55
72
 
56
73
  end
57
74
 
58
- ###################################################
59
- # INSTANCE METHODS HELPERS
60
- ###################################################
61
-
62
- # Get the super methods from klass' ancestors list.
63
- # +is_instance+: boolean for instance or class methods
64
- # +limit_ancestor+: the last ancestor to check for methods
65
- # +default_method+: the method to return case not found
66
- # +first+: returns the first method found; else returns an array
67
- #
68
- def super_methods(name, klass, is_instance, limit_ancestor, default_method,
69
- first = false, &validator)
70
-
71
- ancestors = is_instance ? klass.ancestors : class_ancestors(klass)
72
-
73
- sm = []
74
- limit_index = ancestors.index(limit_ancestor) || ancestors.length - 1
75
-
76
- # skip first ancestor (0) - the class itself
77
- #
78
- for i in 1..limit_index
79
- super_ancestor = ancestors[i]
80
- begin
81
- m = super_ancestor.instance_method(name)
82
- rescue NameError
83
- m = nil
84
- end
85
-
86
- # only owners (and the last) are interesting
87
- next unless m && m.owner == super_ancestor || i == limit_index
88
-
89
- # check if it matches the requisites
90
- next unless validator.call(m)
91
-
92
- return m if first
93
-
94
- sm << m
95
-
96
- end
97
-
98
- # return found methods
99
- return sm unless sm.empty?
100
-
101
- # not found
102
- first ? default_method : [default_method]
103
- end
104
-
105
- ###################################################
106
- # CLASS METHODS HELPERS
107
- ###################################################
108
-
109
- # build ancestors list for class methods lookup
110
- def class_ancestors(c)
75
+ # Raises NoMethodError, limiting the length of +obj.inspect+.
76
+ def no_method_error(obj, m_name)
111
77
 
112
- # only classes have superclass
113
- return c.ancestors if c.instance_of? Module
78
+ # if inspect is too big, shorten it
79
+ obj_name = obj.inspect.to_s
80
+ obj_name = obj_name[0..45] + '...' + obj_name[-1] if obj_name.length > 50
114
81
 
115
- list = []
116
- eigenclass = class << c; self; end
117
- mod_class = eigenclass.included_modules
82
+ msg = "Undefined method `#{m_name}' for #{obj_name}:#{obj.class}"
118
83
 
119
- while c
120
-
121
- s = c.superclass
122
- eigensuper = class << s; self; end
123
-
124
- # if there's no superclass, consider Kernel as included module;
125
- # so it won't be added now, but later, in the correct order.
126
- mod_super = s ? eigensuper.included_modules : [Kernel]
127
-
128
- # add eigenclass and its included modules
129
- # (discarding the ones inherited from superclasses)
130
- list << eigenclass
131
- list += mod_class - mod_super
132
-
133
- # rotate values
134
- c, eigenclass, mod_class = s, eigensuper, mod_super
135
-
136
- end
137
-
138
- # finally adds Class's ancestors (including Kernel)
139
- list += Class.ancestors
84
+ raise NoMethodError.new(msg)
140
85
  end
141
86
 
142
87
  end
143
88
 
144
89
  # +modules_to_protect+ must be the classes themselves, e.g., String, Symbol,
145
- # not their names ("String" or :Symbol).
146
- # Special names are:
147
- # :all => protect all known modules and submodules. It's safer but slooow.
148
- # :core => protect the known top-level modules.
149
- # :default => protect the modules defined in Namebox.default_modules.
150
- # Obs.: :core and :default can be used together and/or with other classes,
151
- # but :all must be the only parameter if it is used.
90
+ # not their names ("String" or :Symbol).<br>
91
+ # Special names are:<br>
92
+ # +:all+ => protect all known modules and submodules. It's safer but slower.<br>
93
+ # +:core+ => protect the known top-level modules.<br>
94
+ # +:default+ => protect the modules defined in +Namebox.default_modules+.<br>
95
+ # Obs.: +:core+ and +:default+ can be used together and/or with other classes,
96
+ # but +:all+ must be the only parameter if it is used.
152
97
  #
153
98
  def initialize *modules_to_protect, &blk
154
99
 
@@ -156,6 +101,9 @@ class Namebox
156
101
  @enabled_files = []
157
102
  @protected_methods = []
158
103
 
104
+ # this_nb will be useful in a closure, where self will be different
105
+ this_nb = self
106
+
159
107
  # by default, get Namebox.default_modules
160
108
  modules_to_protect = Namebox.default_modules if modules_to_protect.empty?
161
109
 
@@ -173,7 +121,8 @@ class Namebox
173
121
  else
174
122
 
175
123
  # take off the symbols
176
- @modules = modules_to_protect.select { |m| m.is_a? Module }
124
+ modules = modules_to_protect.select { |m| m.is_a? Module }
125
+ @modules = modules
177
126
 
178
127
  # include default modules if wanted
179
128
  @modules += Namebox.default_modules if modules_to_protect.include? :default
@@ -184,6 +133,11 @@ class Namebox
184
133
  # avoid redundancy
185
134
  @modules.uniq!
186
135
 
136
+ # include all ancestors for modules and eigenclasses of classes
137
+ modules.each do |m|
138
+ @modules |= m.ancestors
139
+ @modules |= class << m; self; end.ancestors if m.is_a? Class
140
+ end
187
141
  end
188
142
 
189
143
  # modules must be given or Namebox.default_modules must be set
@@ -193,7 +147,22 @@ class Namebox
193
147
  Namebox.caller_info[:file])
194
148
  end
195
149
 
196
- # save preexisting methods
150
+ # select classes to protect against included modules
151
+ @classes = @modules.select { |m| m.is_a? Class }
152
+
153
+ # hash for fast search for instances of eigenclasses
154
+ @eigen_instances = Hash[*@classes.map { |c| [class << c; self; end, c] }.flatten]
155
+
156
+ # include eigenclasses in @classes
157
+ @classes |= @eigen_instances.keys
158
+
159
+ # eigen_instances will be useful as bound local variable in a closure
160
+ # for another object; so we can't use instance var @eigen_instances
161
+ #
162
+ eigen_instances = @eigen_instances
163
+
164
+ # save preexisting methods and included modules
165
+ inc_mods_before = get_included_modules
197
166
  methods_before = get_methods
198
167
 
199
168
  # ###############################################################
@@ -203,155 +172,174 @@ class Namebox
203
172
  #
204
173
  # ###############################################################
205
174
 
206
- # get methods after changes
175
+ # get data after changes
176
+ inc_mods_after = get_included_modules
207
177
  methods_after = get_methods
208
178
 
209
- # compare with preexisting data to discover affected methods
210
- unless methods_after == methods_before
179
+ # compare with preexisting data to discover affected methods and modules
211
180
 
212
- # thru closure, this namebox will be useful inside redefined methods
213
- this_nb = self
214
- methods_after.each do |fullname, info|
181
+ # compare included modules (before vs after)
182
+ unless inc_mods_after == inc_mods_before
215
183
 
216
- # get old method
217
- info_old = methods_before[fullname] || {}
218
- new_m = info[:method]
219
- old_m = info_old[:method]
184
+ inc_mods_after.each do |klass, inc_mods|
220
185
 
221
- # don't touch unmodified methods
222
- next if new_m == old_m
186
+ old_modules = inc_mods_before[klass]
187
+ new_modules = inc_mods - old_modules
223
188
 
224
- # method was modified! take some info
225
- name = info[:name]
226
- klass = info[:class]
227
- is_instance = info[:is_instance]
228
- old_owner = old_m && old_m.owner
189
+ # there's no new included module for this class
190
+ next if new_modules.empty?
229
191
 
230
- # instance and class methods are managed differently
231
- if is_instance
192
+ new_modules.each do |new_module|
232
193
 
233
- ##########################################################
194
+ # Create a protector module to protect the changed methods.
195
+ # This protector module is specifc to new_module and klass.
234
196
  #
235
- # PROTECT INSTANCE METHODS
197
+ protector = Module.new
198
+
199
+ # Create a module to enable a tunnel to bypass the new_module
200
+ # in super method lookup when namebox is closed to new_module.
236
201
  #
237
- ##########################################################
202
+ super_tunnel = Module.new
238
203
 
239
- # new methods to skip
240
- new_methods = Namebox.super_methods(name, klass, is_instance,
241
- old_owner, new_m) do |sm|
242
- sm != old_m
204
+ # Give name to protector module; useful when using Method#owner.
205
+ #
206
+ class << protector; self; end.send(:define_method, :to_s) do
207
+ "Protector:#{new_module}/#{klass}"
243
208
  end
244
209
 
245
- # redefine the method, which will check namebox visibility dinamically
246
- klass.send :define_method, name do |*args, &blk|
210
+ new_module.instance_methods.each do |method_name|
247
211
 
248
- # check namebox visibility
249
- if this_nb.open?
212
+ new_method = new_module.instance_method(method_name)
250
213
 
251
- # call the protected (by namebox) method
252
- # instance methods are unbound; must bind to self
253
- new_m.bind(self).call(*args, &blk)
214
+ protector.send :define_method, method_name do |*args, &blk|
215
+ if this_nb.open?
216
+ new_method.bind(self).call(*args, &blk)
217
+ else
218
+ begin
219
+ super_tunnel.instance_method(method_name).bind(self).call(*args, &blk)
220
+ rescue NoMethodError
254
221
 
255
- else
222
+ # Can throw error in two cases:
223
+ # 1. There's really no super method (Ruby >= 1.9)
224
+ # 2. 1.8.7 does not find super when in a module after binding.
225
+ #
226
+ raise if RUBY_VERSION >= '1.9.0'
256
227
 
257
- # search a different method to call
258
- # must be dynamic because it can change anytime
259
- # (someone can attach a new definition to some ancestor)
228
+ # future method
229
+ m = nil
260
230
 
261
- m = Namebox.super_methods(name, klass, is_instance,
262
- old_owner, old_m, true) do |sm|
263
- !new_methods.include?(sm)
264
- end
231
+ # search the method in old modules
232
+ old_modules.each do |old_module|
233
+ begin
234
+ m = old_module.instance_method(method_name).bind(self)
235
+ break
236
+ rescue NameError
237
+ # try next
238
+ end
239
+ end
265
240
 
266
- unless m
241
+ unless m
267
242
 
268
- # didn't find a method; raise NoMethodError
243
+ # last try: superklass
244
+ begin
245
+ superklass = klass.superclass
269
246
 
270
- # if inspect is too big, shorten it
271
- obj = self.inspect.to_s
272
- obj = obj[0..45] + '...' + obj[-1] if obj.length > 50
247
+ # superklass exist?
248
+ raise NameError unless superklass
273
249
 
274
- raise NoMethodError.new("Undefined instance method `#{name}' " +
275
- "for #{obj}:#{self.class}")
276
- end
250
+ # Ruby 1.8.7 get wrong superclass for eigenclasses
251
+ if superklass == CLASS_EIGENCLASS
277
252
 
278
- # play as self
279
- m.bind(self).call(*args, &blk)
280
- end
281
- end
253
+ # Get a bound method for superclass (although self
254
+ # won't be the same: superclass instead of klass).
255
+ # Can raise NameError if method does not exist.
256
+ #
257
+ m = eigen_instances[klass].superclass.method(method_name)
282
258
 
283
- else
259
+ else
284
260
 
285
- ##########################################################
286
- #
287
- # PROTECT CLASS METHODS
288
- #
289
- ##########################################################
261
+ # Try to get method; raises NameError if it doesn't exist.
262
+ m = superklass.instance_method(method_name).bind(self)
290
263
 
291
- # new methods to skip
292
- new_methods = Namebox.super_methods(name, klass, is_instance,
293
- old_owner, new_m) do |sm|
294
- sm != old_m
295
- end
264
+ end
296
265
 
297
- # redefine the method thru eigenclass
298
- class << klass; self; end.send :define_method, name do |*args, &blk|
266
+ rescue NameError
267
+ Namebox.no_method_error(self, method_name)
268
+ end
269
+ end
299
270
 
300
- # check namebox visibility
301
- if this_nb.open?
271
+ m.call(*args, &blk)
272
+ end
273
+ end
274
+ end
302
275
 
303
- # call the protected (by namebox) method
304
- # new_m is already bound to self; just call it
305
- new_m.call(*args, &blk)
276
+ super_tunnel.send :define_method, method_name do |*args, &blk|
277
+ super(*args, &blk)
278
+ end
306
279
 
307
- else
280
+ end
308
281
 
309
- # search a different method to call
310
- # must be dynamic because it can change anytime
311
- # (someone can attach a new definition to some ancestor)
282
+ # include super_tunnel in new_module
283
+ new_module.send :include, super_tunnel
312
284
 
313
- m = Namebox.super_methods(name, klass, is_instance,
314
- old_owner, old_m, true) do |sm|
315
- !new_methods.include?(sm)
316
- end
285
+ # reincludes the new_module with super_tunnel to allow bind(self)
286
+ klass.send :include, new_module
317
287
 
318
- unless m
288
+ # finally, include the protector in the class
289
+ klass.send :include, protector
319
290
 
320
- # didn't find a method; raise NoMethodError
291
+ end
292
+ end
293
+ end
321
294
 
322
- # if inspect is too big, shorten it
323
- obj = self.inspect.to_s
324
- obj = obj[0..45] + '...' + obj[-1] if obj.length > 50
295
+ # Compare changed methods (before vs after)
296
+ unless methods_after == methods_before
325
297
 
326
- raise NoMethodError.new("Undefined class method `#{name}' " +
327
- "for #{obj}:#{self.class}")
328
- end
298
+ methods_after.each do |fullname, info|
329
299
 
330
- begin
300
+ # get old method
301
+ info_old = methods_before[fullname] || {}
302
+ new_m = info[:method]
303
+ old_m = info_old[:method]
331
304
 
332
- # bind to self (raises TypeError in 1.8.7)
333
- m = m.bind(self) if m.instance_of? UnboundMethod
305
+ # don't touch unmodified methods
306
+ next if new_m == old_m
334
307
 
335
- rescue TypeError
308
+ # method was modified! take some info
309
+ m_name = info[:name]
310
+ klass = info[:class]
311
+ new_bound_m = info[:bound]
312
+ old_bound_m = info_old[:bound]
336
313
 
337
- # Ruby 1.8.7 does not bind singleton methods to descendant class;
338
- # try to use a pre-bound method
339
- m = ObjectSpace.each_object(m.owner).to_a.last.method(name)
314
+ # redefine the method, which will check namebox visibility dinamically
315
+ klass.send :define_method, m_name do |*args, &blk|
340
316
 
341
- end
317
+ # check namebox visibility
318
+ if this_nb.open?
342
319
 
343
- # run!
344
- m.call(*args, &blk)
320
+ # Call the new method. Instance methods are unbound; bind to self.
321
+ m = (new_bound_m || new_m.bind(self)).call(*args, &blk)
322
+
323
+ else
324
+
325
+ # old method or super
326
+ if old_m
327
+ (old_bound_m || old_m.bind(self)).call(*args, &blk)
328
+ else
329
+ begin
330
+ super(*args, &blk)
331
+ rescue NoMethodError
332
+ Namebox.no_method_error(self, m_name)
333
+ end
345
334
  end
346
- end
347
335
 
336
+ end
348
337
  end
349
338
  end
350
339
  end
351
340
  end
352
341
 
353
-
354
- # open a namebox region for visibility in the caller file at caller line
342
+ # Open a namebox region for visibility in the caller file at caller line.
355
343
  def open
356
344
  info = ranges_info
357
345
 
@@ -365,7 +353,7 @@ class Namebox
365
353
  end
366
354
 
367
355
 
368
- # close the namebox region in the caller file
356
+ # Close the namebox visible region in the caller file.
369
357
  def close
370
358
  info = ranges_info
371
359
 
@@ -388,7 +376,7 @@ class Namebox
388
376
  info[:ranges] << r unless info[:ranges].include? r
389
377
  end
390
378
 
391
- # check namebox' visibility
379
+ # Check namebox visibility (openness).
392
380
  def open?
393
381
 
394
382
  # check file before checking ranges
@@ -414,15 +402,14 @@ class Namebox
414
402
  false
415
403
  end
416
404
 
417
-
418
- # open namebox in entire caller file (valid only after called!)
405
+ # Open namebox in entire caller file (valid only after called!).
419
406
  def file_wide
420
407
  @enabled_files << Namebox.caller_info[:file]
421
408
  end
422
409
 
423
410
  private
424
411
 
425
- # Get line ranges info for the caller file
412
+ # Get line ranges info for the caller file.
426
413
  def ranges_info ci = Namebox.caller_info
427
414
 
428
415
  ranges = enabled_ranges[ci[:file]] ||= []
@@ -435,30 +422,61 @@ class Namebox
435
422
  ci
436
423
  end
437
424
 
438
- # Stores enabled line ranges of caller files for this module
425
+ # Stores enabled line ranges of caller files for this module.
439
426
  def enabled_ranges
440
427
  @enabled_ranges ||= {}
441
428
  end
442
429
 
443
- # get methods in use thru modules to protect
430
+ # Get methods in use on modules to protect.
444
431
  def get_methods
445
432
  gm = {}
446
433
 
447
434
  @modules.each do |c|
448
435
 
449
- c.instance_methods.each do |m|
436
+ # get eigenclass
437
+ ec = class << c; self; end
438
+
439
+ c.instance_methods(false).each do |m|
450
440
  fullname = "#{c}##{m}"
451
- gm[fullname] = {:class => c, :is_instance => true, :name => m,
452
- :method => c.instance_method(m) }
441
+ gm[fullname] = {:class => c, :name => m, :method => c.instance_method(m) }
453
442
  end
454
443
 
455
- c.methods.each do |m|
444
+ c.methods(false).each do |m|
456
445
  fullname = "#{c}.#{m}"
457
- gm[fullname] = {:class => c, :name => m, :method => c.method(m) }
446
+ gm[fullname] = {:class => ec, :name => m, :method => ec.instance_method(m),
447
+ :bound => c.method(m) }
458
448
  end
459
449
 
460
450
  end
461
451
 
462
452
  gm
463
453
  end
454
+
455
+ # Get modules included by the classes being protected.
456
+ def get_included_modules
457
+ inc_mods = {}
458
+
459
+ @classes.each do |c|
460
+ super_c = get_superclass(c)
461
+
462
+ # superclass included modules must be [] even when superclass is nil
463
+ super_inc_mods = super_c && super_c.included_modules || []
464
+
465
+ # get modules included by the class only, not by superclasses.
466
+ inc_mods[c] = c.included_modules - super_inc_mods
467
+ end
468
+
469
+ inc_mods
470
+ end
471
+
472
+ # Get superclass of a class; correct values for eigenclasses in 1.8.7.
473
+ def get_superclass(c)
474
+ s = c.superclass
475
+ if s == CLASS_EIGENCLASS
476
+ # Ruby 1.8.7 limitation
477
+ s = class << @eigen_instances[c].superclass; self; end
478
+ end
479
+ s
480
+ end
481
+
464
482
  end
metadata CHANGED
@@ -1,48 +1,70 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: namebox
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.4
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 8
10
+ version: 0.1.8
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Sony Santos
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-01-15 00:00:00.000000000 Z
17
+
18
+ date: 2013-01-20 00:00:00 Z
13
19
  dependencies: []
14
- description: ! 'Please, note: this version is very unstable and it''s under tests.
15
- Came back soon to check new versions with bug fixes.'
20
+
21
+ description: |-
22
+ Create namespace boxes to protect the core classes' methods from changes, like refinements.
23
+
24
+ Note: this is the last version compatible with Ruby 1.8.7. It's slow, memory-consuming and it has some issues about loosing 'self' when in class methods changed by extending modules in subclasses (self will be the class instead of subclass when namebox is closed). Next versions will be only 1.9+-compatible.
16
25
  email: sony.fermino@gmail.com
17
26
  executables: []
27
+
18
28
  extensions: []
29
+
19
30
  extra_rdoc_files: []
20
- files:
31
+
32
+ files:
21
33
  - lib/namebox.rb
22
34
  homepage: http://rubychallenger.blogspot.com.br/2013/01/namebox.html
23
- licenses:
35
+ licenses:
24
36
  - Public Domain
25
37
  post_install_message:
26
38
  rdoc_options: []
27
- require_paths:
39
+
40
+ require_paths:
28
41
  - lib
29
- required_ruby_version: !ruby/object:Gem::Requirement
42
+ required_ruby_version: !ruby/object:Gem::Requirement
30
43
  none: false
31
- requirements:
32
- - - ! '>='
33
- - !ruby/object:Gem::Version
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ hash: 57
48
+ segments:
49
+ - 1
50
+ - 8
51
+ - 7
34
52
  version: 1.8.7
35
- required_rubygems_version: !ruby/object:Gem::Requirement
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
54
  none: false
37
- requirements:
38
- - - ! '>='
39
- - !ruby/object:Gem::Version
40
- version: '0'
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
41
62
  requirements: []
63
+
42
64
  rubyforge_project:
43
65
  rubygems_version: 1.8.24
44
66
  signing_key:
45
67
  specification_version: 3
46
- summary: Create namespace boxes to protect the core classes' methods from changes,
47
- like refinements
68
+ summary: Create namespace boxes to protect the core classes' methods from changes, like refinements.
48
69
  test_files: []
70
+