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,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