Skip to content

Commit

Permalink
Add unified patch generation
Browse files Browse the repository at this point in the history
Doesn't parse/apply them yet
  • Loading branch information
SquidDev committed May 2, 2016
1 parent f157fee commit d6e836a
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 14 deletions.
6 changes: 3 additions & 3 deletions bsrocks/commands/admin/make.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ local function execute(...)
fileWrapper.assertExists(info, "patchspec for " .. name, 0)

local data = serialize.unserialize(fileWrapper.read(info))
local originalSources = fileWrapper.readDir(original)
local changedSources = fileWrapper.readDir(changed)
local originalSources = fileWrapper.readDir(original, fileWrapper.readLines)
local changedSources = fileWrapper.readDir(changed, fileWrapper.readLines)

local files, patches, added, removed = patchspec.makePatches(originalSources, changedSources)
data.patches = patches
data.added = added
data.removed = removed

fileWrapper.writeDir(patch, files)
fileWrapper.writeDir(patch, files, fileWrapper.writeLines)
fileWrapper.write(info, serialize.serialize(data))
end
end
Expand Down
178 changes: 178 additions & 0 deletions bsrocks/lib/diff.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
--[[
(C) Paul Butler 2008-2012 <http://www.paulbutler.org/>
May be used and distributed under the zlib/libpng license
<http://www.opensource.org/licenses/zlib-license.php>
Adaptation to Lua by Philippe Fremy <phil at freehackers dot org>
Lua version copyright 2015
]]
local ipairs = ipairs

local function table_join(t1, t2, t3)
-- return a table containing all elements of t1 then t2 then t3
local t, n = {}, 0
for i,v in ipairs(t1) do
n = n + 1
t[n] = v
end

for i,v in ipairs(t2) do
n = n + 1
t[n] = v
end

if t3 then
for i,v in ipairs(t3) do
n = n + 1
t[n] = v
end
end

return t
end

local function table_subtable( t, start, stop )
-- 0 is first element, stop is last element
local ret = {}
if stop == nil then
stop = #t
end
if start < 0 or stop < 0 or start > stop then
error('Invalid values: '..start..' '..stop )
end
for i,v in ipairs(t) do
if (i-1) >= start and (i-1) < stop then
table.insert( ret, v )
end
end
return ret
end

--[[-
Find the differences between two lists or strings.
Returns a list of pairs, where the first value is in ['+','-','=']
and represents an insertion, deletion, or no change for that list.
The second value of the pair is the list of elements.
@tparam table old the old list of immutable, comparable values (ie. a list of strings)
@tparam table new the new list of immutable, comparable values
@return table A list of pairs, with the first part of the pair being one of three
strings ('-', '+', '=') and the second part being a list of values from
the original old and/or new lists. The first part of the pair
corresponds to whether the list of values is a deletion, insertion, or
unchanged, respectively.
@example
diff( {1,2,3,4}, {1,3,4})
{ {'=', {1} }, {'-', {2} }, {'=', {3, 4}} }
diff( {1,2,3,4}, {2,3,4,1} )
{ {'-', {1}}, {'=', {2, 3, 4}}, {'+', {1}} }
diff(
{ 'The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog' },
{ 'The', 'slow', 'blue', 'cheese', 'drips', 'over', 'the', 'lazy', 'carrot' }
)
{ {'=', {'The'} },
{'-', {'quick', 'brown', 'fox', 'jumps'} },
{'+', {'slow', 'blue', 'cheese', 'drips'} },
{'=', {'over', 'the', 'lazy'} },
{'-', {'dog'} },
{'+', {'carrot'} }
}
]]
local function diff(old, new)
-- Create a map from old values to their indices
local old_index_map = {}
for i, val in ipairs(old) do
if not old_index_map[val] then
old_index_map[val] = {}
end
table.insert( old_index_map[val], i-1 )
end

