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,253 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../y_support'
4
+ require_relative '../y_support/core_ext/array/misc'
5
+
6
+ # Did you ever get in the situation that your code does not work
7
+ # and you don't know what the problem is? In Ruby, error messages
8
+ # are supposed to help you, but how often have you seen error
9
+ # messages telling you completely unhelpful things, such as that
10
+ # some method is not defined for nil:NilClass? Anyone who
11
+ # programmed in Ruby for some time knows that raising good error
12
+ # messages is a pain and the code that raises them tends to make
13
+ # all your methods look ugly. YSupport's answer to this is Literate
14
+ # (loaded by require 'y_support/literate'). Literate offers +#try+
15
+ # block, inside which you can construct verbose error messages with
16
+ # k +#note+ statements. Note that this +#try+ method has nothing to
17
+ # do with the infamous error-swallowing +#try+ method seen and
18
+ # denounced elsewhere. Literate's +#try+ method does not swallow
19
+ # errors – on the contrary, it helps to raise them. Comments you
20
+ # make via +#note+ method (or its alias #») inside +#try+ blocks
21
+ # will be used to construct more informative error messages. Good
22
+ # error messages ultimately lead to better code and more scalable
23
+ # development. Simple example:
24
+ #
25
+ # "lorem ipsum dolor sit amet".try "to split it into words" do
26
+ # note is: "a natural language sentence"
27
+ # note has: "#{size} characters"
28
+ # words = split ' '
29
+ # end
30
+ #
31
+ # This is not really such a good example, because #try method is
32
+ # intended for risky operation, and splitting a string into words
33
+ # is not risky at all. But it clearly shows how Literate works.
34
+ # Since the era of programs written on punch cards, programmers
35
+ # already understood that writing code comments is a good
36
+ # practice. The keystrokes you waste by literate coding will pay
37
+ # off as soon as you start doing any serious programming. With
38
+ # Literate, you can take literate coding to the next level by
39
+ # writing self-documenting code, in which comments introduced by
40
+ # #note method are read by the interpeter and used to construct
41
+ # error messages. All you need to learn is how to use +#try+ and
42
+ # +#note+ methods.
43
+ #
44
+ # Literate relies on +Literate::Attempt+ class, which stays hidden
45
+ # behind the scene, but defines all the error-raising capabilities
46
+ # of +#try+ blocks.
47
+ #
48
+ module Literate
49
+ # Represents a commented attempt to perform a risky operation,
50
+ # which may result in errors. The operation code is supplied as
51
+ # a block. Method #comment defined by this class helps to
52
+ # increase the informative value of error messages.
53
+ #
54
+ class Attempt < BasicObject
55
+ # String construction closures are defined below.
56
+ CONSTRUCT = -> string, prefix: '', postfix: '' do
57
+ s = string.to_s
58
+ if s.empty? then '' else prefix + s + postfix end
59
+ end
60
+
61
+ # Hash of transitive verb forms.
62
+ TRANSITIVE = ::Hash.new do |_, key| "#{key}ing %s" end
63
+ .update( is: "being %s", has: "having %s" )
64
+
65
+ # Hash for construction of statements (error message parts).
66
+ STATE = ::Hash.new do |ꜧ, key| "#{key} %s" end
67
+ .update( is: "%s", has: "has %s" )
68
+
69
+ # An Attempt instance has 4 properties.
70
+ attr_reader :__subject__ # Grammatical subject of the attempt.
71
+ attr_reader :__block__ # Block that performs the attempt.
72
+ attr_reader :__text__ # NL description of the attempt.
73
+ attr_reader :__knowledge_base__
74
+
75
+ # Attempt constructor expects two parameters (:subject and
76
+ # :text) and one block. Argument of the :subject parameter is
77
+ # the main subject of the risky operation attempted inside the
78
+ # block. Argument of the :text parameter is the natural
79
+ # language textual description of the risky opration.
80
+ #
81
+ def initialize( subject: nil, text: nil, &block )
82
+ @__subject__ = subject
83
+ @__text__ = text
84
+ @__block__ = block
85
+ # Knowledge base is a list of subjects and facts known
86
+ # about them.
87
+ @__knowledge_base__ = ::Hash.new do |hash, missing_key|
88
+ hash[ missing_key ] = [ {} ]
89
+ end
90
+ end
91
+
92
+ # Method #note is available inside the #try block
93
+ #
94
+ # note "Concatenation of Foo and Bar",
95
+ # is: "FooBar",
96
+ # has: "6 letters"
97
+ #
98
+ def note *subjects, **statements, &block
99
+ if statements.empty? then
100
+ # No statements were supplied to #note.
101
+ subjects.each { |subject|
102
+ # Fill in the knowledge base ...
103
+ # FIXME: I wonder how the code here works.
104
+ __knowledge_base__[ subject ].push_ordered subject
105
+ }
106
+ # FIXME: I wonder whether returning this is OK.
107
+ return subjects
108
+ end
109
+ # Here, we know that variable statements is not empty.
110
+ # If subjects variable is empty, assume main subject.
111
+ subjects << __subject__ if subjects.empty?
112
+ # Fill the knowledge base ...
113
+ # FIXME: I wonder how the code here works.
114
+ subjects.each { |subject|
115
+ statements.each do |verb, object|
116
+ __knowledge_base__[ subject ].push_named verb => object
117
+ end
118
+ }
119
+ # Return the second element of the first statement.
120
+ return statements.first[ 1 ]
121
+ end
122
+
123
+ # Alias of #note method that allows comments such as this:
124
+ #
125
+ # a = []
126
+ # a.try "to insert number one in it" do
127
+ # » is: "an empty array"
128
+ # push 1
129
+ # end
130
+ #
131
+ alias » note
132
+
133
+ # Runs the attempt.
134
+ #
135
+ def __run__ *args
136
+ begin
137
+ instance_exec *args, &__block__
138
+ rescue ::StandardError => error
139
+ # Error has occured. Show time for Literate::Attempt.
140
+ raise error, __error_message__( error )
141
+ end
142
+ end
143
+
144
+ # Facilitating error messages is the main purpose of Literate.
145
+ # This method is invoked when an error occurs inside the block
146
+ # run by Literate::Attempt instance and it constructs a verbose
147
+ # error message using the facts the Attempt instance knows.
148
+ #
149
+ def __error_message__ error
150
+ # Write the first part of the error message.
151
+ part1 = "When trying #{__text__}"
152
+ # Get the description of the main subject.
153
+ subject, statements = __describe__( __subject__ )
154
+ # Write the 2nd part of the error message.
155
+ part2 = CONSTRUCT.( subject, prefix: ' ' )
156
+ # Construct the descriptive string of the main subject.
157
+ subject_description = statements.map { |verb, object|
158
+ # Generate the statement string.
159
+ STATE[ verb ] % object
160
+ }.join ', ' # join the statement strings with commas
161
+ # Write the third part of the error message.
162
+ part3 = CONSTRUCT.( subject_description,
163
+ prefix: ' (', postfix: ')' )
164
+ # Write the fourth part of the error message.
165
+ part4 = CONSTRUCT.( __circumstances__, prefix: ', ' )
166
+ # Write the fifth part of the error message.
167
+ part5 = ": #{error}"
168
+ part6 = ['.', '!', '?'].include?( part5[-1] ) ? '' : '!'
169
+ return [ part1, part2, part3, part4, part5, part6 ].join
170
+ end
171
+
172
+ # Inside the block, +#try method is delegated to the subject
173
+ # of the attempt.
174
+ #
175
+ def try *args, &block
176
+ __subject__.try *args, &block
177
+ end
178
+
179
+ # Method missing delegates all methods not recognized by
180
+ # Literate::Attempt class (which is a subclass of BasicObject)
181
+ # to the subject of the attempt.
182
+ #
183
+ def method_missing symbol, *args
184
+ __subject__.send symbol, *args
185
+ end
186
+
187
+ # Produces a description of the subject supplied to the method.
188
+ #
189
+ def __describe__ subject
190
+ # Start with the facts known about the subject.
191
+ facts = __knowledge_base__[ subject ].dup
192
+ # FIXME: I wonder what this method is *really* doing.
193
+ # It seems that I wrote this library too quickly and didn't
194
+ # bother with properly defining its list of facts.
195
+ # I did not define what is a "fact", what is a "statement"
196
+ # etc., I just go around using the words and hoping I will
197
+ # understand it after myself later. I found it's quite hard.
198
+ statements = if facts.last.is_a? ::Hash then
199
+ facts.pop
200
+ else {} end
201
+ fs = facts.join ', '
202
+ if statements.empty? then
203
+ return fs, statements
204
+ else
205
+ return facts.empty? ? subject.to_s : fs, statements
206
+ end
207
+ end
208
+
209
+ # Produces description of the circumstances, that is,
210
+ # concatenated descriptions of all facts known to the Attempt
211
+ # instance except those about the main subject of the attempt.
212
+ #
213
+ def __circumstances__
214
+ # Start with all facts known to the Attempt instance.
215
+ base = __knowledge_base__
216
+ # Ignore the facts about the main subject.
217
+ circumstances = base.reject { |s, _| s == __subject__ }
218
+ # Construct descriptive strings of the remaining facts.
219
+ fact_strings = circumstances.map { |subject, _|
220
+ subject, statements = __describe__( subject )
221
+ statements = statements.map { |verb, object|
222
+ TRANSITIVE[ verb ] % object
223
+ }.join( ', ' )
224
+ # Create the fact string.
225
+ subject + CONSTRUCT.( statements, prefix: ' ' )
226
+ }
227
+ # Concatenate the fact strings and return the result.
228
+ return fact_strings.join( ', ' )
229
+ end
230
+ end
231
+ end
232
+
233
+ class Object
234
+ # Try method takes two textual arguments and one block. The first
235
+ # (optional) argument is a natural language description of the
236
+ # method's receiver (with #to_s of the receiver used by
237
+ # default). The second argument is a natural language description
238
+ # of the supplied block's _contract_ -- in other words, what the
239
+ # supplied block tries to do. Finally, the block contains the
240
+ # code to perform the described risky action. Inside the block,
241
+ # +#note+ method is available, which builds up the context
242
+ # information for a good error message, should the risky action
243
+ # raise one.
244
+ #
245
+ def try attempt_description, &block
246
+ # Construct a new Attempt instance.
247
+ attempt = Literate::Attempt.new subject: self,
248
+ text: attempt_description,
249
+ &block
250
+ # Run the block.
251
+ attempt.__run__
252
+ end
253
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'y_support'
3
+ require_relative '../y_support'
4
4
 
