darthjee-core_ext 1.2.6

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +62 -0
  6. data/LICENSE +22 -0
  7. data/README.md +188 -0
  8. data/Rakefile +7 -0
  9. data/core_ext.gemspec +27 -0
  10. data/lib/array.rb +23 -0
  11. data/lib/array/hash_builder.rb +20 -0
  12. data/lib/darthjee.rb +4 -0
  13. data/lib/darthjee/core_ext.rb +12 -0
  14. data/lib/darthjee/core_ext/version.rb +5 -0
  15. data/lib/enumerable.rb +45 -0
  16. data/lib/hash.rb +195 -0
  17. data/lib/hash/deep_hash_constructor.rb +83 -0
  18. data/lib/hash/key_changer.rb +76 -0
  19. data/lib/hash/value_changer.rb +61 -0
  20. data/lib/numeric.rb +6 -0
  21. data/lib/symbol.rb +9 -0
  22. data/spec/lib/array_spec.rb +229 -0
  23. data/spec/lib/enumerable_spec.rb +31 -0
  24. data/spec/lib/hash/deep_hash_constructor_spec.rb +167 -0
  25. data/spec/lib/hash/key_changer_spec.rb +55 -0
  26. data/spec/lib/hash_spec.rb +347 -0
  27. data/spec/lib/numeric_spec.rb +61 -0
  28. data/spec/lib/symbol_spec.rb +35 -0
  29. data/spec/spec_helper.rb +32 -0
  30. data/spec/support/models/hash/value_changer/dummy.rb +15 -0
  31. data/spec/support/models/hash/value_changer/dummy_iteractor.rb +12 -0
  32. data/spec/support/shared_examples/array_random.rb +16 -0
  33. data/spec/support/shared_examples/chain_fetch.rb +88 -0
  34. data/spec/support/shared_examples/chain_hash_keys_changer.rb +88 -0
  35. data/spec/support/shared_examples/clean.rb +143 -0
  36. data/spec/support/shared_examples/expected.rb +6 -0
  37. data/spec/support/shared_examples/hash_keys_changer.rb +85 -0
  38. data/spec/support/shared_examples/keys_appender.rb +43 -0
  39. data/spec/support/shared_examples/keys_camelizer.rb +285 -0
  40. data/spec/support/shared_examples/keys_underscorer.rb +82 -0
  41. data/spec/support/shared_examples/remap.rb +89 -0
  42. data/spec/support/shared_examples/value_changer.rb +92 -0
  43. metadata +217 -0
