Skip to content

Commit

Permalink
refine yard docs & fix travis
Browse files Browse the repository at this point in the history
  • Loading branch information
liufengyun committed Jun 3, 2012
1 parent 8c818e9 commit 753157e
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 39 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
@@ -1,8 +1,11 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- rbx
- rbx-2.0
- ree
- jruby
- ruby-head
script: "bundle exec rake spec"
1 change: 1 addition & 0 deletions .yardopts
@@ -0,0 +1 @@
--no-private
15 changes: 14 additions & 1 deletion README.md
Expand Up @@ -9,15 +9,28 @@ HashDiff is tested on following platforms:

- 1.8.7
- 1.9.2
- 1.9.3
- rbx
- rbx-2.0
- ree
- jruby
- ruby-head

Usage
------------
If you're using bundler, add following:

gem 'hashdiff'

Or, you can run `gem install hashdiff`, then add following line to your ruby file which uses HashDiff:

require 'hashdiff'

Quick Start
-----------

You can find full docs here: [Documentation](http://rubydoc.info/gems/hashdiff)

### Diff

Two simple hash:
Expand All @@ -41,7 +54,7 @@ Array in hash:
a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}
b = {a:[{y:3}, {x:11, z:33}], b:{y:22}}

diff = HashDiff.best_diff(a, b) # best_diff will try to match similar objects in array in order to generate the smallest change set
diff = HashDiff.best_diff(a, b)
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]

### Patch
Expand Down
42 changes: 37 additions & 5 deletions lib/hashdiff/diff.rb
@@ -1,6 +1,22 @@
module HashDiff

# try to make the best diff that generates the smallest change set
# Best diff two objects, which tries to generate the smallest change set.
#
# HashDiff.best_diff is only meaningful in case of comparing two objects which includes similar objects in array.
#
# @param [Arrary, Hash] obj1
# @param [Arrary, Hash] obj2
#
# @return [Array] an array of changes.
# e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
#
# @example
# a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
# b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
# diff = HashDiff.best_diff(a, b)
# diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1].y', 3], ['-', 'x[1]', {}]]
#
# @since 0.0.1
def self.best_diff(obj1, obj2)
diffs_1 = diff(obj1, obj2, "", 0.3)
diffs_2 = diff(obj1, obj2, "", 0.5)
Expand All @@ -10,11 +26,25 @@ def self.best_diff(obj1, obj2)
diffs = diffs.size < diffs_3.size ? diffs : diffs_3
end

# compute the diff of two hashes, return an array of changes
# e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
# Compute the diff of two hashes
#
# NOTE: diff will treat nil as [], {} or "" in comparison according to different context.
#
# @param [Arrary, Hash] obj1
# @param [Arrary, Hash] obj2
# @param [float] similarity A value > 0 and <= 1.
# This parameter should be ignored in common usage.
# Similarity is only meaningful if there're similar objects in arrays. See {best_diff}.
#
# @return [Array] an array of changes.
# e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
#
# @example
# a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
# b = {"a" => 1, "b" => {}}
#
# diff = HashDiff.diff(a, b)
# diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
#
# @since 0.0.1
def self.diff(obj1, obj2, prefix = "", similarity = 0.8)
if obj1.nil? and obj2.nil?
return []
Expand Down Expand Up @@ -82,6 +112,8 @@ def self.diff(obj1, obj2, prefix = "", similarity = 0.8)
result
end

# @private
#
# diff array using LCS algorithm
def self.diff_array(a, b, similarity = 0.8)
change_set = []
Expand Down
3 changes: 2 additions & 1 deletion lib/hashdiff/lcs.rb
@@ -1,5 +1,6 @@
module HashDiff

# @private
#
# caculate array difference using LCS algorithm
# http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
def self.lcs(a, b, similarity = 0.8)
Expand Down
30 changes: 20 additions & 10 deletions lib/hashdiff/patch.rb
@@ -1,22 +1,27 @@
#
# This class provides methods to diff two hash, patch and unpatch hash
# This module provides methods to diff two hash, patch and unpatch hash
#
module HashDiff

