y_support 2.1.18 → 2.4.4

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 (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