@@ -0,0 +1,195 @@
1
+ require 'hash/value_changer'
2
+ require 'hash/deep_hash_constructor'
3
+ require 'hash/key_changer'
4
+
5
+ class Hash
6
+ def chain_fetch(*keys)
7
+ value = self
8
+
9
+ if block_given?
10
+ value = value.fetch(keys.shift) do |*args|
11
+ missed_keys = keys
12
+ keys = []
13
+ yield(*(args + [missed_keys]))
14
+ end until keys.empty?
15
+ else
16
+ value = value.fetch(keys.shift) until keys.empty?
17
+ end
18
+
19
+ value
20
+ end
21
+
22
+ def squash
23
+ {}.tap do |hash|
24
+ each do |key, value|
25
+ if value.is_a? Hash
26
+ value.squash.each do |k, v|
27
+ new_key = [key, k].join('.')
28
+ hash[new_key] = v
29
+ end
30
+ else
31
+ hash[key] = value
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def map_to_hash
38
+ {}.tap do |hash|
39
+ each do |k, v|
40
+ hash[k] = yield(k, v)
41
+ end
42
+ end
43
+ end
44
+
45
+ def remap_keys(remap)
46
+ dup.remap_keys!(remap)
47
+ end
48
+
49
+ def remap_keys!(remap)
50
+ new_hash = {}
51
+ remap.each do |o, n|
52
+ new_hash[n] = delete o
53
+ end
54
+ merge! new_hash
55
+ end
56
+
57
+ def lower_camelize_keys(options = {})
58
+ dup.lower_camelize_keys!(options)
59
+ end
60
+
61
+ def lower_camelize_keys!(options = {})
62
+ options = options.merge({ uppercase_first_letter: false })
63
+
64
+ camelize_keys!(options)
65
+ end
66
+
67
+ def camelize_keys(options = {})
68
+ dup.camelize_keys!(options)
69
+ end
70
+
71
+ def camelize_keys!(options = {})
72
+ Hash::KeyChanger.new(self).camelize_keys(options)
73
+ end
74
+
75
+ def underscore_keys(options = {})
76
+ dup.underscore_keys!(options)
77
+ end
78
+
79
+ def underscore_keys!(options = {})
80
+ Hash::KeyChanger.new(self).underscore_keys(options)
81
+ end
82
+
83
+ def exclusive_merge(hash)
84
+ dup.exclusive_merge!(hash)
85
+ end
86
+
87
+ def exclusive_merge!(hash)
88
+ merge!(hash.slice(*keys))
89
+ end
90
+
91
+ # change all keys returning the new map
92
+ # options: { recursive: true }
93
+ # ex: { "a" =>1 }.change_keys{ |key| key.upcase } == { "A" => 1 }
94
+ def change_keys(options = {}, &block)
95
+ deep_dup.change_keys!(options, &block)
96
+ end
97
+
98
+ # change all keys returning the new map
99
+ # options: { recursive: true }
100
+ # ex: { "a":1 }.change_keys{ |key| key.upcase } == { "A":1 }
101
+ def change_keys!(options = {}, &block)
102
+ Hash::KeyChanger.new(self).change_keys(options, &block)
103
+ end
104
+
105
+ # change all publicaly sending method calls
106
+ # options: { recursive: true }
107
+ # ex: { a: 1 }.chain_change_keys(:to_s, :upcase) == { "A" =>1 }
108
+ def chain_change_keys(*calls)
109
+ deep_dup.chain_change_keys!(*calls)
110
+ end
111
+
112
+ # change all publicaly sending method calls
113
+ # options: { recursive: true }
114
+ # ex: { a: 1 }.chain_change_keys(:to_s, :upcase) == { "A" =>1 }
115
+ def chain_change_keys!(*calls)
116
+ options = calls.extract_options!
117
+
118
+ calls.inject(self) do |h, m|
119
+ h.change_keys!(options, &m)
120
+ end
121
+ end
122
+
123
+ # prepend a string to all keys
124
+ # options {
125
+ # recursive: true,
126
+ # type: :keep [keep, string, symbol] (key type to be returned)
127
+ # }
128
+ # ex: { :a => 1, "b"=> 2 }.prepend_to_keys("foo_") == { :foo_a => 1, "foo_b"=> 2 }
129
+ def prepend_to_keys(str, options = {})
130
+ change_key_text(options) do |key|
131
+ "#{str}#{key}"
132
+ end
133
+ end
134
+
135
+ # append a string to all keys
136
+ # options {
137
+ # recursive: true,
138
+ # type: :keep [keep, string, symbol] (key type to be returned)
139
+ # }
140
+ # ex: { :a => 1, "b"=> 2 }.append_to_keys("_bar") == { :a_bar => 1, "b_bar"=> 2 }
141
+ def append_to_keys(str, options = {})
142
+ change_key_text(options) do |key|
143
+ "#{key}#{str}"
144
+ end
145
+ end
146
+
147
+ # sorts keys for hash
148
+ # options: { recursive: true }
149
+ # ex: { b:1, a:2 }.sort_keys == { a:2, b:1 }
150
+ def sort_keys(options = {})
151
+ options = {
152
+ recursive: true
153
+ }.merge(options)
154
+
155
+ {}.tap do |hash|
156
+ keys.sort.each do |key|
157
+ value = self[key]
158
+ hash[key] = value unless value.is_a?(Hash) && options[:recursive]
159
+ hash[key] = value.sort_keys(options) if value.is_a?(Hash) && options[:recursive]
160
+ end
161
+ end
162
+ end
163
+
164
+ # creates a new hash with changes in its values
165
+ # options: {
166
+ # recursive: true,
167
+ # skip_hash:true
168
+ # }
169
+ # ex: { a:1, b:2 }.change_values{ |v| v+1 } == { a:2, b:3 }
170
+ # ex: { a:1, b:{ c:1 } }.change_values(skip_hash:false) { |v| v.to_s } == { a:"1", b:"{ c=>1 }
171
+ # ex: { a:1, b:{ c:1 } }.change_values(skip_hash:true) { |v| v.to_s } == { a:"1", b:{ c=>"1" } }
172
+ def change_values(options = {}, &block)
173
+ deep_dup.change_values!(options, &block)
174
+ end
175
+
176
+ def change_values!(options = {}, &block)
177
+ Hash::ValueChanger.new(options, &block).change(self)
178
+ end
179
+
180
+ def to_deep_hash(separator = '.')
181
+ Hash::DeepHashConstructor.new(separator).deep_hash(self)
182
+ end
183
+
184
+ private
185
+
186
+ # changes the text of the keys
187
+ # options {
188
+ # recursive: true,
189
+ # type: :keep [keep, string, symbol] (key type to be returned)
190
+ # }
191
+ # ex: { :a => 1, "b"=> 2 }.change_key_text{ |key| key.upcase } == { :A => 1, "B"=> 2 }
192
+ def change_key_text(options = {}, &block)
193
+ Hash::KeyChanger.new(self).change_text(options, &block)
194
+ end
195
+ end
@@ -0,0 +1,83 @@
1
+ class Hash::DeepHashConstructor
2
+ attr_accessor :separator
3
+
4
+ def initialize(separator)
5
+ @separator = separator
6
+ end
7
+
8
+ def deep_hash(object)
9
+ if object.is_a? Array
10
+ array_deep_hash(object)
11
+ elsif object.is_a? Hash
12
+ hash_deep_hash(object)
13
+ else
14
+ object
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def array_deep_hash(array)
21
+ array.map { |v| v.is_a?(Hash) ? deep_hash(v) : v }
22
+ end
23
+
24
+ def hash_deep_hash(hash)
25
+ {}.tap do |new_hash|
26
+ hash.each do |k, v|
27
+ base_key, child_key = split_key(k, separator)
28
+ set_deep_hash_positioned_value(new_hash, base_key, v, child_key)
29
+ end
30
+
31
+ new_hash.each do |k, v|
32
+ new_hash[k] = deep_hash(v)
33
+ end
34
+ end
35
+ end
36
+
37
+ def split_key(key, separator)
38
+ separator_rxp = (separator == '.') ? "\\#{separator}" : separator
39
+ skipper = "[^#{separator}]"
40
+ regexp = Regexp.new("^(#{skipper}*)#{separator_rxp}(.*)")
41
+ match = key.match(regexp)
42
+
43
+ match ? match[1..2] : key
44
+ end
45
+
46
+ def set_deep_hash_array_value(hash, base_key, index, value, key = nil)
47
+ key_without_index = base_key.gsub("[#{index}]", '')
48
+ hash[key_without_index] ||= []
49
+
50
+ if key.nil?
51
+ hash[key_without_index][index] = value
52
+ else
53
+ hash[key_without_index][index] ||= {}
54
+ hash[key_without_index][index][key] = value
55
+ end
56
+ end
57
+
58
+ def set_deep_hash_positioned_value(new_hash, base_key, v, child_key)
59
+ index = array_index(base_key)
60
+
61
+ if index
62
+ set_deep_hash_array_value(new_hash, base_key, index, v, child_key)
63
+ else
64
+ set_deep_hash_value(new_hash, base_key, v, child_key)
65
+ end
66
+ end
67
+
68
+ def array_index(key)
69
+ match = key.match(/\[([^)]+)\]/)
70
+ if match
71
+ match[1].to_i
72
+ end
73
+ end
74
+
75
+ def set_deep_hash_value(hash, base_key, value, key = nil)
76
+ if key.nil?
77
+ hash[base_key] = value
78
+ else
79
+ hash[base_key] ||= {}
80
+ hash[base_key][key] = value
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,76 @@
1
+ class Hash::KeyChanger
2
+ attr_reader :hash, :block
3
+
4
+ def initialize(hash)
5
+ @hash = hash
6
+ end
7
+
8
+ def change_keys(settings = {}, &block)
9
+ merge_options({
10
+ recursive: true
11
+ }, settings)
12
+
13
+ if options[:recursive]
14
+ hash.deep_transform_keys!(&block)
15
+ else
16
+ hash.transform_keys!(&block)
17
+ end
18
+ end
19
+
20
+ def camelize_keys(settings = {})
21
+ merge_options({
22
+ uppercase_first_letter: true
23
+ }, settings)
24
+
25
+ type = options[:uppercase_first_letter] ? :upper : :lower
26
+
27
+ change_keys do |k|
28
+ k.camelize(type)
29
+ end
30
+ end
31
+
32
+ def underscore_keys(settings = {})
33
+ merge_options({}, settings)
34
+
35
+ change_keys do |k|
36
+ k.underscore
37
+ end
38
+ end
39
+
40
+ def change_text(options = {}, &block)
41
+ merge_options({
42
+ type: :keep
43
+ }, options)
44
+
45
+ change_keys do |key|
46
+ cast_new_key block.call(key), key.class
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def merge_options(default, custom)
53
+ @options = {}.merge!(default).merge!(custom).merge!(options)
54
+ end
55
+
56
+ def options
57
+ @options ||= {}
58
+ end
59
+
60
+ def cast_new_key(key, old_clazz)
61
+ case class_cast(old_clazz)
62
+ when :symbol then
63
+ key.to_sym
64
+ when :string then
65
+ key.to_s
66
+ end
67
+ end
68
+
69
+ def keep_class?
70
+ options[:type] == :keep
71
+ end
72
+
73
+ def class_cast(old_clazz)
74
+ keep_class? && old_clazz.to_s.downcase.to_sym || options[:type]
75
+ end
76
+ end
@@ -0,0 +1,61 @@
1
+ class Hash::ValueChanger
2
+ attr_accessor :options, :block
3
+
4
+ def initialize(options, &block)
5
+ @options = {
6
+ recursive: true,
7
+ skip_inner: true
8
+ }.merge(options)
9
+
10
+ @block = block
11
+ end
12
+
13
+ def change(object)
14
+ if object.respond_to?(:change_values)
15
+ change_hash(object)
16
+ elsif is_iterable?(object)
17
+ change_array(object)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def change_hash(original_hash)
24
+ original_hash.tap do |hash|
25
+ original_hash.each do |key, value|
26
+ value = new_value(value)
27
+ hash[key] = value
28
+ end
29
+ end
30
+ end
31
+
32
+ def change_array(array)
33
+ array = array.to_a
34
+
35
+ array.each.with_index do |value, index|
36
+ if value.respond_to?(:change_values)
37
+ value = value.change_values(options, &block)
38
+ elsif is_iterable?(value)
39
+ value = change_array(value)
40
+ end
41
+ array[index] = value
42
+ end
43
+ end
44
+
45
+ def change_value?(value)
46
+ !is_iterable?(value) || !options[:skip_inner]
47
+ end
48
+
49
+ def is_iterable?(value)
50
+ value.respond_to?(:each)
51
+ end
52
+
53
+ def new_value(value)
54
+ value = block.call(value) if change_value?(value)
55
+ apply_recursion?(value) ? change(value) : value
56
+ end
57
+
58
+ def apply_recursion?(value)
59
+ is_iterable?(value) && options[:recursive]
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ class Numeric
2
+ def percent_of(n)
3
+ return Float::INFINITY if n == 0
4
+ (to_f / n.to_f) * 100.0
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ class Symbol
2
+ def camelize(type = :upper)
3
+ to_s.camelize(type).to_sym
4
+ end
5
+
6
+ def underscore
7
+ to_s.underscore.to_sym
8
+ end
9
+ end
@@ -0,0 +1,229 @@
1
+ require 'spec_helper'
2
+
3
+ describe Array do
4
+ describe '#chain_map' do
5
+ let(:array) { [ :a, :long_name, :sym ] }
6
+ let(:mapped) { array.chain_map(:to_s, :size, :to_s) }
7
+
8
+ it 'calls each argument as method of the mapped result' do
9
+ expect(mapped).to eq([ '1', '9', '3' ])
10
+ end
11
+
12
+ context 'when an extra block is given' do
13
+ let(:mapped) do
14
+ array.chain_map(:to_s, :size) do |v|
15
+ "final: #{v}"
16
+ end
17
+ end
18
+
19
+ it 'calls each argument as method of the mapped result' do
20
+ expect(mapped).to eq([ 'final: 1', 'final: 9', 'final: 3' ])
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#as_hash' do
26
+ let(:array) { [1, 2, 3] }
27
+ let(:keys) { %w(a b c) }
28
+ let(:expected) { { 'a' => 1, 'b' => 2, 'c' => 3 } }
29
+
30
+ it 'creates a hash using the array as value and the argument as keys' do
31
+ expect(array.as_hash(keys)).to eq(expected)
32
+ end
33
+
34
+ context 'when there are more keys than values' do
35
+ let(:keys) { %w(a b c d e f) }
36
+ let(:expected) { { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => nil, 'e' => nil, 'f' => nil } }
37
+
38
+ it 'creates a hash with nil values for the extra keys' do
39
+ expect(array.as_hash(keys)).to eq(expected)
40
+ end
41
+
42
+ it { expect { array.as_hash(keys) }.not_to change { keys } }
43
+ it { expect { array.as_hash(keys) }.not_to change { array } }
44
+ end
45
+
46
+ context 'when there are more values than keys' do
47
+ let(:array) { [1, 2, 3, 4, 5, 6, 7] }
48
+
49
+ it { expect { array.as_hash(keys) }.to raise_error(IndexError) }
50
+
51
+ it { expect { array.as_hash(keys) rescue nil }.not_to change { keys } }
52
+ it { expect { array.as_hash(keys) rescue nil }.not_to change { array } }
53
+ end
54
+ end
55
+
56
+ describe '#map_and_find' do
57
+ let(:array) { [1, 2, 3, 4] }
58
+ let(:value) { array.map_and_find(&block) }
59
+
60
+ context 'when block returns nil' do
61
+ let(:block) { proc {} }
62
+ it { expect(value).to be_nil }
63
+ end
64
+
65
+ context 'when block returns false' do
66
+ let(:block) { proc { false } }
67
+ it { expect(value).to be_nil }
68
+ end
69
+
70
+ context 'when block returns a true evaluated value' do
71
+ let(:block) { proc(&:to_s) }
72
+
73
+ it { expect(value).to eq('1') }
74
+
75
+ context 'but not for the first value' do
76
+ let(:transformer) { double(:transformer) }
77
+ let(:block) { proc { |v| transformer.transform(v) } }
78
+
79
+ before do
80
+ allow(transformer).to receive(:transform) do |v|
81
+ v.to_s if v > 1
82
+ end
83
+ value
84
+ end
85
+
86
+ it { expect(value).to eq('2') }
87
+ it 'calls the mapping only until it returns a valid value' do
88
+ expect(transformer).to have_received(:transform).exactly(2)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#random' do
95
+ it_behaves_like 'a method that returns a random element', :random
96
+ end
97
+
98
+ describe '#random!' do
99
+ it_behaves_like 'a method that returns a random element', :random!
100
+
101
+ let(:array) { [ 8,4,2 ] }
102
+
103
+ it 'removes an the returned element' do
104
+ expect do
105
+ array.random!
106
+ end.to change { array.size }.by(-1)
107
+ end
108
+ end
109
+
110
+ describe '#map_and_select' do
111
+ let(:array) { [1, 2, 3, 4].map { |i| { value: i} } }
112
+ let(:filtered) { array.map_and_select(&block) }
113
+
114
+ context 'when block returns nil' do
115
+ let(:block) { proc {} }
116
+ it { expect(filtered).to be_empty }
117
+ end
118
+
119
+ context 'when block returns false' do
120
+ let(:block) { proc { false } }
121
+ it { expect(filtered).to be_empty }
122
+ end
123
+
124
+ context 'when block returns a true evaluated value' do
125
+ let(:block) { proc(&:to_s) }
126
+
127
+ it { expect(filtered).to eq(array.map(&:to_s)) }
128
+
129
+ context 'but not for the first value' do
130
+ let(:transformer) { double(:transformer) }
131
+ let(:block) { proc { |v| transformer.transform(v) } }
132
+
133
+ before do
134
+ allow(transformer).to receive(:transform) do |v|
135
+ v.to_s if v[:value] > 1
136
+ end
137
+ filtered
138
+ end
139
+
140
+ it { expect(filtered).to eq(array[1..-1].map(&:to_s)) }
141
+
142
+ it 'calls the mapping only once per element' do
143
+ expect(transformer).to have_received(:transform).exactly(4)
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '#map_to_hash' do
150
+ context 'whe subject is an array' do
151
+ let(:subject) { %w(word1 wooord2) }
152
+ let(:mapping_block) { proc{ |word| word.length } }
153
+ let(:mapped) { subject.map_to_hash(&mapping_block) }
154
+ let(:expected) { { 'word1' => 5, 'wooord2' => 7 } }
155
+
156
+ it { expect(mapped).to be_a(Hash) }
157
+
158
+ it 'has the original array as keys' do
159
+ expect(mapped.keys).to eq(subject)
160
+ end
161
+
162
+ it 'has the mapped values as values' do
163
+ expect(mapped.values).to eq(subject.map(&mapping_block))
164
+ end
165
+
166
+ it 'correctly map keys to value' do
167
+ expect(mapped).to eq(expected)
168
+ end
169
+
170
+ context 'whe subject is an array' do
171
+ let(:subject) { [%w(w1), %w(w2 w3)] }
172
+ let(:mapped) { subject.map_to_hash(&mapping_block) }
173
+ let(:expected) { { %w(w1) => 1, %w(w2 w3) => 2 } }
174
+
175
+
176
+ it 'has the original array as keys' do
177
+ expect(mapped.keys).to eq(subject)
178
+ end
179
+
180
+ it 'has the mapped values as values' do
181
+ expect(mapped.values).to eq(subject.map(&mapping_block))
182
+ end
183
+
184
+ it 'correctly map keys to value' do
185
+ expect(mapped).to eq(expected)
186
+ end
187
+ end
188
+ end
189
+
190
+ context 'whe subject is a hash' do
191
+ let(:subject) { { a: 1, b: 2 } }
192
+ let(:mapping_block) { proc{ |k, v| "#{k}_#{v}" } }
193
+ let(:mapped) { subject.map_to_hash(&mapping_block) }
194
+ let(:expected) { { a: 'a_1', b: 'b_2' } }
195
+
196
+ it { expect(mapped).to be_a(Hash) }
197
+
198
+ it 'has the original keys as keys' do
199
+ expect(mapped.keys).to eq(subject.keys)
200
+ end
201
+
202
+ it 'has the mapped values as values' do
203
+ expect(mapped.values).to eq(subject.map(&mapping_block))
204
+ end
205
+
206
+ it 'correctly map keys to value' do
207
+ expect(mapped).to eq(expected)
208
+ end
209
+
210
+ context 'when hash uses arrays for keys' do
211
+ let(:subject) { { [:a, :b] => 1, [:c, :d] => 2 } }
212
+ let(:mapping_block) { proc{ |k, v| "#{k.join('_')}_#{v}" } }
213
+ let(:expected) { { [:a, :b]=> 'a_b_1', [:c, :d] => 'c_d_2' } }
214
+
215
+ it 'has the original keys as keys' do
216
+ expect(mapped.keys).to eq(subject.keys)
217
+ end
218
+
219
+ it 'has the mapped values as values' do
220
+ expect(mapped.values).to eq(subject.map(&mapping_block))
221
+ end
222
+
223
+ it 'correctly map keys to value' do
224
+ expect(mapped).to eq(expected)
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end