# apply changes to object
# Apply patch to object
#
# changes: [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
def self.patch(hash, changes)
# @param [Hash, Array] obj the object to be patchted, can be an Array of a Hash
# @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
#
# @return the object after patch
#
# @since 0.0.1
def self.patch!(obj, changes)
changes.each do |change|
parts = decode_property_path(change[1])
last_part = parts.last

dest_node = node(hash, parts[0, parts.size-1])
dest_node = node(obj, parts[0, parts.size-1])

if change[0] == '+'
if dest_node == nil
parent_key = parts[parts.size-2]
parent_node = node(hash, parts[0, parts.size-2])
parent_node = node(obj, parts[0, parts.size-2])
if last_part.is_a?(Fixnum)
dest_node = parent_node[parent_key] = []
else
Expand All @@ -40,13 +45,18 @@ def self.patch(hash, changes)
end
end

hash
obj
end

# undo changes from object.
# Unpatch an object
#
# @param [Hash, Array] obj the object to be unpatchted, can be an Array of a Hash
# @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
#
# @return the object after unpatch
#
# changes: [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']]
def self.unpatch(hash, changes)
# @since 0.0.1
def self.unpatch!(hash, changes)
changes.reverse_each do |change|
parts = decode_property_path(change[1])
last_part = parts.last
Expand Down
10 changes: 10 additions & 0 deletions lib/hashdiff/util.rb
@@ -1,5 +1,7 @@
module HashDiff

# @private
#
# return an array of added properties
# e.g. [[ '+', 'a.b', 45 ], [ '-', 'a.c', 5 ]]
def self.changed(obj, sign, prefix = "")
Expand Down Expand Up @@ -35,6 +37,8 @@ def self.changed(obj, sign, prefix = "")
results
end

# @private
#
# judge whether two objects are similar
def self.similiar?(a, b, similarity = 0.8)
count_a = count_nodes(a)
Expand All @@ -48,6 +52,8 @@ def self.similiar?(a, b, similarity = 0.8)
end
end

# @private
#
# count total nodes for an object
def self.count_nodes(obj)
return 0 unless obj
Expand All @@ -66,6 +72,8 @@ def self.count_nodes(obj)
count
end

# @private
#
# decode property path into an array
#
# e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
Expand All @@ -85,6 +93,8 @@ def self.decode_property_path(path)
parts.flatten
end

# @private
#
# get the node of hash by given path parts
def self.node(hash, parts)
temp = hash
Expand Down
44 changes: 22 additions & 22 deletions spec/hashdiff/patch_spec.rb
Expand Up @@ -6,131 +6,131 @@
b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200}
b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch value type changes" do
a = {"a" => 3}
b = {"a" => {"a1" => 1, "a2" => 2}}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 3}
b = {"a" => {"a1" => 1, "a2" => 2}}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch value array <=> []" do
a = {"a" => 1, "b" => [1, 2]}
b = {"a" => 1, "b" => []}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => [1, 2]}
b = {"a" => 1, "b" => []}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch value array <=> nil" do
a = {"a" => 1, "b" => [1, 2]}
b = {"a" => 1, "b" => nil}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => [1, 2]}
b = {"a" => 1, "b" => nil}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch array value removal" do
a = {"a" => 1, "b" => [1, 2]}
b = {"a" => 1}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => [1, 2]}
b = {"a" => 1}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch hash value removal" do
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
b = {"a" => 1}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
b = {"a" => 1}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch value hash <=> {}" do
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
b = {"a" => 1, "b" => {}}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
b = {"a" => 1, "b" => {}}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch value hash <=> nil" do
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
b = {"a" => 1, "b" => nil}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
b = {"a" => 1, "b" => nil}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch value nil removal" do
a = {"a" => 1, "b" => nil}
b = {"a" => 1}
diff = HashDiff.diff(a, b)

HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = {"a" => 1, "b" => nil}
b = {"a" => 1}
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch similar objects between arrays" do
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]

diff = HashDiff.diff(a, b)
HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

it "should be able to patch similar & equal objects between arrays" do
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]

diff = HashDiff.diff(a, b)
HashDiff.patch(a, diff).should == b
HashDiff.patch!(a, diff).should == b

a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
HashDiff.unpatch(b, diff).should == a
HashDiff.unpatch!(b, diff).should == a
end

end
Expand Down

0 comments on commit 753157e

Please sign in to comment.