y_support 2.0.14 → 2.0.15.p1
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.
- 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
|