hash-deep-merge 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +2 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.txt +7 -0
- data/README.rdoc +59 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/hash-deep-merge.gemspec +63 -0
- data/lib/hash_deep_merge.rb +47 -0
- data/spec/hash-deep-merge_spec.rb +458 -0
- data/spec/spec_helper.rb +15 -0
- metadata +115 -0
data/.document
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
# Add dependencies required to use your gem here.
|
4
|
+
# Example:
|
5
|
+
# gem "activesupport", ">= 2.3.5"
|
6
|
+
|
7
|
+
# Add dependencies to develop your gem here.
|
8
|
+
# Include everything needed to run rake, tests, features, etc.
|
9
|
+
group :development do
|
10
|
+
gem 'rspec' #, "~> 2.3.0"
|
11
|
+
gem 'bundler' #, "~> 1.0.0"
|
12
|
+
gem 'jeweler' #, "~> 1.6.0"
|
13
|
+
gem 'rcov' #, ">= 0"
|
14
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.2)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.6.0)
|
7
|
+
bundler (~> 1.0.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rake (0.9.0)
|
11
|
+
rcov (0.9.9)
|
12
|
+
rspec (2.6.0)
|
13
|
+
rspec-core (~> 2.6.0)
|
14
|
+
rspec-expectations (~> 2.6.0)
|
15
|
+
rspec-mocks (~> 2.6.0)
|
16
|
+
rspec-core (2.6.2)
|
17
|
+
rspec-expectations (2.6.0)
|
18
|
+
diff-lcs (~> 1.1.2)
|
19
|
+
rspec-mocks (2.6.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bundler
|
26
|
+
jeweler
|
27
|
+
rcov
|
28
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
No Copyright
|
2
|
+
|
3
|
+
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
|
4
|
+
|
5
|
+
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
|
6
|
+
|
7
|
+
http://creativecommons.org/publicdomain/zero/1.0/
|
data/README.rdoc
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= hash-deep-merge
|
2
|
+
|
3
|
+
== Introduction
|
4
|
+
This little gem add a method to the Hash class, allowing to merge hashes containing other hashes. (This is known as "deep merge" or "recursive merge".)
|
5
|
+
|
6
|
+
You (may) already know the standard hash merge :
|
7
|
+
h1 = { 'a' => 1, 'b' => 2, 'c' => 3}
|
8
|
+
h2 = { 'b' => 33, 'd' => 42 }
|
9
|
+
h1.merge(h2)
|
10
|
+
-> { 'a' => 1, 'b' => 33, 'c' => 3, 'd' => 42 }
|
11
|
+
|
12
|
+
But now, what if the hash contains other hashes ?
|
13
|
+
h1 = { :option1 => true, :option2 => false, :option_group1 => { :sub_option1 => 23, :sub_option2 => "hey !" } }
|
14
|
+
h2 = { :option2 => true, :option3 => 27, :option_group1 => { :sub_option1 => 18 } }
|
15
|
+
traditional merge doesn't handle that... Here come deep_merge !
|
16
|
+
h1.deep_merge(h2)
|
17
|
+
-> { :option1 => true, :option2 => true, :option_group1 => { :sub_option1 => 18, :sub_option2 => "hey !" }, :option3 => 27 }
|
18
|
+
|
19
|
+
Of course, a deep_merge! (note the !) is available, like merge!
|
20
|
+
|
21
|
+
This should be included in Ruby, but there are some discussions about extra features that prevent it from going forward, cf. http://www.misuse.org/science/2008/05/19/deep_merge-ruby-recursive-merging-for-hashes/
|
22
|
+
|
23
|
+
The version I give you is the simplest one, doing what you need 99% of the time.
|
24
|
+
|
25
|
+
There are plenty of code snippets for deep merge :
|
26
|
+
* http://snippets.dzone.com/posts/show/4706
|
27
|
+
* http://rexchung.com/2007/02/01/hash-recursive-merge-in-ruby/
|
28
|
+
* https://gist.github.com/6391
|
29
|
+
|
30
|
+
But they were not packaged it into a gem. It's done now for your convenience (and mine).
|
31
|
+
|
32
|
+
NOTE : I since found a gem containing a more powerful deep merge, able to not only merge hashes but arrays and strings as well with many options. You may want to look at it :
|
33
|
+
https://github.com/peritor/deep_merge
|
34
|
+
|
35
|
+
== Installation
|
36
|
+
|
37
|
+
If you use bundler, just throw that in your gemfile :
|
38
|
+
gem 'hash-deep-merge'
|
39
|
+
|
40
|
+
You may also install the gem manually :
|
41
|
+
gem install hash-deep-merge
|
42
|
+
|
43
|
+
Isn't it so easy ?
|
44
|
+
|
45
|
+
== Contributing to hash-deep-merge
|
46
|
+
|
47
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
48
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
49
|
+
* Fork the project
|
50
|
+
* Start a feature/bugfix branch
|
51
|
+
* Commit and push until you are happy with your contribution
|
52
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
53
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
54
|
+
|
55
|
+
== Copyright
|
56
|
+
|
57
|
+
Copyright (c) 2011 Offirmo. See LICENSE.txt for
|
58
|
+
further details.
|
59
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "hash-deep-merge"
|
18
|
+
gem.homepage = "http://github.com/Offirmo/hash-deep-merge"
|
19
|
+
gem.license = "CC0 1.0"
|
20
|
+
gem.summary = %Q{add the deep merge feature to class Hash.}
|
21
|
+
gem.description = <<-EOF
|
22
|
+
This gem add the "deep merge" feature to class Hash.
|
23
|
+
It means that if you want to merge hashes that contains other hashes (and so on...), those sub-hashes will be merged as well.
|
24
|
+
This is very handy, for example for merging data taken from YAML files.
|
25
|
+
EOF
|
26
|
+
gem.email = "offirmo.net@gmail.com"
|
27
|
+
gem.authors = ["Offirmo"]
|
28
|
+
# dependencies defined in Gemfile
|
29
|
+
end
|
30
|
+
Jeweler::RubygemsDotOrgTasks.new
|
31
|
+
|
32
|
+
require 'rspec/core'
|
33
|
+
require 'rspec/core/rake_task'
|
34
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
35
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
36
|
+
end
|
37
|
+
|
38
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
39
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
40
|
+
spec.rcov = true
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :spec
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "hash-deep-merge #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{hash-deep-merge}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Offirmo"]
|
12
|
+
s.date = %q{2011-05-23}
|
13
|
+
s.description = %q{This gem add the "deep merge" feature to class Hash.
|
14
|
+
It means that if you want to merge hashes that contains other hashes (and so on...), those sub-hashes will be merged as well.
|
15
|
+
This is very handy, for example for merging data taken from YAML files.
|
16
|
+
}
|
17
|
+
s.email = %q{offirmo.net@gmail.com}
|
18
|
+
s.extra_rdoc_files = [
|
19
|
+
"LICENSE.txt",
|
20
|
+
"README.rdoc"
|
21
|
+
]
|
22
|
+
s.files = [
|
23
|
+
".document",
|
24
|
+
".rspec",
|
25
|
+
"Gemfile",
|
26
|
+
"Gemfile.lock",
|
27
|
+
"LICENSE.txt",
|
28
|
+
"README.rdoc",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"hash-deep-merge.gemspec",
|
32
|
+
"lib/hash_deep_merge.rb",
|
33
|
+
"spec/hash-deep-merge_spec.rb",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/Offirmo/hash-deep-merge}
|
37
|
+
s.licenses = ["CC0 1.0"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.6.2}
|
40
|
+
s.summary = %q{add the deep merge feature to class Hash.}
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
s.specification_version = 3
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
52
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
53
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
54
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
58
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
59
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
60
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def deep_merge!(specialized_hash)
|
4
|
+
return internal_deep_merge!(self, specialized_hash)
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def deep_merge(specialized_hash)
|
9
|
+
return internal_deep_merge!(Hash.new.replace(self), specialized_hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# better, recursive, preserving method
|
16
|
+
# OK OK this is not the most efficient algorithm,
|
17
|
+
# but at last it's *perfectly clear and understandable*
|
18
|
+
# so fork and improve if you need 5% more speed, ok ?
|
19
|
+
def internal_deep_merge!(source_hash, specialized_hash)
|
20
|
+
|
21
|
+
#puts "starting deep merge..."
|
22
|
+
|
23
|
+
specialized_hash.each_pair do |rkey, rval|
|
24
|
+
#puts " potential replacing entry : " + rkey.inspect
|
25
|
+
|
26
|
+
if source_hash.has_key?(rkey) then
|
27
|
+
#puts " found potentially conflicting entry for #{rkey.inspect} : #{rval.inspect}, will merge :"
|
28
|
+
if rval.is_a?(Hash) and source_hash[rkey].is_a?(Hash) then
|
29
|
+
#puts " recursing..."
|
30
|
+
internal_deep_merge!(source_hash[rkey], rval)
|
31
|
+
elsif rval == source_hash[rkey] then
|
32
|
+
#puts " same value, skipping."
|
33
|
+
else
|
34
|
+
#puts " replacing."
|
35
|
+
source_hash[rkey] = rval
|
36
|
+
end
|
37
|
+
else
|
38
|
+
#puts " found new entry #{rkey.inspect}, adding it..."
|
39
|
+
source_hash[rkey] = rval
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#puts "deep merge done."
|
44
|
+
|
45
|
+
return source_hash
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,458 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "HashDeepMerge" do
|
4
|
+
|
5
|
+
|
6
|
+
######################################################################
|
7
|
+
describe "handling of ordinary hashes" do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
# When hashes don't contain other hashes, deep_merge works the same as merge.
|
11
|
+
@hash1 = { 3 => 2, :test => 1, 'toto' => 'titi', :foo => :bar }
|
12
|
+
@hash2 = { 3 => '42', :test => 2, 'toto' => 'titi', :fooz => :barz }
|
13
|
+
|
14
|
+
# Expected results, computed manually.
|
15
|
+
@expected_result_1to2 = { 3 => '42', :test => 2, 'toto' => 'titi', :foo => :bar, :fooz => :barz }
|
16
|
+
@expected_result_2to1 = { 3 => 2, :test => 1, 'toto' => 'titi', :fooz => :barz, :foo => :bar }
|
17
|
+
|
18
|
+
# We store copie to check for modifications,
|
19
|
+
# because some functions are expected to change the values and some not.
|
20
|
+
@hash1_copy = Hash.new.replace(@hash1)
|
21
|
+
@hash2_copy = Hash.new.replace(@hash2)
|
22
|
+
|
23
|
+
# This is not really a test, it's just to remember prerequisites for following tests.
|
24
|
+
# (If we want to test no modifications, we just have to merge a hash with itself)
|
25
|
+
@expected_result_1to2.should_not == @hash1
|
26
|
+
@expected_result_1to2.should_not == @hash2
|
27
|
+
@expected_result_2to1.should_not == @hash1
|
28
|
+
@expected_result_2to1.should_not == @hash2
|
29
|
+
end
|
30
|
+
|
31
|
+
######################## MERGE ########################
|
32
|
+
describe "merge function" do
|
33
|
+
it "should behave the same (exact identity case)" do
|
34
|
+
|
35
|
+
expected_result = @hash1_copy
|
36
|
+
|
37
|
+
# first we do it with the ordinary "merge" function, to check.
|
38
|
+
result = @hash1.merge(@hash1) # merge to itself
|
39
|
+
@hash1.should == @hash1_copy # should not have been modified
|
40
|
+
@hash2.should == @hash2_copy # should not have been modified
|
41
|
+
result.should == expected_result
|
42
|
+
|
43
|
+
# Now we do it with deep_merge
|
44
|
+
result = @hash1.deep_merge(@hash1)
|
45
|
+
@hash1.should == @hash1_copy # should not have been modified
|
46
|
+
@hash2.should == @hash2_copy # should not have been modified
|
47
|
+
result.should == expected_result
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should behave the same (shuffled identity case)" do
|
51
|
+
# really needed ?
|
52
|
+
pending
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should behave the same (different case)" do
|
56
|
+
expected_result = @expected_result_1to2
|
57
|
+
|
58
|
+
# first we do it with the ordinary "merge" function, to check.
|
59
|
+
result = @hash1.merge(@hash2)
|
60
|
+
@hash1.should == @hash1_copy # should not have been modified
|
61
|
+
@hash2.should == @hash2_copy # should not have been modified
|
62
|
+
result.should == expected_result
|
63
|
+
|
64
|
+
# Now we do it with deep_merge
|
65
|
+
result = @hash1.deep_merge(@hash2)
|
66
|
+
@hash1.should == @hash1_copy # should not have been modified
|
67
|
+
@hash2.should == @hash2_copy # should not have been modified
|
68
|
+
result.should == expected_result
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should behave the same (different case reversed)" do
|
72
|
+
expected_result = @expected_result_2to1
|
73
|
+
|
74
|
+
# first we do it with the ordinary "merge" function, to check.
|
75
|
+
result = @hash2.merge(@hash1)
|
76
|
+
@hash1.should == @hash1_copy # should not have been modified
|
77
|
+
@hash2.should == @hash2_copy # should not have been modified
|
78
|
+
result.should == expected_result
|
79
|
+
|
80
|
+
# Now we do it with deep_merge
|
81
|
+
result = @hash2.deep_merge(@hash1)
|
82
|
+
@hash1.should == @hash1_copy # should not have been modified
|
83
|
+
@hash2.should == @hash2_copy # should not have been modified
|
84
|
+
result.should == expected_result
|
85
|
+
end
|
86
|
+
end # merge
|
87
|
+
|
88
|
+
######################## MERGE!!! ########################
|
89
|
+
describe "merge!" do
|
90
|
+
it "should behave the same (exact identity case)" do
|
91
|
+
|
92
|
+
expected_result = @hash1_copy
|
93
|
+
|
94
|
+
# first we do it with the ordinary "merge" function, to check.
|
95
|
+
@hash1.merge!(@hash1) # merge to itself
|
96
|
+
result = @hash1
|
97
|
+
@hash1.should == @hash1_copy # should be equal
|
98
|
+
@hash2.should == @hash2_copy # should not have been modified
|
99
|
+
result.should == expected_result
|
100
|
+
|
101
|
+
# restore @hash1
|
102
|
+
@hash1 = Hash.new.replace(@hash1_copy)
|
103
|
+
@hash1.should == @hash1_copy # should have been restored
|
104
|
+
|
105
|
+
# Now we do it with deep_merge
|
106
|
+
@hash1.deep_merge!(@hash1)
|
107
|
+
result = @hash1
|
108
|
+
@hash1.should == @hash1_copy # should be equal
|
109
|
+
@hash2.should == @hash2_copy # should not have been modified
|
110
|
+
result.should == expected_result
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should behave the same (shuffled identity case)" do
|
114
|
+
# really needed ?
|
115
|
+
pending
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should behave the same (different case)" do
|
119
|
+
expected_result = @expected_result_1to2
|
120
|
+
|
121
|
+
# first we do it with the ordinary "merge!" function, to check.
|
122
|
+
@hash1.merge!(@hash2) # XXX with ! XXX
|
123
|
+
result = @hash1
|
124
|
+
@hash1.should_not == @hash1_copy # *should* have been modified
|
125
|
+
@hash2.should == @hash2_copy # should not have been modified
|
126
|
+
result.should == expected_result
|
127
|
+
|
128
|
+
# restore @hash1
|
129
|
+
@hash1 = Hash.new.replace(@hash1_copy)
|
130
|
+
@hash1.should == @hash1_copy # should have been restored
|
131
|
+
|
132
|
+
# Now we do it with deep_merge
|
133
|
+
result = @hash1.deep_merge!(@hash2) # XXX with ! XXX
|
134
|
+
@hash1.should_not == @hash1_copy # *should* have been modified
|
135
|
+
@hash2.should == @hash2_copy # should not have been modified
|
136
|
+
result.should == expected_result
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should behave the same (different case reversed)" do
|
140
|
+
expected_result = @expected_result_2to1
|
141
|
+
|
142
|
+
# first we do it with the ordinary "merge!" function, to check.
|
143
|
+
@hash2.merge!(@hash1)
|
144
|
+
result = @hash2
|
145
|
+
@hash1.should == @hash1_copy # should not have been modified
|
146
|
+
@hash2.should_not == @hash2_copy # *should* have been modified
|
147
|
+
result.should == expected_result
|
148
|
+
|
149
|
+
# restore @hash2
|
150
|
+
@hash2 = Hash.new.replace(@hash2_copy)
|
151
|
+
@hash2.should == @hash2_copy # should not have been modified
|
152
|
+
|
153
|
+
# Now we do it with deep_merge
|
154
|
+
@hash2.deep_merge!(@hash1)
|
155
|
+
result = @hash2
|
156
|
+
@hash1.should == @hash1_copy # should not have been modified
|
157
|
+
@hash2.should_not == @hash2_copy # *should* have been modified
|
158
|
+
result.should == expected_result
|
159
|
+
end
|
160
|
+
end # merge!
|
161
|
+
end # handling of ordinary hashes
|
162
|
+
|
163
|
+
|
164
|
+
|
165
|
+
######################################################################
|
166
|
+
describe "handling of hashes containing hashes" do
|
167
|
+
|
168
|
+
before(:each) do
|
169
|
+
# same as before, but with sub hashes
|
170
|
+
@hash1 = { 3 => 2, :test => 1, 'toto' => 'titi', :foo => :bar,
|
171
|
+
'sub hash 1' => {
|
172
|
+
3 => 2, :test => 1, 'toto' => 'titi', :foo => :bar },
|
173
|
+
'sub hash 2' => {
|
174
|
+
'hello' => 'world' },
|
175
|
+
}
|
176
|
+
@hash2 = { 3 => '42', :test => 2, 'toto' => 'titi', :fooz => :barz,
|
177
|
+
'sub hash 1' => {
|
178
|
+
3 => '42', :test => 2, 'toto' => 'titi', :fooz => :barz },
|
179
|
+
'sub hash 3' => {
|
180
|
+
'hello' => 'world' },
|
181
|
+
}
|
182
|
+
|
183
|
+
# Expected results, computed manually.
|
184
|
+
@expected_result_1to2 = { 3 => '42',
|
185
|
+
:test => 2,
|
186
|
+
'toto' => 'titi',
|
187
|
+
:foo => :bar,
|
188
|
+
'sub hash 1' => {
|
189
|
+
3 => '42', :test => 2, 'toto' => 'titi', :foo => :bar, :fooz => :barz},
|
190
|
+
'sub hash 2' => {
|
191
|
+
'hello' => 'world' },
|
192
|
+
'sub hash 3' => {
|
193
|
+
'hello' => 'world' },
|
194
|
+
:fooz => :barz,
|
195
|
+
}
|
196
|
+
@expected_result_2to1 = { 3 => 2,
|
197
|
+
:test => 1,
|
198
|
+
'toto' => 'titi',
|
199
|
+
:fooz => :barz,
|
200
|
+
'sub hash 1' => {
|
201
|
+
3 => 2, :test => 1, 'toto' => 'titi', :fooz => :barz, :foo => :bar},
|
202
|
+
'sub hash 3' => {
|
203
|
+
'hello' => 'world' },
|
204
|
+
'sub hash 2' => {
|
205
|
+
'hello' => 'world' },
|
206
|
+
:foo => :bar,
|
207
|
+
}
|
208
|
+
|
209
|
+
# We store copie to check for modifications,
|
210
|
+
# because some functions are expected to change the values and some not.
|
211
|
+
@hash1_copy = Hash.new.replace(@hash1)
|
212
|
+
@hash2_copy = Hash.new.replace(@hash2)
|
213
|
+
|
214
|
+
# This is not really a test, it's just to remember prerequisites for following tests.
|
215
|
+
# (If we want to test no modifications, we just have to merge a hash with itself)
|
216
|
+
@expected_result_1to2.should_not == @hash1
|
217
|
+
@expected_result_1to2.should_not == @hash2
|
218
|
+
@expected_result_2to1.should_not == @hash1
|
219
|
+
@expected_result_2to1.should_not == @hash2
|
220
|
+
end
|
221
|
+
|
222
|
+
######################## MERGE ########################
|
223
|
+
describe "merge" do
|
224
|
+
|
225
|
+
it "should work (exact identity case)" do
|
226
|
+
|
227
|
+
expected_result = @hash1_copy
|
228
|
+
result = @hash1.deep_merge(@hash1)
|
229
|
+
|
230
|
+
@hash1.should == @hash1_copy # should be equal
|
231
|
+
@hash2.should == @hash2_copy # should not have been modified
|
232
|
+
result.should == expected_result
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should work (shuffled identity case)" do
|
236
|
+
# really needed ?
|
237
|
+
pending
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should work (different case)" do
|
241
|
+
|
242
|
+
expected_result = @expected_result_1to2
|
243
|
+
result = @hash1.deep_merge(@hash2)
|
244
|
+
|
245
|
+
@hash1.should == @hash1_copy # should not have been modified
|
246
|
+
@hash2.should == @hash2_copy # should not have been modified
|
247
|
+
result.should == expected_result
|
248
|
+
result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
|
249
|
+
result['sub hash 1'].should == expected_result['sub hash 1']
|
250
|
+
result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
|
251
|
+
result['sub hash 2'].should == expected_result['sub hash 2']
|
252
|
+
result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
|
253
|
+
result['sub hash 3'].should == expected_result['sub hash 3']
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should work (different case reversed)" do
|
257
|
+
|
258
|
+
expected_result = @expected_result_2to1
|
259
|
+
result = @hash2.deep_merge(@hash1)
|
260
|
+
|
261
|
+
@hash1.should == @hash1_copy # should not have been modified
|
262
|
+
@hash2.should == @hash2_copy # should not have been modified
|
263
|
+
result.should == expected_result
|
264
|
+
result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
|
265
|
+
result['sub hash 1'].should == expected_result['sub hash 1']
|
266
|
+
result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
|
267
|
+
result['sub hash 2'].should == expected_result['sub hash 2']
|
268
|
+
result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
|
269
|
+
result['sub hash 3'].should == expected_result['sub hash 3']
|
270
|
+
end
|
271
|
+
|
272
|
+
end # merge
|
273
|
+
|
274
|
+
######################## MERGE!!! ########################
|
275
|
+
describe "merge!" do
|
276
|
+
|
277
|
+
it "should work (exact identity case)" do
|
278
|
+
|
279
|
+
expected_result = @hash1_copy
|
280
|
+
@hash1.deep_merge!(@hash1)
|
281
|
+
result = @hash1
|
282
|
+
|
283
|
+
@hash1.should == @hash1_copy # should be equal
|
284
|
+
@hash2.should == @hash2_copy # should not have been modified
|
285
|
+
result.should == expected_result
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should work (shuffled identity case)" do
|
289
|
+
# really needed ?
|
290
|
+
pending
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should work (different case)" do
|
294
|
+
|
295
|
+
expected_result = @expected_result_1to2
|
296
|
+
@hash1.deep_merge!(@hash2)
|
297
|
+
result = @hash1
|
298
|
+
|
299
|
+
@hash1.should_not == @hash1_copy # *should* have been modified
|
300
|
+
@hash2.should == @hash2_copy # should not have been modified
|
301
|
+
result.should == expected_result
|
302
|
+
result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
|
303
|
+
result['sub hash 1'].should == expected_result['sub hash 1']
|
304
|
+
result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
|
305
|
+
result['sub hash 2'].should == expected_result['sub hash 2']
|
306
|
+
result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
|
307
|
+
result['sub hash 3'].should == expected_result['sub hash 3']
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should work (different case reversed)" do
|
311
|
+
|
312
|
+
expected_result = @expected_result_2to1
|
313
|
+
@hash2.deep_merge!(@hash1)
|
314
|
+
result = @hash2
|
315
|
+
|
316
|
+
@hash1.should == @hash1_copy # should not have been modified
|
317
|
+
@hash2.should_not == @hash2_copy # *should* have been modified
|
318
|
+
result.should == expected_result
|
319
|
+
result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
|
320
|
+
result['sub hash 1'].should == expected_result['sub hash 1']
|
321
|
+
result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
|
322
|
+
result['sub hash 2'].should == expected_result['sub hash 2']
|
323
|
+
result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
|
324
|
+
result['sub hash 3'].should == expected_result['sub hash 3']
|
325
|
+
end
|
326
|
+
|
327
|
+
end # merge
|
328
|
+
end # hash with sub hashes
|
329
|
+
|
330
|
+
######################################################################
|
331
|
+
describe "handling of hashes containing hashes containing hashes" do
|
332
|
+
before(:each) do
|
333
|
+
# same as before, but with sub sub hashes
|
334
|
+
# things start to get complicated, isn't it ?
|
335
|
+
@hash1 = {
|
336
|
+
3 => 2,
|
337
|
+
:test => 1,
|
338
|
+
'toto' => 'titi',
|
339
|
+
:foo => :bar,
|
340
|
+
'sub hash 1' => {
|
341
|
+
3 => 2,
|
342
|
+
:test => 1,
|
343
|
+
'toto' => 'titi',
|
344
|
+
:foo => :bar,
|
345
|
+
'sub sub hash 1' => {
|
346
|
+
3 => '42',
|
347
|
+
:test => 2,
|
348
|
+
'toto' => 'titi',
|
349
|
+
:fooz => :barz
|
350
|
+
},
|
351
|
+
},
|
352
|
+
'sub hash 2' => {
|
353
|
+
'hello' => 'world'
|
354
|
+
},
|
355
|
+
}
|
356
|
+
@hash2 = {
|
357
|
+
3 => '42',
|
358
|
+
:test => 2,
|
359
|
+
'toto' => 'titi',
|
360
|
+
:fooz => :barz,
|
361
|
+
'sub hash 1' => {
|
362
|
+
3 => '42',
|
363
|
+
:test => 2,
|
364
|
+
'toto' => 'titi',
|
365
|
+
:fooz => :barz,
|
366
|
+
'sub sub hash 1' => {
|
367
|
+
3 => 2,
|
368
|
+
:test => 1,
|
369
|
+
'toto' => 'titi',
|
370
|
+
:foo => :bar
|
371
|
+
},
|
372
|
+
},
|
373
|
+
'sub hash 2' => {
|
374
|
+
'hello' => 'worldy'
|
375
|
+
},
|
376
|
+
}
|
377
|
+
|
378
|
+
# Expected results, computed manually.
|
379
|
+
@expected_result_1to2 = {
|
380
|
+
3 => '42',
|
381
|
+
:test => 2,
|
382
|
+
'toto' => 'titi',
|
383
|
+
:foo => :bar,
|
384
|
+
'sub hash 1' => {
|
385
|
+
3 => '42',
|
386
|
+
:test => 2,
|
387
|
+
'toto' => 'titi',
|
388
|
+
:foo => :bar,
|
389
|
+
'sub sub hash 1' => {
|
390
|
+
3 => 2,
|
391
|
+
:test => 1,
|
392
|
+
'toto' => 'titi',
|
393
|
+
:fooz => :barz,
|
394
|
+
:foo => :bar
|
395
|
+
},
|
396
|
+
:fooz => :barz
|
397
|
+
},
|
398
|
+
'sub hash 2' => {
|
399
|
+
'hello' => 'worldy'
|
400
|
+
},
|
401
|
+
:fooz => :barz,
|
402
|
+
}
|
403
|
+
|
404
|
+
# Gaaah ! My head burns !
|
405
|
+
|
406
|
+
# We store copie to check for modifications,
|
407
|
+
# because some functions are expected to change the values and some not.
|
408
|
+
@hash1_copy = Hash.new.replace(@hash1)
|
409
|
+
@hash2_copy = Hash.new.replace(@hash2)
|
410
|
+
|
411
|
+
# This is not really a test, it's just to remember prerequisites for following tests.
|
412
|
+
# (If we want to test no modifications, we just have to merge a hash with itself)
|
413
|
+
@expected_result_1to2.should_not == @hash1
|
414
|
+
@expected_result_1to2.should_not == @hash2
|
415
|
+
end
|
416
|
+
|
417
|
+
######################## MERGE ########################
|
418
|
+
describe "merge" do
|
419
|
+
|
420
|
+
it "should work (exact identity case)" do
|
421
|
+
|
422
|
+
expected_result = @hash1_copy
|
423
|
+
@hash1.deep_merge!(@hash1)
|
424
|
+
result = @hash1
|
425
|
+
|
426
|
+
@hash1.should == @hash1_copy # should be equal
|
427
|
+
@hash2.should == @hash2_copy # should not have been modified
|
428
|
+
result.should == expected_result
|
429
|
+
end
|
430
|
+
|
431
|
+
it "should work (shuffled identity case)" do
|
432
|
+
# really needed ?
|
433
|
+
pending
|
434
|
+
end
|
435
|
+
|
436
|
+
it "should work (different case)" do
|
437
|
+
|
438
|
+
expected_result = @expected_result_1to2
|
439
|
+
@hash1.deep_merge!(@hash2)
|
440
|
+
result = @hash1
|
441
|
+
|
442
|
+
@hash1.should_not == @hash1_copy # *should* have been modified
|
443
|
+
@hash2.should == @hash2_copy # should not have been modified
|
444
|
+
result.should == expected_result
|
445
|
+
result['sub hash 1'].should == expected_result['sub hash 1']
|
446
|
+
result['sub hash 1']['sub sub hash 1'].should == expected_result['sub hash 1']['sub sub hash 1']
|
447
|
+
result['sub hash 2'].should == expected_result['sub hash 2']
|
448
|
+
end
|
449
|
+
end # merge
|
450
|
+
end
|
451
|
+
|
452
|
+
######################################################################
|
453
|
+
describe "handling of hashes containing hashes containing hashes containing hashes" do
|
454
|
+
# herm... maybe we'll stop here ;-)
|
455
|
+
# I'm mad enough already...
|
456
|
+
end
|
457
|
+
|
458
|
+
end # describe "HashDeepMerge"
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'hash_deep_merge'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
# I add some display to clearly mark the beginning of the tests
|
11
|
+
puts "*\n"*12
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash-deep-merge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Offirmo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-05-23 00:00:00.000000000 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: &78820430 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *78820430
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bundler
|
28
|
+
requirement: &78820190 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *78820190
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: jeweler
|
39
|
+
requirement: &78819910 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *78819910
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rcov
|
50
|
+
requirement: &78819670 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *78819670
|
59
|
+
description: ! 'This gem add the "deep merge" feature to class Hash.
|
60
|
+
|
61
|
+
It means that if you want to merge hashes that contains other hashes (and so on...),
|
62
|
+
those sub-hashes will be merged as well.
|
63
|
+
|
64
|
+
This is very handy, for example for merging data taken from YAML files.
|
65
|
+
|
66
|
+
'
|
67
|
+
email: offirmo.net@gmail.com
|
68
|
+
executables: []
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files:
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.rdoc
|
73
|
+
files:
|
74
|
+
- .document
|
75
|
+
- .rspec
|
76
|
+
- Gemfile
|
77
|
+
- Gemfile.lock
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.rdoc
|
80
|
+
- Rakefile
|
81
|
+
- VERSION
|
82
|
+
- hash-deep-merge.gemspec
|
83
|
+
- lib/hash_deep_merge.rb
|
84
|
+
- spec/hash-deep-merge_spec.rb
|
85
|
+
- spec/spec_helper.rb
|
86
|
+
has_rdoc: true
|
87
|
+
homepage: http://github.com/Offirmo/hash-deep-merge
|
88
|
+
licenses:
|
89
|
+
- CC0 1.0
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
hash: 814986959
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.6.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: add the deep merge feature to class Hash.
|
115
|
+
test_files: []
|