corefines 1.0.0

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