summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAustin Ziegler <austin@zieglers.ca>2020-06-30 09:51:08 -0400
committerAustin Ziegler <austin@zieglers.ca>2020-07-01 11:53:51 -0400
commit60613d2af564a93dc5c19c8a71c3f674a5db6fdb (patch)
treed88bb489d8f8e44b323073a32195b951d9d9629b
parent99f65fdc8253c3bf800bc0e17c2cbbde9ade9ba8 (diff)
downloaddiff-lcs-60613d2af564a93dc5c19c8a71c3f674a5db6fdb.tar.gz
# This is a combination of 9 commits.
# This is the 1st commit message: Fix improperly placed chunks Resolve #65 - Also add even more tests for checking `ldiff` results against `diff` results. - Fix issues with diff/ldiff output highlighted by the above tests. - Add a parameter to indicate that the hunk being processed is the _last_ hunk; this results in correct counting of the hunk size. - The misplaced chunks were happening because of an improper `.abs` on `#diff_size`, when the `.abs` needed to be on the finding of the maximum diff size. # This is the commit message #2: Ooops. Debugger # This is the commit message #3: Restore missing test - Fix some more format issues raised by the missing test. - Start fixing Rubocop formatting. # This is the commit message #4: Last RuboCop fixes # This is the commit message #5: Finalize diff-lcs 1.4 # This is the commit message #6: Fix #44 The problem here was the precedence of `or` vs `||`. Switching to `||` resulted in the expected behaviour. # This is the commit message #7: Resolve #43 # This is the commit message #8: Typo # This is the commit message #9: Resolve #35 with a comment
-rw-r--r--.rubocop.yml26
-rw-r--r--Gemfile3
-rw-r--r--History.md14
-rw-r--r--Rakefile29
-rw-r--r--diff-lcs.gemspec8
-rw-r--r--lib/diff/lcs.rb9
-rw-r--r--lib/diff/lcs/block.rb2
-rw-r--r--lib/diff/lcs/hunk.rb147
-rw-r--r--lib/diff/lcs/ldiff.rb36
-rw-r--r--spec/fixtures/ldiff/output.diff-c4
-rw-r--r--spec/fixtures/ldiff/output.diff-u4
-rw-r--r--spec/fixtures/ldiff/output.diff.chef4
-rw-r--r--spec/fixtures/ldiff/output.diff.chef-c15
-rw-r--r--spec/fixtures/ldiff/output.diff.chef-e3
-rw-r--r--spec/fixtures/ldiff/output.diff.chef-f3
-rw-r--r--spec/fixtures/ldiff/output.diff.chef-u7
-rw-r--r--spec/fixtures/ldiff/output.diff.chef27
-rw-r--r--spec/fixtures/ldiff/output.diff.chef2-c20
-rw-r--r--spec/fixtures/ldiff/output.diff.chef2-d7
-rw-r--r--spec/fixtures/ldiff/output.diff.chef2-e7
-rw-r--r--spec/fixtures/ldiff/output.diff.chef2-f7
-rw-r--r--spec/fixtures/ldiff/output.diff.chef2-u16
-rw-r--r--spec/fixtures/new-chef217
-rw-r--r--spec/fixtures/old-chef214
-rw-r--r--spec/hunk_spec.rb31
-rw-r--r--spec/issues_spec.rb64
-rw-r--r--spec/ldiff_spec.rb65
-rw-r--r--spec/spec_helper.rb38
28 files changed, 455 insertions, 152 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 9768185..2ee932a 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -7,7 +7,7 @@ AllCops:
- diff-lcs.gemspec
- research/**/*
-Layout/AlignParameters:
+Layout/ParameterAlignment:
EnforcedStyle: with_fixed_indentation
Layout/DotPosition:
@@ -20,7 +20,7 @@ Layout/ExtraSpacing:
Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented
-Metrics/LineLength:
+Layout/LineLength:
Max: 110
Naming/FileName:
@@ -30,17 +30,15 @@ Naming/FileName:
Naming/MemoizedInstanceVariableName:
Exclude: []
-Naming/UncommunicativeMethodParamName:
+Naming/MethodParameterName:
Exclude:
- lib/diff/lcs/internals.rb
+ - lib/diff/lcs/hunk.rb
- spec/spec_helper.rb
Naming/VariableNumber:
Exclude: []
-Performance/Caller:
- Exclude: []
-
Security/MarshalLoad:
Exclude: []
@@ -91,7 +89,7 @@ Style/RescueStandardError:
Style/SignalException:
EnforcedStyle: semantic
-Layout/IndentHeredoc: { Enabled: false }
+Layout/HeredocIndentation: { Enabled: false }
Metrics/AbcSize: { Enabled: false }
Metrics/BlockLength: { Enabled: false }
Metrics/BlockNesting: { Enabled: false }
@@ -119,3 +117,17 @@ Style/SpecialGlobalVars: { Enabled: false }
Style/SymbolArray: { Enabled: false }
Style/SymbolProc: { Enabled: false }
Style/WordArray: { Enabled: false }
+
+Layout/EmptyLinesAroundAttributeAccessor: { Enabled: true }
+Layout/SpaceAroundMethodCallOperator: { Enabled: true }
+Lint/DeprecatedOpenSSLConstant: { Enabled: true }
+Lint/MixedRegexpCaptureTypes: { Enabled: true }
+Lint/RaiseException: { Enabled: true }
+Lint/StructNewOverride: { Enabled: true }
+Style/ExponentialNotation: { Enabled: true }
+Style/HashEachMethods: { Enabled: true }
+Style/HashTransformKeys: { Enabled: true }
+Style/HashTransformValues: { Enabled: true }
+Style/RedundantRegexpCharacterClass: { Enabled: true }
+Style/RedundantRegexpEscape: { Enabled: true }
+Style/SlicingWithRange: { Enabled: true }
diff --git a/Gemfile b/Gemfile
index db504a7..7b9f817 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,14 +6,15 @@
source 'https://rubygems.org/'
if RUBY_VERSION < '1.9'
+ gem 'hoe', '~> 3.20'
gem 'rake', '< 11'
gem 'rdoc', '< 4'
- gem 'hoe', '~> 3.20'
gem 'ruby-debug'
elsif RUBY_VERSION >= '2.0'
if RUBY_ENGINE == 'ruby'
gem 'simplecov', '~> 0.18'
+
gem 'byebug'
end
end
diff --git a/History.md b/History.md
index ff24b50..3690990 100644
--- a/History.md
+++ b/History.md
@@ -1,5 +1,15 @@
# History
+## 1.4.4 / 2020-07-01
+
+- Fixed an issue reported by Jun Aruga in the Diff::LCS::Ldiff binary text
+ detection. [#44][]
+- Fixed a theoretical issue reported by Jun Aruga in Diff::LCS::Hunk to raise
+ a more useful exception. [#43][]
+- Added documentation that should address custom object issues as reported in
+ [#35][].
+- Fixed more diff errors, in part reported in [#65][].
+
## 1.4.3 / 2020-06-29
- Fixed several issues with the 1.4 on Rubies older than 2.0. Some of this was
@@ -263,8 +273,11 @@
[#29]: https://github.com/halostatue/diff-lcs/pull/29
[#33]: https://github.com/halostatue/diff-lcs/issues/33
[#34]: https://github.com/halostatue/diff-lcs/pull/34
+[#35]: https://github.com/halostatue/diff-lcs/issues/35
[#36]: https://github.com/halostatue/diff-lcs/pull/36
[#38]: https://github.com/halostatue/diff-lcs/issues/38
+[#43]: https://github.com/halostatue/diff-lcs/issues/43
+[#44]: https://github.com/halostatue/diff-lcs/issues/44
[#47]: https://github.com/halostatue/diff-lcs/pull/47
[#48]: https://github.com/halostatue/diff-lcs/issues/48
[#49]: https://github.com/halostatue/diff-lcs/pull/49
@@ -276,3 +289,4 @@
[#60]: https://github.com/halostatue/diff-lcs/issues/60
[#61]: https://github.com/halostatue/diff-lcs/pull/61
[#63]: https://github.com/halostatue/diff-lcs/issues/63
+[#65]: https://github.com/halostatue/diff-lcs/issues/65
diff --git a/Rakefile b/Rakefile
index 7beb3b6..afd9307 100644
--- a/Rakefile
+++ b/Rakefile
@@ -10,18 +10,19 @@ Hoe.plugin :gemspec2
Hoe.plugin :git
if RUBY_VERSION < '1.9'
- class Array
+ class Array #:nodoc:
def to_h
- Hash[*self.flatten(1)]
+ Hash[*flatten(1)]
end
end
- class Gem::Specification
- def metadata=(*)
- end
+ class Gem::Specification #:nodoc:
+ def metadata=(*); end
+
+ def default_value(*); end
end
- class Object
+ class Object #:nodoc:
def caller_locations(*)
[]
end
@@ -55,3 +56,19 @@ if RUBY_VERSION >= '2.0' && RUBY_ENGINE == 'ruby'
end
end
end
+
+task :ruby18 do
+ puts <<-MESSAGE
+You are starting a barebones Ruby 1.8 docker environment. You will need to
+do the following:
+
+- mv Gemfile.lock{,.v2}
+- gem install bundler --version 1.17.2 --no-ri --no-rdoc
+- ruby -S bundle
+- rake
+
+Don't forget to restore your Gemfile.lock after testing.
+
+ MESSAGE
+ sh "docker run -it --rm -v #{Dir.pwd}:/root/diff-lcs bellbind/docker-ruby18-rails2 bash -l"
+end
diff --git a/diff-lcs.gemspec b/diff-lcs.gemspec
index 03f9738..565ba13 100644
--- a/diff-lcs.gemspec
+++ b/diff-lcs.gemspec
@@ -1,16 +1,16 @@
# -*- encoding: utf-8 -*-
-# stub: diff-lcs 1.4.3 ruby lib
+# stub: diff-lcs 1.4.4 ruby lib
Gem::Specification.new do |s|
s.name = "diff-lcs".freeze
- s.version = "1.4.3"
+ s.version = "1.4.4"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.metadata = { "bug_tracker_uri" => "https://github.com/halostatue/diff-lcs/issues", "homepage_uri" => "https://github.com/halostatue/diff-lcs", "source_code_uri" => "https://github.com/halostatue/diff-lcs" } if s.respond_to? :metadata=
s.require_paths = ["lib".freeze]
s.authors = ["Austin Ziegler".freeze]
- s.date = "2020-06-29"
- s.description = "Diff::LCS computes the difference between two Enumerable sequences using the\nMcIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities\nto create a simple HTML diff output format and a standard diff-like tool.\n\nThis is release 1.4, providing a simple extension that allows for\nDiff::LCS::Change objects to be treated implicitly as arrays. Ruby versions\nbelow 2.5 are soft-deprecated.\n\nThis means that older versions are no longer part of the CI test suite. If any\nchanges have been introduced that break those versions, bug reports and patches\nwill be accepted, but it will be up to the reporter to verify any fixes prior\nto release. A future release will completely break compatibility.".freeze
+ s.date = "2020-07-01"
+ s.description = "Diff::LCS computes the difference between two Enumerable sequences using the\nMcIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities\nto create a simple HTML diff output format and a standard diff-like tool.\n\nThis is release 1.4.3, providing a simple extension that allows for\nDiff::LCS::Change objects to be treated implicitly as arrays and fixes a\nnumber of formatting issues.\n\nRuby versions below 2.5 are soft-deprecated, which means that older versions\nare no longer part of the CI test suite. If any changes have been introduced\nthat break those versions, bug reports and patches will be accepted, but it\nwill be up to the reporter to verify any fixes prior to release. The next\nmajor release will completely break compatibility.".freeze
s.email = ["halostatue@gmail.com".freeze]
s.executables = ["htmldiff".freeze, "ldiff".freeze]
s.extra_rdoc_files = ["Code-of-Conduct.md".freeze, "Contributing.md".freeze, "History.md".freeze, "License.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "docs/COPYING.txt".freeze, "docs/artistic.txt".freeze]
diff --git a/lib/diff/lcs.rb b/lib/diff/lcs.rb
index 9d47064..63888a1 100644
--- a/lib/diff/lcs.rb
+++ b/lib/diff/lcs.rb
@@ -49,7 +49,7 @@ module Diff; end unless defined? Diff # rubocop:disable Style/Documentation
# a x b y c z p d q
# a b c a x b y c z
module Diff::LCS
- VERSION = '1.4.3'
+ VERSION = '1.4.4'
end
require 'diff/lcs/callbacks'
@@ -60,6 +60,13 @@ module Diff::LCS # rubocop:disable Style/Documentation
# +self+ and +other+. See Diff::LCS#lcs.
#
# lcs = seq1.lcs(seq2)
+ #
+ # A note when using objects: Diff::LCS only works properly when each object
+ # can be used as a key in a Hash, which typically means that the objects must
+ # implement Object#eql? in a way that two identical values compare
+ # identically for key purposes. That is:
+ #
+ # O.new('a').eql?(O.new('a')) == true
def lcs(other, &block) #:yields self[i] if there are matched subsequences:
Diff::LCS.lcs(self, other, &block)
end
diff --git a/lib/diff/lcs/block.rb b/lib/diff/lcs/block.rb
index fe86793..430702d 100644
--- a/lib/diff/lcs/block.rb
+++ b/lib/diff/lcs/block.rb
@@ -19,7 +19,7 @@ class Diff::LCS::Block
end
def diff_size
- (@insert.size - @remove.size).abs
+ @insert.size - @remove.size
end
def op
diff --git a/lib/diff/lcs/hunk.rb b/lib/diff/lcs/hunk.rb
index d884a1b..49b520e 100644
--- a/lib/diff/lcs/hunk.rb
+++ b/lib/diff/lcs/hunk.rb
@@ -6,26 +6,38 @@ require 'diff/lcs/block'
# each block. (So if we're not using context, every hunk will contain one
# block.) Used in the diff program (bin/ldiff).
class Diff::LCS::Hunk
+ OLD_DIFF_OP_ACTION = { '+' => 'a', '-' => 'd', '!' => 'c' }.freeze #:nodoc:
+ ED_DIFF_OP_ACTION = { '+' => 'a', '-' => 'd', '!' => 'c' }.freeze #:nodoc:
+
+ private_constant :OLD_DIFF_OP_ACTION, :ED_DIFF_OP_ACTION if respond_to?(:private_constant)
+
# Create a hunk using references to both the old and new data, as well as the
# piece of data.
def initialize(data_old, data_new, piece, flag_context, file_length_difference)
# At first, a hunk will have just one Block in it
@blocks = [Diff::LCS::Block.new(piece)]
+
+ if @blocks[0].remove.empty? && @blocks[0].insert.empty?
+ fail "Cannot build a hunk from #{piece.inspect}; has no add or remove actions"
+ end
+
if String.method_defined?(:encoding)
@preferred_data_encoding = data_old.fetch(0, data_new.fetch(0, '')).encoding
end
+
@data_old = data_old
@data_new = data_new
before = after = file_length_difference
after += @blocks[0].diff_size
@file_length_difference = after # The caller must get this manually
- @max_diff_size = @blocks.map { |e| e.diff_size }.max
+ @max_diff_size = @blocks.map { |e| e.diff_size.abs }.max
+
# Save the start & end of each array. If the array doesn't exist (e.g.,
- # we're only adding items in this block), then figure out the line
- # number based on the line number of the other file and the current
- # difference in file lengths.
+ # we're only adding items in this block), then figure out the line number
+ # based on the line number of the other file and the current difference in
+ # file lengths.
if @blocks[0].remove.empty?
a1 = a2 = nil
else
@@ -55,7 +67,7 @@ class Diff::LCS::Hunk
# Change the "start" and "end" fields to note that context should be added
# to this hunk.
- attr_accessor :flag_context
+ attr_accessor :flag_context # rubocop:disable Layout/EmptyLinesAroundAttributeAccessor
undef :flag_context=
def flag_context=(context) #:nodoc: # rubocop:disable Lint/DuplicateMethods
return if context.nil? or context.zero?
@@ -101,18 +113,18 @@ class Diff::LCS::Hunk
end
# Returns a diff string based on a format.
- def diff(format)
+ def diff(format, last = false)
case format
when :old
- old_diff
+ old_diff(last)
when :unified
- unified_diff
+ unified_diff(last)
when :context
- context_diff
+ context_diff(last)
when :ed
self
when :reverse_ed, :ed_finish
- ed_diff(format)
+ ed_diff(format, last)
else
fail "Unknown diff format #{format}."
end
@@ -120,35 +132,34 @@ class Diff::LCS::Hunk
# Note that an old diff can't have any context. Therefore, we know that
# there's only one block in the hunk.
- def old_diff
+ def old_diff(_last = false)
warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
- op_act = { '+' => 'a', '-' => 'd', '!' => 'c' }
block = @blocks[0]
# Calculate item number range. Old diff range is just like a context
# diff range, except the ranges are on one line with the action between
# them.
- s = encode("#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n")
+ s = encode("#{context_range(:old, ',')}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ',')}\n")
# If removing anything, just print out all the remove lines in the hunk
# which is just all the remove lines in the block.
unless block.remove.empty?
- @data_old[@start_old..@end_old].each { |e| s << encode('< ') + e + encode("\n") }
+ @data_old[@start_old..@end_old].each { |e| s << encode('< ') + e.chomp + encode("\n") }
end
s << encode("---\n") if block.op == '!'
unless block.insert.empty?
- @data_new[@start_new..@end_new].each { |e| s << encode('> ') + e + encode("\n") }
+ @data_new[@start_new..@end_new].each { |e| s << encode('> ') + e.chomp + encode("\n") }
end
s
end
private :old_diff
- def unified_diff
+ def unified_diff(last = false)
# Calculate item number range.
- s = encode("@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n")
+ s = encode("@@ -#{unified_range(:old, last)} +#{unified_range(:new, last)} @@\n")
# Outlist starts containing the hunk of the old file. Removing an item
# just means putting a '-' in front of it. Inserting an item requires
@@ -161,7 +172,14 @@ class Diff::LCS::Hunk
# file -- don't take removed items into account.
lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
- outlist = @data_old[lo..hi].map { |e| e.insert(0, encode(' ')) }
+ outlist = @data_old[lo..hi].map { |e| String.new("#{encode(' ')}#{e.chomp}") }
+
+ last_block = blocks[-1]
+
+ if last
+ old_missing_newline = missing_last_newline?(@data_old)
+ new_missing_newline = missing_last_newline?(@data_new)
+ end
@blocks.each do |block|
block.remove.each do |item|
@@ -170,67 +188,100 @@ class Diff::LCS::Hunk
outlist[offset][0, 1] = encode(op)
num_removed += 1
end
+
+ if last && block == last_block && old_missing_newline && !new_missing_newline
+ outlist << encode('\\ No newline at end of file')
+ num_removed += 1
+ end
+
block.insert.each do |item|
op = item.action.to_s # +
offset = item.position - @start_new + num_removed
- outlist[offset, 0] = encode(op) + @data_new[item.position]
+ outlist[offset, 0] = encode(op) + @data_new[item.position].chomp
num_added += 1
end
end
+ outlist << encode('\\ No newline at end of file') if last && new_missing_newline
+
s << outlist.join(encode("\n"))
+
+ s
end
private :unified_diff
- def context_diff
+ def context_diff(last = false)
s = encode("***************\n")
- s << encode("*** #{context_range(:old)} ****\n")
- r = context_range(:new)
+ s << encode("*** #{context_range(:old, ',', last)} ****\n")
+ r = context_range(:new, ',', last)
+
+ if last
+ old_missing_newline = missing_last_newline?(@data_old)
+ new_missing_newline = missing_last_newline?(@data_new)
+ end
# Print out file 1 part for each block in context diff format if there
# are any blocks that remove items
lo, hi = @start_old, @end_old
removes = @blocks.reject { |e| e.remove.empty? }
- if removes
- outlist = @data_old[lo..hi].map { |e| e.insert(0, encode(' ')) }
+
+ unless removes.empty?
+ outlist = @data_old[lo..hi].map { |e| String.new("#{encode(' ')}#{e.chomp}") }
+
+ last_block = removes[-1]
removes.each do |block|
block.remove.each do |item|
- outlist[item.position - lo].insert(0, encode(block.op)) # - or !
+ outlist[item.position - lo][0, 1] = encode(block.op) # - or !
+ end
+
+ if last && block == last_block && old_missing_newline
+ outlist << encode('\\ No newline at end of file')
end
end
- s << outlist.join("\n")
+
+ s << outlist.join(encode("\n")) << encode("\n")
end
- s << encode("\n--- #{r} ----\n")
+ s << encode("--- #{r} ----\n")
lo, hi = @start_new, @end_new
inserts = @blocks.reject { |e| e.insert.empty? }
- if inserts
- outlist = @data_new[lo..hi].collect { |e| e.insert(0, encode(' ')) }
+
+ unless inserts.empty?
+ outlist = @data_new[lo..hi].map { |e| String.new("#{encode(' ')}#{e.chomp}") }
+
+ last_block = inserts[-1]
+
inserts.each do |block|
block.insert.each do |item|
- outlist[item.position - lo].insert(0, encode(block.op)) # - or !
+ outlist[item.position - lo][0, 1] = encode(block.op) # + or !
+ end
+
+ if last && block == last_block && new_missing_newline
+ outlist << encode('\\ No newline at end of file')
end
end
- s << outlist.join("\n")
+ s << outlist.join(encode("\n"))
end
+
s
end
private :context_diff
- def ed_diff(format)
- op_act = { '+' => 'a', '-' => 'd', '!' => 'c' }
+ def ed_diff(format, _last = false)
warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
s =
if format == :reverse_ed
- encode("#{op_act[@blocks[0].op]}#{context_range(:old)}\n")
+ encode("#{ED_DIFF_OP_ACTION[@blocks[0].op]}#{context_range(:old, ',')}\n")
else
- encode("#{context_range(:old, ' ')}#{op_act[@blocks[0].op]}\n")
+ encode("#{context_range(:old, ' ')}#{ED_DIFF_OP_ACTION[@blocks[0].op]}\n")
end
unless @blocks[0].insert.empty?
- @data_new[@start_new..@end_new].each do |e| s << e + encode("\n") end
+ @data_new[@start_new..@end_new].each do |e|
+ s << e.chomp + encode("\n")
+ end
s << encode(".\n")
end
s
@@ -239,7 +290,7 @@ class Diff::LCS::Hunk
# Generate a range of item numbers to print. Only print 1 number if the
# range has only one item in it. Otherwise, it's 'start,end'
- def context_range(mode, op = ',') # rubocop:disable Naming/UncommunicativeMethodParamName
+ def context_range(mode, op, last = false)
case mode
when :old
s, e = (@start_old + 1), (@end_old + 1)
@@ -247,6 +298,9 @@ class Diff::LCS::Hunk
s, e = (@start_new + 1), (@end_new + 1)
end
+ e -= 1 if last
+ e = 1 if e.zero?
+
s < e ? "#{s}#{op}#{e}" : e.to_s
end
private :context_range
@@ -254,7 +308,7 @@ class Diff::LCS::Hunk
# Generate a range of item numbers to print for unified diff. Print number
# where block starts, followed by number of lines in the block
# (don't print number of lines if it's 1)
- def unified_range(mode)
+ def unified_range(mode, last)
case mode
when :old
s, e = (@start_old + 1), (@end_old + 1)
@@ -262,12 +316,25 @@ class Diff::LCS::Hunk
s, e = (@start_new + 1), (@end_new + 1)
end
- length = e - s + 1
+ length = e - s + (last ? 0 : 1)
+
first = length < 2 ? e : s # "strange, but correct"
- length == 1 ? first.to_s : "#{first},#{length}"
+ length <= 1 ? first.to_s : "#{first},#{length}"
end
private :unified_range
+ def missing_last_newline?(data)
+ newline = encode("\n")
+
+ if data[-2]
+ data[-2].end_with?(newline) && !data[-1].end_with?(newline)
+ elsif data[-1]
+ !data[-1].end_with?(newline)
+ else
+ true
+ end
+ end
+
if String.method_defined?(:encoding)
def encode(literal, target_encoding = @preferred_data_encoding)
literal.encode target_encoding
diff --git a/lib/diff/lcs/ldiff.rb b/lib/diff/lcs/ldiff.rb
index 2862267..17b374c 100644
--- a/lib/diff/lcs/ldiff.rb
+++ b/lib/diff/lcs/ldiff.rb
@@ -98,24 +98,19 @@ class << Diff::LCS::Ldiff
# items we've read from each file will differ by FLD (could be 0).
file_length_difference = 0
- if @binary.nil? or @binary
- data_old = IO.read(file_old)
- data_new = IO.read(file_new)
-
- # Test binary status
- if @binary.nil?
- old_txt = data_old[0, 4096].scan(/\0/).empty?
- new_txt = data_new[0, 4096].scan(/\0/).empty?
- @binary = !old_txt or !new_txt
- end
+ data_old = IO.read(file_old)
+ data_new = IO.read(file_new)
+
+ # Test binary status
+ if @binary.nil?
+ old_txt = data_old[0, 4096].scan(/\0/).empty?
+ new_txt = data_new[0, 4096].scan(/\0/).empty?
+ @binary = !old_txt || !new_txt
+ end
- unless @binary
- data_old = data_old.split($/).map { |e| e.chomp }
- data_new = data_new.split($/).map { |e| e.chomp }
- end
- else
- data_old = IO.readlines(file_old).map { |e| e.chomp }
- data_new = IO.readlines(file_new).map { |e| e.chomp }
+ unless @binary
+ data_old = data_old.lines.to_a
+ data_new = data_new.lines.to_a
end
# diff yields lots of pieces, each of which is basically a Block object
@@ -150,20 +145,21 @@ class << Diff::LCS::Ldiff
end
diffs.each do |piece|
- begin
+ begin # rubocop:disable Style/RedundantBegin
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines, file_length_difference)
file_length_difference = hunk.file_length_difference
next unless oldhunk
next if @lines.positive? and hunk.merge(oldhunk)
- output << oldhunk.diff(@format) << "\n"
+ output << oldhunk.diff(@format)
+ output << "\n" if @format == :unified
ensure
oldhunk = hunk
end
end
- last = oldhunk.diff(@format)
+ last = oldhunk.diff(@format, true)
last << "\n" if last.respond_to?(:end_with?) && !last.end_with?("\n")
output << last
diff --git a/spec/fixtures/ldiff/output.diff-c b/spec/fixtures/ldiff/output.diff-c
index 92ea5a2..0e1ad99 100644
--- a/spec/fixtures/ldiff/output.diff-c
+++ b/spec/fixtures/ldiff/output.diff-c
@@ -1,5 +1,5 @@
-*** spec/fixtures/aX 2019-02-01 22:29:34.000000000 -0500
---- spec/fixtures/bXaX 2019-02-01 22:29:43.000000000 -0500
+*** spec/fixtures/aX 2020-06-23 11:15:32.000000000 -0400
+--- spec/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400
***************
*** 1 ****
! aX
diff --git a/spec/fixtures/ldiff/output.diff-u b/spec/fixtures/ldiff/output.diff-u
index c4ba30f..b84f718 100644
--- a/spec/fixtures/ldiff/output.diff-u
+++ b/spec/fixtures/ldiff/output.diff-u
@@ -1,5 +1,5 @@
---- spec/fixtures/aX 2019-02-01 22:29:34.000000000 -0500
-+++ spec/fixtures/bXaX 2019-02-01 22:29:43.000000000 -0500
+--- spec/fixtures/aX 2020-06-23 11:15:32.000000000 -0400
++++ spec/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400
@@ -1 +1 @@
-aX
+bXaX
diff --git a/spec/fixtures/ldiff/output.diff.chef b/spec/fixtures/ldiff/output.diff.chef
new file mode 100644
index 0000000..8b98efb
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef
@@ -0,0 +1,4 @@
+3c3
+< "description": "hi"
+---
+> "description": "lo"
diff --git a/spec/fixtures/ldiff/output.diff.chef-c b/spec/fixtures/ldiff/output.diff.chef-c
new file mode 100644
index 0000000..efbfa19
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef-c
@@ -0,0 +1,15 @@
+*** spec/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400
+--- spec/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400
+***************
+*** 1,4 ****
+ {
+ "name": "x",
+! "description": "hi"
+ }
+\ No newline at end of file
+--- 1,4 ----
+ {
+ "name": "x",
+! "description": "lo"
+ }
+\ No newline at end of file
diff --git a/spec/fixtures/ldiff/output.diff.chef-e b/spec/fixtures/ldiff/output.diff.chef-e
new file mode 100644
index 0000000..775d881
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef-e
@@ -0,0 +1,3 @@
+3c
+ "description": "lo"
+.
diff --git a/spec/fixtures/ldiff/output.diff.chef-f b/spec/fixtures/ldiff/output.diff.chef-f
new file mode 100644
index 0000000..9bf1e67
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef-f
@@ -0,0 +1,3 @@
+c3
+ "description": "lo"
+.
diff --git a/spec/fixtures/ldiff/output.diff.chef-u b/spec/fixtures/ldiff/output.diff.chef-u
index ebf2b12..dbacd88 100644
--- a/spec/fixtures/ldiff/output.diff.chef-u
+++ b/spec/fixtures/ldiff/output.diff.chef-u
@@ -1,8 +1,9 @@
---- spec/fixtures/old-chef 2019-02-01 21:57:15.000000000 -0400
-+++ spec/fixtures/new-chef 2019-02-01 21:57:29.000000000 -0400
-@@ -1,5 +1,5 @@
+--- spec/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400
++++ spec/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400
+@@ -1,4 +1,4 @@
{
"name": "x",
- "description": "hi"
+ "description": "lo"
}
+\ No newline at end of file
diff --git a/spec/fixtures/ldiff/output.diff.chef2 b/spec/fixtures/ldiff/output.diff.chef2
new file mode 100644
index 0000000..496b3dc
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef2
@@ -0,0 +1,7 @@
+2d1
+< recipe[b::default]
+14a14,17
+> recipe[o::new]
+> recipe[p::new]
+> recipe[q::new]
+> recipe[r::new]
diff --git a/spec/fixtures/ldiff/output.diff.chef2-c b/spec/fixtures/ldiff/output.diff.chef2-c
new file mode 100644
index 0000000..8349a7a
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef2-c
@@ -0,0 +1,20 @@
+*** spec/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400
+--- spec/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400
+***************
+*** 1,5 ****
+ recipe[a::default]
+- recipe[b::default]
+ recipe[c::default]
+ recipe[d::default]
+ recipe[e::default]
+--- 1,4 ----
+***************
+*** 12,14 ****
+--- 11,17 ----
+ recipe[l::default]
+ recipe[m::default]
+ recipe[n::default]
++ recipe[o::new]
++ recipe[p::new]
++ recipe[q::new]
++ recipe[r::new]
diff --git a/spec/fixtures/ldiff/output.diff.chef2-d b/spec/fixtures/ldiff/output.diff.chef2-d
new file mode 100644
index 0000000..ca32a49
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef2-d
@@ -0,0 +1,7 @@
+d2
+a14
+recipe[o::new]
+recipe[p::new]
+recipe[q::new]
+recipe[r::new]
+.
diff --git a/spec/fixtures/ldiff/output.diff.chef2-e b/spec/fixtures/ldiff/output.diff.chef2-e
new file mode 100644
index 0000000..89f3fa0
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef2-e
@@ -0,0 +1,7 @@
+14a
+recipe[o::new]
+recipe[p::new]
+recipe[q::new]
+recipe[r::new]
+.
+2d
diff --git a/spec/fixtures/ldiff/output.diff.chef2-f b/spec/fixtures/ldiff/output.diff.chef2-f
new file mode 100644
index 0000000..ca32a49
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef2-f
@@ -0,0 +1,7 @@
+d2
+a14
+recipe[o::new]
+recipe[p::new]
+recipe[q::new]
+recipe[r::new]
+.
diff --git a/spec/fixtures/ldiff/output.diff.chef2-u b/spec/fixtures/ldiff/output.diff.chef2-u
new file mode 100644
index 0000000..ef025c7
--- /dev/null
+++ b/spec/fixtures/ldiff/output.diff.chef2-u
@@ -0,0 +1,16 @@
+--- spec/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400
++++ spec/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400
+@@ -1,5 +1,4 @@
+ recipe[a::default]
+-recipe[b::default]
+ recipe[c::default]
+ recipe[d::default]
+ recipe[e::default]
+@@ -12,3 +11,7 @@
+ recipe[l::default]
+ recipe[m::default]
+ recipe[n::default]
++recipe[o::new]
++recipe[p::new]
++recipe[q::new]
++recipe[r::new]
diff --git a/spec/fixtures/new-chef2 b/spec/fixtures/new-chef2
new file mode 100644
index 0000000..8213c73
--- /dev/null
+++ b/spec/fixtures/new-chef2
@@ -0,0 +1,17 @@
+recipe[a::default]
+recipe[c::default]
+recipe[d::default]
+recipe[e::default]
+recipe[f::default]
+recipe[g::default]
+recipe[h::default]
+recipe[i::default]
+recipe[j::default]
+recipe[k::default]
+recipe[l::default]
+recipe[m::default]
+recipe[n::default]
+recipe[o::new]
+recipe[p::new]
+recipe[q::new]
+recipe[r::new]
diff --git a/spec/fixtures/old-chef2 b/spec/fixtures/old-chef2
new file mode 100644
index 0000000..4a23407
--- /dev/null
+++ b/spec/fixtures/old-chef2
@@ -0,0 +1,14 @@
+recipe[a::default]
+recipe[b::default]
+recipe[c::default]
+recipe[d::default]
+recipe[e::default]
+recipe[f::default]
+recipe[g::default]
+recipe[h::default]
+recipe[i::default]
+recipe[j::default]
+recipe[k::default]
+recipe[l::default]
+recipe[m::default]
+recipe[n::default]
diff --git a/spec/hunk_spec.rb b/spec/hunk_spec.rb
index 277f739..b3616bf 100644
--- a/spec/hunk_spec.rb
+++ b/spec/hunk_spec.rb
@@ -6,28 +6,39 @@ if String.method_defined?(:encoding)
require 'diff/lcs/hunk'
describe Diff::LCS::Hunk do
- let(:old_data) { ['Tu avec carté {count} itém has'.encode('UTF-16LE')] }
- let(:new_data) { ['Tu avec carte {count} item has'.encode('UTF-16LE')] }
+ let(:old_data) { ['Tu a un carté avec {count} itéms'.encode('UTF-16LE')] }
+ let(:new_data) { ['Tu a un carte avec {count} items'.encode('UTF-16LE')] }
let(:pieces) { Diff::LCS.diff old_data, new_data }
let(:hunk) { Diff::LCS::Hunk.new(old_data, new_data, pieces[0], 3, 0) }
it 'produces a unified diff from the two pieces' do
expected = <<-EXPECTED.gsub(/^\s+/, '').encode('UTF-16LE').chomp
@@ -1 +1 @@
- -Tu avec carté {count} itém has
- +Tu avec carte {count} item has
+ -Tu a un carté avec {count} itéms
+ +Tu a un carte avec {count} items
EXPECTED
expect(hunk.diff(:unified)).to eq(expected)
end
+ it 'produces a unified diff from the two pieces (last entry)' do
+ expected = <<-EXPECTED.gsub(/^\s+/, '').encode('UTF-16LE').chomp
+ @@ -1 +1 @@
+ -Tu a un carté avec {count} itéms
+ +Tu a un carte avec {count} items
+ \\ No newline at end of file
+ EXPECTED
+
+ expect(hunk.diff(:unified, true)).to eq(expected)
+ end
+
it 'produces a context diff from the two pieces' do
expected = <<-EXPECTED.gsub(/^\s+/, '').encode('UTF-16LE').chomp
***************
*** 1 ****
- ! Tu avec carté {count} itém has
+ ! Tu a un carté avec {count} itéms
--- 1 ----
- ! Tu avec carte {count} item has
+ ! Tu a un carte avec {count} items
EXPECTED
expect(hunk.diff(:context)).to eq(expected)
@@ -36,9 +47,9 @@ if String.method_defined?(:encoding)
it 'produces an old diff from the two pieces' do
expected = <<-EXPECTED.gsub(/^ +/, '').encode('UTF-16LE').chomp
1c1
- < Tu avec carté {count} itém has
+ < Tu a un carté avec {count} itéms
---
- > Tu avec carte {count} item has
+ > Tu a un carte avec {count} items
EXPECTED
@@ -48,7 +59,7 @@ if String.method_defined?(:encoding)
it 'produces a reverse ed diff from the two pieces' do
expected = <<-EXPECTED.gsub(/^ +/, '').encode('UTF-16LE').chomp
c1
- Tu avec carte {count} item has
+ Tu a un carte avec {count} items
.
EXPECTED
@@ -62,7 +73,7 @@ if String.method_defined?(:encoding)
it 'produces a unified diff' do
expected = <<-EXPECTED.gsub(/^\s+/, '').encode('UTF-16LE').chomp
@@ -1 +1,2 @@
- +Tu avec carte {count} item has
+ +Tu a un carte avec {count} items
EXPECTED
expect(hunk.diff(:unified)).to eq(expected)
diff --git a/spec/issues_spec.rb b/spec/issues_spec.rb
index c9a1e53..ad73123 100644
--- a/spec/issues_spec.rb
+++ b/spec/issues_spec.rb
@@ -56,17 +56,17 @@ describe 'Diff::LCS Issues' do
end
end
- describe "issue #57" do
+ describe 'issue #57' do
it 'should fail with a correct error' do
expect {
- actual = {:category=>"app.rack.request"}
- expected = {:category=>"rack.middleware", :title=>"Anonymous Middleware"}
+ actual = { :category => 'app.rack.request' }
+ expected = { :category => 'rack.middleware', :title => 'Anonymous Middleware' }
expect(actual).to eq(expected)
}.to raise_error(RSpec::Expectations::ExpectationNotMetError)
end
end
- describe "issue #60" do
+ describe 'issue #60' do
it 'should produce unified output with correct context' do
old_data = <<-DATA_OLD.strip.split("\n").map(&:chomp)
{
@@ -95,4 +95,60 @@ describe 'Diff::LCS Issues' do
EXPECTED
end
end
+
+ describe 'issue #65' do
+ def diff_lines(old_lines, new_lines)
+ file_length_difference = 0
+ previous_hunk = nil
+ output = []
+
+ Diff::LCS.diff(old_lines, new_lines).each do |piece|
+ hunk = Diff::LCS::Hunk.new(old_lines, new_lines, piece, 3, file_length_difference)
+ file_length_difference = hunk.file_length_difference
+ maybe_contiguous_hunks = (previous_hunk.nil? || hunk.merge(previous_hunk))
+
+ output << "#{previous_hunk.diff(:unified)}\n" unless maybe_contiguous_hunks
+
+ previous_hunk = hunk
+ end
+ output << "#{previous_hunk.diff(:unified, true)}\n" unless previous_hunk.nil?
+ output.join
+ end
+
+ it 'should not misplace the new chunk' do
+ old_data = [
+ 'recipe[a::default]', 'recipe[b::default]', 'recipe[c::default]',
+ 'recipe[d::default]', 'recipe[e::default]', 'recipe[f::default]',
+ 'recipe[g::default]', 'recipe[h::default]', 'recipe[i::default]',
+ 'recipe[j::default]', 'recipe[k::default]', 'recipe[l::default]',
+ 'recipe[m::default]', 'recipe[n::default]'
+ ]
+
+ new_data = [
+ 'recipe[a::default]', 'recipe[c::default]', 'recipe[d::default]',
+ 'recipe[e::default]', 'recipe[f::default]', 'recipe[g::default]',
+ 'recipe[h::default]', 'recipe[i::default]', 'recipe[j::default]',
+ 'recipe[k::default]', 'recipe[l::default]', 'recipe[m::default]',
+ 'recipe[n::default]', 'recipe[o::new]', 'recipe[p::new]',
+ 'recipe[q::new]', 'recipe[r::new]'
+ ]
+
+ expect(diff_lines(old_data, new_data)).to eq(<<-EODIFF)
+@@ -1,5 +1,4 @@
+ recipe[a::default]
+-recipe[b::default]
+ recipe[c::default]
+ recipe[d::default]
+ recipe[e::default]
+@@ -12,3 +11,7 @@
+ recipe[l::default]
+ recipe[m::default]
+ recipe[n::default]
++recipe[o::new]
++recipe[p::new]
++recipe[q::new]
++recipe[r::new]
+ EODIFF
+ end
+ end
end
diff --git a/spec/ldiff_spec.rb b/spec/ldiff_spec.rb
index 2f4c235..a2468f8 100644
--- a/spec/ldiff_spec.rb
+++ b/spec/ldiff_spec.rb
@@ -5,40 +5,39 @@ require 'spec_helper'
RSpec.describe 'bin/ldiff' do
include CaptureSubprocessIO
- let(:output_diff) { read_fixture }
- let(:output_diff_c) { read_fixture('-c') }
- let(:output_diff_e) { read_fixture('-e') }
- let(:output_diff_f) { read_fixture('-f') }
- let(:output_diff_u) { read_fixture('-u') }
- let(:output_diff_chef) { read_fixture('-u', :base => 'output.diff.chef') }
+ fixtures = [
+ { :name => 'output.diff', :left => 'aX', :right => 'bXaX' },
+ { :name => 'output.diff.chef', :left => 'old-chef', :right => 'new-chef' },
+ { :name => 'output.diff.chef2', :left => 'old-chef2', :right => 'new-chef2' }
+ ].product([nil, '-e', '-f', '-c', '-u']).map { |(fixture, flag)|
+ fixture = fixture.dup
+ fixture[:flag] = flag
+ fixture
+ }
- specify do
- expect(run_ldiff('-u', :left => 'old-chef', :right => 'new-chef')).to eq(output_diff_chef)
- end
-
- specify do
- expect(run_ldiff).to eq(output_diff)
- end
-
- specify do
- expect(run_ldiff('-c')).to eq(output_diff_c)
- end
+ def self.test_ldiff(fixture)
+ desc = [
+ fixture[:flag],
+ "spec/fixtures/#{fixture[:left]}",
+ "spec/fixtures/#{fixture[:right]}",
+ '#',
+ '=>',
+ "spec/fixtures/ldiff/#{fixture[:name]}#{fixture[:flag]}"
+ ].join(' ')
- specify do
- expect(run_ldiff('-e')).to eq(output_diff_e)
- end
-
- specify do
- expect(run_ldiff('-f')).to eq(output_diff_f)
+ it desc do
+ expect(run_ldiff(fixture)).to eq(read_fixture(fixture))
+ end
end
- specify do
- expect(run_ldiff('-u')).to eq(output_diff_u)
+ fixtures.each do |fixture|
+ test_ldiff(fixture)
end
- def read_fixture(flag = nil, options = {})
- base = options.fetch(:base, 'output.diff')
- name = "spec/fixtures/ldiff/#{base}#{flag}"
+ def read_fixture(options)
+ fixture = options.fetch(:name)
+ flag = options.fetch(:flag)
+ name = "spec/fixtures/ldiff/#{fixture}#{flag}"
data = IO.__send__(IO.respond_to?(:binread) ? :binread : :read, name)
clean_data(data, flag)
end
@@ -68,13 +67,15 @@ RSpec.describe 'bin/ldiff' do
\s*
(?:[-+]\d{4}|Z)
}x,
- '*** spec/fixtures/\1 0000-00-00 00:00:00.000000000 -0000'
+ '*** spec/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000'
)
end
- def run_ldiff(flag = nil, options = {})
- left = options.fetch(:left, 'aX')
- right = options.fetch(:right, 'bXaX')
+ def run_ldiff(options)
+ flag = options.fetch(:flag)
+ left = options.fetch(:left)
+ right = options.fetch(:right)
+
stdout, stderr = capture_subprocess_io do
system("ruby -Ilib bin/ldiff #{flag} spec/fixtures/#{left} spec/fixtures/#{right}")
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b1935d0..7212f30 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -5,7 +5,6 @@ require 'pathname'
require 'psych' if RUBY_VERSION >= '1.9'
-
if ENV['COVERAGE']
require 'simplecov'
@@ -45,30 +44,31 @@ module CaptureSubprocessIO
end
def capture_subprocess_io
- _synchronize do
- begin
- require 'tempfile'
+ _synchronize { _capture_subprocess_io { yield } }
+ end
+
+ def _capture_subprocess_io
+ require 'tempfile'
- captured_stdout, captured_stderr = Tempfile.new('out'), Tempfile.new('err')
+ captured_stdout, captured_stderr = Tempfile.new('out'), Tempfile.new('err')
- orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
- $stdout.reopen captured_stdout
- $stderr.reopen captured_stderr
+ orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
+ $stdout.reopen captured_stdout
+ $stderr.reopen captured_stderr
- yield
+ yield
- $stdout.rewind
- $stderr.rewind
+ $stdout.rewind
+ $stderr.rewind
- return captured_stdout.read, captured_stderr.read
- ensure
- captured_stdout.unlink
- captured_stderr.unlink
- $stdout.reopen orig_stdout
- $stderr.reopen orig_stderr
- end
- end
+ [captured_stdout.read, captured_stderr.read]
+ ensure
+ captured_stdout.unlink
+ captured_stderr.unlink
+ $stdout.reopen orig_stdout
+ $stderr.reopen orig_stderr
end
+ private :_capture_subprocess_io
end
require 'diff-lcs'