diff
This commit is contained in:
parent
e9952bacf2
commit
06b2a2a993
2 changed files with 211 additions and 311 deletions
325
source/app.d
325
source/app.d
|
@ -1,322 +1,25 @@
|
||||||
import std.stdio : writeln, stderr, File;
|
import std.stdio : writeln, stderr;
|
||||||
import std.getopt;
|
import std.exception : collectException;
|
||||||
import std.file : read, exists;
|
|
||||||
import std.exception : enforce, collectException;
|
|
||||||
import core.stdc.stdlib : malloc, free, realloc;
|
|
||||||
import core.stdc.string : memcpy;
|
|
||||||
import core.sys.posix.unistd : posix_write = write;
|
|
||||||
|
|
||||||
import xdiff;
|
import diff;
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// RAII для mmfile_t
|
|
||||||
// ------------------------------
|
|
||||||
struct MmFile
|
|
||||||
{
|
|
||||||
mmfile_t mf;
|
|
||||||
|
|
||||||
// фабрика вместо конструктора по умолчанию
|
|
||||||
static MmFile create(long bsize = 8 * 1024, ulong flags = XDL_MMF_ATOMIC)
|
|
||||||
{
|
|
||||||
MmFile m;
|
|
||||||
enforce(xdl_init_mmfile(&m.mf, bsize, flags) == 0, "xdl_init_mmfile failed");
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
void deinit()
|
|
||||||
{
|
|
||||||
xdl_free_mmfile(&mf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Коллбэки вывода
|
|
||||||
// ------------------------------
|
|
||||||
extern (C) int mmfileOut(void* priv, mmbuffer_t* mb, int nbuf)
|
|
||||||
{
|
|
||||||
auto mmfOut = cast(mmfile_t*) priv;
|
|
||||||
return xdl_writem_mmfile(mmfOut, mb, nbuf) < 0 ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern (C) int fdOut(void* priv, mmbuffer_t* mb, int nbuf)
|
|
||||||
{
|
|
||||||
const fd = cast(int) cast(size_t) priv; // 1=stdout, 2=stderr
|
|
||||||
foreach (i; 0 .. nbuf)
|
|
||||||
{
|
|
||||||
auto ptr = mb[i].ptr;
|
|
||||||
auto left = cast(size_t) mb[i].size;
|
|
||||||
while (left)
|
|
||||||
{
|
|
||||||
auto n = posix_write(fd, ptr, left);
|
|
||||||
if (n < 0)
|
|
||||||
return -1;
|
|
||||||
ptr += n;
|
|
||||||
left -= cast(size_t) n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Загрузка/выгрузка
|
|
||||||
// ------------------------------
|
|
||||||
void loadMmFile(ref MmFile m, string path)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
enforce(exists(path), "no such file: " ~ path);
|
|
||||||
auto data = cast(const(char)[]) read(path); // читает весь файл
|
|
||||||
auto dst = cast(char*) xdl_mmfile_writeallocate(&m.mf, data.length);
|
|
||||||
enforce(dst !is null, "xdl_mmfile_writeallocate failed (null)");
|
|
||||||
if (data.length)
|
|
||||||
memcpy(dst, data.ptr, data.length);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// пробрасываем с контекстом пути
|
|
||||||
throw new Exception("loadMmFile(" ~ path ~ "): " ~ e.msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dumpMmFile(const ref MmFile m, string path)
|
|
||||||
{
|
|
||||||
File f = File(path, "wb");
|
|
||||||
scope (exit)
|
|
||||||
f.close();
|
|
||||||
long sz = 0;
|
|
||||||
auto blk = cast(char*) xdl_mmfile_first(cast(mmfile_t*)&m.mf, &sz);
|
|
||||||
while (blk !is null)
|
|
||||||
{
|
|
||||||
f.rawWrite(blk[0 .. cast(size_t) sz]);
|
|
||||||
blk = cast(char*) xdl_mmfile_next(cast(mmfile_t*)&m.mf, &sz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Аллокатор
|
|
||||||
// ------------------------------
|
|
||||||
extern (C) void* wrap_malloc(void*, uint size)
|
|
||||||
{
|
|
||||||
return malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern (C) void wrap_free(void*, void* ptr)
|
|
||||||
{
|
|
||||||
free(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern (C) void* wrap_realloc(void*, void* ptr, uint size)
|
|
||||||
{
|
|
||||||
return realloc(ptr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void installAllocator()
|
|
||||||
{
|
|
||||||
memallocator_t malt;
|
|
||||||
malt.priv = null;
|
|
||||||
malt.malloc = &wrap_malloc;
|
|
||||||
malt.free = &wrap_free;
|
|
||||||
malt.realloc = &wrap_realloc;
|
|
||||||
enforce(xdl_set_allocator(&malt) == 0, "xdl_set_allocator failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// Команды
|
|
||||||
// ------------------------------
|
|
||||||
int doDiff(ref MmFile a, ref MmFile b, long ctxlen)
|
|
||||||
{
|
|
||||||
xpparam_t xpp;
|
|
||||||
xpp.flags = 0;
|
|
||||||
xdemitconf_t xecfg;
|
|
||||||
xecfg.ctxlen = ctxlen;
|
|
||||||
xdemitcb_t ecb;
|
|
||||||
ecb.priv = cast(void*) cast(size_t) 1;
|
|
||||||
ecb.outf = &fdOut;
|
|
||||||
return xdl_diff(&a.mf, &b.mf, &xpp, &xecfg, &ecb);
|
|
||||||
}
|
|
||||||
|
|
||||||
int doPatch(ref MmFile orig, ref MmFile patch, int mode)
|
|
||||||
{
|
|
||||||
xdemitcb_t outcb;
|
|
||||||
outcb.priv = cast(void*) cast(size_t) 1;
|
|
||||||
outcb.outf = &fdOut;
|
|
||||||
xdemitcb_t rejcb;
|
|
||||||
rejcb.priv = cast(void*) cast(size_t) 2;
|
|
||||||
rejcb.outf = &fdOut;
|
|
||||||
return xdl_patch(&orig.mf, &patch.mf, mode, &outcb, &rejcb);
|
|
||||||
}
|
|
||||||
|
|
||||||
int doBDiff(ref MmFile a, ref MmFile b, long bsize)
|
|
||||||
{
|
|
||||||
bdiffparam_t bdp;
|
|
||||||
bdp.bsize = bsize;
|
|
||||||
xdemitcb_t ecb;
|
|
||||||
ecb.priv = cast(void*) cast(size_t) 1;
|
|
||||||
ecb.outf = &fdOut;
|
|
||||||
return xdl_bdiff(&a.mf, &b.mf, &bdp, &ecb);
|
|
||||||
}
|
|
||||||
|
|
||||||
int doRaBDiff(ref MmFile a, ref MmFile b)
|
|
||||||
{
|
|
||||||
xdemitcb_t ecb;
|
|
||||||
ecb.priv = cast(void*) cast(size_t) 1;
|
|
||||||
ecb.outf = &fdOut;
|
|
||||||
return xdl_rabdiff(&a.mf, &b.mf, &ecb);
|
|
||||||
}
|
|
||||||
|
|
||||||
int doBPatch(ref MmFile base, ref MmFile binPatch)
|
|
||||||
{
|
|
||||||
xdemitcb_t ecb;
|
|
||||||
ecb.priv = cast(void*) cast(size_t) 1;
|
|
||||||
ecb.outf = &fdOut;
|
|
||||||
return xdl_bpatch(&base.mf, &binPatch.mf, &ecb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
// CLI
|
|
||||||
// ------------------------------
|
|
||||||
enum Cmd
|
|
||||||
{
|
|
||||||
diff,
|
|
||||||
patch,
|
|
||||||
bdiff,
|
|
||||||
rabdiff,
|
|
||||||
bpatch
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cmd parseCmd(string s)
|
|
||||||
{
|
|
||||||
final switch (s)
|
|
||||||
{
|
|
||||||
case "diff":
|
|
||||||
return Cmd.diff;
|
|
||||||
case "patch":
|
|
||||||
return Cmd.patch;
|
|
||||||
case "bdiff":
|
|
||||||
return Cmd.bdiff;
|
|
||||||
case "rabdiff":
|
|
||||||
return Cmd.rabdiff;
|
|
||||||
case "bpatch":
|
|
||||||
return Cmd.bpatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Короткая справка
|
|
||||||
void printUsage(string prg)
|
|
||||||
{
|
|
||||||
stderr.writefln(
|
|
||||||
"use: %1$s diff [-C N] FROM TO\n" ~
|
|
||||||
" %1$s patch ORIG PATCH\n" ~
|
|
||||||
" %1$s bdiff [-B N] FROM TO\n" ~
|
|
||||||
" %1$s rabdiff FROM TO\n" ~
|
|
||||||
" %1$s bpatch ORIG PATCH\n",
|
|
||||||
prg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Парсинг подкоманды + её опций. Возвращаем уже «очищенный» список позиционных.
|
|
||||||
struct Parsed
|
|
||||||
{
|
|
||||||
Cmd cmd;
|
|
||||||
long ctxlen = 3; // для diff
|
|
||||||
long bsize = 16; // для bdiff
|
|
||||||
string[] positional; // оставшиеся позиционные (пути)
|
|
||||||
}
|
|
||||||
|
|
||||||
Parsed parseArgs(string[] args)
|
|
||||||
{
|
|
||||||
enforce(args.length >= 2, "no subcommand");
|
|
||||||
auto sub = args[1];
|
|
||||||
auto cmd = parseCmd(sub);
|
|
||||||
|
|
||||||
long ctxlen = 3;
|
|
||||||
long bsize = 16;
|
|
||||||
bool helpWanted = false;
|
|
||||||
|
|
||||||
// Опции и позиционные ТОЛЬКО после подкоманды
|
|
||||||
string[] optArgs = args.length > 2 ? args[2 .. $].dup : [];
|
|
||||||
|
|
||||||
// getopt выкинет опции из optArgs и оставит только позиционные
|
|
||||||
auto r = getopt(optArgs,
|
|
||||||
"h|help", &helpWanted,
|
|
||||||
"C", &ctxlen, // актуально для `diff`
|
|
||||||
"B", &bsize // актуально для `bdiff`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
);
|
|
||||||
enforce(!helpWanted, "help");
|
|
||||||
|
|
||||||
Parsed p;
|
|
||||||
p.cmd = cmd;
|
|
||||||
p.ctxlen = ctxlen;
|
|
||||||
p.bsize = bsize;
|
|
||||||
p.positional = optArgs; // здесь уже только пути
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(string[] args)
|
int main(string[] args)
|
||||||
{
|
{
|
||||||
// 1) Парсим
|
if (args.length != 3)
|
||||||
Parsed p;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
p = parseArgs(args);
|
stderr.writeln("use: ", args[0], " OLD NEW");
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
printUsage(args[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (p.positional.length != 2)
|
|
||||||
{
|
|
||||||
// каждая подкоманда требует ровно 2 пути
|
|
||||||
printUsage(args[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
auto p1 = p.positional[0];
|
|
||||||
auto p2 = p.positional[1];
|
|
||||||
|
|
||||||
// 2) Аллокатор libxdiff
|
|
||||||
installAllocator();
|
|
||||||
|
|
||||||
// 3) Готовим входы
|
|
||||||
auto mf1 = MmFile.create();
|
|
||||||
scope (exit)
|
|
||||||
mf1.deinit();
|
|
||||||
auto mf2 = MmFile.create();
|
|
||||||
scope (exit)
|
|
||||||
mf2.deinit();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
loadMmFile(mf1, p1);
|
|
||||||
loadMmFile(mf2, p2);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
stderr.writeln(e.msg);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Запускаем действие
|
auto eng = new DiffEngine(3);
|
||||||
int rc = 0;
|
|
||||||
final switch (p.cmd)
|
string result; // сюда положим результат
|
||||||
|
auto ex = collectException(result = eng.diffFiles(args[1], args[2]));
|
||||||
|
if (ex !is null)
|
||||||
{
|
{
|
||||||
case Cmd.diff:
|
stderr.writeln(ex.msg);
|
||||||
rc = doDiff(mf1, mf2, p.ctxlen);
|
return 2;
|
||||||
break;
|
|
||||||
case Cmd.patch:
|
|
||||||
rc = doPatch(mf1, mf2, XDL_PATCH_NORMAL);
|
|
||||||
break;
|
|
||||||
case Cmd.bdiff:
|
|
||||||
rc = doBDiff(mf1, mf2, p.bsize);
|
|
||||||
break;
|
|
||||||
case Cmd.rabdiff:
|
|
||||||
rc = doRaBDiff(mf1, mf2);
|
|
||||||
break;
|
|
||||||
case Cmd.bpatch:
|
|
||||||
rc = doBPatch(mf1, mf2);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return rc < 0 ? 2 : 0;
|
writeln(result);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
197
source/diff.d
Normal file
197
source/diff.d
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
module diff;
|
||||||
|
|
||||||
|
import std.exception : enforce;
|
||||||
|
import std.file : read, exists; // бинарное чтение
|
||||||
|
import std.array : Appender, appender;
|
||||||
|
import std.string : representation, format;
|
||||||
|
import std.datetime : Clock, UTC;
|
||||||
|
|
||||||
|
import core.stdc.string : memcpy;
|
||||||
|
import core.stdc.stdlib : malloc, free, realloc;
|
||||||
|
|
||||||
|
import xdiff;
|
||||||
|
|
||||||
|
//-----------------------------
|
||||||
|
// Аллокатор для libxdiff (обязателен на ряде сборок)
|
||||||
|
//-----------------------------
|
||||||
|
private extern (C) void* _wrap_malloc(void*, uint size)
|
||||||
|
{
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private extern (C) void _wrap_free(void*, void* ptr)
|
||||||
|
{
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private extern (C) void* _wrap_realloc(void*, void* ptr, uint size)
|
||||||
|
{
|
||||||
|
return realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private __gshared bool _allocatorReady = false;
|
||||||
|
|
||||||
|
private void ensureAllocator()
|
||||||
|
{
|
||||||
|
if (_allocatorReady)
|
||||||
|
return;
|
||||||
|
memallocator_t malt;
|
||||||
|
malt.priv = null;
|
||||||
|
malt.malloc = &_wrap_malloc;
|
||||||
|
malt.free = &_wrap_free;
|
||||||
|
malt.realloc = &_wrap_realloc;
|
||||||
|
enforce(xdl_set_allocator(&malt) == 0, "xdl_set_allocator failed");
|
||||||
|
_allocatorReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------
|
||||||
|
// RAII для mmfile_t
|
||||||
|
//-----------------------------
|
||||||
|
private struct MmFile
|
||||||
|
{
|
||||||
|
mmfile_t mf;
|
||||||
|
|
||||||
|
static MmFile fromBytes(const(ubyte)[] data,
|
||||||
|
long bsize = 8 * 1024,
|
||||||
|
ulong flags = XDL_MMF_ATOMIC)
|
||||||
|
{
|
||||||
|
ensureAllocator(); // критично: до init/writeallocate
|
||||||
|
MmFile m;
|
||||||
|
enforce(xdl_init_mmfile(&m.mf, bsize, flags) == 0, "xdl_init_mmfile failed");
|
||||||
|
|
||||||
|
if (data.length)
|
||||||
|
{
|
||||||
|
auto dst = cast(char*) xdl_mmfile_writeallocate(&m.mf, data.length);
|
||||||
|
enforce(dst !is null, "xdl_mmfile_writeallocate failed");
|
||||||
|
memcpy(dst, data.ptr, data.length);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
xdl_free_mmfile(&mf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------
|
||||||
|
// Синк-коллектор вывода libxdiff
|
||||||
|
//-----------------------------
|
||||||
|
private class BufferSink
|
||||||
|
{
|
||||||
|
Appender!(ubyte[]) outbuf;
|
||||||
|
|
||||||
|
this()
|
||||||
|
{
|
||||||
|
outbuf = appender!(ubyte[])();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern (C) static int writeCB(void* priv, mmbuffer_t* mb, int nbuf)
|
||||||
|
{
|
||||||
|
auto self = cast(BufferSink) priv;
|
||||||
|
foreach (i; 0 .. nbuf)
|
||||||
|
{
|
||||||
|
size_t sz = cast(size_t) mb[i].size;
|
||||||
|
if (sz == 0)
|
||||||
|
continue;
|
||||||
|
self.outbuf.put((cast(ubyte*) mb[i].ptr)[0 .. sz]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string toStringOwned() @trusted
|
||||||
|
{
|
||||||
|
return cast(string) outbuf.data.idup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------
|
||||||
|
// Публичный движок unified-diff
|
||||||
|
//-----------------------------
|
||||||
|
final class DiffEngine
|
||||||
|
{
|
||||||
|
private long _ctxlen;
|
||||||
|
private bool _stripTrailingNewline;
|
||||||
|
|
||||||
|
this(long ctxlen = 3, bool stripTrailingNewline = false)
|
||||||
|
{
|
||||||
|
_ctxlen = ctxlen;
|
||||||
|
_stripTrailingNewline = stripTrailingNewline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Diff двух текстов (UTF-8 строки)
|
||||||
|
string diffText(string oldText, string newText,
|
||||||
|
string oldLabel = "old", string newLabel = "new")
|
||||||
|
{
|
||||||
|
const(ubyte)[] a = _prep(oldText);
|
||||||
|
const(ubyte)[] b = _prep(newText);
|
||||||
|
return diffBytes(a, b, oldLabel, newLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Diff по путям (файлы читаются целиком, бинарно)
|
||||||
|
string diffFiles(string oldPath, string newPath,
|
||||||
|
string oldLabel = "", string newLabel = "")
|
||||||
|
{
|
||||||
|
enforce(exists(oldPath), "no such file: " ~ oldPath);
|
||||||
|
enforce(exists(newPath), "no such file: " ~ newPath);
|
||||||
|
|
||||||
|
auto a = cast(const(ubyte)[]) read(oldPath); // бинарное чтение
|
||||||
|
auto b = cast(const(ubyte)[]) read(newPath);
|
||||||
|
|
||||||
|
if (oldLabel.length == 0)
|
||||||
|
oldLabel = oldPath;
|
||||||
|
if (newLabel.length == 0)
|
||||||
|
newLabel = newPath;
|
||||||
|
|
||||||
|
return diffBytes(a, b, oldLabel, newLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Базовый метод: diff двух буферов
|
||||||
|
string diffBytes(const(ubyte)[] oldBytes, const(ubyte)[] newBytes,
|
||||||
|
string oldLabel = "old", string newLabel = "new")
|
||||||
|
{
|
||||||
|
auto a = MmFile.fromBytes(oldBytes);
|
||||||
|
auto b = MmFile.fromBytes(newBytes);
|
||||||
|
|
||||||
|
auto sink = new BufferSink;
|
||||||
|
xdemitcb_t ecb;
|
||||||
|
ecb.priv = cast(void*) sink;
|
||||||
|
ecb.outf = &BufferSink.writeCB;
|
||||||
|
|
||||||
|
xpparam_t xpp;
|
||||||
|
xpp.flags = 0;
|
||||||
|
|
||||||
|
xdemitconf_t xecfg;
|
||||||
|
xecfg.ctxlen = _ctxlen;
|
||||||
|
|
||||||
|
auto pre = formatHeaders(oldLabel, newLabel);
|
||||||
|
|
||||||
|
auto rc = xdl_diff(&a.mf, &b.mf, &xpp, &xecfg, &ecb);
|
||||||
|
enforce(rc >= 0, format("xdl_diff failed (rc=%s)", rc));
|
||||||
|
|
||||||
|
return pre ~ sink.toStringOwned();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string formatHeaders(string oldLabel, string newLabel)
|
||||||
|
{
|
||||||
|
auto ts = Clock.currTime(UTC()).toISOExtString(); // YYYY-MM-DDTHH:MM:SS
|
||||||
|
return "--- " ~ oldLabel ~ "\t" ~ ts ~ "\n"
|
||||||
|
~ "+++ " ~ newLabel ~ "\t" ~ ts ~ "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const(ubyte)[] _prep(string s) @trusted
|
||||||
|
{
|
||||||
|
auto bytes = cast(const(ubyte)[]) s.representation;
|
||||||
|
if (_stripTrailingNewline && (bytes.length && bytes[$ - 1] != '\n'))
|
||||||
|
{
|
||||||
|
ubyte[] tmp;
|
||||||
|
tmp.length = bytes.length + 1;
|
||||||
|
if (bytes.length)
|
||||||
|
memcpy(tmp.ptr, bytes.ptr, bytes.length);
|
||||||
|
tmp[$ - 1] = cast(ubyte) '\n';
|
||||||
|
return cast(const(ubyte)[]) tmp;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue