hashdiff 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|