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