pax_global_header00006660000000000000000000000064126203673260014521gustar00rootroot0000000000000052 comment=14cc3997484e023d25a7ab670e119d9ff38a5a26 ruby-tdiff-0.3.3/000077500000000000000000000000001262036732600135775ustar00rootroot00000000000000ruby-tdiff-0.3.3/.document000066400000000000000000000000341262036732600154130ustar00rootroot00000000000000- ChangeLog.md LICENSE.txt ruby-tdiff-0.3.3/.gemtest000066400000000000000000000000001262036732600152360ustar00rootroot00000000000000ruby-tdiff-0.3.3/.gitignore000066400000000000000000000000121262036732600155600ustar00rootroot00000000000000doc/ pkg/ ruby-tdiff-0.3.3/.rspec000066400000000000000000000000401262036732600147060ustar00rootroot00000000000000--colour --format documentation ruby-tdiff-0.3.3/.yardopts000066400000000000000000000000741262036732600154460ustar00rootroot00000000000000--markup markdown --title "TDiff Documentation" --protected ruby-tdiff-0.3.3/ChangeLog.md000066400000000000000000000020671262036732600157550ustar00rootroot00000000000000### 0.3.3 / 2012-05-28 * Require ruby >= 1.8.7. * Added {TDiff::VERSION}. * Replaced ore-tasks with [rubygems-tasks](https://github.com/postmodern/rubygems-tasks#readme). ### 0.3.2 / 2010-11-28 * Added {TDiff#tdiff_recursive} to only handle recursively traversing and diffing the children nodes. * Added {TDiff::Unordered#tdiff_recursive_unordered} to only handle recursively traversing and diffing the children nodes, without respecting the order of the nodes. ### 0.3.1 / 2010-11-28 * Fixed a typo in {TDiff::Unordered#tdiff_unordered}, which was causing all nodes to be marked as added. ### 0.3.0 / 2010-11-15 * Changed {TDiff#tdiff_equal} to compare `self` with another node. ### 0.2.0 / 2010-11-14 * Added {TDiff::Unordered}. ### 0.1.0 / 2010-11-13 * Initial release: * Provides the {TDiff} mixin. * Allows custom node equality and traversal logic by overriding the {TDiff#tdiff_equal} and {TDiff#tdiff_each_child} methods. * Implements the [Longest Common Subsequence (LCS)](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem). ruby-tdiff-0.3.3/LICENSE.txt000066400000000000000000000020401262036732600154160ustar00rootroot00000000000000Copyright (c) 2010 Hal Brodigan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-tdiff-0.3.3/README.md000066400000000000000000000041171262036732600150610ustar00rootroot00000000000000# TDiff * [Source](https://github.com/postmodern/tdiff) * [Issues](https://github.com/postmodern/tdiff/issues) * Postmodern (postmodern.mod3 at gmail.com) ## Description Calculates the differences between two tree-like structures. Similar to Rubys built-in [TSort](http://rubydoc.info/docs/ruby-stdlib/1.9.2/TSort) module. ## Features * Provides the {TDiff} mixin. * Provides the {TDiff::Unordered} mixin for unordered diffing. * Allows custom node equality and traversal logic by overriding the {TDiff#tdiff_equal} and {TDiff#tdiff_each_child} methods. * Implements the [Longest Common Subsequence (LCS)](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem) algorithm. ## Examples Diff two HTML documents: require 'nokogiri' require 'tdiff' class Nokogiri::XML::Node include TDiff def tdiff_equal(node) if (self.text? && node.text?) self.text == node.text elsif (self.respond_to?(:root) && node.respond_to?(:root)) self.root.tdiff_equal(node.root) elsif (self.respond_to?(:name) && node.respond_to?(:name)) self.name == node.name else false end end def tdiff_each_child(node,&block) node.children.each(&block) end end doc1 = Nokogiri::HTML('

one

three

') doc2 = Nokogiri::HTML('

one

two

three

') doc1.at('div').tdiff(doc2.at('div')) do |change,node| puts "#{change} #{node.to_html}".ljust(30) + node.parent.path end ### Output +

