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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.adoc +198 -0
  4. data/Rakefile +23 -0
  5. data/lib/corefines/array.rb +30 -0
  6. data/lib/corefines/enumerable.rb +64 -0
  7. data/lib/corefines/hash.rb +164 -0
  8. data/lib/corefines/module.rb +95 -0
  9. data/lib/corefines/object.rb +389 -0
  10. data/lib/corefines/string.rb +211 -0
  11. data/lib/corefines/support/alias_submodules.rb +103 -0
  12. data/lib/corefines/support/classes_including_module.rb +27 -0
  13. data/lib/corefines/support/fake_refinements.rb +86 -0
  14. data/lib/corefines/symbol.rb +32 -0
  15. data/lib/corefines/version.rb +3 -0
  16. data/lib/corefines.rb +14 -0
  17. data/spec/array/second_spec.rb +10 -0
  18. data/spec/array/third_spec.rb +10 -0
  19. data/spec/enumerable/index_by_spec.rb +13 -0
  20. data/spec/enumerable/map_send_spec.rb +24 -0
  21. data/spec/hash/compact_spec.rb +48 -0
  22. data/spec/hash/op_plus_spec.rb +11 -0
  23. data/spec/hash/rekey_spec.rb +100 -0
  24. data/spec/hash/symbolize_keys_spec.rb +21 -0
  25. data/spec/module/alias_class_method_spec.rb +21 -0
  26. data/spec/module/alias_method_chain_spec.rb +76 -0
  27. data/spec/object/blank_spec.rb +128 -0
  28. data/spec/object/deep_dup_spec.rb +61 -0
  29. data/spec/object/else_spec.rb +24 -0
  30. data/spec/object/in_spec.rb +21 -0
  31. data/spec/object/instance_values_spec.rb +22 -0
  32. data/spec/object/then_if_spec.rb +64 -0
  33. data/spec/object/then_spec.rb +26 -0
  34. data/spec/object/try_spec.rb +47 -0
  35. data/spec/spec_helper.rb +30 -0
  36. data/spec/string/color_spec.rb +82 -0
  37. data/spec/string/concat_spec.rb +36 -0
  38. data/spec/string/decolor_spec.rb +27 -0
  39. data/spec/string/remove_spec.rb +57 -0
  40. data/spec/string/unindent_spec.rb +66 -0
  41. data/spec/support/alias_submodules_spec.rb +83 -0
  42. data/spec/support/classes_including_module_spec.rb +35 -0
  43. data/spec/support/fake_refinements_spec.rb +118 -0
  44. data/spec/symbol/call_spec.rb +16 -0
  45. 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