5
5
  # Object, whose business is to stay local to methods. Optional signature
6
6
  # provides additional level of safety in ensuring object locality. (Signature
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ module NameMagic::ArrayMethods
4
+ # Maps an array of some objects into an array of their names,
5
+ # obtained by applying +#full_name+ method to them. Takes one
6
+ # optional argument, which regulates its behavior regarding
7
+ # unnamed objects. If set to _nil_ (default), unnamed objects
8
+ # will be mapped to _nil_ (default behavior of the +#name+
9
+ # method). If set to _true_, unnamed objects will be mapped to
10
+ # themselves. If set to _false_, unnamed objects will not be
11
+ # mapped at all -- the returned array will contain only the names
12
+ # of the named objects.
13
+ #
14
+ def names option=nil
15
+ # unnamed --> nil
16
+ return map &:name if option.nil?
17
+ # unnamed --> instance
18
+ return map { |e| e.name || e } if option == true
19
+ # unnamed squeezed out
20
+ return map( &:name ).compact if option == false
21
+ fail ArgumentError, "Unknown option: #{option}"
22
+ end
23
+ # FIXME: The remaining thing to do to achieve compatibility with
24
+ # Ruby's #name is to put "full_name" in the body, and "name" in
25
+ # the alias...
26
+ alias full_names names
27
+
28
+ # Maps an array to an array of the element names, obtained by
29
+ # applying +#_name_+ method to them. Takes one optional argument,
30
+ # which regulates its behavior regarding unnamed objects. If set
31
+ # to _nil_ (default) unnamed objects will be mapped to _nil_
32
+ # (default behavior of +#_name_+ method). If set to _true_,
33
+ # unnamed objects will be mapped to themselves. If set to
34
+ # _false_, unnamed objects will not be mapped at all -- the
35
+ # returned array will contain only the names of the named
36
+ # objects.
37
+ #
38
+ def _names_ option=nil
39
+ # unnamed --> nil
40
+ return map &:_name_ if option.nil?
41
+ # unnamed --> instance
42
+ return map { |e| e.name || e } if option == true
43
+ # unnamed squeezed out
44
+ return map( &:_name_ ).compact if option == false
45
+ fail ArgumentError, "Unknown option: #{option}"
46
+ end
47
+ alias ɴ _names_
48
+ end