y_support 2.0.14 → 2.0.15.p1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/y_support/name_magic/array.rb +18 -0
- data/lib/y_support/name_magic/class_methods.rb +68 -0
- data/lib/y_support/name_magic/namespace_methods.rb +224 -0
- data/lib/y_support/name_magic.rb +100 -325
- data/lib/y_support/version.rb +1 -1
- data/test/name_magic_test.rb +5 -7
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 326d4643c9c678771d09d5c90046d9e1858f887b
|
4
|
+
data.tar.gz: 7a87cc4045556e716700a2888573d27bbb99e7d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5049da6a9a4ecde2993d84b81ec38e7ed8a4ca099b49c9c4b807e7926da0fa49f3b993ffe4ccd8e5f8222aeaa5e6ffe31ed4a225bd7701455b886b293de78513
|
7
|
+
data.tar.gz: 297c2bde7a494d9c10731f62827fa6f7b068308796314581739ef175521ea0b770025f02ed2a87b7f67a9124e366c976667175d24176a4b37744e681853f63df
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# Maps an array to an array of the element names, obtained by applying +#name+
|
5
|
+
# method to them. Takes one optional argument, which regulates its behavior
|
6
|
+
# regarding unnamed objects. If set to _nil_ (default) unnamed objects will
|
7
|
+
# be mapped to _nil_ (default behavior of the +#name+ method). If set to
|
8
|
+
# _true_, unnamed objects will be mapped to themselves. If set to _false_,
|
9
|
+
# unnamed object will not be mapped at all -- the returned array will contain
|
10
|
+
# only the names of the named objects.
|
11
|
+
#
|
12
|
+
def names option=nil
|
13
|
+
return map &:name if option.nil? # unnamed --> nil
|
14
|
+
return map { |e| e.name || e } if option == true # unnamed --> instance
|
15
|
+
return map( &:name ).compact if option == false # unnamed squeezed out
|
16
|
+
fail ArgumentError, "Unknown option: #{option}"
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module NameMagic::ClassMethods
|
4
|
+
# Presents the instances registered by the namespace. Takes one optional
|
5
|
+
# argument. If set to _false_, the method returns all the instances
|
6
|
+
# registered by the namespace. If set to _true_ (default), only returns
|
7
|
+
# those instances registered by the namespace, which are of the exact same
|
8
|
+
# class as the method's receiver. Example:
|
9
|
+
#
|
10
|
+
# class Animal; include NameMagic end
|
11
|
+
# Cat, Dog = Class.new( Animal ), Class.new( Animal )
|
12
|
+
# Spot = Dog.new
|
13
|
+
# Livia = Cat.new
|
14
|
+
# Animal.instances #=> returns 2 instances
|
15
|
+
# Dog.instances #=> returns 1 instance
|
16
|
+
# Dog.instances( false ) #=> returns 2 instances of the namespace Animal
|
17
|
+
#
|
18
|
+
def instances option=false
|
19
|
+
const_magic
|
20
|
+
return __instances__.keys if option
|
21
|
+
__instances__.keys.select { |i| i.kind_of? self }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Presents the instance names. Takes one optional argument, same as
|
25
|
+
# #instances method. Unnamed instances are completely disregarded.
|
26
|
+
#
|
27
|
+
def instance_names option=false
|
28
|
+
instances( option ).names( false )
|
29
|
+
end
|
30
|
+
|
31
|
+
# In addition the ability to name objects upon constant assignment, as common
|
32
|
+
# with eg. Class instances, NameMagic redefines class method #new so that it
|
33
|
+
# swallows the named argument :name (alias :ɴ), and takes care of naming the
|
34
|
+
# instance accordingly. Also, :name_avid named argument mey be supplied, which
|
35
|
+
# makes the naming avid (able to overwrite the name already in use by
|
36
|
+
# another object) if set to _true_.
|
37
|
+
#
|
38
|
+
def new *args, &block
|
39
|
+
oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract hash
|
40
|
+
nm = if oo[:name] then oo.delete :name # consume :name if supplied
|
41
|
+
elsif oo[:ɴ] then oo.delete :ɴ # consume :ɴ if supplied
|
42
|
+
else nil end
|
43
|
+
avid = oo[:name_avid] ? oo.delete( :name_avid ) : false # => true/false
|
44
|
+
# Avoid overwriting existing names unless avid:
|
45
|
+
fail NameError, "#{self} instance #{nm} already exists!" if
|
46
|
+
__instances__.keys.include? nm unless avid
|
47
|
+
args << oo unless oo.empty? # prepare the arguments
|
48
|
+
new_before_name_magic( *args, &block ).tap do |new_inst| # instantiate
|
49
|
+
__instances__.update new_inst => nil # Instance is created unnamed...
|
50
|
+
namespace.new_instance_closure.tap { |λ|
|
51
|
+
λ.( new_inst ) if λ
|
52
|
+
if nm then # name has been supplied, we can name the instance:
|
53
|
+
avid ? new_inst.name!( nm ) : new_inst.name = nm
|
54
|
+
else # name hasn't been supplied, making the instance avid:
|
55
|
+
__avid_instances__ << new_inst
|
56
|
+
end
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Calls #new in _avid_ _mode_ (name_avid: true); see #new method for avid mode
|
62
|
+
# explanation.
|
63
|
+
#
|
64
|
+
def new! *args, &block
|
65
|
+
oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract options
|
66
|
+
new *args, oo.update( name_avid: true ), &block
|
67
|
+
end
|
68
|
+
end # module NameMagic::ClassMethods
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module NameMagic
|
4
|
+
module NamespaceMethods
|
5
|
+
# Presents class-owned namespace. By default, this is the class itself, but
|
6
|
+
# may be overriden to use some other module as a namespace.
|
7
|
+
#
|
8
|
+
def namespace
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sets the namespace of the class.
|
13
|
+
#
|
14
|
+
def namespace= modul
|
15
|
+
modul.extend ::NameMagic::NamespaceMethods unless modul == self
|
16
|
+
tap { define_singleton_method :namespace do modul end }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Makes the class/module its own namespace. This is useful especially to tell
|
20
|
+
# the subclasses of a class using NameMagic to maintain their own namespaces.
|
21
|
+
#
|
22
|
+
def namespace!
|
23
|
+
nil.tap { self.namespace = self }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Presents the instances registered by the namespace.
|
27
|
+
#
|
28
|
+
def instances
|
29
|
+
const_magic
|
30
|
+
__instances__.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
# Presents the instance names. Takes one optional argument, same as
|
34
|
+
# #instances method. Unnamed instances are completely disregarded.
|
35
|
+
#
|
36
|
+
def instance_names
|
37
|
+
instances.names( false )
|
38
|
+
end
|
39
|
+
|
40
|
+
# Presents namespace-owned @instances hash of pairs <code>{ instance =>
|
41
|
+
# instance_name }</code>. Unnamed instances have nil value. (Also, this
|
42
|
+
# method does not trigger #const_magic.)
|
43
|
+
#
|
44
|
+
def __instances__
|
45
|
+
namespace.instance_variable_get( :@instances ) ||
|
46
|
+
namespace.instance_variable_set( :@instances, {} )
|
47
|
+
end
|
48
|
+
|
49
|
+
# Presents namespace-owned @avid_instances array of avid instances. "Avid"
|
50
|
+
# means that the instance is able to overwrite a name used by another
|
51
|
+
# registered instance. (Also, this method does not trigger const_magic).
|
52
|
+
#
|
53
|
+
def __avid_instances__
|
54
|
+
namespace.instance_variable_get( :@avid_instances ) ||
|
55
|
+
namespace.instance_variable_set( :@avid_instances, [] )
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the instance identified by the argument. NameError is raised, if
|
59
|
+
# the argument does not identify an instance. (It can be an instance name
|
60
|
+
# (string/symbol), or an instance itself, in which case, it is just returned
|
61
|
+
# back without changes.)
|
62
|
+
#
|
63
|
+
def instance identifier
|
64
|
+
puts "#instance( #{identifier} )" if DEBUG
|
65
|
+
# In @instances hash, value 'nil' indicates a nameless instance!
|
66
|
+
fail TypeError, "'nil' is not an instance identifier!" if identifier.nil?
|
67
|
+
ii = instances
|
68
|
+
return identifier if ii.include? identifier # return the instance back
|
69
|
+
begin # identifier not a registered instace -- treat it as a name
|
70
|
+
ary = [identifier, identifier.to_sym]
|
71
|
+
ii.find do |i| ary.include? i.name end
|
72
|
+
rescue NoMethodError
|
73
|
+
end or raise NameError, "No instance #{identifier} in #{self}."
|
74
|
+
end
|
75
|
+
|
76
|
+
# Searches all the modules in the the object space for constants containing
|
77
|
+
# receiver class objects, and names the found instances accordingly. The
|
78
|
+
# number of the remaining nameless instances is returned.
|
79
|
+
#
|
80
|
+
def const_magic
|
81
|
+
return 0 if nameless_instances.size == 0
|
82
|
+
serve_all_modules
|
83
|
+
return nameless_instances.size
|
84
|
+
end # def const_magic
|
85
|
+
|
86
|
+
# Returns those instances, which are nameless (whose name is set to nil).
|
87
|
+
#
|
88
|
+
def nameless_instances
|
89
|
+
__instances__.select { |key, val| val.nil? }.keys
|
90
|
+
end
|
91
|
+
alias unnamed_instances nameless_instances
|
92
|
+
alias anonymous_instances nameless_instances
|
93
|
+
|
94
|
+
# Clears namespace-owned references to a specified instance. (This is
|
95
|
+
# different from "unnaming" an instance by setting <code>inst.name =
|
96
|
+
# nil</code>, which makes the instance anonymous, but still registered.)
|
97
|
+
#
|
98
|
+
def forget( instance_identifier )
|
99
|
+
inst = begin
|
100
|
+
instance( instance_identifier )
|
101
|
+
rescue ArgumentError
|
102
|
+
return nil # nothing to forget
|
103
|
+
end
|
104
|
+
ɴ = inst.nil? ? nil : inst.name
|
105
|
+
namespace.send :remove_const, ɴ if ɴ # clear constant assignment
|
106
|
+
__instances__.delete( inst ) # remove @instances entry
|
107
|
+
__avid_instances__.delete( inst ) # remove if any
|
108
|
+
return inst # return the forgotten instance
|
109
|
+
end
|
110
|
+
|
111
|
+
# Clears namespace-owned references to an instance, without performing
|
112
|
+
# #const_magic first. The argument should be a registered instance. Returns
|
113
|
+
# the instance name, or _false_, if there was no such registered instance.
|
114
|
+
#
|
115
|
+
def __forget__( instance )
|
116
|
+
return false unless __instances__.keys.include? instance
|
117
|
+
namespace.send :remove_const, instance.name if instance.name
|
118
|
+
__avid_instances__.delete( instance )
|
119
|
+
__instances__.delete instance
|
120
|
+
end
|
121
|
+
|
122
|
+
# Clears namespace-owned references to all the anonymous instances.
|
123
|
+
#
|
124
|
+
def forget_nameless_instances
|
125
|
+
nameless_instances.each { |inst, ɴ|
|
126
|
+
__instances__.delete inst
|
127
|
+
__avid_instances__.delete inst # also from here
|
128
|
+
}
|
129
|
+
end
|
130
|
+
alias forget_unnamed_instances forget_nameless_instances
|
131
|
+
alias forget_anonymous_instances forget_nameless_instances
|
132
|
+
|
133
|
+
# Clears namespace-owned references to all the instances.
|
134
|
+
#
|
135
|
+
def forget_all_instances
|
136
|
+
__instances__.clear # clears @instances
|
137
|
+
constants( false ).each { |ß| # clear constants in the namespace
|
138
|
+
namespace.send :remove_const, ß if const_get( ß ).is_a? self
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
# Registers a hook to execute whenever name magic creates a new instance of
|
143
|
+
# the class including NameMagic. The block should take one argument (the new
|
144
|
+
# instance that was created) and is called in #new method right after
|
145
|
+
# instantiation, but before naming.
|
146
|
+
#
|
147
|
+
def new_instance_closure &block
|
148
|
+
namespace.new_instance_closure &block unless namespace == self
|
149
|
+
@new_instance_closure = block if block
|
150
|
+
@new_instance_closure ||= -> instance { instance }
|
151
|
+
end
|
152
|
+
alias new_instance_hook new_instance_closure
|
153
|
+
|
154
|
+
# Registers a hook to execute whenever name setting is performed on an
|
155
|
+
# instance. The block should take three arguments (instance, name, old_name).
|
156
|
+
# The output value of the block is the name to be actually used – the hook
|
157
|
+
# thus allows to define transformations on the name when naming. It is the
|
158
|
+
# responsibility of the block to output a suitable symbol (capitalized,
|
159
|
+
# usable as a constant name etc.)
|
160
|
+
#
|
161
|
+
def name_set_closure &block
|
162
|
+
namespace.name_set_closure &block unless namespace == self
|
163
|
+
@name_set_closure = block if block
|
164
|
+
@name_set_closure ||= -> name, instance, old_name=nil { name }
|
165
|
+
end
|
166
|
+
alias name_set_hook name_set_closure
|
167
|
+
|
168
|
+
# Registers a hook to execute whenever the instance is asked about its
|
169
|
+
# name. The name object contained in __instances__[self] is subjected
|
170
|
+
# to the name_get_closure before being returned as instance name.
|
171
|
+
#
|
172
|
+
def name_get_closure &block
|
173
|
+
namespace.name_get_closure &block unless namespace == self
|
174
|
+
@name_get_closure = block if block
|
175
|
+
@name_get_closure ||= -> name { name }
|
176
|
+
end
|
177
|
+
alias name_get_hook name_get_closure
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# Checks all the constants in some module's namespace, recursively.
|
182
|
+
#
|
183
|
+
def serve_all_modules
|
184
|
+
todo = ( nameless_instances + __avid_instances__ ).map( &:object_id ).uniq
|
185
|
+
ObjectSpace.each_object Module do |ɱ|
|
186
|
+
ɱ.constants( false ).each do |const_ß|
|
187
|
+
begin
|
188
|
+
◉ = ɱ.const_get( const_ß ) # insurance against const. loading fails
|
189
|
+
rescue LoadError, StandardError; next end
|
190
|
+
next unless todo.include? ◉.object_id
|
191
|
+
puts "NameMagic: Anonymous object under #{const_ß}!" if DEBUG
|
192
|
+
if __avid_instances__.map( &:object_id ).include? ◉.object_id # avid
|
193
|
+
puts "NameMagic: It is avid." if DEBUG
|
194
|
+
__avid_instances__ # 1. remove from avid list
|
195
|
+
.delete_if { |inst| inst.object_id == ◉.object_id }
|
196
|
+
◉.name! const_ß # 2. name rudely
|
197
|
+
else puts "NameMagic: It is not avid." if DEBUG # not avid
|
198
|
+
ɴ = validate_name( name_set_closure.( const_ß, ◉, nil ) ).to_sym
|
199
|
+
puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
|
200
|
+
conflicter = begin; namespace.const_get( ɴ ); rescue NameError; end
|
201
|
+
if conflicter then
|
202
|
+
msg = "Another #{self}-registered instance named '#{ɴ}' exists!"
|
203
|
+
fail NameError, msg unless conflicter == ◉
|
204
|
+
else # add the instance to the namespace
|
205
|
+
__instances__.update( ◉ => ɴ )
|
206
|
+
namespace.const_set( ɴ, ◉ )
|
207
|
+
end
|
208
|
+
end
|
209
|
+
todo.delete ◉.object_id # remove the id from todo list
|
210
|
+
break if todo.empty? # and break the loop if done
|
211
|
+
end # each
|
212
|
+
end # each_object Module
|
213
|
+
end # def serve_all_modules
|
214
|
+
|
215
|
+
# Checks whether a name starts with a capital letter.
|
216
|
+
#
|
217
|
+
def validate_name name
|
218
|
+
name.to_s.tap do |ɴ| # check whether the name starts with 'A'..'Z'
|
219
|
+
fail NameError, "#{self}-registered name must start with a capital " +
|
220
|
+
" letter 'A'..'Z' ('#{ɴ}' given)!" unless ( ?A..?Z ) === ɴ[0]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end # module NamespaceMethods
|
224
|
+
end # module NameMagic
|
data/lib/y_support/name_magic.rb
CHANGED
@@ -1,26 +1,61 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
|
2
3
|
require 'y_support'
|
4
|
+
require_relative 'name_magic/array'
|
5
|
+
require_relative 'name_magic/namespace_methods'
|
6
|
+
require_relative 'name_magic/class_methods'
|
3
7
|
|
4
8
|
# This mixin imitates Ruby constant magic and automates the named argument
|
5
|
-
# :name (alias :ɴ). One
|
9
|
+
# :name (alias :ɴ). One can write:
|
6
10
|
#
|
7
|
-
#
|
8
|
-
#
|
11
|
+
# require 'y_support/name_magic'
|
12
|
+
# class Foo; include NameMagic end
|
13
|
+
# Bar = Foo.new
|
9
14
|
#
|
10
15
|
# and the resulting object will know its #name:
|
11
16
|
#
|
12
|
-
#
|
17
|
+
# Bar.name #=> :Bar
|
18
|
+
# Foo::Bar #=> <Foo:0x.....>
|
19
|
+
#
|
20
|
+
# This is done by searching whole Ruby namespace for constants, triggered by the
|
21
|
+
# method #const_magic defined in the namespace mixin. (Once the object is named,
|
22
|
+
# subsequent constant assignments have no effects.) By default, the namespace
|
23
|
+
# is the class, in which NameMagic is included, but it is possible to prescribe
|
24
|
+
# another module as a namespace:
|
25
|
+
#
|
26
|
+
# Quux = Module.new
|
27
|
+
# class FooBar
|
28
|
+
# include NameMagic
|
29
|
+
# self.namespace = Quux
|
30
|
+
# end
|
31
|
+
# FooBar.new name: "Baz"
|
32
|
+
# FooBar::Baz #=> NameError
|
33
|
+
# Quux::Baz #=> <FooBar:0x.....>
|
34
|
+
#
|
35
|
+
# When subclassing the classes with NameMagic included, namespace setting does
|
36
|
+
# not change:
|
13
37
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
38
|
+
# class Animal; include NameMagic end
|
39
|
+
# class Dog < Animal; end
|
40
|
+
# class Cat < Animal; end
|
41
|
+
# Dog.namespace #=> Animal
|
42
|
+
# Cat.namespace #=> Animal
|
43
|
+
# Livia = Cat.new
|
44
|
+
# Cat.instance_names #=> []
|
45
|
+
# Animal.instance_names #=> [:Livia]
|
19
46
|
#
|
20
|
-
#
|
21
|
-
# named argument:
|
47
|
+
# To make the subclasses use each their own namespace, use +#namespace!+ method:
|
22
48
|
#
|
23
|
-
#
|
49
|
+
# Dog.namespace!
|
50
|
+
#
|
51
|
+
# NameMagic also provides an alternative way to create named objects by taking
|
52
|
+
# care of :name (alias :ɴ) named argument of the constructor:
|
53
|
+
#
|
54
|
+
# Dog.new name: "Spot"
|
55
|
+
# Dog.new ɴ: :Rover
|
56
|
+
# Dog.instance_names #=> [:Spot, :Rover]
|
57
|
+
# Animal.instance_names #=> []
|
58
|
+
#
|
24
59
|
#
|
25
60
|
# Lastly, a name can be assigned by #name= accssor, as in
|
26
61
|
#
|
@@ -33,345 +68,85 @@ require 'y_support'
|
|
33
68
|
module NameMagic
|
34
69
|
DEBUG = false
|
35
70
|
|
36
|
-
def self.included
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
alias :original_method_new :new # Make space to decorate #new
|
71
|
+
def self.included modul
|
72
|
+
if modul.is_a? Class then # decorate #new
|
73
|
+
class << modul
|
74
|
+
alias :new_before_name_magic :new
|
41
75
|
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# Attach namespace methods also to the namespace, if given.
|
76
|
+
modul.extend NameMagic::NamespaceMethods
|
77
|
+
modul.extend NameMagic::ClassMethods
|
78
|
+
# Attach namespace methods also to the namespace, if given
|
46
79
|
begin
|
47
|
-
if
|
48
|
-
|
80
|
+
if modul.namespace == modul then
|
81
|
+
modul.define_singleton_method :namespace do modul end
|
49
82
|
else
|
50
|
-
|
83
|
+
modul.namespace.extend NameMagic::NamespaceMethods
|
51
84
|
end
|
52
85
|
rescue NoMethodError
|
53
86
|
end
|
54
|
-
else # it is a Module
|
55
|
-
|
56
|
-
|
57
|
-
this_included.( ç )
|
58
|
-
ɱ_included.( ç )
|
59
|
-
end
|
87
|
+
else # it is a Module -- infect it with this #include
|
88
|
+
orig, this = modul.method( :included ), method( :included )
|
89
|
+
modul.define_singleton_method :included do |m| this.( m ); orig.( m ) end
|
60
90
|
end
|
61
91
|
end # self.included
|
62
92
|
|
63
|
-
#
|
93
|
+
# The namespace of the instance's class.
|
94
|
+
#
|
95
|
+
def namespace
|
96
|
+
self.class.namespace
|
97
|
+
end
|
98
|
+
|
99
|
+
# Retrieves an instance name.
|
64
100
|
#
|
65
101
|
def name
|
66
102
|
self.class.const_magic
|
67
|
-
|
68
|
-
if ɴ then
|
69
|
-
name_get_closure = self.class.instance_variable_get :@name_get_closure
|
70
|
-
name_get_closure ? name_get_closure.( ɴ ) : ɴ
|
71
|
-
else nil end
|
103
|
+
__name__ or ( yield self if block_given? )
|
72
104
|
end
|
73
105
|
alias ɴ name
|
74
106
|
|
75
|
-
# Retrieves
|
107
|
+
# Retrieves the instance name. Does not trigger #const_magic before doing so.
|
76
108
|
#
|
77
|
-
def
|
78
|
-
|
109
|
+
def __name__
|
110
|
+
ɴ = self.class.__instances__[ self ]
|
111
|
+
namespace.name_get_closure.( ɴ ) if ɴ
|
79
112
|
end
|
80
|
-
alias ɴ_ name_or_object_id
|
81
113
|
|
82
114
|
# Names an instance, cautiously (ie. no overwriting of existing names).
|
83
115
|
#
|
84
116
|
def name=( ɴ )
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
self.class.__forget__ old_ɴ # forget the old name of self
|
117
|
+
old_ɴ = namespace.__instances__[ self ] # previous name
|
118
|
+
if ɴ then puts "NameMagic: Naming with argument #{ɴ}." if DEBUG
|
119
|
+
ɴ = namespace.send( :validate_name, # honor the hook
|
120
|
+
namespace.name_set_closure.( ɴ, self, old_ɴ ) ).to_sym
|
121
|
+
puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
|
122
|
+
return if old_ɴ == ɴ # already named as required
|
123
|
+
fail NameError, "Name '#{ɴ}' already exists in #{namespace} namespace!" if
|
124
|
+
self.class.__instances__.rassoc( ɴ )
|
125
|
+
namespace.const_set ɴ, self # write a constant
|
126
|
+
namespace.__instances__[ self ] = ɴ # write to @instances
|
127
|
+
namespace.__forget__ old_ɴ # forget the old name of self
|
128
|
+
else puts "NameMagic: Unnaming #{old_ɴ || self}" if DEBUG
|
129
|
+
namespace.__instances__.update( self => nil ) # unname in @instances
|
130
|
+
namespace.send :remove_const, old_ɴ if old_ɴ # remove namespace const.
|
131
|
+
end
|
101
132
|
end
|
102
133
|
|
103
134
|
# Names an instance, aggresively (overwrites existing names).
|
104
135
|
#
|
105
136
|
def name!( ɴ )
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
self.class.__instances__[ self ] = ɴ # write to __instances__
|
120
|
-
self.class.__forget__ old_ɴ # forget the old name of self
|
121
|
-
return true
|
122
|
-
end
|
123
|
-
|
124
|
-
module NamespaceMethods
|
125
|
-
# Presents class-owned @instances hash of { instance => name } pairs.
|
126
|
-
#
|
127
|
-
def instances
|
128
|
-
const_magic
|
129
|
-
__instances__.keys.select { |i| i.kind_of? self }
|
130
|
-
end
|
131
|
-
|
132
|
-
# Presents an array of all the instance names (disregarding anonymous
|
133
|
-
# instances).
|
134
|
-
#
|
135
|
-
def instance_names
|
136
|
-
instances.map( &:name ).compact
|
137
|
-
end
|
138
|
-
|
139
|
-
# Presents class-owned @instances without const_magic.
|
140
|
-
#
|
141
|
-
def __instances__
|
142
|
-
namespace.instance_variable_get( :@instances ) ||
|
143
|
-
namespace.instance_variable_set( :@instances, {} )
|
144
|
-
end
|
145
|
-
|
146
|
-
# Presents class-owned @avid_instances (no const_magic).
|
147
|
-
#
|
148
|
-
def __avid_instances__
|
149
|
-
namespace.instance_variable_get( :@avid_instances ) ||
|
150
|
-
namespace.instance_variable_set( :@avid_instances, [] )
|
151
|
-
end
|
152
|
-
|
153
|
-
# Presents class-owned namespace. Normally, this is the class itself,
|
154
|
-
# but can be overriden so as to define constants holding the instances
|
155
|
-
# in some other module.
|
156
|
-
#
|
157
|
-
def namespace
|
158
|
-
self
|
159
|
-
end
|
160
|
-
|
161
|
-
# Makes the class use the namespace supplied as an argument.
|
162
|
-
#
|
163
|
-
def namespace= namespc
|
164
|
-
namespc.extend ::NameMagic::NamespaceMethods unless namespc == self
|
165
|
-
tap { define_singleton_method :namespace do namespc end }
|
166
|
-
end
|
167
|
-
|
168
|
-
# Makes the class/module use itself as a namespace. (Useful eg. with
|
169
|
-
# parametrized subclassing to tell the subclasses to maintain each their
|
170
|
-
# own namespaces.)
|
171
|
-
#
|
172
|
-
def namespace!
|
173
|
-
self.namespace = self
|
174
|
-
end
|
175
|
-
|
176
|
-
# Returns the instance identified by the argument. NameError is raised, if
|
177
|
-
# the argument does not identify an instance. (It can be an instance name
|
178
|
-
# as string, symbol, or an instance itself, in which case, the instance in
|
179
|
-
# question is merely returned without changes.)
|
180
|
-
#
|
181
|
-
def instance arg
|
182
|
-
# In @instances hash, name 'nil' means nameless!
|
183
|
-
puts "NameMagic: #instance called with argument #{arg}." if DEBUG
|
184
|
-
msg = "'nil' is not a valid argument type for NameMagic#instance method!"
|
185
|
-
fail TypeError, msg if arg.nil?
|
186
|
-
# if the argument is an actual instance, just return it
|
187
|
-
ii = instances
|
188
|
-
return arg if ii.include? arg
|
189
|
-
# otherwise, assume arg is a name
|
190
|
-
begin
|
191
|
-
ii.find { |i| i.name == arg || i.name == arg.to_sym }
|
192
|
-
rescue NoMethodError
|
193
|
-
end or raise NameError, "No instance #{arg} in #{namespace}."
|
194
|
-
end
|
195
|
-
|
196
|
-
# The method will search all the modules in the the object space for
|
197
|
-
# receiver class objects assigned to constants, and name these instances
|
198
|
-
# accordingly. Number of the remaining nameless instances is returned.
|
199
|
-
#
|
200
|
-
def const_magic
|
201
|
-
return 0 if nameless_instances.size == 0
|
202
|
-
serve_all_modules
|
203
|
-
return nameless_instances.size
|
204
|
-
end # def const_magic
|
205
|
-
|
206
|
-
# Returns those instances, which are nameless (@instances hash value is nil).
|
207
|
-
#
|
208
|
-
def nameless_instances
|
209
|
-
__instances__.select { |key, val| val.nil? }.keys
|
210
|
-
end
|
211
|
-
|
212
|
-
# Clears class-owned references to a specified instance.
|
213
|
-
#
|
214
|
-
def forget( which_instance )
|
215
|
-
inst = begin
|
216
|
-
instance( which_instance )
|
217
|
-
rescue ArgumentError
|
218
|
-
return nil # nothing to forget
|
219
|
-
end
|
220
|
-
ɴ = inst.nil? ? nil : inst.name
|
221
|
-
namespace.send :remove_const, ɴ if ɴ # clear constant assignment
|
222
|
-
__instances__.delete( inst ) # remove @instances entry
|
223
|
-
__avid_instances__.delete( inst ) # remove if any
|
224
|
-
return inst # return forgotten instance
|
225
|
-
end
|
226
|
-
|
227
|
-
# Clears class-owned references to a specified instance without performing
|
228
|
-
# #const_magic first. The argument must be an instance of the target class.
|
229
|
-
#
|
230
|
-
def __forget__( instance )
|
231
|
-
name = __instances__.delete instance # remove @instances entry
|
232
|
-
__avid_instances__.delete( instance ) # remove if any
|
233
|
-
namespace.send :remove_const, name if name
|
234
|
-
return instance
|
235
|
-
end
|
236
|
-
|
237
|
-
# Clears class-owned references anonymous instances.
|
238
|
-
#
|
239
|
-
def forget_anonymous_instances
|
240
|
-
nameless_instances.each { |inst, ɴ|
|
241
|
-
__instances__.delete inst
|
242
|
-
__avid_instances__.delete inst
|
243
|
-
}
|
244
|
-
end
|
245
|
-
alias :forget_nameless_instances :forget_anonymous_instances
|
246
|
-
|
247
|
-
# Clears class-owned references to all the instances.
|
248
|
-
#
|
249
|
-
def forget_all_instances
|
250
|
-
__instances__.clear # clears @instances
|
251
|
-
constants( false ).each { |ß| # clear constants in the namespace
|
252
|
-
namespace.send :remove_const, ß if const_get( ß ).is_a? self
|
253
|
-
}
|
254
|
-
end
|
255
|
-
|
256
|
-
# Registers a hook to execute whenever name magic creates a new instance
|
257
|
-
# of the class including NameMagic. The block should take one argument
|
258
|
-
# (the new instance that was created) and is called in #new method right
|
259
|
-
# after instantiation, but before naming.
|
260
|
-
#
|
261
|
-
def new_instance_closure █ @new_instance_closure = block end
|
262
|
-
|
263
|
-
# Registers a hook to execute whenever name setting is performed on an
|
264
|
-
# instance. The block should take three arguments (instance, name, old_name).
|
265
|
-
# The output value of the block is the name to be actually used – the hook
|
266
|
-
# thus allows to define transformations on the name when naming. It is the
|
267
|
-
# responsibility of the block to output a suitable symbol (capitalized,
|
268
|
-
# usable as a constant name etc.)
|
269
|
-
#
|
270
|
-
def name_set_closure █ @name_set_closure = block end
|
271
|
-
|
272
|
-
# Registers a hook to execute whenever the instance is asked about its
|
273
|
-
# name. The name object contained in __instances__[self] is subjected
|
274
|
-
# to the name_get_closure before being returned as instance name.
|
275
|
-
#
|
276
|
-
def name_get_closure █ @name_get_closure = block end
|
277
|
-
|
278
|
-
private
|
279
|
-
|
280
|
-
# Checks all the constants in some module's namespace, recursively.
|
281
|
-
#
|
282
|
-
def serve_all_modules
|
283
|
-
todo = ( nameless_instances + __avid_instances__ ).map( &:object_id ).uniq
|
284
|
-
ObjectSpace.each_object Module do |ɱ| # for all the modules...
|
285
|
-
# ( puts ɱ if DEBUG ) rescue
|
286
|
-
ɱ.constants( false ).each do |const_ß| # and all the constants...
|
287
|
-
begin # insurance against constant dynamic loading fails
|
288
|
-
◉ = ɱ.const_get( const_ß )
|
289
|
-
rescue LoadError, StandardError
|
290
|
-
next
|
291
|
-
end
|
292
|
-
if todo.include? ◉.object_id then # we found a wanted object
|
293
|
-
puts "NameMagic: Wanted object found under #{const_ß}." if DEBUG
|
294
|
-
if __avid_instances__.map( &:object_id ).include? ◉.object_id # avid
|
295
|
-
puts "NameMagic: It is avid." if DEBUG
|
296
|
-
__avid_instances__ # 1. remove from avid list
|
297
|
-
.delete_if { |instance| instance.object_id == ◉.object_id }
|
298
|
-
◉.name! const_ß # 2. name rudely
|
299
|
-
else # not avid
|
300
|
-
puts "NameMagic: It is not avid." if DEBUG
|
301
|
-
ɴ = if @name_set_closure then # honor name_set_closure
|
302
|
-
@name_set_closure.( const_ß, ◉, nil )
|
303
|
-
.tap { |r| puts "The resulting name is #{r}." }
|
304
|
-
else const_ß end
|
305
|
-
ɴ = validate_capitalization( ɴ ).to_sym
|
306
|
-
puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
|
307
|
-
conflicter = begin # be cautious
|
308
|
-
namespace.const_get( ɴ )
|
309
|
-
rescue NameError
|
310
|
-
end
|
311
|
-
if conflicter then
|
312
|
-
puts "NameMagic: Conflicter exists named #{ɴ}." if DEBUG
|
313
|
-
raise NameError, "Another #{self} named '#{ɴ}' already " +
|
314
|
-
"exists!" unless conflicter == ◉
|
315
|
-
else
|
316
|
-
puts "NameMagic: No conflicter named #{ɴ}, about to use it." if DEBUG
|
317
|
-
__instances__[ ◉ ] = ɴ # add the instance to the namespace
|
318
|
-
namespace.const_set ɴ, ◉ # add the instance to the namespace
|
319
|
-
end
|
320
|
-
end
|
321
|
-
todo.delete ◉.object_id # remove the id from todo list
|
322
|
-
break if todo.empty?
|
323
|
-
end
|
324
|
-
end # each
|
325
|
-
end # each_object Module
|
326
|
-
end # def serve_all_modules
|
327
|
-
|
328
|
-
# Checks whether a name starts with a capital letter.
|
329
|
-
#
|
330
|
-
def validate_capitalization name
|
331
|
-
ɴ = name.to_s
|
332
|
-
# check whether the name starts with 'A'..'Z'
|
333
|
-
raise NameError, "#{self.class} name must start with a capital " +
|
334
|
-
" letter 'A'..'Z' ('#{ɴ}' was given)!" unless ( ?A..?Z ) === ɴ[0]
|
335
|
-
return ɴ
|
137
|
+
old_ɴ = namespace.__instances__[ self ] # previous name
|
138
|
+
if ɴ then puts "NameMagic: Rudely naming with #{ɴ}." if DEBUG
|
139
|
+
ɴ = namespace.send( :validate_name, # honor the hook
|
140
|
+
namespace.name_set_closure.( ɴ, self, old_ɴ ) ).to_sym
|
141
|
+
puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
|
142
|
+
return false if old_ɴ == ɴ # already named as required
|
143
|
+
pair = namespace.__instances__.rassoc( ɴ )
|
144
|
+
namespace.__forget__( pair[0] ) if pair # rudely forget the collider
|
145
|
+
namespace.const_set ɴ, self # write a constant
|
146
|
+
namespace.__instances__[ self ] = ɴ # write to @instances
|
147
|
+
namespace.__forget__ old_ɴ # forget the old name of self
|
148
|
+
else
|
149
|
+
self.name = nil # unnaming, no collider issues
|
336
150
|
end
|
337
151
|
end
|
338
|
-
|
339
|
-
module ClassMethods
|
340
|
-
# In addition to 'constant magic' ability (name upon constant assignment),
|
341
|
-
# NameMagic redefines class method #new so that it eats parameter :name,
|
342
|
-
# alias :ɴ, and takes care of naming the instance accordingly. Option
|
343
|
-
# :name_avid can also be supplied (true/false), which makes the naming
|
344
|
-
# avid if true. (Avid, or aggresive naming means that the instance being
|
345
|
-
# named overwrites whatever was stored under that name earlier.)
|
346
|
-
#
|
347
|
-
def new *args, &block
|
348
|
-
oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract hash
|
349
|
-
ɴß = if oo[:name] then oo.delete :name # consume :name if supplied
|
350
|
-
elsif oo[:ɴ] then oo.delete :ɴ # consume :ɴ if supplied
|
351
|
-
else nil end
|
352
|
-
avid = oo[:name_avid] ? oo.delete( :name_avid ) : false # => true/false
|
353
|
-
# Avoid overwriting existing names unless avid:
|
354
|
-
raise NameError, "#{self} instance #{ɴß} already exists!" if
|
355
|
-
__instances__.keys.include? ɴß unless avid
|
356
|
-
# Instantiate:
|
357
|
-
args << oo unless oo.empty? # fuse hash
|
358
|
-
new_inst = original_method_new *args, &block
|
359
|
-
__instances__.merge! new_inst => nil # Instance is created unnamed
|
360
|
-
# honor the hook
|
361
|
-
@new_instance_closure.( new_inst ) if @new_instance_closure
|
362
|
-
if ɴß then # name was supplied, name the instance
|
363
|
-
if avid then new_inst.name! ɴß else new_inst.name = ɴß end
|
364
|
-
else # name wasn't supplied, make the instance avid
|
365
|
-
__avid_instances__ << new_inst
|
366
|
-
end
|
367
|
-
return new_inst # return the new instance
|
368
|
-
end
|
369
|
-
|
370
|
-
# Calls #new in avid mode (name_avid: true).
|
371
|
-
#
|
372
|
-
def new! *args, &block
|
373
|
-
oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract options
|
374
|
-
new *args, oo.merge!( name_avid: true ), &block
|
375
|
-
end
|
376
|
-
end # module ClassMethods
|
377
152
|
end # module NameMagic
|
data/lib/y_support/version.rb
CHANGED
data/test/name_magic_test.rb
CHANGED
@@ -14,12 +14,10 @@ describe NameMagic do
|
|
14
14
|
@reporter = Object.new
|
15
15
|
puts "..."
|
16
16
|
@reporter.singleton_class.class_exec { attr_reader :report, :naming }
|
17
|
-
@ç.new_instance_closure do |instance|
|
18
|
-
@reporter.define_singleton_method :report do
|
19
|
-
"New instance reported"
|
20
|
-
end
|
17
|
+
@ç.namespace.new_instance_closure do |instance|
|
18
|
+
@reporter.define_singleton_method :report do "Instance reported" end
|
21
19
|
end
|
22
|
-
@ç.name_set_closure do |name, instance, old_name|
|
20
|
+
@ç.namespace.name_set_closure do |name, instance, old_name|
|
23
21
|
@reporter.define_singleton_method :name_set do
|
24
22
|
"Name of the new instance was #{name}"
|
25
23
|
end
|
@@ -39,14 +37,14 @@ describe NameMagic do
|
|
39
37
|
@ç.nameless_instances.must_be_empty
|
40
38
|
@reporter.report.must_equal nil
|
41
39
|
x = @ç.new( name: "Boris" )
|
42
|
-
@reporter.report.must_equal "
|
40
|
+
@reporter.report.must_equal "Instance reported"
|
43
41
|
@reporter.name_set.must_equal "Name of the new instance was Boris"
|
44
42
|
x.name.must_equal :Boris
|
45
43
|
@reporter.name_get.must_equal "Name get closure called on Boris"
|
46
44
|
ufo = @ç.new
|
47
45
|
@ç.nameless_instances.must_equal [ufo]
|
48
46
|
UFO = @ç.new
|
49
|
-
@reporter.report.must_equal "
|
47
|
+
@reporter.report.must_equal "Instance reported"
|
50
48
|
@reporter.name_set.must_equal "Name of the new instance was Boris"
|
51
49
|
UFO.name
|
52
50
|
@reporter.name_set.must_equal "Name of the new instance was UFO"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: y_support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.15.p1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- boris
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-06-
|
11
|
+
date: 2013-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -75,6 +75,9 @@ files:
|
|
75
75
|
- lib/y_support/local_object.rb
|
76
76
|
- lib/y_support/misc.rb
|
77
77
|
- lib/y_support/name_magic.rb
|
78
|
+
- lib/y_support/name_magic/array.rb
|
79
|
+
- lib/y_support/name_magic/class_methods.rb
|
80
|
+
- lib/y_support/name_magic/namespace_methods.rb
|
78
81
|
- lib/y_support/null_object.rb
|
79
82
|
- lib/y_support/respond_to.rb
|
80
83
|
- lib/y_support/stdlib_ext.rb
|
@@ -123,9 +126,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
126
|
version: '0'
|
124
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
128
|
requirements:
|
126
|
-
- - '
|
129
|
+
- - '>'
|
127
130
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
131
|
+
version: 1.3.1
|
129
132
|
requirements: []
|
130
133
|
rubyforge_project:
|
131
134
|
rubygems_version: 2.0.3
|