hashdiff 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,11 @@
1
+ language: ruby
1
2
  rvm:
2
3
  - 1.8.7
3
4
  - 1.9.2
5
+ - 1.9.3
4
6
  - rbx
5
7
  - rbx-2.0
6
8
  - ree
7
9
  - jruby
8
10
  - ruby-head
11
+ script: "bundle exec rake spec"
@@ -0,0 +1 @@
1
+ --no-private
data/README.md CHANGED
@@ -9,15 +9,28 @@ HashDiff is tested on following platforms:
9
9
 
10
10
  - 1.8.7
11
11
  - 1.9.2
12
+ - 1.9.3
12
13
  - rbx
13
14
  - rbx-2.0
14
15
  - ree
15
16
  - jruby
16
17
  - ruby-head
17
18
 
19
+ Usage
20
+ ------------
21
+ If you're using bundler, add following:
22
+
23
+ gem 'hashdiff'
24
+
25
+ Or, you can run `gem install hashdiff`, then add following line to your ruby file which uses HashDiff:
26
+
27
+ require 'hashdiff'
28
+
18
29
  Quick Start
19
30
  -----------
20
31
 
32
+ You can find full docs here: [Documentation](http://rubydoc.info/gems/hashdiff)
33
+
21
34
  ### Diff
22
35
 
23
36
  Two simple hash:
@@ -41,7 +54,7 @@ Array in hash:
41
54
  a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}
42
55
  b = {a:[{y:3}, {x:11, z:33}], b:{y:22}}
43
56
 
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
57
+ diff = HashDiff.best_diff(a, b)
45
58
  diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
46
59
 
47
60
  ### Patch
@@ -1,6 +1,22 @@
1
1
  module HashDiff
2
2
 
3
- # try to make the best diff that generates the smallest change set
3
+ # Best diff two objects, which tries to generate the smallest change set.
4
+ #
5
+ # HashDiff.best_diff is only meaningful in case of comparing two objects which includes similar objects in array.
6
+ #
7
+ # @param [Arrary, Hash] obj1
8
+ # @param [Arrary, Hash] obj2
9
+ #
10
+ # @return [Array] an array of changes.
11
+ # e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
12
+ #
13
+ # @example
14
+ # a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
15
+ # b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
16
+ # diff = HashDiff.best_diff(a, b)
17
+ # diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1].y', 3], ['-', 'x[1]', {}]]
18
+ #
19
+ # @since 0.0.1
4
20
  def self.best_diff(obj1, obj2)
5
21
  diffs_1 = diff(obj1, obj2, "", 0.3)
6
22
  diffs_2 = diff(obj1, obj2, "", 0.5)
@@ -10,30 +26,36 @@ module HashDiff
10
26
  diffs = diffs.size < diffs_3.size ? diffs : diffs_3
11
27
  end
12
28
 
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']]
29
+ # Compute the diff of two hashes
30
+ #
31
+ # @param [Arrary, Hash] obj1
32
+ # @param [Arrary, Hash] obj2
33
+ # @param [float] similarity A value > 0 and <= 1.
34
+ # This parameter should be ignored in common usage.
35
+ # Similarity is only meaningful if there're similar objects in arrays. See {best_diff}.
36
+ #
37
+ # @return [Array] an array of changes.
38
+ # e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
39
+ #
40
+ # @example
41
+ # a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
42
+ # b = {"a" => 1, "b" => {}}
15
43
  #
16
- # NOTE: diff will treat nil as [], {} or "" in comparison according to different context.
17
- #
44
+ # diff = HashDiff.diff(a, b)
45
+ # diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
46
+ #
47
+ # @since 0.0.1
18
48
  def self.diff(obj1, obj2, prefix = "", similarity = 0.8)
49
+ if obj1.nil? and obj2.nil?
50
+ return []
51
+ end
52
+
19
53
  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
54
+ return [['-', prefix, nil]] + changed(obj2, '+', prefix)
27
55
  end
28
56
 
29
57
  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
58
+ return changed(obj1, '-', prefix) + [['+', prefix, nil]]
37
59
  end
38
60
 
39
61
  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))
@@ -90,6 +112,8 @@ module HashDiff
90
112
  result
91
113
  end
92
114
 
