197 lines
4.7 KiB
D
197 lines
4.7 KiB
D
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;
|
|
}
|
|
}
|