corefines 1.0.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.adoc +198 -0
- data/Rakefile +23 -0
- data/lib/corefines/array.rb +30 -0
- data/lib/corefines/enumerable.rb +64 -0
- data/lib/corefines/hash.rb +164 -0
- data/lib/corefines/module.rb +95 -0
- data/lib/corefines/object.rb +389 -0
- data/lib/corefines/string.rb +211 -0
- data/lib/corefines/support/alias_submodules.rb +103 -0
- data/lib/corefines/support/classes_including_module.rb +27 -0
- data/lib/corefines/support/fake_refinements.rb +86 -0
- data/lib/corefines/symbol.rb +32 -0
- data/lib/corefines/version.rb +3 -0
- data/lib/corefines.rb +14 -0
- data/spec/array/second_spec.rb +10 -0
- data/spec/array/third_spec.rb +10 -0
- data/spec/enumerable/index_by_spec.rb +13 -0
- data/spec/enumerable/map_send_spec.rb +24 -0
- data/spec/hash/compact_spec.rb +48 -0
- data/spec/hash/op_plus_spec.rb +11 -0
- data/spec/hash/rekey_spec.rb +100 -0
- data/spec/hash/symbolize_keys_spec.rb +21 -0
- data/spec/module/alias_class_method_spec.rb +21 -0
- data/spec/module/alias_method_chain_spec.rb +76 -0
- data/spec/object/blank_spec.rb +128 -0
- data/spec/object/deep_dup_spec.rb +61 -0
- data/spec/object/else_spec.rb +24 -0
- data/spec/object/in_spec.rb +21 -0
- data/spec/object/instance_values_spec.rb +22 -0
- data/spec/object/then_if_spec.rb +64 -0
- data/spec/object/then_spec.rb +26 -0
- data/spec/object/try_spec.rb +47 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/string/color_spec.rb +82 -0
- data/spec/string/concat_spec.rb +36 -0
- data/spec/string/decolor_spec.rb +27 -0
- data/spec/string/remove_spec.rb +57 -0
- data/spec/string/unindent_spec.rb +66 -0
- data/spec/support/alias_submodules_spec.rb +83 -0
- data/spec/support/classes_including_module_spec.rb +35 -0
- data/spec/support/fake_refinements_spec.rb +118 -0
- data/spec/symbol/call_spec.rb +16 -0
- metadata +175 -0
@@ -0,0 +1,389 @@
|
|
1
|
+
require 'corefines/support/alias_submodules'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Corefines
|
5
|
+
module Object
|
6
|
+
##
|
7
|
+
# @!method blank?
|
8
|
+
# An object is blank if it's +false+, empty, or a whitespace string.
|
9
|
+
# For example, <tt>'', ' ', "\t\n\r", "\u00a0", nil, [], {}</tt> are
|
10
|
+
# all blank.
|
11
|
+
#
|
12
|
+
# This simplifies
|
13
|
+
#
|
14
|
+
# address.nil? || address.empty?
|
15
|
+
#
|
16
|
+
# to
|
17
|
+
#
|
18
|
+
# address.blank?
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
#
|
22
|
+
# @!method presence
|
23
|
+
# Returns object if it's not {#blank?}, otherwise returns +nil+.
|
24
|
+
# +obj.presence+ is equivalent to <tt>obj.blank? ? nil : obj</tt>.
|
25
|
+
#
|
26
|
+
# This is handy for any representation of objects where blank is the same
|
27
|
+
# as not present at all. For example, this simplifies a common check for
|
28
|
+
# HTTP POST/query parameters:
|
29
|
+
#
|
30
|
+
# state = params[:state] if params[:state].present?
|
31
|
+
# country = params[:country] if params[:country].present?
|
32
|
+
# region = state || country || 'CZ'
|
33
|
+
#
|
34
|
+
# becomes...
|
35
|
+
#
|
36
|
+
# region = params[:state].presence || params[:country].presence || 'CZ'
|
37
|
+
#
|
38
|
+
# @return [Object, nil] object if it's not {#blank?}, otherwise +nil+.
|
39
|
+
#
|
40
|
+
module Blank
|
41
|
+
|
42
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
43
|
+
|
44
|
+
refine ::Object do
|
45
|
+
def blank?
|
46
|
+
respond_to?(:empty?) ? !!empty? : !self
|
47
|
+
end
|
48
|
+
|
49
|
+
def presence
|
50
|
+
self unless blank?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
refine ::NilClass do
|
55
|
+
def blank?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
refine ::FalseClass do
|
61
|
+
def blank?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
refine ::TrueClass do
|
67
|
+
def blank?
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
refine ::Array do
|
73
|
+
alias_method :blank?, :empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
refine ::Hash do
|
77
|
+
alias_method :blank?, :empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
refine ::Numeric do
|
81
|
+
def blank?
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
refine ::String do
|
87
|
+
def blank?
|
88
|
+
BLANK_RE === self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# @!method deep_dup
|
95
|
+
# Returns a deep copy of itself.
|
96
|
+
#
|
97
|
+
# If +self+ is an instance of +Array+, +Set+ or +Hash+, then it
|
98
|
+
# recursively copies its elements as well. If a nested object responds to
|
99
|
+
# +deep_dup+ (note: +respond_to?+ doesn't see refined methods), then it
|
100
|
+
# use it.
|
101
|
+
#
|
102
|
+
# @return [Object] a copy of +self+, or +self+ if it's not duplicable.
|
103
|
+
#
|
104
|
+
module DeepDup
|
105
|
+
refine ::Object do
|
106
|
+
# NOTE: Refinement is not active inside itself, so we can't simply call
|
107
|
+
# deep_dup on a nested object like ActiveSupport does. Thus we must use
|
108
|
+
# this ugly approach instead...
|
109
|
+
def deep_dup(obj = self)
|
110
|
+
return obj.deep_dup if obj != self && obj.respond_to?(:deep_dup)
|
111
|
+
|
112
|
+
case obj
|
113
|
+
when ::NilClass, ::FalseClass, ::TrueClass, ::Symbol, ::Numeric, ::Method
|
114
|
+
obj # not duplicable
|
115
|
+
when ::Array
|
116
|
+
obj.map { |o| deep_dup(o) }
|
117
|
+
when ::Set
|
118
|
+
::Set[obj.map { |o| deep_dup(o) }]
|
119
|
+
when ::Hash
|
120
|
+
obj.each_with_object({}) { |(k, v), h| h[deep_dup(k)] = deep_dup(v) }
|
121
|
+
else
|
122
|
+
begin
|
123
|
+
obj.dup
|
124
|
+
rescue TypeError, NoMethodError
|
125
|
+
obj
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# @!method else
|
134
|
+
# Returns +self+ if +self+ evaluates to +true+, otherwise returns the
|
135
|
+
# evaluation of the block.
|
136
|
+
#
|
137
|
+
# @yield [self] gives +self+ to the block.
|
138
|
+
# @return [Object] +self+ if +self+ evaluates to +true+, otherwise
|
139
|
+
# returns the evaluation of the block.
|
140
|
+
#
|
141
|
+
module Else
|
142
|
+
refine ::Object do
|
143
|
+
def else
|
144
|
+
self ? self : yield(self)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# @!method in?(other)
|
151
|
+
# @example Array
|
152
|
+
# characters = ["Konata", "Kagami", "Tsukasa"]
|
153
|
+
# "Konata".in?(characters) # => true
|
154
|
+
#
|
155
|
+
# @example String
|
156
|
+
# "f".in?("flynn") # => true
|
157
|
+
# "x".in?("flynn") # => false
|
158
|
+
#
|
159
|
+
# @param other [#include?]
|
160
|
+
# @return [Boolean] +true+ if this object is included in the _other_
|
161
|
+
# object, +false+ otherwise.
|
162
|
+
# @raise ArgumentError if the _other_ doesn't respond to +#include?+.
|
163
|
+
#
|
164
|
+
module In
|
165
|
+
refine ::Object do
|
166
|
+
def in?(other)
|
167
|
+
other.include? self
|
168
|
+
rescue NoMethodError
|
169
|
+
fail ArgumentError, "The parameter passed to #in? must respond to #include?"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# @!method instance_values
|
176
|
+
# @example
|
177
|
+
# class C
|
178
|
+
# def initialize(x, y)
|
179
|
+
# @x, @y = x, y
|
180
|
+
# end
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# C.new(0, 1).instance_values
|
184
|
+
# => {x: 0, y: 1}
|
185
|
+
#
|
186
|
+
# @return [Hash] a hash with symbol keys that maps instance variable
|
187
|
+
# names without "@" to their corresponding values.
|
188
|
+
#
|
189
|
+
module InstanceValues
|
190
|
+
refine ::Object do
|
191
|
+
def instance_values
|
192
|
+
ary = instance_variables.map do |name|
|
193
|
+
[ name[1..-1].to_sym, instance_variable_get(name) ]
|
194
|
+
end
|
195
|
+
::Hash[ary]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# @!method then
|
202
|
+
# Returns +self+ if +self+ evaluate to +false+, otherwise returns
|
203
|
+
# evaluation of the block.
|
204
|
+
#
|
205
|
+
# This simplifies something like:
|
206
|
+
#
|
207
|
+
# if m = "Flynn <flynn@encom.com>".match(/<([^>]+)>/)
|
208
|
+
# m[1]
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# to:
|
212
|
+
#
|
213
|
+
# "Flynn <flynn@encom.com>".match(/<([^>]+)>/).then { |m| m[1] }
|
214
|
+
#
|
215
|
+
#
|
216
|
+
# Since +then+ passes +self+ to the block, it can be also used for
|
217
|
+
# chaining, so something like:
|
218
|
+
#
|
219
|
+
# html = parse_html(input)
|
220
|
+
# html = find_nodes(html, "//section")
|
221
|
+
# html = remove_nodes(html, "//p")
|
222
|
+
#
|
223
|
+
# can be rewritten to:
|
224
|
+
#
|
225
|
+
# parse_html(input)
|
226
|
+
# .then { |h| find_nodes(h, "//section") }
|
227
|
+
# .then { |h| remove_nodes(h, "//p") }
|
228
|
+
#
|
229
|
+
# @yield [self] gives +self+ to the block.
|
230
|
+
# @return [Object] evaluation of the block, or +self+ if no block given
|
231
|
+
# or +self+ evaluates to false.
|
232
|
+
#
|
233
|
+
module Then
|
234
|
+
refine ::Object do
|
235
|
+
def then
|
236
|
+
if block_given? && self
|
237
|
+
yield self
|
238
|
+
else
|
239
|
+
self
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# @!method then_if(*conditions)
|
247
|
+
# Returns +self+ if +self+ or any of the _conditions_ evaluates to
|
248
|
+
# +false+, otherwise returns the evaluation of the block.
|
249
|
+
#
|
250
|
+
# @example
|
251
|
+
# "foo".then_if(:empty?) { "N/A" } # => "foo"
|
252
|
+
# "".then_if(:empty?) { "N/A" } # => "N/A"
|
253
|
+
#
|
254
|
+
# Each condition may be of the type:
|
255
|
+
# * +Symbol+ - name of the method to be invoked using +public_send+.
|
256
|
+
# * +Array+ - name of the method followed by arguments to be invoked
|
257
|
+
# using +public_send+.
|
258
|
+
# * +Proc+ - proc to be called with +self+ as the argument.
|
259
|
+
# * Any other object to be evaluated as +true+, or +false+.
|
260
|
+
#
|
261
|
+
# @param *conditions conditions to evaluate.
|
262
|
+
# @yield [self] gives +self+ to the block.
|
263
|
+
# @return [Object] evaluation of the block, or +self+ if any condition
|
264
|
+
# evaluates to +false+, or no condition given and +self+ evaluates to
|
265
|
+
# +false+.
|
266
|
+
#
|
267
|
+
module ThenIf
|
268
|
+
refine ::Object do
|
269
|
+
def then_if(*conditions)
|
270
|
+
return self if conditions.empty? && !self
|
271
|
+
return self unless conditions.all? do |arg|
|
272
|
+
case arg
|
273
|
+
when ::Symbol then public_send(arg)
|
274
|
+
when ::Array then public_send(*arg)
|
275
|
+
when ::Proc then arg.call(self)
|
276
|
+
else arg
|
277
|
+
end
|
278
|
+
end
|
279
|
+
yield self
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# @!method try(method, *args, &block)
|
286
|
+
# Invokes the public method identified by the symbol _method_, passing it
|
287
|
+
# any arguments and/or the block specified, just like the regular Ruby
|
288
|
+
# +public_send+ does.
|
289
|
+
#
|
290
|
+
# *Unlike* that method however, a +NoMethodError+ exception will *not* be
|
291
|
+
# be raised and +nil+ will be returned instead, if the receiving object
|
292
|
+
# doesn't respond to the _method_.
|
293
|
+
#
|
294
|
+
# This method is defined to be able to write:
|
295
|
+
#
|
296
|
+
# @person.try(:name)
|
297
|
+
#
|
298
|
+
# instead of:
|
299
|
+
#
|
300
|
+
# @person.name if @person
|
301
|
+
#
|
302
|
+
# +try+ calls can be chained:
|
303
|
+
#
|
304
|
+
# @person.try(:spouse).try(:name)
|
305
|
+
#
|
306
|
+
# instead of:
|
307
|
+
#
|
308
|
+
# @person.spouse.name if @person && @person.spouse
|
309
|
+
#
|
310
|
+
# +try+ will also return +nil+ if the receiver does not respond to the
|
311
|
+
# method:
|
312
|
+
#
|
313
|
+
# @person.try(:unknown_method) # => nil
|
314
|
+
#
|
315
|
+
# instead of:
|
316
|
+
#
|
317
|
+
# @person.unknown_method if @person.respond_to?(:unknown_method) # => nil
|
318
|
+
#
|
319
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it
|
320
|
+
# responds to the method:
|
321
|
+
#
|
322
|
+
# nil.try(:to_i) # => nil, rather than 0
|
323
|
+
#
|
324
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
325
|
+
#
|
326
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
327
|
+
# ...
|
328
|
+
# end
|
329
|
+
#
|
330
|
+
# The number of arguments in the signature must match. If the object
|
331
|
+
# responds to the method, the call is attempted and +ArgumentError+ is
|
332
|
+
# still raised in case of argument mismatch.
|
333
|
+
#
|
334
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't
|
335
|
+
# work with instances of classes that do not have +Object+ among their
|
336
|
+
# ancestors, like direct subclasses of +BasicObject+. For example, using
|
337
|
+
# +try+ with +SimpleDelegator+ will delegate +try+ to the target instead
|
338
|
+
# of calling it on the delegator itself.
|
339
|
+
#
|
340
|
+
# @param method [Symbol] name of the method to invoke.
|
341
|
+
# @param args arguments to pass to the _method_.
|
342
|
+
# @return [Object, nil] result of calling the _method_, or +nil+ if
|
343
|
+
# doesn't respond to it.
|
344
|
+
#
|
345
|
+
# @!method try!(method, *args, &block)
|
346
|
+
# Same as {#try}, but raises a +NoMethodError+ exception if the receiver
|
347
|
+
# is not +nil+ and does not implement the tried method.
|
348
|
+
#
|
349
|
+
# @example
|
350
|
+
# "a".try!(:upcase) # => "A"
|
351
|
+
# nil.try!(:upcase) # => nil
|
352
|
+
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
|
353
|
+
#
|
354
|
+
# @param method (see #try)
|
355
|
+
# @param args (see #try)
|
356
|
+
# @return (see #try)
|
357
|
+
#
|
358
|
+
module Try
|
359
|
+
refine ::Object do
|
360
|
+
def try(method = nil, *args, &block)
|
361
|
+
try!(method, *args, &block) if respond_to? method
|
362
|
+
end
|
363
|
+
|
364
|
+
def try!(method = nil, *args, &block)
|
365
|
+
public_send method, *args, &block
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
refine ::NilClass do
|
370
|
+
def try(*args)
|
371
|
+
nil
|
372
|
+
end
|
373
|
+
|
374
|
+
def try!(*args)
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
include Support::AliasSubmodules
|
381
|
+
|
382
|
+
class << self
|
383
|
+
alias_method :blank?, :blank
|
384
|
+
alias_method :in?, :in
|
385
|
+
alias_method :presence, :blank
|
386
|
+
alias_method :try!, :try
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'corefines/support/alias_submodules'
|
2
|
+
|
3
|
+
module Corefines
|
4
|
+
module String
|
5
|
+
|
6
|
+
ESCAPE_SEQUENCE = /\033\[([0-9]+);([0-9]+);([0-9]+)m(.+?)\033\[0m|([^\033]+)/m
|
7
|
+
private_constant :ESCAPE_SEQUENCE
|
8
|
+
|
9
|
+
##
|
10
|
+
# @!method color
|
11
|
+
# @example
|
12
|
+
# "Roses are red".color(:red) # => "\e[0;31;49mRoses are red\e[0m"
|
13
|
+
# "Roses are red".color(text: :red, mode: :bold) # => "\e[1;31;49mRoses are red\e[0m"
|
14
|
+
# "Violets are blue".color(background: 'blue') # => "\e[0;39;44mViolets are blue\e[0m"
|
15
|
+
# "Sugar is sweet".color(text: 7) # => "\e[0;37;49mSugar is sweet\e[0m"
|
16
|
+
#
|
17
|
+
# @overload color(text_color)
|
18
|
+
# @param text_color [#to_sym, Fixnum] text (foreground) color (see
|
19
|
+
# {COLOR_CODES}).
|
20
|
+
#
|
21
|
+
# @overload color(opts)
|
22
|
+
# @option opts [#to_sym, Fixnum] :mode text attributes (see
|
23
|
+
# {MODE_CODES}).
|
24
|
+
# @option opts [#to_sym, Fixnum] :text,:fg text (foreground) color (see
|
25
|
+
# {COLOR_CODES}).
|
26
|
+
# @option opts [#to_sym, Fixnum] :background,:bg background color (see
|
27
|
+
# {COLOR_CODES}).
|
28
|
+
#
|
29
|
+
# @return [String] a copy of this string colored for command line output
|
30
|
+
# using ANSI escape codes.
|
31
|
+
# @see Decolor#decolor
|
32
|
+
#
|
33
|
+
module Color
|
34
|
+
|
35
|
+
COLOR_CODES = {
|
36
|
+
black: 0, light_black: 60,
|
37
|
+
red: 1, light_red: 61,
|
38
|
+
green: 2, light_green: 62,
|
39
|
+
yellow: 3, light_yellow: 63,
|
40
|
+
blue: 4, light_blue: 64,
|
41
|
+
magenta: 5, light_magenta: 65,
|
42
|
+
cyan: 6, light_cyan: 66,
|
43
|
+
white: 7, light_white: 67,
|
44
|
+
default: 9
|
45
|
+
}
|
46
|
+
|
47
|
+
MODE_CODES = {
|
48
|
+
default: 0, # Turn off all attributes.
|
49
|
+
bold: 1, # Set bold mode.
|
50
|
+
underline: 4, # Set underline mode.
|
51
|
+
blink: 5, # Set blink mode.
|
52
|
+
swap: 7, # Exchange foreground and background colors.
|
53
|
+
hide: 8 # Hide text (foreground color would be the same as background).
|
54
|
+
}
|
55
|
+
|
56
|
+
refine ::String do
|
57
|
+
def color(opts)
|
58
|
+
opts = {text: opts} unless opts.is_a? ::Hash
|
59
|
+
opts[:fg] ||= opts[:text] || opts[:color]
|
60
|
+
opts[:bg] ||= opts[:background]
|
61
|
+
|
62
|
+
scan(ESCAPE_SEQUENCE).reduce('') do |str, match|
|
63
|
+
mode = Color.mode_code(opts[:mode]) || match[0] || 0
|
64
|
+
fg_color = Color.color_code(opts[:fg], 30) || match[1] || 39
|
65
|
+
bg_color = Color.color_code(opts[:bg], 40) || match[2] || 49
|
66
|
+
text = match[3] || match[4]
|
67
|
+
|
68
|
+
str << "\033[#{mode};#{fg_color};#{bg_color}m#{text}\033[0m"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def self.color_code(color, offset)
|
76
|
+
return color + offset if color.is_a? ::Fixnum
|
77
|
+
COLOR_CODES[color.to_sym] + offset if color && COLOR_CODES[color.to_sym]
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.mode_code(mode)
|
81
|
+
return mode if mode.is_a? ::Fixnum
|
82
|
+
MODE_CODES[mode.to_sym] if mode
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# @!method concat!(obj, separator = nil)
|
88
|
+
# Appends (concatenates) the given object to _str_. If the _separator_ is
|
89
|
+
# set and this _str_ is not empty, then it appends the _separator_ before
|
90
|
+
# the _obj_.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# "".concat!("Greetings", ", ") # => "Greetings"
|
94
|
+
# "Greetings".concat!("programs!", ", ") #=> "Greetings, programs!"
|
95
|
+
#
|
96
|
+
# @param obj [String, Integer] the string, or codepoint to append.
|
97
|
+
# @param separator [String, nil] the separator to append when this _str_ is
|
98
|
+
# not empty.
|
99
|
+
# @return [String] self
|
100
|
+
#
|
101
|
+
module Concat
|
102
|
+
refine ::String do
|
103
|
+
def concat!(obj, separator = nil)
|
104
|
+
if separator && !self.empty?
|
105
|
+
self << separator << obj
|
106
|
+
else
|
107
|
+
self << obj
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# @!method decolor
|
115
|
+
# @return [String] a copy of this string without ANSI escape codes
|
116
|
+
# (e.g. colors).
|
117
|
+
# @see Color#color
|
118
|
+
#
|
119
|
+
module Decolor
|
120
|
+
refine ::String do
|
121
|
+
def decolor
|
122
|
+
scan(ESCAPE_SEQUENCE).reduce('') do |str, match|
|
123
|
+
str << (match[3] || match[4])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# @!method remove(*patterns)
|
131
|
+
# Returns a copy of this string with *all* occurrences of the _patterns_
|
132
|
+
# removed.
|
133
|
+
#
|
134
|
+
# The pattern is typically a +Regexp+; if given as a +String+, any
|
135
|
+
# regular expression metacharacters it contains will be interpreted
|
136
|
+
# literally, e.g. +'\\d'+ will match a backlash followed by 'd', instead
|
137
|
+
# of a digit.
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# str = "This is a good day to die"
|
141
|
+
# str.remove(" to die") # => "This is a good day"
|
142
|
+
# str.remove(/\s*to.*$/) # => "This is a good day"
|
143
|
+
# str.remove("to die", /\s*$/) # => "This is a good day"
|
144
|
+
#
|
145
|
+
# @param *patterns [Regexp, String] patterns to remove from the string.
|
146
|
+
# @return [String] a new string.
|
147
|
+
#
|
148
|
+
# @!method remove!(*patterns)
|
149
|
+
# Removes all the occurrences of the _patterns_ in place.
|
150
|
+
#
|
151
|
+
# @see #remove
|
152
|
+
# @param *patterns (see #remove)
|
153
|
+
# @return [String] self
|
154
|
+
#
|
155
|
+
module Remove
|
156
|
+
refine ::String do
|
157
|
+
def remove(*patterns)
|
158
|
+
dup.remove!(*patterns)
|
159
|
+
end
|
160
|
+
|
161
|
+
def remove!(*patterns)
|
162
|
+
patterns.each { |pattern| gsub!(pattern, '') }
|
163
|
+
self
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# @!method unindent
|
170
|
+
# Remove excessive indentation. Useful for multi-line strings embeded in
|
171
|
+
# already indented code.
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# <<-EOF.unindent
|
175
|
+
# Greetings,
|
176
|
+
# programs!
|
177
|
+
# EOF
|
178
|
+
# => "Greetings\n programs"
|
179
|
+
#
|
180
|
+
# Technically, it looks for the least indented line in the whole
|
181
|
+
# string (blank lines are ignored) and removes that amount of leading
|
182
|
+
# whitespace.
|
183
|
+
#
|
184
|
+
# @return [String] a new unindented string.
|
185
|
+
#
|
186
|
+
#
|
187
|
+
# @!method strip_heredoc
|
188
|
+
# Alias for {#unindent}.
|
189
|
+
#
|
190
|
+
# @return [String] a new unindented string.
|
191
|
+
#
|
192
|
+
module Unindent
|
193
|
+
refine ::String do
|
194
|
+
def unindent
|
195
|
+
leading_space = scan(/^[ \t]*(?=\S)/).min
|
196
|
+
indent = leading_space ? leading_space.size : 0
|
197
|
+
gsub /^[ \t]{#{indent}}/, ''
|
198
|
+
end
|
199
|
+
|
200
|
+
alias_method :strip_heredoc, :unindent
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
include Support::AliasSubmodules
|
205
|
+
|
206
|
+
class << self
|
207
|
+
alias_method :concat!, :concat
|
208
|
+
alias_method :remove!, :remove
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Corefines
|
2
|
+
module Support
|
3
|
+
##
|
4
|
+
# @private
|
5
|
+
# When this module is included, then it:
|
6
|
+
#
|
7
|
+
# 1. Defines an "alias" for each submodule, i.e. singleton method that
|
8
|
+
# returns the submodule and it's named after the submodule, but the name
|
9
|
+
# is converted to underscore_separated or an operator. For example,
|
10
|
+
# instead of <tt>using Corefines::String::ToHtml</tt> one can write
|
11
|
+
# <tt>using Corefines::String::to_html</tt>.
|
12
|
+
#
|
13
|
+
# 2. Includes all the submodules into the module. This allows to use all
|
14
|
+
# refinements inside the submodules just by _using_ their parent module.
|
15
|
+
#
|
16
|
+
# 3. Defines method {[]}.
|
17
|
+
#
|
18
|
+
# @!method self.[](*names)
|
19
|
+
# @example
|
20
|
+
# Corefines::Object[:blank?, :then]
|
21
|
+
#
|
22
|
+
# @param names [Array<Symbol>] names of submodules aliases to include
|
23
|
+
# into the returned module.
|
24
|
+
# @return [Module] a new module that includes the named submodules.
|
25
|
+
#
|
26
|
+
module AliasSubmodules
|
27
|
+
|
28
|
+
OPERATORS_MAP = {
|
29
|
+
:op_plus => :+,
|
30
|
+
:op_minus => :-,
|
31
|
+
:op_pow => :**,
|
32
|
+
:op_mul => :*,
|
33
|
+
:op_div => :/,
|
34
|
+
:op_mod => :%,
|
35
|
+
:op_tilde => :~,
|
36
|
+
:op_cmp => :<=>,
|
37
|
+
:op_lshift => :<<,
|
38
|
+
:op_rshift => :>>,
|
39
|
+
:op_lt => :<,
|
40
|
+
:op_gt => :>,
|
41
|
+
:op_case => :===,
|
42
|
+
:op_equal => :==,
|
43
|
+
:op_apply => :=~,
|
44
|
+
:op_lt_eq => :<=,
|
45
|
+
:op_gt_eq => :>=,
|
46
|
+
:op_or => :|,
|
47
|
+
:op_and => :&,
|
48
|
+
:op_xor => :^,
|
49
|
+
:op_store => :[]=,
|
50
|
+
:op_fetch => :[]
|
51
|
+
}
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def self.included(target)
|
56
|
+
target.constants.each do |const|
|
57
|
+
submodule = target.const_get(const)
|
58
|
+
next unless submodule.instance_of? ::Module
|
59
|
+
|
60
|
+
# Defines method-named "alias" for the submodule. (1)
|
61
|
+
target.define_singleton_method(method_name(const)) { submodule }
|
62
|
+
|
63
|
+
# Includes the submodule of the target into target. (2)
|
64
|
+
target.send(:include, submodule)
|
65
|
+
end
|
66
|
+
|
67
|
+
target.extend ClassMethods
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.method_name(module_name)
|
71
|
+
name = underscore(module_name)
|
72
|
+
OPERATORS_MAP[name.to_sym] || name
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.underscore(camel_cased_word)
|
76
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z]/
|
77
|
+
|
78
|
+
camel_cased_word.to_s.dup.tap do |s|
|
79
|
+
s.gsub! /([A-Z\d]+)([A-Z][a-z])/, '\1_\2'
|
80
|
+
s.gsub! /([a-z\d])([A-Z])/, '\1_\2'
|
81
|
+
s.downcase!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
module ClassMethods
|
87
|
+
|
88
|
+
def [](*names)
|
89
|
+
::Module.new.tap do |mod|
|
90
|
+
names.each do |mth|
|
91
|
+
unless respond_to? mth
|
92
|
+
fail ArgumentError, "no such refinements submodule with alias '#{mth}'"
|
93
|
+
end
|
94
|
+
mod.send(:include, public_send(mth))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private_constant :ClassMethods, :OPERATORS_MAP
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|