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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +675 -0
  3. data/Rakefile +1 -1
  4. data/lib/y_support/abstract_algebra.rb +1 -0
  5. data/lib/y_support/all.rb +1 -0
  6. data/lib/y_support/core_ext/array.rb +2 -2
  7. data/lib/y_support/core_ext/class/misc.rb +2 -3
  8. data/lib/y_support/core_ext/class.rb +2 -2
  9. data/lib/y_support/core_ext/enumerable/misc.rb +1 -1
  10. data/lib/y_support/core_ext/enumerable.rb +2 -2
  11. data/lib/y_support/core_ext/hash/misc.rb +26 -39
  12. data/lib/y_support/core_ext/hash.rb +2 -2
  13. data/lib/y_support/core_ext/module/misc.rb +39 -1
  14. data/lib/y_support/core_ext/module.rb +2 -2
  15. data/lib/y_support/core_ext/numeric/misc.rb +0 -2
  16. data/lib/y_support/core_ext/numeric.rb +2 -2
  17. data/lib/y_support/core_ext/object/inspection.rb +0 -2
  18. data/lib/y_support/core_ext/object/misc.rb +31 -16
  19. data/lib/y_support/core_ext/object.rb +3 -3
  20. data/lib/y_support/core_ext/string/misc.rb +7 -7
  21. data/lib/y_support/core_ext/string.rb +2 -2
  22. data/lib/y_support/core_ext/symbol/misc.rb +2 -3
  23. data/lib/y_support/core_ext/symbol.rb +2 -2
  24. data/lib/y_support/core_ext.rb +0 -2
  25. data/lib/y_support/inert_recorder.rb +1 -1
  26. data/lib/y_support/kde.rb +1 -0
  27. data/lib/y_support/name_magic/array.rb +27 -7
  28. data/lib/y_support/name_magic/class_methods.rb +52 -58
  29. data/lib/y_support/name_magic/hash.rb +18 -4
  30. data/lib/y_support/name_magic/namespace_methods.rb +241 -174
  31. data/lib/y_support/name_magic.rb +82 -38
  32. data/lib/y_support/null_object.rb +8 -7
  33. data/lib/y_support/respond_to.rb +1 -2
  34. data/lib/y_support/stdlib_ext/matrix/misc.rb +0 -2
  35. data/lib/y_support/stdlib_ext/matrix.rb +2 -2
  36. data/lib/y_support/stdlib_ext.rb +1 -0
  37. data/lib/y_support/try.rb +1 -1
  38. data/lib/y_support/typing/array/typing.rb +45 -4
  39. data/lib/y_support/typing/array.rb +1 -1
  40. data/lib/y_support/typing/enumerable/typing.rb +26 -28
  41. data/lib/y_support/typing/enumerable.rb +1 -1
  42. data/lib/y_support/typing/hash/typing.rb +47 -29
  43. data/lib/y_support/typing/hash.rb +1 -1
  44. data/lib/y_support/typing/module/typing.rb +4 -6
  45. data/lib/y_support/typing/module.rb +1 -1
  46. data/lib/y_support/typing/object/typing.rb +64 -61
  47. data/lib/y_support/typing/object.rb +1 -1
  48. data/lib/y_support/typing.rb +4 -26
  49. data/lib/y_support/unicode.rb +5 -7
  50. data/lib/y_support/version.rb +1 -1
  51. data/lib/y_support/x.rb +11 -8
  52. data/lib/y_support.rb +2 -2
  53. data/test/abstract_algebra_test.rb +44 -55
  54. data/test/inert_recorder_test.rb +13 -18
  55. data/test/local_object_test.rb +13 -18
  56. data/test/misc_test.rb +15 -10
  57. data/test/name_magic_test.rb +1 -1
  58. data/test/null_object_test.rb +19 -26
  59. data/test/respond_to_test.rb +15 -16
  60. data/test/typing_test.rb +100 -103
  61. data/test/x_test.rb +16 -19
  62. data/y_support.gemspec +21 -17
  63. metadata +37 -8
  64. data/LICENSE +0 -22
@@ -1,193 +1,260 @@
1
1
  # encoding: utf-8
2
2
 
