y_support 2.1.18 → 2.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/y_support/all.rb +2 -32
  3. data/lib/y_support/core_ext/array.rb +2 -2
  4. data/lib/y_support/core_ext/class.rb +2 -2
  5. data/lib/y_support/core_ext/enumerable.rb +2 -2
  6. data/lib/y_support/core_ext/hash/misc.rb +23 -10
  7. data/lib/y_support/core_ext/hash.rb +2 -2
  8. data/lib/y_support/core_ext/module/misc.rb +9 -0
  9. data/lib/y_support/core_ext/module.rb +2 -2
  10. data/lib/y_support/core_ext/numeric.rb +2 -2
  11. data/lib/y_support/core_ext/object/inspection.rb +8 -2
  12. data/lib/y_support/core_ext/object.rb +3 -3
  13. data/lib/y_support/core_ext/string/misc.rb +9 -12
  14. data/lib/y_support/core_ext/string.rb +2 -2
  15. data/lib/y_support/core_ext/symbol.rb +2 -2
  16. data/lib/y_support/core_ext.rb +1 -1
  17. data/lib/y_support/flex_coerce/class_methods.rb +49 -0
  18. data/lib/y_support/flex_coerce/flex_proxy.rb +121 -0
  19. data/lib/y_support/flex_coerce/module_methods.rb +37 -0
  20. data/lib/y_support/flex_coerce.rb +24 -0
  21. data/lib/y_support/kde.rb +1 -1
  22. data/lib/y_support/literate.rb +253 -0
  23. data/lib/y_support/local_object.rb +1 -1
  24. data/lib/y_support/name_magic/array_methods.rb +48 -0
  25. data/lib/y_support/name_magic/class_methods.rb +205 -161
  26. data/lib/y_support/name_magic/hash_methods.rb +33 -0
  27. data/lib/y_support/name_magic/namespace.rb +449 -0
  28. data/lib/y_support/name_magic.rb +358 -100
  29. data/lib/y_support/null_object.rb +1 -1
  30. data/lib/y_support/respond_to.rb +1 -1
  31. data/lib/y_support/stdlib_ext/matrix/misc.rb +2 -2
  32. data/lib/y_support/stdlib_ext/matrix.rb +2 -2
  33. data/lib/y_support/stdlib_ext.rb +1 -1
  34. data/lib/y_support/typing/array.rb +1 -1
  35. data/lib/y_support/typing/enumerable.rb +1 -1
  36. data/lib/y_support/typing/hash.rb +1 -1
  37. data/lib/y_support/typing/module.rb +1 -1
  38. data/lib/y_support/typing/object/typing.rb +17 -15
  39. data/lib/y_support/typing/object.rb +1 -1
  40. data/lib/y_support/typing.rb +14 -10
  41. data/lib/y_support/unicode.rb +1 -1
  42. data/lib/y_support/version.rb +1 -1
  43. data/lib/y_support/x.rb +2 -1
  44. data/test/flex_coerce_test.rb +134 -0
  45. data/test/literate_test.rb +231 -0
  46. data/test/misc_test.rb +49 -27
  47. data/test/name_magic_test.rb +907 -60
  48. data/test/typing_test.rb +7 -7
  49. metadata +14 -13
  50. data/lib/y_support/abstract_algebra.rb +0 -234
  51. data/lib/y_support/name_magic/array.rb +0 -38
  52. data/lib/y_support/name_magic/hash.rb +0 -31
  53. data/lib/y_support/name_magic/namespace_methods.rb +0 -260
  54. data/lib/y_support/try.rb +0 -133
  55. data/test/abstract_algebra_test.rb +0 -138
  56. data/test/performance_test_example.rb +0 -23
  57. data/test/try_test.rb +0 -102
