This commit is contained in:
Alexander Zhirov 2025-08-31 01:30:39 +03:00
parent e9952bacf2
commit 06b2a2a993
Signed by: alexander
GPG key ID: C8D8BE544A27C511
2 changed files with 211 additions and 311 deletions

View file

@ -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
View 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;
}
}