3
- module NameMagic
4
- module NamespaceMethods
5
- # Presents the instances registered in this namespace.
6
- #
7
- def instances *args
8
- const_magic
9
- __instances__.keys
10
- end
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
- # Presents the instance names registered in this namespace.
13
- #
14
- def instance_names *args
15
- instances.names( false )
16
- end
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
- # Presents namespace-owned +@instances+ hash. The hash consists of pairs
19
- # <code>{ instance => instance_name }</code>. Unnamed instances have +nil+
20
- # assigned to them as their name. (The method does not trigger
21
- # +#const_magic+.)
22
- #
23
- def __instances__
24
- @instances ||= {}
25
- end
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
- # Avid instances registered in this namespace. ("Avid" means that the
28
- # instance is able to steal (overwrite) a name from another registered
29
- # instance. (The method does not trigger +#const_magic+.)
30
- #
31
- def __avid_instances__
32
- @avid_instances ||= []
33
- end
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
- # Returns the instance identified by the argument, which can be typically
36
- # a name (string/symbol). If a registered instance is supplied, it will be
37
- # returned unchanged.
38
- #
39
- def instance id, *args
40
- # puts "#instance( #{identifier} )" if DEBUG
41
- # In @instances hash, value 'nil' indicates a nameless instance!
42
- fail TypeError, "'nil' is not an instance identifier!" if id.nil?
43
- ii = instances
44
- return id if ii.include? id # return the instance back
45
- begin # identifier not a registered instace -- treat it as a name
46
- ary = [id, id.to_sym]
47
- ii.find { |inst| ary.include? inst.name }
48
- rescue NoMethodError
49
- end or fail NameError, "No instance #{id} in #{self}."
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
- # Searches all the modules in the the object space for constants containing
53
- # receiver class objects, and names the found instances accordingly. The
54
- # number of the remaining nameless instances is returned.
55
- #
56
- def const_magic
57
- puts "#{self}#const_magic invoked!" if ::NameMagic::DEBUG
58
- return 0 if nameless_instances.size == 0
59
- serve_all_modules
60
- return nameless_instances.size
61
- end
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
- # Returns those instances, which are nameless (whose name is set to nil).
64
- #
65
- def nameless_instances *args
66
- __instances__.select { |key, val| val.nil? }.keys
67
- end
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
- # Clears namespace-owned references to a specified instance. (This is
70
- # different from "unnaming" an instance by setting <code>inst.name =
71
- # nil</code>, which makes the instance anonymous, but still registered.)
72
- #
73
- def forget instance_identifier, *args
74
- inst = begin
75
- instance( instance_identifier )
76
- rescue ArgumentError
77
- return nil # nothing to forget
78
- end
79
- ɴ = inst.nil? ? nil : inst.name
80
- namespace.send :remove_const, ɴ if ɴ # clear constant assignment
81
- __instances__.delete( inst ) # remove @instances entry
82
- __avid_instances__.delete( inst ) # remove if any
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
- # Clears namespace-owned references to an instance, without performing
87
- # #const_magic first. The argument should be a registered instance. Returns
88
- # the instance name, or _false_, if there was no such registered instance.
89
- #
90
- def __forget__( instance, *args )
91
- return false unless __instances__.keys.include? instance
92
- namespace.send :remove_const, instance.name if instance.name
93
- __avid_instances__.delete( instance )
94
- __instances__.delete instance
95
- end
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
- # Clears namespace-owned references to all the anonymous instances.
98
- #
99
- def forget_nameless_instances
100
- nameless_instances.each { |inst, ɴ|
101
- __instances__.delete inst
102
- __avid_instances__.delete inst # also from here
103
- }
104
- end
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
- # Clears namespace-owned references to all the instances.
107
- #
108
- def forget_all_instances
109
- __instances__.clear # clears @instances
110
- constants( false ).each { |ß| # clear constants in the namespace
111
- namespace.send :remove_const, ß if const_get( ß ).is_a? self
112
- }
113
- end
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
- # Registers a hook to execute upon instantiation. Expects a unary block, whose
116
- # argument represents the new instance. It is called right after instantiation,
117
- # but before naming the instance.
118
- #
119
- def new_instance_hook &block
120
- @new_instance_hook = block if block
121
- @new_instance_hook ||= -> instance { instance }
122
- end
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
- # Registers a hook to execute upon instance naming. Expects a ternary block,
125
- # with arguments instance, name, old_name, representing respectively the
126
- # instance to be named, the requested name, and the previous name of that
127
- # instance (if any). The output of the block should be the name to actually
128
- # be used. In other words, the hook can be used (among other things) to check
129
- # and/or modify the requested name when christening the instance. It is the
130
- # responsibility of this block to output a symbol that can be used as a Ruby
131
- # constant name.
132
- #
133
- def name_set_hook &block
134
- @name_set_hook = block if block
135
- @name_set_hook ||= -> name, instance, old_name=nil { name }
136
- end
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
- # Registers a hook to execute whenever the instance is asked its name. The
139
- # instance names are objects that are kept in a hash referred to by
140
- # +@instances+ variable owned by the namespace. Normally, +NameMagic#name+
141
- # simply returns the name of the instance, as found in the +@instances+ hash.
142
- # When +name_get_hook+ is defined, this name is transformed by it before being
143
- # returned.
144
- #
145
- def name_get_hook &block
146
- @name_get_hook = block if block
147
- @name_get_hook ||= -> name { name }
148
- end
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
- private
151
-
152
- # Checks all the constants in some module's namespace, recursively.
153
- #
154
- def serve_all_modules
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 # module NamespaceMethods
193
- end # module NameMagic
259
+ end
260
+ end # module NameMagic::NamespaceMethods
@@ -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 (alias :ɴ). One can write:
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.name #=> :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.instance_names #=> []
48
- # Animal.instance_names #=> [:Livia]
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.instance_names #=> [:Spot, :Rover]
60
- # Animal.instance_names #=> []
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 an instance name.
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 name
105
+ def _name_
102
106
  self.class.const_magic
103
107
  __name__ or ( yield self if block_given? )
104
108
  end
105
- alias ɴ name
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 ] # previous name
118
- if ɴ then # puts "NameMagic: Naming with argument #{ɴ}." if DEBUG
119
- ɴ = namespace.send( :validate_name, # honor the hook
120
- namespace.name_set_hook.( ɴ, self, old_ɴ ) ).to_sym
121
- # puts "NameMagic: Name adjusted to #{ɴ}." if DEBUG
122
- return if old_ɴ == ɴ # already named as required
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
- if ɴ then # puts "NameMagic: Rudely naming with #{ɴ}." if DEBUG
139
- ɴ = namespace.send( :validate_name, # honor the hook
140
- namespace.name_set_hook.( ɴ, 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
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
+