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.
- checksums.yaml +4 -4
- data/lib/y_support/all.rb +2 -32
- data/lib/y_support/core_ext/array.rb +2 -2
- data/lib/y_support/core_ext/class.rb +2 -2
- data/lib/y_support/core_ext/enumerable.rb +2 -2
- data/lib/y_support/core_ext/hash/misc.rb +23 -10
- data/lib/y_support/core_ext/hash.rb +2 -2
- data/lib/y_support/core_ext/module/misc.rb +9 -0
- data/lib/y_support/core_ext/module.rb +2 -2
- data/lib/y_support/core_ext/numeric.rb +2 -2
- data/lib/y_support/core_ext/object/inspection.rb +8 -2
- data/lib/y_support/core_ext/object.rb +3 -3
- data/lib/y_support/core_ext/string/misc.rb +9 -12
- data/lib/y_support/core_ext/string.rb +2 -2
- data/lib/y_support/core_ext/symbol.rb +2 -2
- data/lib/y_support/core_ext.rb +1 -1
- data/lib/y_support/flex_coerce/class_methods.rb +49 -0
- data/lib/y_support/flex_coerce/flex_proxy.rb +121 -0
- data/lib/y_support/flex_coerce/module_methods.rb +37 -0
- data/lib/y_support/flex_coerce.rb +24 -0
- data/lib/y_support/kde.rb +1 -1
- data/lib/y_support/literate.rb +253 -0
- data/lib/y_support/local_object.rb +1 -1
- data/lib/y_support/name_magic/array_methods.rb +48 -0
- data/lib/y_support/name_magic/class_methods.rb +205 -161
- data/lib/y_support/name_magic/hash_methods.rb +33 -0
- data/lib/y_support/name_magic/namespace.rb +449 -0
- data/lib/y_support/name_magic.rb +358 -100
- data/lib/y_support/null_object.rb +1 -1
- data/lib/y_support/respond_to.rb +1 -1
- data/lib/y_support/stdlib_ext/matrix/misc.rb +2 -2
- data/lib/y_support/stdlib_ext/matrix.rb +2 -2
- data/lib/y_support/stdlib_ext.rb +1 -1
- data/lib/y_support/typing/array.rb +1 -1
- data/lib/y_support/typing/enumerable.rb +1 -1
- data/lib/y_support/typing/hash.rb +1 -1
- data/lib/y_support/typing/module.rb +1 -1
- data/lib/y_support/typing/object/typing.rb +17 -15
- data/lib/y_support/typing/object.rb +1 -1
- data/lib/y_support/typing.rb +14 -10
- data/lib/y_support/unicode.rb +1 -1
- data/lib/y_support/version.rb +1 -1
- data/lib/y_support/x.rb +2 -1
- data/test/flex_coerce_test.rb +134 -0
- data/test/literate_test.rb +231 -0
- data/test/misc_test.rb +49 -27
- data/test/name_magic_test.rb +907 -60
- data/test/typing_test.rb +7 -7
- metadata +14 -13
- data/lib/y_support/abstract_algebra.rb +0 -234
- data/lib/y_support/name_magic/array.rb +0 -38
- data/lib/y_support/name_magic/hash.rb +0 -31
- data/lib/y_support/name_magic/namespace_methods.rb +0 -260
- data/lib/y_support/try.rb +0 -133
- data/test/abstract_algebra_test.rb +0 -138
- data/test/performance_test_example.rb +0 -23
- 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
|
@@ -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
|