115
+ # @private
116
+ #
93
117
  # diff array using LCS algorithm
94
118
  def self.diff_array(a, b, similarity = 0.8)
95
119
  change_set = []
@@ -1,5 +1,6 @@
1
1
  module HashDiff
2
-
2
+ # @private
3
+ #
3
4
  # caculate array difference using LCS algorithm
4
5
  # http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
5
6
  def self.lcs(a, b, similarity = 0.8)
@@ -1,22 +1,27 @@
1
1
  #
2
- # This class provides methods to diff two hash, patch and unpatch hash
2
+ # This module provides methods to diff two hash, patch and unpatch hash
3
3
  #
4
4
  module HashDiff
5
5
 
6
- # apply changes to object
6
+ # Apply patch to object
7
7
  #
8
- # changes: [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
9
- def self.patch(hash, changes)
8
+ # @param [Hash, Array] obj the object to be patchted, can be an Array of a Hash
9
+ # @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
10
+ #
11
+ # @return the object after patch
12
+ #
13
+ # @since 0.0.1
14
+ def self.patch!(obj, changes)
10
15
  changes.each do |change|
11
16
  parts = decode_property_path(change[1])
12
17
  last_part = parts.last
13
18
 
14
- dest_node = node(hash, parts[0, parts.size-1])
19
+ dest_node = node(obj, parts[0, parts.size-1])
15
20
 
16
21
  if change[0] == '+'
17
22
  if dest_node == nil
18
23
  parent_key = parts[parts.size-2]
19
- parent_node = node(hash, parts[0, parts.size-2])
24
+ parent_node = node(obj, parts[0, parts.size-2])
20
25
  if last_part.is_a?(Fixnum)
21
26
  dest_node = parent_node[parent_key] = []
22
27
  else
@@ -40,13 +45,18 @@ module HashDiff
40
45
  end
41
46
  end
42
47
 
43
- hash
48
+ obj
44
49
  end
45
50
 
46
- # undo changes from object.
51
+ # Unpatch an object
52
+ #
53
+ # @param [Hash, Array] obj the object to be unpatchted, can be an Array of a Hash
54
+ # @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
55
+ #
56
+ # @return the object after unpatch
47
57
  #
48
- # changes: [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
49
- def self.unpatch(hash, changes)
58
+ # @since 0.0.1
59
+ def self.unpatch!(hash, changes)
50
60
  changes.reverse_each do |change|
51
61
  parts = decode_property_path(change[1])
52
62
  last_part = parts.last
@@ -1,5 +1,7 @@
1
1
  module HashDiff
2
2
 
3
+ # @private
4
+ #
3
5
  # return an array of added properties
4
6
  # e.g. [[ '+', 'a.b', 45 ], [ '-', 'a.c', 5 ]]
5
7
  def self.changed(obj, sign, prefix = "")
@@ -35,6 +37,8 @@ module HashDiff
35
37
  results
36
38
  end
37
39
 
40
+ # @private
41
+ #
38
42
  # judge whether two objects are similar
39
43
  def self.similiar?(a, b, similarity = 0.8)
40
44
  count_a = count_nodes(a)
@@ -48,6 +52,8 @@ module HashDiff
48
52
  end
49
53
  end
50
54
 
55
+ # @private
56
+ #
51
57
  # count total nodes for an object
52
58
  def self.count_nodes(obj)
53
59
  return 0 unless obj
@@ -66,6 +72,8 @@ module HashDiff
66
72
  count
67
73
  end
68
74
 
75
+ # @private
76
+ #
69
77
  # decode property path into an array
70
78
  #
71
79
  # e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
@@ -85,6 +93,8 @@ module HashDiff
85
93
  parts.flatten
86
94
  end
87
95
 
96
+ # @private
97
+ #
88
98
  # get the node of hash by given path parts
89
99
  def self.node(hash, parts)
90
100
  temp = hash
@@ -1,3 +1,3 @@
1
1
  module HashDiff
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -67,13 +67,12 @@ describe HashDiff do
67
67
  diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]]
68
68
  end
69
69
 
70
- # treat nil as empty array
71
70
  it "should be able to diff value changes: array <=> nil" do
72
71
  a = {"a" => 1, "b" => [1, 2]}
73
72
  b = {"a" => 1, "b" => nil}
