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.
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
+