hashdiff 0.0.2
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.
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +28 -0
- data/README.md +70 -0
- data/Rakefile +13 -0
- data/hashdiff.gemspec +24 -0
- data/lib/hashdiff.rb +5 -0
- data/lib/hashdiff/diff.rb +142 -0
- data/lib/hashdiff/lcs.rb +64 -0
- data/lib/hashdiff/patch.rb +86 -0
- data/lib/hashdiff/util.rb +97 -0
- data/lib/hashdiff/version.rb +3 -0
- data/spec/hashdiff/diff_array_spec.rb +60 -0
- data/spec/hashdiff/diff_spec.rb +137 -0
- data/spec/hashdiff/lcs_spec.rb +36 -0
- data/spec/hashdiff/patch_spec.rb +139 -0
- data/spec/hashdiff/util_spec.rb +9 -0
- data/spec/spec_helper.rb +13 -0
- metadata +100 -0
data/.gitignore
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile ~/.gitignore_global
|
6
|
+
|
7
|
+
# Ignore bundler config
|
8
|
+
/.bundle
|
9
|
+
/doc
|
10
|
+
/.yardoc
|
11
|
+
|
12
|
+
*.swp
|
13
|
+
*.bak
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
hashdiff (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
bluecloth (2.2.0)
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
rspec (2.10.0)
|
12
|
+
rspec-core (~> 2.10.0)
|
13
|
+
rspec-expectations (~> 2.10.0)
|
14
|
+
rspec-mocks (~> 2.10.0)
|
15
|
+
rspec-core (2.10.1)
|
16
|
+
rspec-expectations (2.10.0)
|
17
|
+
diff-lcs (~> 1.1.3)
|
18
|
+
rspec-mocks (2.10.1)
|
19
|
+
yard (0.8.1)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bluecloth
|
26
|
+
hashdiff!
|
27
|
+
rspec (~> 2.0)
|
28
|
+
yard
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
HashDiff
|
2
|
+
=========
|
3
|
+
|
4
|
+
HashDiff is a ruby library to compute the smallest difference between two hashes.
|
5
|
+
|
6
|
+
Requirements
|
7
|
+
------------
|
8
|
+
HashDiff is tested on following platforms:
|
9
|
+
|
10
|
+
- 1.8.7
|
11
|
+
- 1.9.2
|
12
|
+
- rbx
|
13
|
+
- rbx-2.0
|
14
|
+
- ree
|
15
|
+
- jruby
|
16
|
+
- ruby-head
|
17
|
+
|
18
|
+
Quick Start
|
19
|
+
-----------
|
20
|
+
|
21
|
+
### Diff
|
22
|
+
|
23
|
+
Two simple hash:
|
24
|
+
|
25
|
+
a = {a:3, b:2}
|
26
|
+
b = {}
|
27
|
+
|
28
|
+
diff = HashDiff.diff(a, b)
|
29
|
+
diff.should == [['-', 'a', 3], ['-', 'b', 2]]
|
30
|
+
|
31
|
+
More complex hash:
|
32
|
+
|
33
|
+
a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
|
34
|
+
b = {a:{y:3}, b:{y:3, z:30}}
|
35
|
+
|
36
|
+
diff = HashDiff.diff(a, b)
|
37
|
+
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]
|
38
|
+
|
39
|
+
Array in hash:
|
40
|
+
|
41
|
+
a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}
|
42
|
+
b = {a:[{y:3}, {x:11, z:33}], b:{y:22}}
|
43
|
+
|
44
|
+
diff = HashDiff.best_diff(a, b) # best_diff will try to match similar objects in array in order to generate the smallest change set
|
45
|
+
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
|
46
|
+
|
47
|
+
### Patch
|
48
|
+
|
49
|
+
patch example:
|
50
|
+
|
51
|
+
a = {a: 3}
|
52
|
+
b = {a: {a1: 1, a2: 2}}
|
53
|
+
|
54
|
+
diff = HashDiff.diff(a, b)
|
55
|
+
HashDiff.patch(a, diff).should == b
|
56
|
+
|
57
|
+
unpatch example:
|
58
|
+
|
59
|
+
a = [{a: 1, b: 2, c: 3, d: 4, e: 5}, {x: 5, y: 6, z: 3}, 1]
|
60
|
+
b = [1, {a: 1, b: 2, c: 3, e: 5}]
|
61
|
+
|
62
|
+
diff = HashDiff.diff(a, b) # diff two array is OK
|
63
|
+
HashDiff.unpatch(b, diff).should == a
|
64
|
+
|
65
|
+
|
66
|
+
License
|
67
|
+
-------
|
68
|
+
|
69
|
+
HashDiff is distributed under the MIT-LICENSE.
|
70
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
11
|
+
spec.pattern = "./spec/**/*_spec.rb"
|
12
|
+
end
|
13
|
+
|
data/hashdiff.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path("../lib", __FILE__)
|
2
|
+
require 'hashdiff/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = %q{hashdiff}
|
6
|
+
s.version = HashDiff::VERSION
|
7
|
+
s.summary = %q{ HashDiff is a diff lib to compute the smallest difference between two hashes. }
|
8
|
+
s.description = %q{ HashDiff is a diff lib to compute the smallest difference between two hashes. }
|
9
|
+
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n")
|
12
|
+
|
13
|
+
s.require_paths = ['lib']
|
14
|
+
s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
|
15
|
+
|
16
|
+
s.authors = ["Liu Fengyun"]
|
17
|
+
s.email = ["liufengyunchina@gmail.com"]
|
18
|
+
|
19
|
+
s.homepage = "https://github.com/liufengyun/hashdiff"
|
20
|
+
|
21
|
+
s.add_development_dependency("rspec", "~> 2.0")
|
22
|
+
s.add_development_dependency("yard")
|
23
|
+
s.add_development_dependency("bluecloth")
|
24
|
+
end
|
data/lib/hashdiff.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
module HashDiff
|
2
|
+
|
3
|
+
# try to make the best diff that generates the smallest change set
|
4
|
+
def self.best_diff(obj1, obj2)
|
5
|
+
diffs_1 = diff(obj1, obj2, "", 0.3)
|
6
|
+
diffs_2 = diff(obj1, obj2, "", 0.5)
|
7
|
+
diffs_3 = diff(obj1, obj2, "", 0.8)
|
8
|
+
|
9
|
+
diffs = diffs_1.size < diffs_2.size ? diffs_1 : diffs_2
|
10
|
+
diffs = diffs.size < diffs_3.size ? diffs : diffs_3
|
11
|
+
end
|
12
|
+
|
13
|
+
# compute the diff of two hashes, return an array of changes
|
14
|
+
# e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
|
15
|
+
#
|
16
|
+
# NOTE: diff will treat nil as [], {} or "" in comparison according to different context.
|
17
|
+
#
|
18
|
+
def self.diff(obj1, obj2, prefix = "", similarity = 0.8)
|
19
|
+
if obj1.nil?
|
20
|
+
if obj2.is_a?(Array)
|
21
|
+
return diff([], obj2, prefix, similarity)
|
22
|
+
elsif obj2.is_a?(Hash)
|
23
|
+
return diff({}, obj2, prefix, similarity)
|
24
|
+
else
|
25
|
+
return diff('', obj2, prefix, similarity)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if obj2.nil?
|
30
|
+
if obj1.is_a?(Array)
|
31
|
+
return diff(obj1, [], prefix, similarity)
|
32
|
+
elsif obj1.is_a?(Hash)
|
33
|
+
return diff(obj1, {}, prefix, similarity)
|
34
|
+
else
|
35
|
+
return diff(obj1, '', prefix, similarity)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if !(obj1.is_a?(Array) and obj2.is_a?(Array)) and !(obj1.is_a?(Hash) and obj2.is_a?(Hash)) and !(obj1.is_a?(obj2.class) or obj2.is_a?(obj1.class))
|
40
|
+
return changed(obj1, '-', prefix) + changed(obj2, '+', prefix)
|
41
|
+
end
|
42
|
+
|
43
|
+
result = []
|
44
|
+
if obj1.is_a?(Array)
|
45
|
+
changeset = diff_array(obj1, obj2, similarity) do |lcs|
|
46
|
+
# use a's index for similarity
|
47
|
+
lcs.each do |pair|
|
48
|
+
result.concat(diff(obj1[pair[0]], obj2[pair[1]], "#{prefix}[#{pair[0]}]", similarity))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
changeset.each do |change|
|
53
|
+
if change[0] == '-'
|
54
|
+
result.concat(changed(change[2], '-', "#{prefix}[#{change[1]}]"))
|
55
|
+
elsif change[0] == '+'
|
56
|
+
result.concat(changed(change[2], '+', "#{prefix}[#{change[1]}]"))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
elsif obj1.is_a?(Hash)
|
60
|
+
prefix = prefix.empty? ? "" : "#{prefix}."
|
61
|
+
|
62
|
+
deleted_keys = []
|
63
|
+
common_keys = []
|
64
|
+
|
65
|
+
obj1.each do |k, v|
|
66
|
+
if obj2.key?(k)
|
67
|
+
common_keys << k
|
68
|
+
else
|
69
|
+
deleted_keys << k
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# add deleted properties
|
74
|
+
deleted_keys.each {|k| result.concat(changed(obj1[k], '-', "#{prefix}#{k}")) }
|
75
|
+
|
76
|
+
# recursive comparison for common keys
|
77
|
+
common_keys.each {|k| result.concat(diff(obj1[k], obj2[k], "#{prefix}#{k}", similarity)) }
|
78
|
+
|
79
|
+
# added properties
|
80
|
+
obj2.each do |k, v|
|
81
|
+
unless obj1.key?(k)
|
82
|
+
result.concat(changed(obj2[k], '+', "#{prefix}#{k}"))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
return [] if obj1 == obj2
|
87
|
+
return [['~', prefix, obj1, obj2]]
|
88
|
+
end
|
89
|
+
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
# diff array using LCS algorithm
|
94
|
+
def self.diff_array(a, b, similarity = 0.8)
|
95
|
+
change_set = []
|
96
|
+
if a.size == 0 and b.size == 0
|
97
|
+
return []
|
98
|
+
elsif a.size == 0
|
99
|
+
b.each_index do |index|
|
100
|
+
change_set << ['+', index, b[index]]
|
101
|
+
end
|
102
|
+
return change_set
|
103
|
+
elsif b.size == 0
|
104
|
+
a.each_index do |index|
|
105
|
+
i = a.size - index - 1
|
106
|
+
change_set << ['-', i, a[i]]
|
107
|
+
end
|
108
|
+
return change_set
|
109
|
+
end
|
110
|
+
|
111
|
+
links = lcs(a, b, similarity)
|
112
|
+
|
113
|
+
# yield common
|
114
|
+
yield links if block_given?
|
115
|
+
|
116
|
+
# padding the end
|
117
|
+
links << [a.size, b.size]
|
118
|
+
|
119
|
+
last_x = -1
|
120
|
+
last_y = -1
|
121
|
+
links.each do |pair|
|
122
|
+
x, y = pair
|
123
|
+
|
124
|
+
# remove from a, beginning from the end
|
125
|
+
(x > last_x + 1) and (x - last_x - 2).downto(0).each do |i|
|
126
|
+
change_set << ['-', last_y + i + 1, a[i + last_x + 1]]
|
127
|
+
end
|
128
|
+
|
129
|
+
# add from b, beginning from the head
|
130
|
+
(y > last_y + 1) and 0.upto(y - last_y - 2).each do |i|
|
131
|
+
change_set << ['+', last_y + i + 1, b[i + last_y + 1]]
|
132
|
+
end
|
133
|
+
|
134
|
+
# update flags
|
135
|
+
last_x = x
|
136
|
+
last_y = y
|
137
|
+
end
|
138
|
+
|
139
|
+
change_set
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
data/lib/hashdiff/lcs.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module HashDiff
|
2
|
+
|
3
|
+
# caculate array difference using LCS algorithm
|
4
|
+
# http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
5
|
+
def self.lcs(a, b, similarity = 0.8)
|
6
|
+
return [] if a.size == 0 or b.size == 0
|
7
|
+
|
8
|
+
a_start = b_start = 0
|
9
|
+
a_finish = a.size - 1
|
10
|
+
b_finish = b.size - 1
|
11
|
+
vector = []
|
12
|
+
|
13
|
+
lcs = []
|
14
|
+
(0..b_finish).each do |bi|
|
15
|
+
lcs[bi] = []
|
16
|
+
(0..a_finish).each do |ai|
|
17
|
+
if similiar?(a[ai], b[bi], similarity)
|
18
|
+
topleft = (ai > 0 and bi > 0)? lcs[bi-1][ai-1][1] : 0
|
19
|
+
lcs[bi][ai] = [:topleft, topleft + 1]
|
20
|
+
elsif
|
21
|
+
top = (bi > 0)? lcs[bi-1][ai][1] : 0
|
22
|
+
left = (ai > 0)? lcs[bi][ai-1][1] : 0
|
23
|
+
count = (top > left) ? top : left
|
24
|
+
|
25
|
+
direction = :both
|
26
|
+
if top > left
|
27
|
+
direction = :top
|
28
|
+
elsif top < left
|
29
|
+
direction = :left
|
30
|
+
else
|
31
|
+
if bi == 0
|
32
|
+
direction = :top
|
33
|
+
elsif ai == 0
|
34
|
+
direction = :left
|
35
|
+
else
|
36
|
+
direction = :both
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
lcs[bi][ai] = [direction, count]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
x = a_finish
|
46
|
+
y = b_finish
|
47
|
+
while x >= 0 and y >= 0 and lcs[y][x][1] > 0
|
48
|
+
if lcs[y][x][0] == :both
|
49
|
+
x -= 1
|
50
|
+
elsif lcs[y][x][0] == :topleft
|
51
|
+
vector.insert(0, [x, y])
|
52
|
+
x -= 1
|
53
|
+
y -= 1
|
54
|
+
elsif lcs[y][x][0] == :top
|
55
|
+
y -= 1
|
56
|
+
elsif lcs[y][x][0] == :left
|
57
|
+
x -= 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
vector
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#
|
2
|
+
# This class provides methods to diff two hash, patch and unpatch hash
|
3
|
+
#
|
4
|
+
module HashDiff
|
5
|
+
|
6
|
+
# apply changes to object
|
7
|
+
#
|
8
|
+
# changes: [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
|
9
|
+
def self.patch(hash, changes)
|
10
|
+
changes.each do |change|
|
11
|
+
parts = decode_property_path(change[1])
|
12
|
+
last_part = parts.last
|
13
|
+
|
14
|
+
dest_node = node(hash, parts[0, parts.size-1])
|
15
|
+
|
16
|
+
if change[0] == '+'
|
17
|
+
if dest_node == nil
|
18
|
+
parent_key = parts[parts.size-2]
|
19
|
+
parent_node = node(hash, parts[0, parts.size-2])
|
20
|
+
if last_part.is_a?(Fixnum)
|
21
|
+
dest_node = parent_node[parent_key] = []
|
22
|
+
else
|
23
|
+
dest_node = parent_node[parent_key] = {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if last_part.is_a?(Fixnum)
|
28
|
+
dest_node.insert(last_part, change[2])
|
29
|
+
else
|
30
|
+
dest_node[last_part] = change[2]
|
31
|
+
end
|
32
|
+
elsif change[0] == '-'
|
33
|
+
if last_part.is_a?(Fixnum)
|
34
|
+
dest_node.delete_at(last_part)
|
35
|
+
else
|
36
|
+
dest_node.delete(last_part)
|
37
|
+
end
|
38
|
+
elsif change[0] == '~'
|
39
|
+
dest_node[last_part] = change[3]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
|
46
|
+
# undo changes from object.
|
47
|
+
#
|
48
|
+
# changes: [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
|
49
|
+
def self.unpatch(hash, changes)
|
50
|
+
changes.reverse_each do |change|
|
51
|
+
parts = decode_property_path(change[1])
|
52
|
+
last_part = parts.last
|
53
|
+
|
54
|
+
dest_node = node(hash, parts[0, parts.size-1])
|
55
|
+
|
56
|
+
if change[0] == '+'
|
57
|
+
if last_part.is_a?(Fixnum)
|
58
|
+
dest_node.delete_at(last_part)
|
59
|
+
else
|
60
|
+
dest_node.delete(last_part)
|
61
|
+
end
|
62
|
+
elsif change[0] == '-'
|
63
|
+
if dest_node == nil
|
64
|
+
parent_key = parts[parts.size-2]
|
65
|
+
parent_node = node(hash, parts[0, parts.size-2])
|
66
|
+
if last_part.is_a?(Fixnum)
|
67
|
+
dest_node = parent_node[parent_key] = []
|
68
|
+
else
|
69
|
+
dest_node = parent_node[parent_key] = {}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if last_part.is_a?(Fixnum)
|
74
|
+
dest_node.insert(last_part, change[2])
|
75
|
+
else
|
76
|
+
dest_node[last_part] = change[2]
|
77
|
+
end
|
78
|
+
elsif change[0] == '~'
|
79
|
+
dest_node[last_part] = change[2]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
hash
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module HashDiff
|
2
|
+
|
3
|
+
# return an array of added properties
|
4
|
+
# e.g. [[ '+', 'a.b', 45 ], [ '-', 'a.c', 5 ]]
|
5
|
+
def self.changed(obj, sign, prefix = "")
|
6
|
+
return [[sign, prefix, obj]] unless obj
|
7
|
+
|
8
|
+
results = []
|
9
|
+
if obj.is_a?(Array)
|
10
|
+
if sign == '+'
|
11
|
+
# add from the begining
|
12
|
+
results << [sign, prefix, []]
|
13
|
+
obj.each_index do |index|
|
14
|
+
results.concat(changed(obj[index], sign, "#{prefix}[#{index}]"))
|
15
|
+
end
|
16
|
+
elsif sign == '-'
|
17
|
+
# delete from the end
|
18
|
+
obj.each_index do |index|
|
19
|
+
i = obj.size - index - 1
|
20
|
+
results.concat(changed(obj[i], sign, "#{prefix}[#{i}]"))
|
21
|
+
end
|
22
|
+
results << [sign, prefix, []]
|
23
|
+
end
|
24
|
+
elsif obj.is_a?(Hash)
|
25
|
+
results << [sign, prefix, {}] if sign == '+'
|
26
|
+
prefix_t = prefix.empty? ? "" : "#{prefix}."
|
27
|
+
obj.each do |k, v|
|
28
|
+
results.concat(changed(v, sign, "#{prefix_t}#{k}"))
|
29
|
+
end
|
30
|
+
results << [sign, prefix, {}] if sign == '-'
|
31
|
+
else
|
32
|
+
return [[sign, prefix, obj]]
|
33
|
+
end
|
34
|
+
|
35
|
+
results
|
36
|
+
end
|
37
|
+
|
38
|
+
# judge whether two objects are similar
|
39
|
+
def self.similiar?(a, b, similarity = 0.8)
|
40
|
+
count_a = count_nodes(a)
|
41
|
+
count_b = count_nodes(b)
|
42
|
+
count_diff = diff(a, b, "", similarity).count
|
43
|
+
|
44
|
+
if count_a + count_b == 0
|
45
|
+
return true
|
46
|
+
else
|
47
|
+
(1 - count_diff.to_f/(count_a + count_b).to_f) >= similarity
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# count total nodes for an object
|
52
|
+
def self.count_nodes(obj)
|
53
|
+
return 0 unless obj
|
54
|
+
|
55
|
+
count = 0
|
56
|
+
if obj.is_a?(Array)
|
57
|
+
count = obj.size
|
58
|
+
obj.each {|e| count += count_nodes(e) }
|
59
|
+
elsif obj.is_a?(Hash)
|
60
|
+
count = obj.size
|
61
|
+
obj.each {|k, v| count += count_nodes(v) }
|
62
|
+
else
|
63
|
+
return 1
|
64
|
+
end
|
65
|
+
|
66
|
+
count
|
67
|
+
end
|
68
|
+
|
69
|
+
# decode property path into an array
|
70
|
+
#
|
71
|
+
# e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
|
72
|
+
def self.decode_property_path(path)
|
73
|
+
parts = path.split('.').collect do |part|
|
74
|
+
if part =~ /^(\w*)\[(\d+)\]$/
|
75
|
+
if $1.size > 0
|
76
|
+
[$1, $2.to_i]
|
77
|
+
else
|
78
|
+
$2.to_i
|
79
|
+
end
|
80
|
+
else
|
81
|
+
part
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
parts.flatten
|
86
|
+
end
|
87
|
+
|
88
|
+
# get the node of hash by given path parts
|
89
|
+
def self.node(hash, parts)
|
90
|
+
temp = hash
|
91
|
+
parts.each do |part|
|
92
|
+
temp = temp[part]
|
93
|
+
end
|
94
|
+
temp
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiff do
|
4
|
+
it "should be able to diff two equal array" do
|
5
|
+
a = [1, 2, 3]
|
6
|
+
b = [1, 2, 3]
|
7
|
+
|
8
|
+
diff = HashDiff.diff_array(a, b)
|
9
|
+
diff.should == []
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to diff two arrays with one element in common" do
|
13
|
+
a = [1, 2, 3]
|
14
|
+
b = [1, 8, 7]
|
15
|
+
|
16
|
+
diff = HashDiff.diff_array(a, b)
|
17
|
+
diff.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to diff two arrays with nothing in common" do
|
21
|
+
a = [1, 2]
|
22
|
+
b = []
|
23
|
+
|
24
|
+
diff = HashDiff.diff_array(a, b)
|
25
|
+
diff.should == [['-', 1, 2], ['-', 0, 1]]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be able to diff an empty array with an non-empty array" do
|
29
|
+
a = []
|
30
|
+
b = [1, 2]
|
31
|
+
|
32
|
+
diff = HashDiff.diff_array(a, b)
|
33
|
+
diff.should == [['+', 0, 1], ['+', 1, 2]]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to diff two arrays with two elements in common" do
|
37
|
+
a = [1, 3, 5, 7]
|
38
|
+
b = [2, 3, 7, 5]
|
39
|
+
|
40
|
+
diff = HashDiff.diff_array(a, b)
|
41
|
+
diff.should == [['-', 0, 1], ['+', 0, 2], ['+', 2, 7], ['-', 4, 7]]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be able to test two arrays with two common elements in different order" do
|
45
|
+
a = [1, 3, 4, 7]
|
46
|
+
b = [2, 3, 7, 5]
|
47
|
+
|
48
|
+
diff = HashDiff.diff_array(a, b)
|
49
|
+
diff.should == [['-', 0, 1], ['+', 0, 2], ['-', 2, 4], ['+', 3, 5]]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be able to diff two arrays with similar elements" do
|
53
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
54
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
55
|
+
diff = HashDiff.diff_array(a, b)
|
56
|
+
diff.should == [['+', 0, 1], ['-', 2, 3]]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiff do
|
4
|
+
it "should be able to diff two empty hashes" do
|
5
|
+
diff = HashDiff.diff({}, {})
|
6
|
+
diff.should == []
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be able to diff an hash with an empty hash" do
|
10
|
+
a = {a:3, b:2}
|
11
|
+
b = {}
|
12
|
+
|
13
|
+
diff = HashDiff.diff(a, b)
|
14
|
+
diff.should == [['-', 'a', 3], ['-', 'b', 2]]
|
15
|
+
|
16
|
+
diff = HashDiff.diff(b, a)
|
17
|
+
diff.should == [['+', 'a', 3], ['+', 'b', 2]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to diff two equal hashes" do
|
21
|
+
diff = HashDiff.diff({a:2, b:2}, {a:2, b:2})
|
22
|
+
diff.should == []
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be able to diff changes in hash value which is array" do
|
26
|
+
diff = HashDiff.diff({a:2, b:[1, 2, 3]}, {a:2, b:[1, 3, 4]})
|
27
|
+
diff.should == [['-', 'b[1]', 2], ['+', 'b[2]', 4]]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be able to diff changes in hash value which is hash" do
|
31
|
+
diff = HashDiff.diff({a:{x:2, y:3, z:4}, b:{x:3, z:45}}, {a:{y:3}, b:{y:3, z:30}})
|
32
|
+
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to diff similar objects in array" do
|
36
|
+
diff = HashDiff.best_diff({a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}, {a:[{y:3}, {x:11, z:33}], b:{y:22}})
|
37
|
+
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should be able to diff addition of key value pair' do
|
41
|
+
a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200}
|
42
|
+
b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
|
43
|
+
|
44
|
+
diff = HashDiff.diff(a, b)
|
45
|
+
diff.should == [['+', 'g', 300]]
|
46
|
+
|
47
|
+
diff = HashDiff.diff(b, a)
|
48
|
+
diff.should == [['-', 'g', 300]]
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should be able to diff value type changes' do
|
52
|
+
a = {"a" => 3}
|
53
|
+
b = {"a" => {"a1" => 1, "a2" => 2}}
|
54
|
+
|
55
|
+
diff = HashDiff.diff(a, b)
|
56
|
+
diff.should == [['-', 'a', 3], ['+', 'a', {}], ['+', 'a.a1', 1], ['+', 'a.a2', 2]]
|
57
|
+
|
58
|
+
diff = HashDiff.diff(b, a)
|
59
|
+
diff.should == [['-', 'a.a1', 1], ['-', 'a.a2', 2], ['-', 'a', {}], ['+', 'a', 3]]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be able to diff value changes: array <=> []" do
|
63
|
+
a = {"a" => 1, "b" => [1, 2]}
|
64
|
+
b = {"a" => 1, "b" => []}
|
65
|
+
|
66
|
+
diff = HashDiff.diff(a, b)
|
67
|
+
diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]]
|
68
|
+
end
|
69
|
+
|
70
|
+
# treat nil as empty array
|
71
|
+
it "should be able to diff value changes: array <=> nil" do
|
72
|
+
a = {"a" => 1, "b" => [1, 2]}
|
73
|
+
b = {"a" => 1, "b" => nil}
|
74
|
+
|
75
|
+
diff = HashDiff.diff(a, b)
|
76
|
+
diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]]
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should be able to diff value chagnes: remove array completely" do
|
80
|
+
a = {"a" => 1, "b" => [1, 2]}
|
81
|
+
b = {"a" => 1}
|
82
|
+
|
83
|
+
diff = HashDiff.diff(a, b)
|
84
|
+
diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1], ['-', 'b', []]]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should be able to diff value changes: remove whole hash" do
|
88
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
89
|
+
b = {"a" => 1}
|
90
|
+
|
91
|
+
diff = HashDiff.diff(a, b)
|
92
|
+
diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2], ['-', 'b', {}]]
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should be able to diff value changes: hash <=> {}" do
|
96
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
97
|
+
b = {"a" => 1, "b" => {}}
|
98
|
+
|
99
|
+
diff = HashDiff.diff(a, b)
|
100
|
+
diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
|
101
|
+
end
|
102
|
+
|
103
|
+
# treat nil as empty hash
|
104
|
+
it "should be able to diff value changes: hash <=> nil" do
|
105
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
106
|
+
b = {"a" => 1, "b" => nil}
|
107
|
+
|
108
|
+
diff = HashDiff.diff(a, b)
|
109
|
+
diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should be able to diff similar objects in array" do
|
113
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
114
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
115
|
+
|
116
|
+
diff = HashDiff.diff(a, b)
|
117
|
+
diff.should == [['-', '[0].d', 4], ['+', '[0]', 1], ['-', '[2]', 3]]
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should be able to diff similar & equal objects in array" do
|
121
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3]
|
122
|
+
b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3]
|
123
|
+
|
124
|
+
diff = HashDiff.diff(a, b)
|
125
|
+
diff.should == [['-', '[0].d', 4], ['-', '[1].x', 5], ['-', '[1].y', 6], ['-', '[1].z', 3], ['-', '[1]', {}]]
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should be able to best diff" do
|
129
|
+
a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
|
130
|
+
b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
|
131
|
+
|
132
|
+
diff = HashDiff.best_diff(a, b)
|
133
|
+
diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1].y', 3], ['-', 'x[1]', {}]]
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiff do
|
4
|
+
it "should be able to find LCS between two equal array" do
|
5
|
+
a = [1, 2, 3]
|
6
|
+
b = [1, 2, 3]
|
7
|
+
|
8
|
+
lcs = HashDiff.lcs(a, b)
|
9
|
+
lcs.should == [[0, 0], [1, 1], [2, 2]]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to find LCS with one common elements" do
|
13
|
+
a = [1, 2, 3]
|
14
|
+
b = [1, 8, 7]
|
15
|
+
|
16
|
+
lcs = HashDiff.lcs(a, b)
|
17
|
+
lcs.should == [[0, 0]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to find LCS with two common elements" do
|
21
|
+
a = [1, 3, 5, 7]
|
22
|
+
b = [2, 3, 7, 5]
|
23
|
+
|
24
|
+
lcs = HashDiff.lcs(a, b)
|
25
|
+
lcs.should == [[1, 1], [2, 3]]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be able to find LCS with two common elements in different ordering" do
|
29
|
+
a = [1, 3, 4, 7]
|
30
|
+
b = [2, 3, 7, 5]
|
31
|
+
|
32
|
+
lcs = HashDiff.lcs(a, b)
|
33
|
+
lcs.should == [[1, 1], [3, 2]]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiff do
|
4
|
+
it "it should be able to patch key addition" do
|
5
|
+
a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200}
|
6
|
+
b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
|
7
|
+
diff = HashDiff.diff(a, b)
|
8
|
+
|
9
|
+
HashDiff.patch(a, diff).should == b
|
10
|
+
|
11
|
+
a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200}
|
12
|
+
b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
|
13
|
+
HashDiff.unpatch(b, diff).should == a
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to patch value type changes" do
|
17
|
+
a = {"a" => 3}
|
18
|
+
b = {"a" => {"a1" => 1, "a2" => 2}}
|
19
|
+
diff = HashDiff.diff(a, b)
|
20
|
+
|
21
|
+
HashDiff.patch(a, diff).should == b
|
22
|
+
|
23
|
+
a = {"a" => 3}
|
24
|
+
b = {"a" => {"a1" => 1, "a2" => 2}}
|
25
|
+
HashDiff.unpatch(b, diff).should == a
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be able to patch value array <=> []" do
|
29
|
+
a = {"a" => 1, "b" => [1, 2]}
|
30
|
+
b = {"a" => 1, "b" => []}
|
31
|
+
diff = HashDiff.diff(a, b)
|
32
|
+
|
33
|
+
HashDiff.patch(a, diff).should == b
|
34
|
+
|
35
|
+
a = {"a" => 1, "b" => [1, 2]}
|
36
|
+
b = {"a" => 1, "b" => []}
|
37
|
+
HashDiff.unpatch(b, diff).should == a
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be able to patch value array <=> nil" do
|
41
|
+
a = {"a" => 1, "b" => [1, 2]}
|
42
|
+
b = {"a" => 1, "b" => nil}
|
43
|
+
diff = HashDiff.diff(a, b)
|
44
|
+
|
45
|
+
# NOTE: nil is treated as [] in this context
|
46
|
+
HashDiff.patch(a, diff).should == {"a" => 1, "b" => []}
|
47
|
+
|
48
|
+
a = {"a" => 1, "b" => [1, 2]}
|
49
|
+
b = {"a" => 1, "b" => nil}
|
50
|
+
HashDiff.unpatch(b, diff).should == a
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should be able to patch array value removal" do
|
54
|
+
a = {"a" => 1, "b" => [1, 2]}
|
55
|
+
b = {"a" => 1}
|
56
|
+
diff = HashDiff.diff(a, b)
|
57
|
+
|
58
|
+
HashDiff.patch(a, diff).should == b
|
59
|
+
|
60
|
+
a = {"a" => 1, "b" => [1, 2]}
|
61
|
+
b = {"a" => 1}
|
62
|
+
HashDiff.unpatch(b, diff).should == a
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should be able to patch hash value removal" do
|
66
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
67
|
+
b = {"a" => 1}
|
68
|
+
diff = HashDiff.diff(a, b)
|
69
|
+
|
70
|
+
HashDiff.patch(a, diff).should == b
|
71
|
+
|
72
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
73
|
+
b = {"a" => 1}
|
74
|
+
HashDiff.unpatch(b, diff).should == a
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should be able to patch value hash <=> {}" do
|
78
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
79
|
+
b = {"a" => 1, "b" => {}}
|
80
|
+
diff = HashDiff.diff(a, b)
|
81
|
+
|
82
|
+
HashDiff.patch(a, diff).should == b
|
83
|
+
|
84
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
85
|
+
b = {"a" => 1, "b" => {}}
|
86
|
+
HashDiff.unpatch(b, diff).should == a
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be able to patch value hash <=> nil" do
|
90
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
91
|
+
b = {"a" => 1, "b" => nil}
|
92
|
+
diff = HashDiff.diff(a, b)
|
93
|
+
|
94
|
+
# NOTE: nil will be taken as {} in the context
|
95
|
+
HashDiff.patch(a, diff).should == {"a" => 1, "b" => {}}
|
96
|
+
|
97
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
98
|
+
b = {"a" => 1, "b" => nil}
|
99
|
+
HashDiff.unpatch(b, diff).should == a
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should be able to patch value nil removal" do
|
103
|
+
a = {"a" => 1, "b" => nil}
|
104
|
+
b = {"a" => 1}
|
105
|
+
diff = HashDiff.diff(a, b)
|
106
|
+
|
107
|
+
HashDiff.patch(a, diff).should == b
|
108
|
+
|
109
|
+
a = {"a" => 1, "b" => nil}
|
110
|
+
b = {"a" => 1}
|
111
|
+
HashDiff.unpatch(b, diff).should == a
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should be able to patch similar objects between arrays" do
|
115
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
116
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
117
|
+
|
118
|
+
diff = HashDiff.diff(a, b)
|
119
|
+
HashDiff.patch(a, diff).should == b
|
120
|
+
|
121
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
122
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
123
|
+
HashDiff.unpatch(b, diff).should == a
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should be able to patch similar & equal objects between arrays" do
|
127
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
|
128
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
129
|
+
|
130
|
+
diff = HashDiff.diff(a, b)
|
131
|
+
HashDiff.patch(a, diff).should == b
|
132
|
+
|
133
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
|
134
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
135
|
+
HashDiff.unpatch(b, diff).should == a
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/autorun'
|
6
|
+
|
7
|
+
require 'hashdiff'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.mock_framework = :rspec
|
11
|
+
|
12
|
+
config.include RSpec::Matchers
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hashdiff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Liu Fengyun
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-22 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &8876060 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *8876060
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
requirement: &8874480 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *8874480
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bluecloth
|
38
|
+
requirement: &8859740 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *8859740
|
47
|
+
description: ! ' HashDiff is a diff lib to compute the smallest difference between
|
48
|
+
two hashes. '
|
49
|
+
email:
|
50
|
+
- liufengyunchina@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- .rspec
|
57
|
+
- .travis.yml
|
58
|
+
- Gemfile
|
59
|
+
- Gemfile.lock
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- hashdiff.gemspec
|
63
|
+
- lib/hashdiff.rb
|
64
|
+
- lib/hashdiff/diff.rb
|
65
|
+
- lib/hashdiff/lcs.rb
|
66
|
+
- lib/hashdiff/patch.rb
|
67
|
+
- lib/hashdiff/util.rb
|
68
|
+
- lib/hashdiff/version.rb
|
69
|
+
- spec/hashdiff/diff_array_spec.rb
|
70
|
+
- spec/hashdiff/diff_spec.rb
|
71
|
+
- spec/hashdiff/lcs_spec.rb
|
72
|
+
- spec/hashdiff/patch_spec.rb
|
73
|
+
- spec/hashdiff/util_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
homepage: https://github.com/liufengyun/hashdiff
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.8.7
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.8.11
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: HashDiff is a diff lib to compute the smallest difference between two hashes.
|
99
|
+
test_files: []
|
100
|
+
has_rdoc:
|