@@ -0,0 +1,449 @@
1
+ # encoding: utf-8
2
+
3
+ # Module methods for the modules serving as +NameMagic+
4
+ # namespaces. What is a +NameMagic+ namespace? For a class that
5
+ # includes +NameMagic+, namespace is the "civil registry" of all
6
+ # instances, both named and nameless. For this purpose, namespace
7
+ # has variable +@instances+. The registry of instances is a hash of
8
+ # pairs <tt>{ instance => name }</tt>. Nameless instances have
9
+ # _nil_ value instead of name in the registry. In general Ruby,
10
+ # namespace would mean that the module holds the instances in
11
+ # constants looking like <tt>Namespace::Name</tt>. In Ruby core, we
12
+ # can see such behavior with Struct class, which has native
13
+ # constant magic and stores its instances in constants using Struct
14
+ # as a namespace. NameMagic used to perform this constant
15
+ # assignment in the namespace prior to YSupport version ~2.0. This
16
+ # is one of the reasons why names of instances must start with a
17
+ # capital letter and be usable as constant names. Since YSupport
18
+ # version 2.0+, NameMagic does no constant assignment on its own --
19
+ # all constant assignments are left to the user. In this way,
20
+ # NameMagic now sees constant assignment purely as a way to learn
21
+ # instance names intended by the user.
22
+ #
23
+ # The instance registry is accessible via +#instances+
24
+ # method. Individual instances can be queried for by +#instance+
25
+ # method, eg. by their names. Until Matz provides the possibility
26
+ # of constant magic in every class, which I requested some time
27
+ # ago, the registry of instances will remain the essential part,
28
+ # without which +NameMagic wouldn't work.
29
+ #
30
+ # === Life cycle of instances of +NameMagic+ user classes
31
+ #
32
+ # Let us consider for example Human class that uses NameMagic.
33
+ #
34
+ # class Human
35
+ # require 'y_support/name_magic'
36
+ # include NameMagic
37
+ # end
38
+ #
39
+ # Life cycle of Human in instances of begins, unsurprisingly, by
40
+ # instantiation. All instances are created nameless:
41
+ #
42
+ # newborn = Human.new
43
+ # newborn.name #=> nil
44
+ #
45
+ # User has several ways of naming the instances. Typical for
46
+ # +NameMagic+ is naming by constant assignment:
47
+ #
48
+ # Fred = newborn
49
+ # newborn.name #=> :Fred
50
+ #
51
+ # +NameMagic makes it possible to supply :name parameter directly
52
+ # to the #new method. Such instances are named immediately:
53
+ #
54
+ # newborn = Human.new name: "Joe"
55
+ # newborn.name #=> :Joe
56
+ #
57
+ # Another way is to name the instances using #name= method.
58
+ #
59
+ # newborn = Human.new
60
+ # newborn.name #=> nil
61
+ # newborn.name = "Mike"
62
+ # newborn.name #=> Mike
63
+ #
64
+ # Just for the record, we have created three instances:
65
+ #
66
+ # Human.instances #=> [Fred, Joe, Mike]
67
+ #
68
+ # In other words, at some point in their life, instances may or may
69
+ # not undergo baptism, which involves a complicated procedure of
70
+ # searching all existing Ruby modules for constants to which
71
+ # nameless instances of the class in question are
72
+ # assigned. Baptized instances then know their names, and can be
73
+ # accessed by their names through the instance registry:
74
+ #
75
+ # Human.instance( "Mike" ) #=> Mike
76
+ #
77
+ # Namespace gives the user 2 hook methods, #instantiation_exec and
78
+ # #exec_when_naming. The first one is executed upon instantiation
79
+ # and passed one argument, the new instance. The second one is
80
+ # executed when the namespace baptizes a new instance, and passed
81
+ # three arguments: suggested name, instance, and previous name if
82
+ # any. (Renaming instances may require special care.) Consequantly,
83
+ # you should define unary block with #instantiation_exec and
84
+ # ternary one with #exec_when_naming. Example:
85
+ #
86
+ # Human.instantiation_exec do |instance|
87
+ # puts "Instance with object id #{instance.object_id} created!"
88
+ # end
89
+ # newborn = Human.new #=> Instance with object id 75756140 created!
90
+ #
91
+ # The naming hook can also be used to censor and modify the
92
+ # intended name. Consider the following censorship:
93
+ #
94
+ # Human.exec_when_naming do |name, instance, old_name|
95
+ # fail NameError, "#{name.capitalize} is not a saint in the " +
96
+ # "Church of Emacs!" unless name.end_with? "gnucius"
97
+ # "St_IGNUcius"
98
+ # end
99
+ #
100
+ # Now we can no longer use ordinary names, we have to use names of
101
+ # saints in the Church of Emacs!
102
+ #
103
+ # newborn.name = "Dave"
104
+ # #=> NameError: Dave is not a saint in the Church of Emacs!
105
+ #
106
+ # Name Ignucius is OK, but the censor corrects it to St_IGNUcius:
107
+ #
108
+ # newborn.name = "Ignucius" #=> St_IGNUcius
109
+ #
110
+ # Life cycle of an instance ends when it is deleted from the
111
+ # instance registry and garbage-collected (unless something else
112
+ # holds a reference to it). Example:
113
+ #
114
+ # Human.instances #=> [Fred, Joe, Mike, St_IGNUcius]
115
+ # Human.forget "St_IGNUcius"
116
+ # Human.instances #=> [Fred, Joe, Mike]
117
+ #
118
+ # St. IGNUcius has just been deleted from the registry.
119
+ #
120
+ # Human.forget_all_instances
121
+ # Human.instances #=> []
122
+ #
123
+ # All Human instances have now been deleted from the registry, but
124
+ # only Joe and Mike are garbage-collected, because Fred is still
125
+ # assigned to a constant. Fred still exists, but he and his name
126
+ # has been deleted from the instance registry.
127
+ #
128
+ # Fred #=> #<Human:0x89449b0>
129
+ #
130
+ # We could even re-register Fred by recreating his entry, although
131
+ # this is far from the way +NameMagic+ works in everyday life.
132
+ #
133
+ # Human.__instances__.merge! Fred => :Fred
134
+ # Human.instances #=> [Fred]
135
+ #
136
+ # === Avidity of the instances
137
+ #
138
+ # After the offered name is checked and modified by the name set
139
+ # hook closure, there is one more remaining problem to worry about:
140
+ # Whether the name is already used by another instance in the same
141
+ # namespace. If the name is taken, the ensuing action depends on
142
+ # whether the instance being named is _avid_. Avid instances are
143
+ # so eager to get a name, that they will steal the offered name for
144
+ # themselves even if other instances already use the name, making
145
+ # the conflicting instance nameless in the process. In +NameMagic+,
146
+ # it turns out to be convenient to make the new instances avid by
147
+ # default, unless the name was explicitly supplied to the
148
+ # constructor by +:name+ argument, or avidity suppressed by setting
149
+ # +:name_avid option to _false_.
150
+ #
151
+ # Techincally, avid instances are registered as an array kept by
152
+ # the namespace under the variable +@avid_instances+.
153
+ #
154
+ # === Forgetting instances
155
+ #
156
+ # As mentioned earlier, namespace can de-register, or forget
157
+ # instances. For this purpose, see methods +#forget+, +#__forget__,
158
+ # +#forget_nameless_instances+, +#forget_all_instances+.
159
+ #
160
+ # === Ersatz constant magic
161
+ #
162
+ # To imitate built-in constant magic of some Ruby classes,
163
+ # +NamespaceMethods+ provides ersatz method +#const_magic+, that
164
+ # searches all the modules in the object space for the pertinent
165
+ # instances newly assigned to constants. Method +#const_magic+ is
166
+ # called automatically before executing almost every public method
167
+ # of +NameMagic+, thus keeping the "civil registry"
168
+ # up-to-date. While not exactly computationally efficient, it tends
169
+ # to make the user code more readable and pays off in most
170
+ # usecases. For efficiency, we are looking forward to the
171
+ # +#const_assigned+ hook promised by Ruby core team...
172
+ #
173
+ # The namespace method versions that _do_ _not_ perform ersatz
174
+ # constant magic are generally denoted by underlines: Eg. methods
175
+ # +#__instances__+ and +#__forget__+ do not perform constant magic,
176
+ # while +#instances+ and +#forget+ do.
177
+ #
178
+ module NameMagic::Namespace
179
+ # Orders the namespace to disallow unnaming instances. As a
180
+ # consequence, the instances' names will now be permanent.
181
+ #
182
+ def permanent_names!
183
+ @permanent_names = true
184
+ end
185
+
186
+ # Inquirer whether unnaming instances has been disallowed in
187
+ # the namespace.
188
+ #
189
+ def permanent_names?
190
+ @permanent_names
191
+ end
192
+
193
+ # Presents the instances registered in this namespace.
194
+ #
195
+ def instances *args
196
+ const_magic
197
+ __instances__.keys
198
+ end
199
+
200
+ # Presents namespace-owned +@instances+ hash. The hash consists
201
+ # of pairs <code>{ instance => instance_name }</code>. Unnamed
202
+ # instances have +nil+ value instead of their name. This method
203
+ # does not trigger +#const_magic+.
204
+ #
205
+ def __instances__
206
+ @instances ||= {}
207
+ end
208
+
209
+ # Avid instances registered in this namespace. ("Avid" means that
210
+ # the instance will steal (overwrite) a name from another
211
+ # instance, should there be a conflict. The method does not
212
+ # trigger +#const_magic+.
213
+ #
214
+ def __avid_instances__
215
+ @avid_instances ||= []
216
+ end
217
+
218
+ # Returns the instance identified by the argument.
219
+ #
220
+ def instance arg
221
+ # In @instances hash, nil value denotes nameless instances!
222
+ fail TypeError,
223
+ "Nil is not an instance identifier!" if arg.nil?
224
+ # Get the list of all instances.
225
+ ii = instances
226
+ # If arg belongs to the list, just return it back.
227
+ return arg if ii.include? arg
228
+ # Assume that arg is an instance name.
229
+ name = arg.to_sym
230
+ registry = __instances__
231
+ ii.find { |i| registry[ i ] == name } or
232
+ fail NameError, "No instance #{arg} in #{self}!"
233
+ end
234
+
235
+ # Searches all the modules in the the object space for constants
236
+ # referring to receiver class objects, and names the found
237
+ # instances accordingly. Internally, it works by invoking
238
+ # private procedure +#search_all_modules. The return value is
239
+ # the remaining number of nameless instances.
240
+ #
241
+ def const_magic
242
+ return 0 if nameless_instances.size == 0
243
+ search_all_modules
244
+ return nameless_instances.size
245
+ end
246
+
247
+ # Returns those instances, whose name is nil. This method does
248
+ # not trigger #const_magic.
249
+ #
250
+ def nameless_instances *args
251
+ __instances__.select { |key, val| val.nil? }.keys
252
+ end
253
+
254
+ # Removes the specified instance from the registry. Note that
255
+ # this is different from "unnaming" an instance by setting
256
+ # <code>inst.name = nil</code>, which makes the instance
257
+ # anonymous, but still registered.
258
+ #
259
+ def forget instance, *args
260
+ instance = begin
261
+ instance instance
262
+ rescue ArgumentError
263
+ return nil # nothing to forget
264
+ end
265
+ ɴ = instance.nil? ? nil : instance.name
266
+ # namespace.send :remove_const, ɴ if ɴ
267
+ __instances__.delete( instance )
268
+ __avid_instances__.delete( instance )
269
+ return instance
270
+ end
271
+
272
+ # Removes the specified instance from the registry, without
273
+ # performing #const_magic first. The argument should be a
274
+ # registered instance. Returns instance name for forgotten named
275
+ # instances, _nil_ for forgotten nameless instances, and _false_
276
+ # if the argument was not a registered instance.
277
+ #
278
+ def __forget__ instance
279
+ return false unless __instances__.keys.include? instance
280
+ # namespace.send :remove_const, instance.name if instance.name
281
+ __avid_instances__.delete( instance )
282
+ __instances__.delete instance
283
+ end
284
+
285
+ # Removes all anonymous instances from the registry.
286
+ #
287
+ def forget_nameless_instances
288
+ const_magic # #nameless_instances doesn't trigger it
289
+ nameless_instances.each { |instance|
290
+ __instances__.delete( instance )
291
+ __avid_instances__.delete( instance )
292
+ }
293
+ end
294
+
295
+ # Clears references to all the instances.
296
+ #
297
+ def forget_all_instances
298
+ instances.map { |instance| __forget__ instance }
299
+ # constants( false ).each { |sym|
300
+ # namespace.send :remove_const, sym if
301
+ # const_get( sym ).is_a? self }
302
+ end
303
+
304
+ # Registers a block to execute when a new instance of the
305
+ # +NameMagic+ user class is created. (In other words, this method
306
+ # provides user class'es instantiation hook.) Expects a unary
307
+ # block, whose argument is the new instance. Return value of the
308
+ # block is unimportant. The block will be executed in the context
309
+ # of the user class. If no block is given, the method returns the
310
+ # previously defined block, if any, or a default block that does
311
+ # nothing.
312
+ #
313
+ def instantiation_exec &block
314
+ @instantiation_exec = block if block
315
+ @instantiation_exec ||= -> instance { }
316
+ end
317
+ # Note: This alias must stay while the dependencies need it.
318
+ alias new_instance_hook instantiation_exec
319
+
320
+ # Registers a block to execute just prior to naming of an
321
+ # instance. (In other words, this method provides user class'es
322
+ # naming hook.) The block will be executed in the context of the
323
+ # user class and will be supplied three ordered arguments:
324
+ # suggested name, instance, and previous name. The block should
325
+ # thus be written as ternary, expecting these three arguments.
326
+ # The block can be used to validate / censor the suggested name
327
+ # and for this reason, it should return the censored name that
328
+ # will actually be requested for the instance. (Of course, just
329
+ # like there is no duty to use this hook, if you do use it, there
330
+ # is likewise no duty to censor the suggested name in it. You can
331
+ # just return the suggested name unchanged from the block.) The
332
+ # point is that the return value of the block will actually be
333
+ # used to name the instance. If no block is given, the method
334
+ # returns the previously defined block, if any, or a default
335
+ # block that does nothing and returns the suggested name without
336
+ # any changes.
337
+ #
338
+ def exec_when_naming &block
339
+ @exec_when_naming = block if block
340
+ @exec_when_naming ||=
341
+ -> name, instance, previous_name=nil { name }
342
+ end
343
+ # Note: This alias must stay while the dependencies need it.
344
+ alias name_set_hook exec_when_naming
345
+
346
+ # Registers a block to execute just prior to unnaming of an
347
+ # instance. (In other words, this method provides user class'es
348
+ # unnaming hook.) The block will be executed in the context of
349
+ # the user class and will be supplied two ordered arguments:
350
+ # instance and its previous name. The block can thus be written
351
+ # as up to binary. Return value of the block is unimportant. If
352
+ # no block is given, the method returns the block defined
353
+ # earlier, if any, or a default block that does nothing.
354
+ #
355
+ def exec_when_unnaming &block
356
+ @exec_when_unnaming = block if block
357
+ @exec_when_unnaming ||= -> instance, previous_name=nil { }
358
+ end
359
+
360
+ # Checks whether a name is acceptable as a constant name.
361
+ #
362
+ def validate_name name
363
+ # Note that the #try method (provided by 'y_support/literate')
364
+ # allows us to call the methods of name without mentioning
365
+ # it explicitly as the receiver, and it also allows us to
366
+ # raise errors without explicitly constructing the error
367
+ # messages. Thus, chars.first actually means name.chars.first.
368
+ # Error message (when error occurs) is constructed from
369
+ # the #try description and the #note strings, which act at
370
+ # the same time as code comments. End of advertisement for
371
+ # 'y_support/literate'.
372
+ #
373
+ name.to_s.try "to validate the suggested instance name" do
374
+ note "rejecting non-capitalized names"
375
+ fail NameError unless ( ?A..?Z ) === chars.first
376
+ note "rejecting names with spaces"
377
+ fail NameError if chars.include? ' '
378
+ end
379
+ # Return value is the validated name.
380
+ return name
381
+ end
382
+
383
+ private
384
+
385
+ # Searches all modules for user class instances.
386
+ #
387
+ def search_all_modules
388
+ # Set up the list of object ids to search. These are ids
389
+ # of all unnamed registered instances.
390
+ todo = ( nameless_instances + __avid_instances__ )
391
+ .map( &:object_id )
392
+ .uniq
393
+ # Browse all modules in the deep ObjectSpace for those ids.
394
+ ObjectSpace.each_object Module do |ɱ|
395
+ ɱ.constants( false ).each do |const_ß|
396
+ # Some constants cause unexpected problems. The line
397
+ # below is the result of trial-and-error programming
398
+ # and I am afraid to delete it quite yet.
399
+ next if ɱ == Object && const_ß == :Config
400
+ # Those constants that raise certain errors upon attempts
401
+ # to access their contents are handled by this
402
+ # begin-rescue-end statement.
403
+ begin
404
+ instance = ɱ.const_get( const_ß )
405
+ rescue LoadError, StandardError
406
+ next # go on to the next constant
407
+ end
408
+ # We now go on to the next iteration of the loop if the
409
+ # constant which we are checking does not refer to the
410
+ # object with id we are searching for.
411
+ next unless todo.include? instance.object_id
412
+ # At this point, we have ascertained that the constant
413
+ # we are looking at contains unnamed instance.
414
+ if instance.avid? then
415
+ begin
416
+ # Name it rudely.
417
+ instance.name! const_ß
418
+ ensure
419
+ # Remove the "avid" flag from the instance.
420
+ instance.make_not_avid!
421
+ end
422
+ else
423
+ # Avid flag is not set, name the instance politely.
424
+ #
425
+ # Note that instances of NameMagic user classes are
426
+ # created avid. So if the anonymous instance is not
427
+ # avid at this point, it means it must have lost its
428
+ # avidity either being a previously named and now
429
+ # unnamed instance, or by the user explicitly invoking
430
+ # its #make_not_avid! method. In the first case, we do
431
+ # not want to see NameErrors raised ad infinitum just
432
+ # because there is some now-unnamed instance assigned
433
+ # to some forgotten constant in the deep namespace.
434
+ # In the second case, we must assume that the user
435
+ # takes the responsibility. So we will swallow NameError
436
+ # here:
437
+ begin
438
+ instance.name = const_ß
439
+ rescue NameError
440
+ end
441
+ end
442
+ # Remove the instance object id from todo list.
443
+ todo.delete instance.object_id
444
+ # Quit looping once todo list is empty.
445
+ break if todo.empty?
446
+ end
447
+ end
448
+ end
449
+ end # module NameMagic::NamespaceMethods