Вот что у меня получилось
class Parser
def parse(text)
if text.encoding.name != "UTF-8"
encoded_text = @full_diff.encode("UTF-8", "binary", { :invalid => :replace, :undef => :replace })
else
encoded_text = text
end
hunks = []
hunk = nil
added_line_number = nil
deleted_line_number = nil
lines = encoded_text.strip.split("\n")
lines.each_with_index do |line, index|
if m = /^diff --git a\/(.*?) b\/(.*?)$/.match(line)
raise "Diff formatting error, 'diff --git' is the last line" if index + 1 >= lines.length
# new hunk
added_line_number = nil
delete_line_number = nil
hunk = Hunk.new(m[1], m[2])
hunk.type = hunk_type(lines[index + 1], m[1], m[2])
hunks.push(hunk)
elsif /^Binary files /.match(line)
hunk.is_binary = true
elsif m = /^@@ \-(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.match(line)
# (e.g. @@ -19,6 +19,7 @@)
deleted_line_number = Integer(m[1])
added_line_number = Integer(m[2])
else
if !added_line_number.nil?
if line.start_with?('+')
# added line
hunk.lines.push SourceLine.new(added_line_number, SourceLine::Type::Added, line[1..-1])
added_line_number += 1
elsif line.start_with?('-')
# deleted line
hunk.lines.push SourceLine.new(deleted_line_number, SourceLine::Type::Deleted, line[1..-1])
deleted_line_number += 1
else
# unmodified line
added_line_number += 1
deleted_line_number += 1
end
end
end
end
hunks
end
def hunk_type(line, original, renamed)
case line
when /^new file /
type = Hunk::Type::Added
when /^deleted file /
type = Hunk::Type::Deleted
else
type = original == renamed ? Hunk::Type::Modified : Hunk::Type::Renamed
end
type
end
private :hunk_type
end
end
module Type
Added = 'added'
Deleted = 'deleted'
Modified = 'modified'
Renamed = 'renamed'
end
class Hunk
module Type
Added = 'added'
Deleted = 'deleted'
Modified = 'modified'
Renamed = 'renamed'
end
attr_accessor :original_path, :renamed_path, :type, :lines, :is_binary
alias_method :is_binary?, :is_binary
def initialize(original_path, renamed_path)
self.is_binary = false
self.lines = []
self.original_path = original_path
self.renamed_path = renamed_path
end
end
class SourceLine
module Type
Added = 'added'
Deleted = 'deleted'
end
attr_accessor :number, :type, :text
def initialize(number, type, text)
self.number = number
self.type = type
self.text = text
end
end