one

/html/body/div + /html/body/div

one

/html/body/div /html/body/div

three

/html/body/div - one /html/body/div/p[1] + two /html/body/div/p[2] three /html/body/div/p[2] ## Requirements * [ruby](http://www.ruby-lang.org/) >= 1.8.7 ## Install $ gem install tdiff ## Copyright See {file:LICENSE.txt} for details. ruby-tdiff-0.3.3/Rakefile000066400000000000000000000012301262036732600152400ustar00rootroot00000000000000require 'rubygems' require 'rake' begin gem 'rubygems-tasks', '~> 0.1' require 'rubygems/tasks' Gem::Tasks.new rescue LoadError => e warn e.message warn "Run `gem install rubygems-tasks` to install 'rubygems/tasks'." end begin gem 'rspec', '~> 2.4' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new rescue LoadError => e task :spec do abort "Please run `gem install rspec` to install RSpec." end end task :test => :spec task :default => :spec begin gem 'yard', '~> 0.7' require 'yard' YARD::Rake::YardocTask.new rescue LoadError => e task :yard do abort "Please run `gem install yard` to install YARD." end end ruby-tdiff-0.3.3/gemspec.yml000066400000000000000000000007061262036732600157500ustar00rootroot00000000000000name: tdiff summary: Calculates the differences between two tree-like structures. description: Calculates the differences between two tree-like structures. Similar to Rubys built-in TSort module. license: MIT authors: Postmodern email: postmodern.mod3@gmail.com homepage: https://github.com/postmodern/tdiff#readme has_yard: true required_ruby_version: ">= 1.8.7" development_dependencies: rubygems-tasks: ~> 0.1 rspec: ~> 2.4 yard: ~> 0.7 ruby-tdiff-0.3.3/lib/000077500000000000000000000000001262036732600143455ustar00rootroot00000000000000ruby-tdiff-0.3.3/lib/tdiff.rb000066400000000000000000000001101262036732600157560ustar00rootroot00000000000000require 'tdiff/tdiff' require 'tdiff/unordered' require 'tdiff/version' ruby-tdiff-0.3.3/lib/tdiff/000077500000000000000000000000001262036732600154415ustar00rootroot00000000000000ruby-tdiff-0.3.3/lib/tdiff/tdiff.rb000066400000000000000000000072071262036732600170700ustar00rootroot00000000000000# # {TDiff} adds the ability to calculate the differences between two tree-like # objects. Simply include {TDiff} into the class which represents the tree # nodes and define the {#tdiff_each_child} and {#tdiff_equal} methods. # module TDiff # # Default method which will enumerate over every child of a parent node. # # @param [Object] node # The parent node. # # @yield [child] # The given block will be passed each child of the parent node. # def tdiff_each_child(node,&block) node.each(&block) if node.kind_of?(Enumerable) end # # Default method which compares nodes. # # @param [Object] node # A node from the new tree. # # @return [Boolean] # Specifies whether the nodes are equal. # def tdiff_equal(node) self == node end # # Finds the differences between `self` and another tree. # # @param [#tdiff_each_child] tree # The other tree. # # @yield [change, node] # The given block will be passed the added or removed nodes. # # @yieldparam [' ', '+', '-'] change # The state-change of the node. # # @yieldparam [Object] node # A node from one of the two trees. # # @return [Enumerator] # If no block is given, an Enumerator object will be returned. # def tdiff(tree,&block) return enum_for(:tdiff,tree) unless block # check if the nodes differ unless tdiff_equal(tree) yield '-', self yield '+', tree return self end yield ' ', self tdiff_recursive(tree,&block) return self end protected # # Recursively compares the differences between the children nodes. # # @param [#tdiff_each_child] tree # The other tree. # # @yield [change, node] # The given block will be passed the added or removed nodes. # # @yieldparam [' ', '+', '-'] change # The state-change of the node. # # @yieldparam [Object] node # A node from one of the two trees. # # @since 0.3.2 # def tdiff_recursive(tree,&block) c = Hash.new { |hash,key| hash[key] = Hash.new(0) } x = enum_for(:tdiff_each_child,self) y = enum_for(:tdiff_each_child,tree) x.each_with_index do |xi,i| y.each_with_index do |yj,j| c[i][j] = if xi.tdiff_equal(yj) c[i-1][j-1] + 1 else if c[i][j-1] > c[i-1][j] c[i][j-1] else c[i-1][j] end end end end unchanged = [] changes = [] x_backtrack = x.each_with_index.reverse_each y_backtrack = y.each_with_index.reverse_each next_child = lambda { |children| begin children.next rescue StopIteration # end of iteration, return a -1 index [nil, -1] end } xi, i = next_child[x_backtrack] yj, j = next_child[y_backtrack] until (i == -1 && j == -1) if (i != -1 && j != -1 && xi.tdiff_equal(yj)) changes.unshift [' ', xi] unchanged.unshift [xi, yj] xi, i = next_child[x_backtrack] yj, j = next_child[y_backtrack] else if (j >= 0 && (i == -1 || c[i][j-1] >= c[i-1][j])) changes.unshift ['+', yj] yj, j = next_child[y_backtrack] elsif (i >= 0 && (j == -1 || c[i][j-1] < c[i-1][j])) changes.unshift ['-', xi] xi, i = next_child[x_backtrack] end end end # explicitly discard the c matrix c = nil # sequentially iterate over the changed nodes changes.each(&block) changes = nil # recurse down through unchanged nodes unchanged.each { |x,y| x.tdiff_recursive(y,&block) } unchanged = nil end end ruby-tdiff-0.3.3/lib/tdiff/unordered.rb000066400000000000000000000053041262036732600177570ustar00rootroot00000000000000require 'tdiff/tdiff' module TDiff # # Calculates the differences between two trees, without respecting the # order of children nodes. # module Unordered # # Includes {TDiff}. # def self.included(base) base.send :include, TDiff end # # Finds the differences between `self` and another tree, not respecting # the order of the nodes. # # @param [#tdiff_each_child] tree # The other tree. # # @yield [change, node] # The given block will be passed the added or removed nodes. # # @yieldparam [' ', '+', '-'] change # The state-change of the node. # # @yieldparam [Object] node # A node from one of the two trees. # # @return [Enumerator] # If no block is given, an Enumerator object will be returned. # # @since 0.2.0 # def tdiff_unordered(tree,&block) return enum_for(:tdiff_unordered,tree) unless block # check if the nodes differ unless tdiff_equal(tree) yield '-', self yield '+', tree return self end yield ' ', self tdiff_recursive_unordered(tree,&block) return self end protected # # Recursively compares the differences between the children nodes, # without respecting the order of the nodes. # # @param [#tdiff_each_child] tree # The other tree. # # @yield [change, node] # The given block will be passed the added or removed nodes. # # @yieldparam [' ', '+', '-'] change # The state-change of the node. # # @yieldparam [Object] node # A node from one of the two trees. # # @since 0.3.2 # def tdiff_recursive_unordered(tree,&block) x = enum_for(:tdiff_each_child,self) y = enum_for(:tdiff_each_child,tree) unchanged = {} changes = [] x.each_with_index do |xi,i| y.each_with_index do |yj,j| if (!unchanged.has_value?(yj) && xi.tdiff_equal(yj)) unchanged[xi] = yj changes << [i, ' ', xi] break end end unless unchanged.has_key?(xi) changes << [i, '-', xi] end end y.each_with_index do |yj,j| unless unchanged.has_value?(yj) changes << [j, '+', yj] end end # order the changes by index to match the behavior of `tdiff` changes.sort_by { |change| change[0] }.each do |index,change,node| yield change, node end # explicitly release the changes variable changes = nil # recurse down the unchanged nodes unchanged.each do |xi,yj| xi.tdiff_recursive_unordered(yj,&block) end unchanged = nil end end end ruby-tdiff-0.3.3/lib/tdiff/version.rb000066400000000000000000000000451262036732600174520ustar00rootroot00000000000000module TDiff VERSION = '0.3.3' end ruby-tdiff-0.3.3/metadata.yml000066400000000000000000000051251262036732600161050ustar00rootroot00000000000000--- !ruby/object:Gem::Specification name: tdiff version: !ruby/object:Gem::Version version: 0.3.3 prerelease: platform: ruby authors: - Postmodern autorequire: bindir: bin cert_chain: [] date: 2012-05-28 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rubygems-tasks requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '0.1' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '0.1' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '2.4' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '2.4' - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '0.7' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '0.7' description: Calculates the differences between two tree-like structures. Similar to Rubys built-in TSort module. email: postmodern.mod3@gmail.com executables: [] extensions: [] extra_rdoc_files: - ChangeLog.md - LICENSE.txt - README.md files: - .document - .gemtest - .gitignore - .rspec - .yardopts - ChangeLog.md - LICENSE.txt - README.md - Rakefile - gemspec.yml - lib/tdiff.rb - lib/tdiff/tdiff.rb - lib/tdiff/unordered.rb - lib/tdiff/version.rb - spec/classes/node.rb - spec/helpers/trees.rb - spec/spec_helper.rb - spec/tdiff_examples.rb - spec/tdiff_spec.rb - spec/unordered_spec.rb - tdiff.gemspec homepage: https://github.com/postmodern/tdiff#readme licenses: - MIT post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.8.7 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: Calculates the differences between two tree-like structures. test_files: [] ruby-tdiff-0.3.3/spec/000077500000000000000000000000001262036732600145315ustar00rootroot00000000000000ruby-tdiff-0.3.3/spec/classes/000077500000000000000000000000001262036732600161665ustar00rootroot00000000000000ruby-tdiff-0.3.3/spec/classes/node.rb000066400000000000000000000003601262036732600174370ustar00rootroot00000000000000require 'tdiff' class Node < Struct.new(:name, :children) include TDiff include TDiff::Unordered def tdiff_each_child(node,&block) node.children.each(&block) end def tdiff_equal(node) self.name == node.name end end ruby-tdiff-0.3.3/spec/helpers/000077500000000000000000000000001262036732600161735ustar00rootroot00000000000000ruby-tdiff-0.3.3/spec/helpers/trees.rb000066400000000000000000000035021262036732600176420ustar00rootroot00000000000000require 'classes/node' module Helpers module Trees def self.included(base) base.module_eval do before(:all) do @tree = Node.new('root', [ Node.new('leaf1', [ Node.new('subleaf1', []), Node.new('subleaf2', []) ]), Node.new('leaf2', [ Node.new('subleaf1', []), Node.new('subleaf2', []) ]) ]) @different_root = Node.new('wrong', []) @added = Node.new('root', [ Node.new('leaf1', [ Node.new('subleaf1', []), Node.new('subleaf3', []), Node.new('subleaf2', []) ]), Node.new('leaf2', [ Node.new('subleaf1', []), Node.new('subleaf2', []) ]) ]) @removed = Node.new('root', [ Node.new('leaf1', [ Node.new('subleaf1', []) ]), Node.new('leaf2', [ Node.new('subleaf1', []), Node.new('subleaf2', []) ]) ]) @changed_order = Node.new('root', [ Node.new('leaf2', [ Node.new('subleaf1', []), Node.new('subleaf2', []) ]), Node.new('leaf1', [ Node.new('subleaf1', []), Node.new('subleaf2', []) ]) ]) end end end end end ruby-tdiff-0.3.3/spec/spec_helper.rb000066400000000000000000000000771262036732600173530ustar00rootroot00000000000000gem 'rspec', '~> 2.4' require 'rspec' require 'helpers/trees' ruby-tdiff-0.3.3/spec/tdiff_examples.rb000066400000000000000000000020541262036732600200510ustar00rootroot00000000000000require 'spec_helper' require 'helpers/trees' shared_examples_for 'TDiff' do |method| include Helpers::Trees it "should tell if two trees are identical" do @tree.send(method,@tree).all? { |change,node| change == ' ' }.should == true end it "should stop if the root nodes have changed" do changes = @tree.send(method,@different_root).to_a changes.length.should == 2 changes[0][0].should == '-' changes[0][1].should == @tree changes[1][0].should == '+' changes[1][1].should == @different_root end it "should tell when sub-nodes are added" do changes = @tree.send(method,@added).select { |change,node| change == '+' } changes.length.should == 1 changes[0][0].should == '+' changes[0][1].should == @added.children[0].children[1] end it "should tell when sub-nodes are removed" do changes = @tree.send(method,@removed).select { |change,node| change == '-' } changes.length.should == 1 changes[0][0].should == '-' changes[0][1].should == @tree.children[0].children[1] end end ruby-tdiff-0.3.3/spec/tdiff_spec.rb000066400000000000000000000014431262036732600171660ustar00rootroot00000000000000require 'spec_helper' require 'tdiff_examples' require 'tdiff/tdiff' describe TDiff do include Helpers::Trees it_should_behave_like 'TDiff', :tdiff it "should detect when the order of children has changed" do changes = @tree.tdiff(@changed_order).to_a changes.length.should == 6 changes[0][0].should == ' ' changes[0][1].should == @tree changes[1][0].should == '-' changes[1][1].should == @tree.children[0] changes[2][0].should == ' ' changes[2][1].should == @tree.children[1] changes[3][0].should == '+' changes[3][1].should == @changed_order.children[1] changes[4][0].should == ' ' changes[4][1].should == @tree.children[1].children[0] changes[5][0].should == ' ' changes[5][1].should == @tree.children[1].children[1] end end ruby-tdiff-0.3.3/spec/unordered_spec.rb000066400000000000000000000010321262036732600200530ustar00rootroot00000000000000require 'spec_helper' require 'tdiff_examples' require 'tdiff/unordered' describe TDiff::Unordered do include Helpers::Trees it "should include TDiff when included" do base = Class.new do include TDiff::Unordered end base.should include(TDiff) end it_should_behave_like 'TDiff', :tdiff_unordered it "should not detect when the order of children has changed" do changes = @tree.tdiff_unordered(@changed_order).select do |change,node| change != ' ' end changes.should be_empty end end ruby-tdiff-0.3.3/tdiff.gemspec000066400000000000000000000037421262036732600162460ustar00rootroot00000000000000# encoding: utf-8 require 'yaml' Gem::Specification.new do |gem| gemspec = YAML.load_file('gemspec.yml') gem.name = gemspec.fetch('name') gem.version = gemspec.fetch('version') do lib_dir = File.join(File.dirname(__FILE__),'lib') $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir) require 'tdiff/version' TDiff::VERSION end gem.summary = gemspec['summary'] gem.description = gemspec['description'] gem.licenses = Array(gemspec['license']) gem.authors = Array(gemspec['authors']) gem.email = gemspec['email'] gem.homepage = gemspec['homepage'] glob = lambda { |patterns| gem.files & Dir[*patterns] } gem.files = `git ls-files`.split($/) gem.files = glob[gemspec['files']] if gemspec['files'] gem.executables = gemspec.fetch('executables') do glob['bin/*'].map { |path| File.basename(path) } end gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.' gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb'] gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb'] gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}'] gem.require_paths = Array(gemspec.fetch('require_paths') { %w[ext lib].select { |dir| File.directory?(dir) } }) gem.requirements = gemspec['requirements'] gem.required_ruby_version = gemspec['required_ruby_version'] gem.required_rubygems_version = gemspec['required_rubygems_version'] gem.post_install_message = gemspec['post_install_message'] split = lambda { |string| string.split(/,\s*/) } if gemspec['dependencies'] gemspec['dependencies'].each do |name,versions| gem.add_dependency(name,split[versions]) end end if gemspec['development_dependencies'] gemspec['development_dependencies'].each do |name,versions| gem.add_development_dependency(name,split[versions]) end end end