74
73
 
75
74
  diff = HashDiff.diff(a, b)
76
- diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]]
75
+ diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1], ['-', 'b', []], ['+', 'b', nil]]
77
76
  end
78
77
 
79
78
  it "should be able to diff value chagnes: remove array completely" do
@@ -100,13 +99,12 @@ describe HashDiff do
100
99
  diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
101
100
  end
102
101
 
103
- # treat nil as empty hash
104
102
  it "should be able to diff value changes: hash <=> nil" do
105
103
  a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
106
104
  b = {"a" => 1, "b" => nil}
107
105
 
108
106
  diff = HashDiff.diff(a, b)
109
- diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
107
+ diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2], ['-', 'b', {}], ['+', 'b', nil]]
110
108
  end
111
109
 
112
110
  it "should be able to diff similar objects in array" do
@@ -6,11 +6,11 @@ describe HashDiff do
6
6
  b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
7
7
  diff = HashDiff.diff(a, b)
8
8
 
9
- HashDiff.patch(a, diff).should == b
9
+ HashDiff.patch!(a, diff).should == b
10
10
 
11
11
  a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200}
12
12
  b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
13
- HashDiff.unpatch(b, diff).should == a
13
+ HashDiff.unpatch!(b, diff).should == a
14
14
  end
15
15
 
16
16
  it "should be able to patch value type changes" do
@@ -18,11 +18,11 @@ describe HashDiff do
18
18
  b = {"a" => {"a1" => 1, "a2" => 2}}
19
19
  diff = HashDiff.diff(a, b)
20
20
 
21
- HashDiff.patch(a, diff).should == b
21
+ HashDiff.patch!(a, diff).should == b
22
22
 
23
23
  a = {"a" => 3}
24
24
  b = {"a" => {"a1" => 1, "a2" => 2}}
25
- HashDiff.unpatch(b, diff).should == a
25
+ HashDiff.unpatch!(b, diff).should == a
26
26
  end
27
27
 
28
28
  it "should be able to patch value array <=> []" do
@@ -30,11 +30,11 @@ describe HashDiff do
30
30
  b = {"a" => 1, "b" => []}
31
31
  diff = HashDiff.diff(a, b)
32
32
 
33
- HashDiff.patch(a, diff).should == b
33
+ HashDiff.patch!(a, diff).should == b
34
34
 
35
35
  a = {"a" => 1, "b" => [1, 2]}
36
36
  b = {"a" => 1, "b" => []}
37
- HashDiff.unpatch(b, diff).should == a
37
+ HashDiff.unpatch!(b, diff).should == a
38
38
  end
39
39
 
40
40
  it "should be able to patch value array <=> nil" do
@@ -42,12 +42,11 @@ describe HashDiff do
42
42
  b = {"a" => 1, "b" => nil}
43
43
  diff = HashDiff.diff(a, b)
44
44
 
45
- # NOTE: nil is treated as [] in this context
46
- HashDiff.patch(a, diff).should == {"a" => 1, "b" => []}
45
+ HashDiff.patch!(a, diff).should == b
47
46
 
48
47
  a = {"a" => 1, "b" => [1, 2]}
49
48
  b = {"a" => 1, "b" => nil}
50
- HashDiff.unpatch(b, diff).should == a
49
+ HashDiff.unpatch!(b, diff).should == a
51
50
  end
52
51
 
53
52
  it "should be able to patch array value removal" do
@@ -55,11 +54,11 @@ describe HashDiff do
55
54
  b = {"a" => 1}
56
55
  diff = HashDiff.diff(a, b)
57
56
 
58
- HashDiff.patch(a, diff).should == b
57
+ HashDiff.patch!(a, diff).should == b
59
58
 
60
59
  a = {"a" => 1, "b" => [1, 2]}
61
60
  b = {"a" => 1}
62
- HashDiff.unpatch(b, diff).should == a
61
+ HashDiff.unpatch!(b, diff).should == a
63
62
  end
64
63
 
65
64
  it "should be able to patch hash value removal" do
@@ -67,11 +66,11 @@ describe HashDiff do
67
66
  b = {"a" => 1}
68
67
  diff = HashDiff.diff(a, b)
69
68
 
70
- HashDiff.patch(a, diff).should == b
69
+ HashDiff.patch!(a, diff).should == b
71
70
 
