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,27 @@
1
+ module Corefines
2
+ module Support
3
+
4
+ @classes_including_module = {}
5
+
6
+ ##
7
+ # @private
8
+ # Finds all classes that includes the specified module.
9
+ # Results are cached to speed-up repeated calls.
10
+ #
11
+ # @param mod [Module] the module.
12
+ # @yield [Class] gives each class that includes the _mod_.
13
+ #
14
+ def self.classes_including_module(mod)
15
+ @classes_including_module[mod] ||=
16
+ ::ObjectSpace.each_object(::Class).select do |klass|
17
+ begin
18
+ klass.included_modules.include?(mod)
19
+ rescue
20
+ # ignore errors
21
+ end
22
+ end
23
+
24
+ @classes_including_module[mod].each { |e| yield e }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ module Corefines
2
+ module Support
3
+ ##
4
+ # @private
5
+ # Faked Refinements somehow mimics refinements on Ruby engines that doesn't
6
+ # support them yet. It provides the same interface, but actually does just
7
+ # plain monkey-patching; the changes are *not* scoped like when using real
8
+ # refinements.
9
+ #
10
+ # It's useful when you want to use refinements, but also need to support
11
+ # older versions of Ruby, at least limited.
12
+ #
13
+ module FakeRefinements
14
+
15
+ MUTEX = Mutex.new
16
+ private_constant :MUTEX
17
+
18
+ def self.define_refine(target)
19
+ target.send(:define_method, :refine) do |klass, &block|
20
+ fail TypeError, "wrong argument type #{klass.class} (expected Class)" unless klass.is_a? ::Class
21
+ fail ArgumentError, "no block given" unless block
22
+
23
+ MUTEX.synchronize do
24
+ (@__pending_refinements ||= []) << [klass, block]
25
+ end
26
+ __refine__(klass) { module_eval &block } if respond_to?(:__refine__, true)
27
+
28
+ self
29
+ end
30
+ target.send(:private, :refine)
31
+ end
32
+
33
+ def self.define_using(target)
34
+ target.send(:define_method, :using) do |mod|
35
+ refinements = mod.instance_variable_get(:@__pending_refinements)
36
+
37
+ if refinements && !refinements.empty?
38
+ MUTEX.synchronize do
39
+ refinements.delete_if do |klass, block|
40
+ klass.class_eval &block
41
+ true
42
+ end
43
+ end
44
+ end
45
+
46
+ # Evaluate refinements from submodules recursively.
47
+ mod.included_modules.each do |submodule|
48
+ using submodule
49
+ end
50
+
51
+ self
52
+ end
53
+ target.send(:private, :using)
54
+ end
55
+
56
+ def self.alias_private_method(klass, new_name, old_name)
57
+ return if !klass.private_method_defined?(old_name) ||
58
+ klass.private_method_defined?(new_name)
59
+
60
+ klass.send(:alias_method, new_name, old_name)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ # If Module doesn't define method +using+, then refinements are apparently not
68
+ # supported on this platform, or running on MRI 2.0 that allows +using+ only on
69
+ # top-level. Since refinements in MRI 2.0 are experimental and behaves
70
+ # differently than in later versions, we use "faked refinements" even so.
71
+ unless Module.private_method_defined? :using
72
+
73
+ warn "corefines: Your Ruby doesn't support refinements, so I'll fake them \
74
+ using plain monkey-patching (not scoped!)."
75
+
76
+ m = Corefines::Support::FakeRefinements
77
+
78
+ # Define using on Module and the main object.
79
+ [Module, self.singleton_class].each do |klass|
80
+ m.alias_private_method(klass, :__using__, :using)
81
+ m.define_using(klass)
82
+ end
83
+
84
+ m.alias_private_method(Module, :__refine__, :refine)
85
+ m.define_refine(Module)
86
+ end
@@ -0,0 +1,32 @@
1
+ require 'corefines/support/alias_submodules'
2
+
3
+ module Corefines
4
+ module Symbol
5
+
6
+ ##
7
+ # @!method call(*args, &block)
8
+ # An useful extension for +&:symbol+ which makes it possible to pass
9
+ # arguments for method in a block
10
+ #
11
+ # @example
12
+ # [1, 2, 3].map(&:to_s.(2)) #=> ['1', '10', '11']
13
+ # %w[Chloe Drew Uma].map(&:gsub.('e', 'E')) #=> %w[ChloE DrEw Uma]
14
+ #
15
+ # @param *args arguments being passed to the method.
16
+ # @param block a block being passed to the method.
17
+ # @return [Proc] a proc that invokes the method, identified by this
18
+ # symbol and passing it the given arguments, on an object passed to it.
19
+ #
20
+ module Call
21
+ refine ::Symbol do
22
+ def call(*args, &block)
23
+ proc do |recv|
24
+ recv.__send__(self, *args, &block)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ include Support::AliasSubmodules
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Corefines
2
+ VERSION = '1.0.0'
3
+ end
data/lib/corefines.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'corefines/version'
2
+ require 'corefines/support/fake_refinements'
3
+
4
+ # Refinements
5
+ require 'corefines/array'
6
+ require 'corefines/enumerable'
7
+ require 'corefines/hash'
8
+ require 'corefines/module'
9
+ require 'corefines/object'
10
+ require 'corefines/string'
11
+ require 'corefines/symbol'
12
+
13
+ # Provide an alternative, shorter, module name.
14
+ CF = Corefines unless defined? CF
@@ -0,0 +1,10 @@
1
+ describe Array do
2
+ using Corefines::Array::second
3
+
4
+ describe '#second' do
5
+
6
+ it "returns the second element" do
7
+ expect(%w(a b c).second).to eq 'b'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ describe Array do
2
+ using Corefines::Array::third
3
+
4
+ describe '#third' do
5
+
6
+ it "returns the third element" do
7
+ expect(%w(a b c).third).to eq 'c'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ describe Enumerable do
2
+ using Corefines::Enumerable::index_by
3
+
4
+ describe '#index_by' do
5
+
6
+ let(:input) { %w[alpha bee beta gamma] }
7
+ let(:expected) { {a: 'alpha', b: 'beta', g: 'gamma'} }
8
+
9
+ it "returns hash with the array's elements keyed be result of the block" do
10
+ expect(input.index_by { |s| s[0].to_sym }).to eq expected
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ describe Enumerable do
2
+ using Corefines::Enumerable::map_send
3
+
4
+ describe '#map_send' do
5
+
6
+ context Array do
7
+ it "sends a message to each element and collects the result" do
8
+ expect([1, 2, 3].map_send(:+, 3)).to eq [4, 5, 6]
9
+ end
10
+ end
11
+
12
+ context Hash do
13
+ it "sends a message to each element and collects the result" do
14
+ expect({a: 1, b: 2}.map_send(:at, 1)).to eq [1, 2]
15
+ end
16
+ end
17
+
18
+ context Set do
19
+ it "sends a message to each element and collects the result" do
20
+ expect(Set.new([1, 2, 3]).map_send(:+, 3)).to eq [4, 5, 6]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ describe Hash do
2
+ using Corefines::Hash::compact
3
+
4
+ let!(:original) { subject.dup }
5
+
6
+ describe '#compact' do
7
+
8
+ context "hash with some nil values" do
9
+ subject(:hash) { {a: true, b: false, c: nil, d: 'foo'} }
10
+
11
+ it "returns a new hash with no key-value pairs which value is nil" do
12
+ expect(hash.compact).to eq({a: true, b: false, d: 'foo'})
13
+ expect(hash).to eql original
14
+ end
15
+ end
16
+
17
+ context "hash with only nil values" do
18
+ subject(:hash) { {a: nil, b: nil} }
19
+
20
+ it "returns a new empty hash" do
21
+ expect(hash.compact).to be_empty
22
+ expect(hash).to eql original
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#compact!' do
28
+
29
+ context "hash with some nil values" do
30
+ subject(:hash) { {a: true, b: false, c: nil, d: 'foo'} }
31
+ let(:expected) { {a: true, b: false, d: 'foo'} }
32
+
33
+ it "removes key-value pairs which value is nil" do
34
+ expect(hash.compact!).to eq expected
35
+ expect(hash).to eq expected
36
+ end
37
+ end
38
+
39
+ context "hash with only nil values" do
40
+ subject(:hash) { {a: nil, b: nil} }
41
+
42
+ it "removes all key-value pairs" do
43
+ expect(hash.compact!).to be_empty
44
+ expect(hash).to be_empty
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ describe Hash do
2
+ using Corefines::Hash::+
3
+
4
+ describe '#+' do
5
+ it 'merges hashes' do
6
+ a = {a: 1, b: 2}
7
+ b = {b: 3, c: 4}
8
+ expect(a + b).to eq({a: 1, b: 3, c: 4})
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ describe Hash do
2
+ using Corefines::Hash::rekey
3
+
4
+ let(:symbols) { {a: 1, b: 2, c: 3} }
5
+ let(:strings) { {'a' => 1, 'b' => 2, 'c' => 3} }
6
+ let(:mixed) { {'a' => 'foo', :b => 42, true => 66} }
7
+ let(:mixed_symbolized) { {:a => 'foo', :b => 42, true => 66} }
8
+
9
+ subject(:hash) { symbols }
10
+ let!(:original) { hash.dup }
11
+
12
+
13
+ describe '#rekey' do
14
+
15
+ context "with no arguments" do
16
+ subject(:hash) { mixed }
17
+
18
+ it "returns a new hash with symbolized keys" do
19
+ expect(hash.rekey).to eq mixed_symbolized
20
+ expect(hash).to eql original
21
+ end
22
+ end
23
+
24
+ context "with key_map" do
25
+ it "returns a new hash with translated keys" do
26
+ expect(hash.rekey(a: :x, c: 'y', d: 'z')).to eq({:x => 1, :b => 2, 'y' => 3})
27
+ expect(hash).to eql original
28
+ end
29
+ end
30
+
31
+ context "with block" do
32
+ it "yields key and value to the block" do
33
+ yielded = []
34
+ hash.rekey { |k, v| yielded << [k, v] }
35
+ expect(yielded).to eq [[:a, 1], [:b, 2], [:c, 3]]
36
+ end
37
+
38
+ it "returns a new hash with transformed keys" do
39
+ expect(hash.rekey { |k| k.to_s }).to eq strings
40
+ expect(hash).to eql original
41
+ end
42
+ end
43
+
44
+ context "with symbol proc" do
45
+ it "returns a new hash with transformed keys" do
46
+ expect(hash.rekey(&:to_s)).to eq strings
47
+ expect(hash).to eql original
48
+ end
49
+ end
50
+
51
+ context "with both key_map and block" do
52
+ it { expect { hash.rekey(a: 'x') { |k| k.to_s } }.to raise_error ArgumentError }
53
+ end
54
+ end
55
+
56
+
57
+ describe '#rekey!' do
58
+
59
+ context "with no arguments" do
60
+ subject(:hash) { mixed }
61
+
62
+ it "transforms keys to symbols and returns self" do
63
+ expect(hash.rekey!).to eq mixed_symbolized
64
+ expect(hash).to eq mixed_symbolized
65
+ end
66
+ end
67
+
68
+ context "with key_map" do
69
+ it "translates keys and returns self" do
70
+ expected = {:x => 1, :b => 2, 'y' => 3}
71
+ expect(hash.rekey!(a: :x, c: 'y', d: 'z')).to eq expected
72
+ expect(hash).to eq expected
73
+ end
74
+ end
75
+
76
+ context "with block" do
77
+ it "yields key and value to the block" do
78
+ yielded = []
79
+ hash.rekey! { |k, v| yielded << [k, v] }
80
+ expect(yielded).to eq [[:a, 1], [:b, 2], [:c, 3]]
81
+ end
82
+
83
+ it "transforms keys and returns self" do
84
+ expect(hash.rekey! { |k| k.to_s }).to eq strings
85
+ expect(hash).to eq strings
86
+ end
87
+ end
88
+
89
+ context "with symbol proc" do
90
+ it "transforms keys and returns self" do
91
+ expect(hash.rekey!(&:to_s)).to eq strings
92
+ expect(hash).to eq strings
93
+ end
94
+ end
95
+
96
+ context "with key_map and block" do
97
+ it { expect { hash.rekey!(a: 'x') { |k| k.to_s } }.to raise_error ArgumentError }
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,21 @@
1
+ describe Hash do
2
+ using Corefines::Hash::symbolize_keys
3
+
4
+ subject(:mixed) { {'a' => 'foo', :b => 42, true => 66} }
5
+ let(:symbolized) { {:a => 'foo', :b => 42, true => 66} }
6
+ let!(:original) { mixed.dup }
7
+
8
+ describe '#symbolize_keys' do
9
+ it "returns a new hash with keys converted to symbols" do
10
+ expect(mixed.symbolize_keys).to eq symbolized
11
+ expect(mixed).to eql original
12
+ end
13
+ end
14
+
15
+ describe '#symbolize_keys!' do
16
+ it "converts keys to symbols" do
17
+ expect(mixed.symbolize_keys!).to eq symbolized
18
+ expect(mixed).to eq symbolized
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ describe Module do
2
+ using Corefines::Module::alias_class_method
3
+
4
+ describe '#alias_class_method' do
5
+
6
+ subject :klass do
7
+ Class.new do
8
+ def self.salute
9
+ 'Meow!'
10
+ end
11
+ end
12
+ end
13
+
14
+ it "defines new class method that calls the old class method" do
15
+ klass.alias_class_method :say_hello!, :salute
16
+
17
+ expect(klass).to respond_to :say_hello!
18
+ expect(klass.say_hello!).to eq klass.salute
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,76 @@
1
+ describe Module do
2
+ using Corefines::Module::alias_method_chain
3
+
4
+ describe '#alias_method_chain' do
5
+
6
+ context "regular method" do
7
+
8
+ subject :obj do
9
+ Class.new {
10
+ def say; 'say' end
11
+ def say_with_accent; 'say_with_accent' end
12
+ alias_method_chain :say, :accent
13
+ }.new
14
+ end
15
+
16
+ it "defines method alias x_without_y -> x" do
17
+ expect(obj.say_without_accent).to eq 'say'
18
+ end
19
+
20
+ it "defines method alias x -> x_with_y" do
21
+ expect(obj.say).to eq 'say_with_accent'
22
+ end
23
+ end
24
+
25
+ context "method with punctuation" do
26
+
27
+ subject :obj do
28
+ Class.new {
29
+ def say!; 'say!' end
30
+ def say_with_accent!; 'say_with_accent!' end
31
+ alias_method_chain :say!, :accent
32
+ }.new
33
+ end
34
+
35
+ it "defines method alias x_without_y! -> x!" do
36
+ expect(obj.say_without_accent!).to eq 'say!'
37
+ end
38
+
39
+ it "defines method alias x! -> x_with_y!" do
40
+ expect(obj.say!).to eq 'say_with_accent!'
41
+ end
42
+ end
43
+
44
+ context "feature with punctuation" do
45
+
46
+ subject :obj do
47
+ Class.new {
48
+ def say; 'say' end
49
+ def say_with_accent?; 'say_with_accent?' end
50
+ alias_method_chain :say, :accent?
51
+ }.new
52
+ end
53
+
54
+ it "defines method alias x_without_y? -> x" do
55
+ expect(obj.say_without_accent?).to eq 'say'
56
+ end
57
+
58
+ it "defines method alias x -> x_with_y?" do
59
+ expect(obj.say).to eq 'say_with_accent?'
60
+ end
61
+ end
62
+
63
+
64
+ [:public, :protected, :private].each do |visibility|
65
+ it "preserves #{visibility} method visibility" do
66
+ klass = Class.new.class_eval <<-CLASS
67
+ def say; end
68
+ def say_with_accent; end
69
+ #{visibility} :say
70
+ alias_method_chain :say, :accent
71
+ CLASS
72
+ expect(klass.send("#{visibility}_method_defined?", :say)).to be true
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,128 @@
1
+ # coding: utf-8
2
+ using Corefines::Object::blank?
3
+
4
+ describe Object do
5
+ let(:obj) { Object.new }
6
+
7
+ let :obj_empty do
8
+ Class.new { define_method(:empty?) { 0 } }.new
9
+ end
10
+
11
+ let :obj_not_empty do
12
+ Class.new { define_method(:empty?) { nil } }.new
13
+ end
14
+
15
+ describe '#blank?' do
16
+ it { expect(obj.blank?).to be false }
17
+
18
+ context "when object responds to #empty?" do
19
+ it "returns true when #empty? is truthy" do
20
+ expect(obj_empty.blank?).to be true
21
+ end
22
+
23
+ it "returns false when #empty? is falsy" do
24
+ expect(obj_not_empty.blank?).to be false
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#presence' do
30
+ it { expect(obj.presence).to be obj }
31
+
32
+ context "when object responds to #empty?" do
33
+ it "returns nil when #empty? is truthy" do
34
+ expect(obj_empty.presence).to be nil
35
+ end
36
+
37
+ it "returns caller when #empty? is falsy" do
38
+ expect(obj_not_empty.presence).to be obj_not_empty
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe NilClass do
45
+ describe '#blank?' do
46
+ it { expect(nil.blank?).to be true }
47
+ end
48
+
49
+ describe '#presence' do
50
+ it { expect(nil.presence).to be_nil }
51
+ end
52
+ end
53
+
54
+ describe FalseClass do
55
+ describe '#blank?' do
56
+ it { expect(false.blank?).to be true }
57
+ end
58
+
59
+ describe '#presence' do
60
+ it { expect(false.presence).to be_nil }
61
+ end
62
+ end
63
+
64
+ describe TrueClass do
65
+ describe '#blank?' do
66
+ it { expect(true.blank?).to be false }
67
+ end
68
+
69
+ describe '#presence' do
70
+ it { expect(true.presence).to be true }
71
+ end
72
+ end
73
+
74
+ describe Array do
75
+ describe '#blank?' do
76
+ it { expect([].blank?).to be true }
77
+ it { expect([1].blank?).to be false }
78
+ end
79
+
80
+ describe '#presence' do
81
+ it { expect([].presence).to be_nil }
82
+ it { expect([1].presence).to eq [1] }
83
+ end
84
+ end
85
+
86
+ describe Hash do
87
+ describe '#blank?' do
88
+ it { expect({}.blank?).to be true }
89
+ it { expect({x: 0}.blank?).to be false }
90
+ end
91
+
92
+ describe '#presence' do
93
+ it { expect({}.presence).to be_nil }
94
+ it { expect({x: 0}.presence).to eq({x: 0}) }
95
+ end
96
+ end
97
+
98
+ describe Numeric do
99
+ [ 0, 1, 1.1 ].each do |val|
100
+ describe '#blank?' do
101
+ it { expect(val.blank?).to be false }
102
+ end
103
+
104
+ describe '#presence' do
105
+ it { expect(val.presence).to be val }
106
+ end
107
+ end
108
+ end
109
+
110
+ describe String do
111
+ [ '', ' ', " \n\t \r ", ' ', "\u00a0" ].each do |val|
112
+ describe '#blank?' do
113
+ it { expect(val.blank?).to be true }
114
+ end
115
+
116
+ describe '#presence' do
117
+ it { expect(val.presence).to be_nil }
118
+ end
119
+ end
120
+
121
+ describe '#blank?' do
122
+ it { expect('x'.blank?).to be false }
123
+ end
124
+
125
+ describe '#presence' do
126
+ it { expect('x'.presence).to eq 'x' }
127
+ end
128
+ end