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