y_support 2.1.5 → 2.1.12
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/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
|
+
|