corefines 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|