--[[
Find the largest substring common to old and new.
We use a dynamic programming approach here.
We iterate over each value in the `new` list, calling the
index `inew`. At each iteration, `overlap[i]` is the
length of the largest suffix of `old[:i]` equal to a suffix
of `new[:inew]` (or unset when `old[i]` != `new[inew]`).
At each stage of iteration, the new `overlap` (called
`_overlap` until the original `overlap` is no longer needed)
is built from the old one.
If the length of overlap exceeds the largest substring
seen so far (`sub_length`), we update the largest substring
to the overlapping strings.
`sub_start_old` is the index of the beginning of the largest overlapping
substring in the old list. `sub_start_new` is the index of the beginning
of the same substring in the new list. `sub_length` is the length that
overlaps in both.
These track the largest overlapping substring seen so far, so naturally
we start with a 0-length substring.
]]
local overlap = {}
local sub_start_old = 0
local sub_start_new = 0
local sub_length = 0

for inewInc, val in ipairs(new) do
local inew = inewInc-1
local _overlap = {}
if old_index_map[val] then
for _,iold in ipairs(old_index_map[val]) do
-- now we are considering all values of iold such that
-- `old[iold] == new[inew]`.
if iold <= 0 then
_overlap[iold] = 1
else
_overlap[iold] = (overlap[iold - 1] or 0) + 1
end
if (_overlap[iold] > sub_length) then
sub_length = _overlap[iold]
sub_start_old = iold - sub_length + 1
sub_start_new = inew - sub_length + 1
end
end
end
overlap = _overlap
end

if sub_length == 0 then
-- If no common substring is found, we return an insert and delete...
local oldRet = {}
local newRet = {}

if #old > 0 then
oldRet = { {'-', old} }
end
if #new > 0 then
newRet = { {'+', new} }
end

return table_join( oldRet, newRet )
else
-- ...otherwise, the common substring is unchanged and we recursively
-- diff the text before and after that substring
return table_join(
diff(
table_subtable( old, 0, sub_start_old),
table_subtable( new, 0, sub_start_new)
),
{ {'=', table_subtable(new,sub_start_new,sub_start_new + sub_length) } },
diff(
table_subtable( old, sub_start_old + sub_length ),
table_subtable( new, sub_start_new + sub_length )
)
)
end
end

return diff
46 changes: 40 additions & 6 deletions bsrocks/lib/files.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,48 @@ local function read(file)
return contents
end

local function readLines(file)
local handle = fs.open(file, "r")
local out, n = {}, 0

for line in handle.readLine do
n = n + 1
out[n] = line
end

handle.close()

-- Trim trailing lines
while out[n] == "" do
out[n] = nil
n = n - 1
end

return out
end

local function write(file, contents)
local handle = fs.open(file, "w")
handle.write(contents)
handle.close()
end

local function writeLines(file, contents)
local handle = fs.open(file, "w")
for i = 1, #contents do
handle.writeLine(contents[i])
end
handle.close()
end

local function assertExists(file, name, level)
if not fs.exists(file) then
error("Cannot find " .. name .. " (Looking for " .. file .. ")", level or 1)
end
end

local function readDir(directory)
local function readDir(directory, reader)
reader = reader or read
local offset = #directory + 2
local stack, n = { directory }, 1

Expand All @@ -33,23 +62,28 @@ local function readDir(directory)
stack[n] = fs.combine(top, file)
end
else
files[top:sub(offset)] = read(top)
files[top:sub(offset)] = reader(top)
end
end

return files
end

local function writeDir(dir, files)
local function writeDir(dir, files, writer)
writer = writer or write
for file, contents in pairs(files) do
write(fs.combine(dir, file), contents)
writer(fs.combine(dir, file), contents)
end
end

return {
read = read,
write = write,
assertExists = assertExists,
readLines = readLines,
readDir = readDir,

write = write,
writeLines = writeLines,
writeDir = writeDir,

assertExists = assertExists,
}
Loading

0 comments on commit d6e836a

Please sign in to comment.