72
71
  a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
73
72
  b = {"a" => 1}
74
- HashDiff.unpatch(b, diff).should == a
73
+ HashDiff.unpatch!(b, diff).should == a
75
74
  end
76
75
 
77
76
  it "should be able to patch value hash <=> {}" do
@@ -79,11 +78,11 @@ describe HashDiff do
79
78
  b = {"a" => 1, "b" => {}}
80
79
  diff = HashDiff.diff(a, b)
81
80
 
82
- HashDiff.patch(a, diff).should == b
81
+ HashDiff.patch!(a, diff).should == b
83
82
 
84
83
  a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
85
84
  b = {"a" => 1, "b" => {}}
86
- HashDiff.unpatch(b, diff).should == a
85
+ HashDiff.unpatch!(b, diff).should == a
87
86
  end
88
87
 
89
88
  it "should be able to patch value hash <=> nil" do
@@ -91,12 +90,11 @@ describe HashDiff do
91
90
  b = {"a" => 1, "b" => nil}
92
91
  diff = HashDiff.diff(a, b)
93
92
 
94
- # NOTE: nil will be taken as {} in the context
95
- HashDiff.patch(a, diff).should == {"a" => 1, "b" => {}}
93
+ HashDiff.patch!(a, diff).should == b
96
94
 
97
95
  a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
98
96
  b = {"a" => 1, "b" => nil}
99
- HashDiff.unpatch(b, diff).should == a
97
+ HashDiff.unpatch!(b, diff).should == a
100
98
  end
101
99
 
102
100
  it "should be able to patch value nil removal" do
@@ -104,11 +102,11 @@ describe HashDiff do
104
102
  b = {"a" => 1}
105
103
  diff = HashDiff.diff(a, b)
106
104
 
107
- HashDiff.patch(a, diff).should == b
105
+ HashDiff.patch!(a, diff).should == b
108
106
 
109
107
  a = {"a" => 1, "b" => nil}
110
108
  b = {"a" => 1}
111
- HashDiff.unpatch(b, diff).should == a
109
+ HashDiff.unpatch!(b, diff).should == a
112
110
  end
113
111
 
114
112
  it "should be able to patch similar objects between arrays" do
@@ -116,11 +114,11 @@ describe HashDiff do
116
114
  b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
117
115
 
118
116
  diff = HashDiff.diff(a, b)
119
- HashDiff.patch(a, diff).should == b
117
+ HashDiff.patch!(a, diff).should == b
120
118
 
121
119
  a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
122
120
  b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
123
- HashDiff.unpatch(b, diff).should == a
121
+ HashDiff.unpatch!(b, diff).should == a
124
122
  end
125
123
 
126
124
  it "should be able to patch similar & equal objects between arrays" do
@@ -128,11 +126,11 @@ describe HashDiff do
128
126
  b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
129
127
 
130
128
  diff = HashDiff.diff(a, b)
131
- HashDiff.patch(a, diff).should == b
129
+ HashDiff.patch!(a, diff).should == b
132
130
 
133
131
  a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
134
132
  b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
135
- HashDiff.unpatch(b, diff).should == a
133
+ HashDiff.unpatch!(b, diff).should == a
136
134
  end
137
135
 
138
136
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashdiff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-22 00:00:00.000000000Z
12
+ date: 2012-06-03 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &8876060 !ruby/object:Gem::Requirement
16
+ requirement: &14927020 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '2.0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *8876060
24
+ version_requirements: *14927020
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yard
27
- requirement: &8874480 !ruby/object:Gem::Requirement
27
+ requirement: &14926640 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *8874480
35
+ version_requirements: *14926640
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bluecloth
38
- requirement: &8859740 !ruby/object:Gem::Requirement
38
+ requirement: &14926180 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *8859740
46
+ version_requirements: *14926180
47
47
  description: ! ' HashDiff is a diff lib to compute the smallest difference between
48
48
  two hashes. '
49
49
  email:
@@ -55,6 +55,7 @@ files:
55
55
  - .gitignore
56
56
  - .rspec
57
57
  - .travis.yml
58
+ - .yardopts
58
59
  - Gemfile
59
60
  - Gemfile.lock
60
61
  - README.md