y_support 2.1.5 → 2.1.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +675 -0
- data/Rakefile +1 -1
- data/lib/y_support/abstract_algebra.rb +1 -0
- data/lib/y_support/all.rb +1 -0
- data/lib/y_support/core_ext/array.rb +2 -2
- data/lib/y_support/core_ext/class/misc.rb +2 -3
- data/lib/y_support/core_ext/class.rb +2 -2
- data/lib/y_support/core_ext/enumerable/misc.rb +1 -1
- data/lib/y_support/core_ext/enumerable.rb +2 -2
- data/lib/y_support/core_ext/hash/misc.rb +26 -39
- data/lib/y_support/core_ext/hash.rb +2 -2
- data/lib/y_support/core_ext/module/misc.rb +39 -1
- data/lib/y_support/core_ext/module.rb +2 -2
- data/lib/y_support/core_ext/numeric/misc.rb +0 -2
- data/lib/y_support/core_ext/numeric.rb +2 -2
- data/lib/y_support/core_ext/object/inspection.rb +0 -2
- data/lib/y_support/core_ext/object/misc.rb +31 -16
- data/lib/y_support/core_ext/object.rb +3 -3
- data/lib/y_support/core_ext/string/misc.rb +7 -7
- data/lib/y_support/core_ext/string.rb +2 -2
- data/lib/y_support/core_ext/symbol/misc.rb +2 -3
- data/lib/y_support/core_ext/symbol.rb +2 -2
- data/lib/y_support/core_ext.rb +0 -2
- data/lib/y_support/inert_recorder.rb +1 -1
- data/lib/y_support/kde.rb +1 -0
- data/lib/y_support/name_magic/array.rb +27 -7
- data/lib/y_support/name_magic/class_methods.rb +52 -58
- data/lib/y_support/name_magic/hash.rb +18 -4
- data/lib/y_support/name_magic/namespace_methods.rb +241 -174
- data/lib/y_support/name_magic.rb +82 -38
- data/lib/y_support/null_object.rb +8 -7
- data/lib/y_support/respond_to.rb +1 -2
- data/lib/y_support/stdlib_ext/matrix/misc.rb +0 -2
- data/lib/y_support/stdlib_ext/matrix.rb +2 -2
- data/lib/y_support/stdlib_ext.rb +1 -0
- data/lib/y_support/try.rb +1 -1
- data/lib/y_support/typing/array/typing.rb +45 -4
- data/lib/y_support/typing/array.rb +1 -1
- data/lib/y_support/typing/enumerable/typing.rb +26 -28
- data/lib/y_support/typing/enumerable.rb +1 -1
- data/lib/y_support/typing/hash/typing.rb +47 -29
- data/lib/y_support/typing/hash.rb +1 -1
- data/lib/y_support/typing/module/typing.rb +4 -6
- data/lib/y_support/typing/module.rb +1 -1
- data/lib/y_support/typing/object/typing.rb +64 -61
- data/lib/y_support/typing/object.rb +1 -1
- data/lib/y_support/typing.rb +4 -26
- data/lib/y_support/unicode.rb +5 -7
- data/lib/y_support/version.rb +1 -1
- data/lib/y_support/x.rb +11 -8
- data/lib/y_support.rb +2 -2
- data/test/abstract_algebra_test.rb +44 -55
- data/test/inert_recorder_test.rb +13 -18
- data/test/local_object_test.rb +13 -18
- data/test/misc_test.rb +15 -10
- data/test/name_magic_test.rb +1 -1
- data/test/null_object_test.rb +19 -26
- data/test/respond_to_test.rb +15 -16
- data/test/typing_test.rb +100 -103
- data/test/x_test.rb +16 -19
- data/y_support.gemspec +21 -17
- metadata +37 -8
- data/LICENSE +0 -22
@@ -1,193 +1,260 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
# Module methods for the modules serving as +NameMagic+ namespaces. A namespace
|
4
|
+
# for a certain class featuring +NameMagic+ holds "civil registry" of all its
|
5
|
+
# instances, be they named or nameless. For this purpose, the namespace owns
|
6
|
+
# +@instances+ hash of pairs <tt>{ instance => name }</tt>, with _nil_ values
|
7
|
+
# denoting nameless instances. For named instances, the namespace also holds
|
8
|
+
# references to them in constants in the style <tt>Namespace::Name</tt>. This
|
9
|
+
# is one of the reasons why instance names in +NameMagic+ must start with
|
10
|
+
# a capital letter and generally must be usable as constant names. The list of
|
11
|
+
# instances is accessible via +#instances+ method. Individual instances can be
|
12
|
+
# queried by +#instance+ method, eg. by their names.
|
13
|
+
#
|
14
|
+
# === Life cycle of instances of classes featuring +NameMagic+
|
15
|
+
#
|
16
|
+
# +NameMagic+ offers 3 hooks for the instances of its user classes. These hooks
|
17
|
+
# are closures invoked at the relevant points of the instances' life cycle:
|
18
|
+
#
|
19
|
+
# * new instance hook -- when the instance is created
|
20
|
+
# * name set hook -- when the instance is offered a name
|
21
|
+
# * name get hook -- when the instance's name is queried
|
22
|
+
#
|
23
|
+
# These three hooks are stored in instance variables owned by the namespace,
|
24
|
+
# accesible by methods +#new_instance_hook, +#name_set_hook+ and
|
25
|
+
# +#name_get_hook+. If called with a block, these methods also serve to set
|
26
|
+
# their respective hook closures.
|
27
|
+
#
|
28
|
+
# When an instance is first created, unary +new_instance_hook+ is called.
|
29
|
+
# When an instance is offered a name, +name_set_hook+ is called. It is a
|
30
|
+
# ternary closure with 3 ordered arguments +name+, +instance+ and +old_name+,
|
31
|
+
# receiving respectively the name offered, the instance, and the previous
|
32
|
+
# name of the instance (if any). The return value of the closure will be used
|
33
|
+
# to actually name the instance. This closure can thus be used to check and
|
34
|
+
# modify the names before they are actually used. Finally, when the instances'
|
35
|
+
# name is queried, third closure, unary +name_get_hook+ is applied to modify
|
36
|
+
# the name output. The purpose of the name get hook is not to really change
|
37
|
+
# the name upon reading, but mainly to tweak the preferred form or spelling
|
38
|
+
# where multiple forms of are possible for the same name. (For example, the
|
39
|
+
# standard form in the +@instances+ hash could be in camel case, such as
|
40
|
+
# "AcinonyxJubatus", while preferred querying output would be a binomial name
|
41
|
+
# with whitespaces, "Acinonyx jubatus".)
|
42
|
+
#
|
43
|
+
# === Avidity of the instances
|
44
|
+
#
|
45
|
+
# After the offered name is checked and modified by the name set hook closure,
|
46
|
+
# there is one more remaining problem to worry about: Whether the name is
|
47
|
+
# already used by another instance in the same namespace. If the name is taken,
|
48
|
+
# the ensuing action depends on whether the instance being named is _avid_.
|
49
|
+
# Avid instances are so eager to get a name, that they will steal the offered
|
50
|
+
# name even if it is already in use, making the conflicting instance nameless
|
51
|
+
# in the process. In +NameMagic+, it turns out to be convenient to make the
|
52
|
+
# new instances avid by default, unless the name was explicitly supplied to the
|
53
|
+
# constructor by +:name+ argument, or avidity suppressed by setting +:name_avid
|
54
|
+
# option to _false_.
|
55
|
+
#
|
56
|
+
# Techincally, avid instances are registered as an array kept by the namespace
|
57
|
+
# under the variable +@avid_instances+.
|
58
|
+
#
|
59
|
+
# === Forgetting instances
|
60
|
+
#
|
61
|
+
# A namespace can de-register, or forget instances. For this purpose, see
|
62
|
+
# methods +#forget+, +#__forget__, +#forget_nameless_instances+,
|
63
|
+
# +#forget_all_instances+.
|
64
|
+
#
|
65
|
+
# === Ersatz constant magic
|
66
|
+
#
|
67
|
+
# To imitate built-in constant magic of some Ruby classes, +NamespaceMethods+
|
68
|
+
# provides ersatz method +#const_magic+, that searches all the modules in the
|
69
|
+
# object space for the pertinent instances newly assigned to constants. Method
|
70
|
+
# +#const_magic+ is then called before executing almost every public method of
|
71
|
+
# +NameMagic+, thus keeping the "civil registry" up-to-date. While not exactly
|
72
|
+
# computationally efficient, it tends to make the user code more readable and
|
73
|
+
# pays off in most usecases. For efficiency, we are looking forward to the
|
74
|
+
# +#const_assigned+ hook promised by Ruby core team...
|
75
|
+
#
|
76
|
+
# The namespace method versions that _do_ _not_ perform ersatz constant magic
|
77
|
+
# are generally denoted by underlines: Eg. methods +#__instances__+ and
|
78
|
+
# +#__forget__+ do not perform constant magic, while +#instances+ and +#forget+
|
79
|
+
# do.
|
80
|
+
#
|
81
|
+
module NameMagic::NamespaceMethods
|
82
|
+
# Presents the instances registered in this namespace.
|
83
|
+
#
|
84
|
+
def instances *args
|
85
|
+
const_magic
|
86
|
+
__instances__.keys
|
87
|
+
end
|
11
88
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
89
|
+
# Deprecated method to get full names of the named instances.
|
90
|
+
#
|
91
|
+
def instance_names
|
92
|
+
warn "Method #instance_names is deprecated. Use 'instances._names_' or 'instances.names' instead!"
|
93
|
+
instances.names false
|
94
|
+
end
|
17
95
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
96
|
+
# Presents namespace-owned +@instances+ hash. The hash consists of pairs
|
97
|
+
# <code>{ instance => instance_name }</code>. Unnamed instances have +nil+
|
98
|
+
# assigned to them as their name. (The method does not trigger
|
99
|
+
# +#const_magic+.)
|
100
|
+
#
|
101
|
+
def __instances__
|
102
|
+
@instances ||= {}
|
103
|
+
end
|
26
104
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
105
|
+
# Avid instances registered in this namespace. ("Avid" means that the
|
106
|
+
# instance is able to steal (overwrite) a name from another registered
|
107
|
+
# instance. (The method does not trigger +#const_magic+.)
|
108
|
+
#
|
109
|
+
def __avid_instances__
|
110
|
+
@avid_instances ||= []
|
111
|
+
end
|
34
112
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
113
|
+
# Returns the instance identified by the argument, which can be typically
|
114
|
+
# a name (string/symbol). If a registered instance is supplied, it will be
|
115
|
+
# returned unchanged.
|
116
|
+
#
|
117
|
+
def instance id, *args
|
118
|
+
# puts "#instance( #{identifier} )" if DEBUG
|
119
|
+
# In @instances hash, value 'nil' indicates a nameless instance!
|
120
|
+
fail TypeError, "'nil' is not an instance identifier!" if id.nil?
|
121
|
+
ii = instances
|
122
|
+
return id if ii.include? id # return the instance back
|
123
|
+
begin # identifier not a registered instance -- treat it as a name
|
124
|
+
ary = [id, id.to_sym]
|
125
|
+
ihsh = __instances__
|
126
|
+
ii.find { |inst| ary.include? ihsh[ inst ] or ary.include? inst.name }
|
127
|
+
rescue NoMethodError
|
128
|
+
end or fail NameError, "No instance #{id} in #{self}."
|
129
|
+
end
|
51
130
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
131
|
+
# Searches all the modules in the the object space for constants referring
|
132
|
+
# to receiver class objects, and names the found instances accordingly.
|
133
|
+
# Internally, it works by invoking private procedure +#search_all_modules.
|
134
|
+
# The return value is the remaining number of nameless instances.
|
135
|
+
#
|
136
|
+
def const_magic
|
137
|
+
puts "#{self}#const_magic invoked!" if ::NameMagic::DEBUG
|
138
|
+
return 0 if nameless_instances.size == 0
|
139
|
+
search_all_modules
|
140
|
+
return nameless_instances.size
|
141
|
+
end
|
62
142
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
143
|
+
# Returns those instances, which are nameless (whose name is set to nil).
|
144
|
+
#
|
145
|
+
def nameless_instances *args
|
146
|
+
__instances__.select { |key, val| val.nil? }.keys
|
147
|
+
end
|
68
148
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return inst # return the forgotten instance
|
84
|
-
end
|
149
|
+
# Clears namespace-owned references to a specified instance. (This is
|
150
|
+
# different from "unnaming" an instance by setting <code>inst.name =
|
151
|
+
# nil</code>, which makes the instance anonymous, but still registered.)
|
152
|
+
#
|
153
|
+
def forget instance_identifier, *args
|
154
|
+
inst = begin; instance( instance_identifier ); rescue ArgumentError
|
155
|
+
return nil # nothing to forget
|
156
|
+
end
|
157
|
+
ɴ = inst.nil? ? nil : inst.name
|
158
|
+
namespace.send :remove_const, ɴ if ɴ # clear constant assignment
|
159
|
+
__instances__.delete( inst ) # remove @instances entry
|
160
|
+
__avid_instances__.delete( inst ) # remove if any
|
161
|
+
return inst # return the forgotten instance
|
162
|
+
end
|
85
163
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
164
|
+
# Clears namespace-owned references to an instance, without performing
|
165
|
+
# #const_magic first. The argument should be a registered instance. Returns
|
166
|
+
# the instance name, or _false_, if there was no such registered instance.
|
167
|
+
#
|
168
|
+
def __forget__( instance, *args )
|
169
|
+
return false unless __instances__.keys.include? instance
|
170
|
+
namespace.send :remove_const, instance.name if instance.name
|
171
|
+
__avid_instances__.delete( instance )
|
172
|
+
__instances__.delete instance
|
173
|
+
end
|
96
174
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
175
|
+
# Clears namespace-owned references to all the anonymous instances.
|
176
|
+
#
|
177
|
+
def forget_nameless_instances
|
178
|
+
nameless_instances.each { |inst|
|
179
|
+
__instances__.delete( inst )
|
180
|
+
__avid_instances__.delete( inst ) # also from avid instances
|
181
|
+
}
|
182
|
+
end
|
105
183
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
184
|
+
# Clears namespace-owned references to all the instances.
|
185
|
+
#
|
186
|
+
def forget_all_instances
|
187
|
+
__instances__.clear
|
188
|
+
constants( false ).each { |sym|
|
189
|
+
namespace.send :remove_const, sym if const_get( sym ).is_a? self
|
190
|
+
}
|
191
|
+
end
|
114
192
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
193
|
+
# Registers a hook to execute upon instantiation. Expects a unary block, whose
|
194
|
+
# argument represents the new instance. It is called right after instantiation,
|
195
|
+
# but before naming the instance. Without a block, it acts as a getter.
|
196
|
+
#
|
197
|
+
def new_instance_hook &block
|
198
|
+
@new_instance_hook = block if block
|
199
|
+
@new_instance_hook ||= -> instance { instance }
|
200
|
+
end
|
123
201
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
202
|
+
# Registers a hook to execute upon instance naming. Expects a ternary block,
|
203
|
+
# with arguments name, instance and old_name, representing respectively the
|
204
|
+
# the requested name, the instance to be named, and the previous name of that
|
205
|
+
# instance (if any). The output of the block should be the name to actually
|
206
|
+
# be used. In other words, the hook can be used (among other things) to check
|
207
|
+
# and/or modify the requested name when christening the instance. It is the
|
208
|
+
# responsibility of this block to output a symbol that can be used as a Ruby
|
209
|
+
# constant name. Without a block, this method acts as a getter.
|
210
|
+
#
|
211
|
+
def name_set_hook &block
|
212
|
+
@name_set_hook = block if block
|
213
|
+
@name_set_hook ||= -> name, instance, old_name=nil { name }
|
214
|
+
end
|
137
215
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
216
|
+
# Registers a hook to execute whenever the instance is asked its name. The
|
217
|
+
# instance names are objects that are kept in a hash referred to by
|
218
|
+
# +@instances+ variable owned by the namespace. Normally, +NameMagic#name+
|
219
|
+
# simply returns the name of the instance, as found in the +@instances+ hash.
|
220
|
+
# When +name_get_hook+ is defined, this name is transformed by it before being
|
221
|
+
# returned. Without a block, this method acts as a getter.
|
222
|
+
#
|
223
|
+
def name_get_hook &block
|
224
|
+
@name_get_hook = block if block
|
225
|
+
@name_get_hook ||= -> name { name }
|
226
|
+
end
|
149
227
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
#
|
154
|
-
|
155
|
-
todo = ( nameless_instances + __avid_instances__ ).map( &:object_id ).uniq
|
156
|
-
ObjectSpace.each_object Module do |ɱ|
|
157
|
-
ɱ.constants( false ).each do |const_ß|
|
158
|
-
begin
|
159
|
-
◉ = ɱ.const_get( const_ß ) # insurance against const. loading fails
|
160
|
-
rescue LoadError, StandardError; next end
|
161
|
-
next unless todo.include? ◉.object_id
|
162
|
-
# puts "NameMagic: Anonymous object under #{const_ß}!" if DEBUG
|
163
|
-
if ◉.avid? then # puts "NameMagic: It is avid." if DEBUG
|
164
|
-
◉.make_not_avid! # 1. Remove it from the list of avid instances.
|
165
|
-
◉.name! const_ß # 2. Name it rudely.
|
166
|
-
else # puts "NameMagic: It is not avid." if DEBUG
|
167
|
-
ɴ = validate_name( name_set_hook.( const_ß, ◉, nil ) ).to_sym
|
168
|
-
# puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
|
169
|
-
conflicter = begin; const_get( ɴ ); rescue NameError; end
|
170
|
-
if conflicter then
|
171
|
-
msg = "Another #{self}-registered instance named '#{ɴ}' exists!"
|
172
|
-
fail NameError, msg unless conflicter == ◉
|
173
|
-
else # add the instance to the namespace
|
174
|
-
__instances__.update( ◉ => ɴ )
|
175
|
-
const_set( ɴ, ◉ )
|
176
|
-
end
|
177
|
-
end
|
178
|
-
todo.delete ◉.object_id # remove the id from todo list
|
179
|
-
break if todo.empty? # and break the loop if done
|
180
|
-
end # each
|
181
|
-
end # each_object Module
|
182
|
-
end # def serve_all_modules
|
183
|
-
|
184
|
-
# Checks whether a name starts with a capital letter.
|
185
|
-
#
|
186
|
-
def validate_name name
|
187
|
-
name.to_s.tap do |ɴ| # check whether the name starts with 'A'..'Z'
|
188
|
-
fail NameError, "#{self}-registered name must start with a capital " +
|
228
|
+
# Checks whether a name is acceptable as a constant name.
|
229
|
+
#
|
230
|
+
def validate_name name
|
231
|
+
name.to_s.tap do |ɴ| # check if the name starts with 'A'..'Z'
|
232
|
+
fail NameError, "#{self}-registered name must start with a capital " +
|
189
233
|
" letter 'A'..'Z' ('#{ɴ}' given)!" unless ( ?A..?Z ) === ɴ[0]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
# Checks all the constants in some module's namespace, recursively.
|
240
|
+
#
|
241
|
+
def search_all_modules
|
242
|
+
todo = ( nameless_instances + __avid_instances__ ).map( &:object_id ).uniq
|
243
|
+
ObjectSpace.each_object Module do |ɱ|
|
244
|
+
ɱ.constants( false ).each do |const_ß|
|
245
|
+
begin; instance = ɱ.const_get( const_ß ) # Some constants cause
|
246
|
+
rescue LoadError, StandardError; next end # errors upon loading.
|
247
|
+
next unless todo.include? instance.object_id
|
248
|
+
# puts "NameMagic: Anonymous object under #{const_ß}!" if DEBUG
|
249
|
+
if instance.avid? then # puts "NameMagic: It is avid." if DEBUG
|
250
|
+
instance.make_not_avid! # Remove from the avid list.
|
251
|
+
instance.name! const_ß # Name it rudely.
|
252
|
+
else # puts "NameMagic: It is not avid." if DEBUG
|
253
|
+
instance.name = const_ß # Name it cautiously.
|
254
|
+
end
|
255
|
+
todo.delete instance.object_id # Remove from todo list.
|
256
|
+
break if todo.empty? # Abandon the loop if done.
|
190
257
|
end
|
191
258
|
end
|
192
|
-
end
|
193
|
-
end # module NameMagic
|
259
|
+
end
|
260
|
+
end # module NameMagic::NamespaceMethods
|
data/lib/y_support/name_magic.rb
CHANGED
@@ -5,11 +5,10 @@ require 'y_support/core_ext/hash/misc'
|
|
5
5
|
|
6
6
|
require_relative 'name_magic/array'
|
7
7
|
require_relative 'name_magic/hash'
|
8
|
-
require_relative 'name_magic/namespace_methods'
|
9
|
-
require_relative 'name_magic/class_methods'
|
10
8
|
|
11
9
|
# This mixin imitates Ruby constant magic and automates the named argument
|
12
|
-
# :name
|
10
|
+
# :name, alias :ɴ (Character "ɴ", Unicode small capital N, generally stands
|
11
|
+
# for "name" ins YSupport). One can write:
|
13
12
|
#
|
14
13
|
# require 'y_support/name_magic'
|
15
14
|
# class Foo; include NameMagic end
|
@@ -17,7 +16,8 @@ require_relative 'name_magic/class_methods'
|
|
17
16
|
#
|
18
17
|
# and the resulting object will know its #name:
|
19
18
|
#
|
20
|
-
# Bar.
|
19
|
+
# Bar._name_ #=> :Bar
|
20
|
+
# Bar.full_name #=> "Foo::Bar"
|
21
21
|
# Foo::Bar #=> <Foo:0x.....>
|
22
22
|
#
|
23
23
|
# This is done by searching whole Ruby namespace for constants, triggered by the
|
@@ -44,8 +44,8 @@ require_relative 'name_magic/class_methods'
|
|
44
44
|
# Dog.namespace #=> Animal
|
45
45
|
# Cat.namespace #=> Animal
|
46
46
|
# Livia = Cat.new
|
47
|
-
# Cat.
|
48
|
-
# Animal.
|
47
|
+
# Cat.instances._names_ #=> []
|
48
|
+
# Animal.instances._names_ #=> [:Livia]
|
49
49
|
#
|
50
50
|
# To make the subclasses use each their own namespace, use +#namespace!+ method:
|
51
51
|
#
|
@@ -56,9 +56,8 @@ require_relative 'name_magic/class_methods'
|
|
56
56
|
#
|
57
57
|
# Dog.new name: "Spot"
|
58
58
|
# Dog.new ɴ: :Rover
|
59
|
-
# Dog.
|
60
|
-
# Animal.
|
61
|
-
#
|
59
|
+
# Dog.instances._names_ #=> [:Spot, :Rover]
|
60
|
+
# Animal.instances._names_ #=> []
|
62
61
|
#
|
63
62
|
# Lastly, a name can be assigned by #name= accssor, as in
|
64
63
|
#
|
@@ -71,6 +70,9 @@ require_relative 'name_magic/class_methods'
|
|
71
70
|
module NameMagic
|
72
71
|
DEBUG = false
|
73
72
|
|
73
|
+
require_relative 'name_magic/namespace_methods'
|
74
|
+
require_relative 'name_magic/class_methods'
|
75
|
+
|
74
76
|
def self.included target
|
75
77
|
if target.is_a? Class then # decorate #new
|
76
78
|
target.singleton_class.class_exec do
|
@@ -83,7 +85,7 @@ module NameMagic
|
|
83
85
|
namespace
|
84
86
|
end
|
85
87
|
end
|
86
|
-
target.singleton_class.class_exec { prepend NameMagic::ClassMethods }
|
88
|
+
target.singleton_class.class_exec { prepend ::NameMagic::ClassMethods }
|
87
89
|
else # it is a Module -- infect it with this #include
|
88
90
|
orig, this = target.method( :included ), method( :included )
|
89
91
|
target.define_singleton_method :included do |m| this.( m ); orig.( m ) end
|
@@ -96,14 +98,27 @@ module NameMagic
|
|
96
98
|
self.class.namespace
|
97
99
|
end
|
98
100
|
|
99
|
-
# Retrieves
|
101
|
+
# Retrieves the instance's name not prefixed by the namespace as a symbol.
|
102
|
+
# Underlines (+#_name_+) distinguish this method from +#name+ method, which
|
103
|
+
# returns full name string for compatibility with vanilla Ruby +Module#name+.
|
100
104
|
#
|
101
|
-
def
|
105
|
+
def _name_
|
102
106
|
self.class.const_magic
|
103
107
|
__name__ or ( yield self if block_given? )
|
104
108
|
end
|
105
|
-
alias ɴ
|
109
|
+
alias ɴ _name_
|
110
|
+
# FIXME: Delete the line below! Do it! Make #name return #full_name, as compatible with Class#name behavior!!!
|
111
|
+
alias name _name_
|
106
112
|
|
113
|
+
# Returns the instance's full name, a string in the style of those returned
|
114
|
+
# by +Module#name+ method, eg. "Namespace::Name".
|
115
|
+
#
|
116
|
+
def full_name
|
117
|
+
"#{namespace.name || namespace.inspect}::#{namespace.instances[ self ]}"
|
118
|
+
end
|
119
|
+
# FIXME: Uncomment the line below! Do it! Make #name return #full_name, as compatible with Class#name behavior!!!
|
120
|
+
# alias name full_name
|
121
|
+
|
107
122
|
# Retrieves the instance name. Does not trigger #const_magic before doing so.
|
108
123
|
#
|
109
124
|
def __name__
|
@@ -113,41 +128,34 @@ module NameMagic
|
|
113
128
|
|
114
129
|
# Names an instance, cautiously (ie. no overwriting of existing names).
|
115
130
|
#
|
116
|
-
def name=(
|
117
|
-
old_ɴ = namespace.__instances__[ self ]
|
118
|
-
if
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
131
|
+
def name=( name )
|
132
|
+
old_ɴ = namespace.__instances__[ self ] # previous name
|
133
|
+
if name.nil? then
|
134
|
+
namespace.__instances__.update( self => nil ) # unname in @instances
|
135
|
+
namespace.send :remove_const, old_ɴ if old_ɴ # remove namespace const.
|
136
|
+
else
|
137
|
+
ɴ = honor_name_set_hooks( name, old_ɴ )
|
138
|
+
return if old_ɴ == ɴ # already named as required
|
123
139
|
fail NameError, "Name '#{ɴ}' already exists in #{namespace} namespace!" if
|
124
140
|
self.class.__instances__.rassoc( ɴ )
|
141
|
+
namespace.__forget__ old_ɴ # forget the old name of self
|
125
142
|
namespace.const_set ɴ, self # write a constant
|
126
143
|
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
144
|
end
|
132
145
|
end
|
133
146
|
|
134
147
|
# Names an instance, aggresively (overwrites existing names).
|
135
148
|
#
|
136
|
-
def name!(
|
149
|
+
def name!( name )
|
137
150
|
old_ɴ = namespace.__instances__[ self ] # previous name
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
150
|
-
end
|
151
|
+
return self.name = nil if name.nil? # no collider concerns
|
152
|
+
ɴ = honor_name_set_hooks( name, old_ɴ )
|
153
|
+
return false if old_ɴ == ɴ # already named as required
|
154
|
+
pair = namespace.__instances__.rassoc( ɴ )
|
155
|
+
namespace.__forget__( pair[0] ) if pair # rudely forget the collider
|
156
|
+
namespace.__forget__ old_ɴ # forget the old name of self
|
157
|
+
namespace.const_set ɴ, self # write a constant
|
158
|
+
namespace.__instances__[ self ] = ɴ # write to @instances
|
151
159
|
end
|
152
160
|
|
153
161
|
# Is the instance avid for a name? (Will it overwrite other instance names?)
|
@@ -161,4 +169,40 @@ module NameMagic
|
|
161
169
|
def make_not_avid!
|
162
170
|
namespace.__avid_instances__.delete_if { |i| i.object_id == object_id }
|
163
171
|
end
|
172
|
+
|
173
|
+
# Registers a hook to execute upon instance naming. Instance's `#name_set_hook`
|
174
|
+
# Behaves analogically as namespace's `#name_set_hook`, and is executed right
|
175
|
+
# after the namespace's hook. Expects a block with a single argument, name of
|
176
|
+
# the instance. The return value of the block is not used and should be _nil_.
|
177
|
+
# Without a block, this method acts as a getter.
|
178
|
+
#
|
179
|
+
def name_set_hook &block
|
180
|
+
tap { @name_set_hook = block } if block
|
181
|
+
@name_set_hook ||= -> name { nil }
|
182
|
+
end
|
183
|
+
|
184
|
+
# Default +#to_s+ method for +NameMagic+ includers, returning the name.
|
185
|
+
#
|
186
|
+
def to_s
|
187
|
+
name ? name.to_s : super
|
188
|
+
end
|
189
|
+
|
190
|
+
# Default +#inspect+ method for +NameMagic+ includers.
|
191
|
+
#
|
192
|
+
def inspect
|
193
|
+
to_s
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
# Honors name set hooks, first for the namespace, then for the instance.
|
199
|
+
# Takes 2 arguments, name and old name of this instance. Returns the final
|
200
|
+
# name to be used
|
201
|
+
#
|
202
|
+
def honor_name_set_hooks suggested_name, old_name
|
203
|
+
ɴ = namespace.name_set_hook.( suggested_name, self, old_name ).to_sym
|
204
|
+
# puts "NameMagic: Name adjusted to #{name}." if DEBUG
|
205
|
+
namespace.validate_name( ɴ ).to_sym.tap { |ɴ| name_set_hook.( ɴ ) }
|
206
|
+
end
|
164
207
|
end # module NameMagic
|
208
|
+
|