From 06b2a2a9932d07ec36aaaffe0266b3d5b2720693 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 31 Aug 2025 01:30:39 +0300 Subject: [PATCH 1/4] diff --- source/app.d | 325 +++----------------------------------------------- source/diff.d | 197 ++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 311 deletions(-) create mode 100644 source/diff.d diff --git a/source/app.d b/source/app.d index 7b3a7ed..30e9f34 100644 --- a/source/app.d +++ b/source/app.d @@ -1,322 +1,25 @@ -import std.stdio : writeln, stderr, File; -import std.getopt; -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 std.stdio : writeln, stderr; +import std.exception : collectException; -import xdiff; - -// ------------------------------ -// 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; -} +import diff; int main(string[] args) { - // 1) Парсим - Parsed p; - try + if (args.length != 3) { - p = parseArgs(args); - } - 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); + stderr.writeln("use: ", args[0], " OLD NEW"); return 1; } - // 4) Запускаем действие - int rc = 0; - final switch (p.cmd) + auto eng = new DiffEngine(3); + + string result; // сюда положим результат + auto ex = collectException(result = eng.diffFiles(args[1], args[2])); + if (ex !is null) { - case Cmd.diff: - rc = doDiff(mf1, mf2, p.ctxlen); - 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; + stderr.writeln(ex.msg); + return 2; } - return rc < 0 ? 2 : 0; + writeln(result); + return 0; } diff --git a/source/diff.d b/source/diff.d new file mode 100644 index 0000000..d3b1310 --- /dev/null +++ b/source/diff.d @@ -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; + } +} From 32203fadc0f4adee00f7f06380200f97cab1f2f1 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 31 Aug 2025 15:50:02 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/diff.d | 386 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 246 insertions(+), 140 deletions(-) diff --git a/source/diff.d b/source/diff.d index d3b1310..eea1a55 100644 --- a/source/diff.d +++ b/source/diff.d @@ -1,197 +1,303 @@ +/* ============================================================ + Модуль: diff + Назначение: тонкая ООП-обёртка над libxdiff для вычисления unified-diff + Вход: строки/пути/массивы байт + Выход: единая строка с патчем (включая заголовки ---/+++) + + Краткая схема работы: + - Готовим два mmfile_t (формат libxdiff) из входных байтов. + - Настраиваем callback, который будет собирать текст диффа в буфер. + - Вызываем xdl_diff(..), который сравнивает и «льёт» вывод через callback. + - Склеиваем заголовки (---/+++) и «тело» патча, отдаём как string. + + Замечания: + - Всё в памяти: входы читаются целиком, результат накапливается целиком. + - Для бинарного сравнения лучше использовать bdiff/rabdiff/bpatch (можно добавить). + - Заголовки формируются в этом модуле (libxdiff их сам не печатает). + - При необходимости можно ввести нормализацию CRLF/пробелов флагами xpp/xecfg. + ============================================================ */ + 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 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 core.stdc.string : memcpy; // копирование в буферы libxdiff +import core.stdc.stdlib : malloc, free, realloc; // системный аллокатор для прокидывания в xdiff -import xdiff; +import xdiff; // C-API libxdiff (xdiff.h) + +/* ============================================================ + Аллокатор для libxdiff + ------------------------------------------------------------ + На ряде сборок libxdiff ожидает, что клиент задаст функции + выделения памяти через xdl_set_allocator(). Если этого не + сделать, xdl_mmfile_writeallocate() может возвращать null. + ============================================================ */ -//----------------------------- -// Аллокатор для libxdiff (обязателен на ряде сборок) -//----------------------------- private extern (C) void* _wrap_malloc(void*, uint size) { - return malloc(size); + // Проксируем к системному malloc + return malloc(size); } private extern (C) void _wrap_free(void*, void* ptr) { - free(ptr); + // Проксируем к системному free + free(ptr); } private extern (C) void* _wrap_realloc(void*, void* ptr, uint size) { - return realloc(ptr, size); + // Проксируем к системному realloc + return realloc(ptr, size); } +// Глобальный флаг «аллокатор уже установлен». __gshared — на уровне процесса. 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; + // Важно: вызывать до любых операций с mmfile_t + if (_allocatorReady) + return; + + memallocator_t malt; + malt.priv = null; + malt.malloc = &_wrap_malloc; + malt.free = &_wrap_free; + malt.realloc = &_wrap_realloc; + + // Если вернуть не 0 — значит libxdiff отверг конфигурацию + enforce(xdl_set_allocator(&malt) == 0, "xdl_set_allocator failed"); + _allocatorReady = true; + + /* Потокобезопасность: + Это «ленивая» инициализация без синхронизации. В многопоточной среде + второй параллельный вызов может повторно вызвать xdl_set_allocator(), + что обычно безвредно. Если нужна строгая гарантия — оберните это место + мьютексом/atomic-флагом. */ } -//----------------------------- -// RAII для mmfile_t -//----------------------------- +/* ============================================================ + RAII для mmfile_t + ------------------------------------------------------------ + Упрощает корректную инициализацию/очистку mmfile_t. Метод + fromBytes() создаёт mmfile, выделяет буфер и копирует данные. + ============================================================ */ + private struct MmFile { - mmfile_t mf; + mmfile_t mf; // «ручка» на содержимое в терминах libxdiff - 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"); + static MmFile fromBytes(const(ubyte)[] data, + long bsize = 8 * 1024, + ulong flags = XDL_MMF_ATOMIC) + { + // Критично: аллокатор должен быть настроен до init/writeallocate + ensureAllocator(); - 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; - } + MmFile m; + // Инициализируем внутренние структуры mmfile + enforce(xdl_init_mmfile(&m.mf, bsize, flags) == 0, "xdl_init_mmfile failed"); - ~this() - { - xdl_free_mmfile(&mf); - } + // Копируем полезные данные в непрерывный блок, выделенный libxdiff + if (data.length) + { + auto dst = cast(char*) xdl_mmfile_writeallocate(&m.mf, data.length); + // Если тут null — проверьте flags/bsize; попробуйте flags=0, bsize=32*1024 + enforce(dst !is null, "xdl_mmfile_writeallocate failed"); + memcpy(dst, data.ptr, data.length); + } + return m; + } + + ~this() + { + // Освобождаем ресурсы mmfile_t (всегда вызывается, даже при исключениях) + xdl_free_mmfile(&mf); + } } -//----------------------------- -// Синк-коллектор вывода libxdiff -//----------------------------- +/* ============================================================ + Синк-коллектор вывода libxdiff + ------------------------------------------------------------ + libxdiff печатает результат через C-callback (outf). Мы даём + свой writeCB(), который складывает куски в Appender!(ubyte[]). + ============================================================ */ + private class BufferSink { - Appender!(ubyte[]) outbuf; + // Appender — дешёвая обёртка над динамическим массивом + Appender!(ubyte[]) outbuf; - this() - { - outbuf = appender!(ubyte[])(); - } + 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; - } + // Сигнатура коллбэка задана libxdiff: priv — наш контекст, mb — массив буферов + 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; + // mmbuffer_t.ptr — char*; приводим к ubyte* и аппендим + self.outbuf.put((cast(ubyte*) mb[i].ptr)[0 .. sz]); + } + return 0; // 0 — успех + } - string toStringOwned() @trusted - { - return cast(string) outbuf.data.idup; - } + // Возвращает независимую копию накопленных байт в виде string (UTF-8 предполагается) + string toStringOwned() @trusted + { + // @trusted: мы знаем, что libxdiff генерирует ASCII/UTF-8 + return cast(string) outbuf.data.idup; + } } -//----------------------------- -// Публичный движок unified-diff -//----------------------------- +/* ============================================================ + Публичный движок unified-diff + ------------------------------------------------------------ + Основной API: + - diffText: сравнить две строки + - diffFiles: сравнить два файла (читаются бинарно) + - diffBytes: сравнить два массива байт + Параметры: + - ctxlen: длина контекста (кол-во строк вокруг изменений) + - stripTrailingNewline: при true добавляет '\n', если его нет + ============================================================ */ + final class DiffEngine { - private long _ctxlen; - private bool _stripTrailingNewline; + private long _ctxlen; // длина контекста unified-diff + private bool _stripTrailingNewline; // косметика: выравнивание окончания файла - this(long ctxlen = 3, bool stripTrailingNewline = false) - { - _ctxlen = ctxlen; - _stripTrailingNewline = 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 двух текстов (UTF-8 строки). Метки для заголовков можно задать явно. + string diffText(string oldText, string newText, + string oldLabel = "old", string newLabel = "new") + { + // representation — дешёвый способ получить «сырые» байты из string + 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); + /// 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); + // Бинарное чтение исключает «сюрпризы» перекодировок/CRLF + 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; + // Если метки не заданы — используем сами пути + if (oldLabel.length == 0) + oldLabel = oldPath; + if (newLabel.length == 0) + newLabel = newPath; - return diffBytes(a, b, oldLabel, newLabel); - } + 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); + /// Базовый метод: diff двух массивов байт (ядро логики) + string diffBytes(const(ubyte)[] oldBytes, const(ubyte)[] newBytes, + string oldLabel = "old", string newLabel = "new") + { + // 1) Готовим источники для libxdiff + 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; + // 2) Готовим приёмник вывода: callback + приватный контекст + auto sink = new BufferSink; + xdemitcb_t ecb; + ecb.priv = cast(void*) sink; // будет возвращено в writeCB через priv + ecb.outf = &BufferSink.writeCB; - xpparam_t xpp; - xpp.flags = 0; + // 3) Параметры xdiff + xpparam_t xpp; + xpp.flags = 0; /* здесь можно задать доп. флаги парсера: + - XDF_* / XDF_INDENT_HEURISTIC и т.п. (см. xdiff.h) + - игнор пробелов/табов и др., если поддерживается */ - xdemitconf_t xecfg; - xecfg.ctxlen = _ctxlen; + xdemitconf_t xecfg; + xecfg.ctxlen = _ctxlen; // длина контекста в unified-diff - auto pre = formatHeaders(oldLabel, newLabel); + // 4) Формируем заголовки unified-diff (libxdiff сам их не печатает) + 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)); + // 5) Запускаем сравнение — результат «потечёт» в writeCB -> sink.outbuf + 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(); - } + // 6) Склеиваем заголовок и «тело» патча, отдаём как одну строку + 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"; - } + /// Заголовки unified-diff в стиле `--- LABEL \t UTC` / `+++ LABEL \t UTC` + static string formatHeaders(string oldLabel, string newLabel) + { + // Ставим ISO-подобный штамп в UTC, чтобы было стабильно и однозначно + 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; - } + // Подготовка входной строки к подаче в MmFile: по желанию добавляем завершающий '\n' + const(ubyte)[] _prep(string s) @trusted + { + // Быстрый доступ к байтам строки (без аллокаций/копий) + auto bytes = cast(const(ubyte)[]) s.representation; + + // Если включено «сглаживание» и у строки нет завершающего '\n' — добавим его + if (_stripTrailingNewline && (bytes.length && bytes[$ - 1] != '\n')) + { + // Собираем ubyte[] на 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; + } } + +/* ============================================================ + ИДЕИ ДЛЯ РАСШИРЕНИЯ (можно реализовать позже) + ------------------------------------------------------------ + 1) Флаги сравнения: + - Пробросить наружу xpp.flags (игнор пробелов, эвристики и т.п.). + + 2) Нормализация перевода строк: + - Добавить опцию для унификации CRLF->LF на входе, чтобы не шуметь. + + 3) Вывод напрямую в FD/файл: + - Реализовать методы diffToFD(int fd)/diffToFile(string path), + где ecb.outf = fdOut, аналогичный твоему CLI примеру. + + 4) Бинарные режимы: + - Обернуть xdl_bdiff/xdl_rabdiff/xdl_bpatch в том же стиле, + если потребуется компактный бинарный патч. + + 5) Потокобезопасность ensureAllocator(): + - Заменить «ленивый флаг» на mutex/atomic, если модуль будет + вызываться конкурентно из нескольких потоков на старте. + ============================================================ */ From ef491965f63798e939f9f763a6614861b608538f Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 31 Aug 2025 16:47:55 +0300 Subject: [PATCH 3/4] from rust --- dub.json | 6 +- dub.selections.json | 6 + source/app.d | 32 ++--- source/diff.d | 303 -------------------------------------------- 4 files changed, 21 insertions(+), 326 deletions(-) create mode 100644 dub.selections.json delete mode 100644 source/diff.d diff --git a/dub.json b/dub.json index d9c5233..33c9c1e 100644 --- a/dub.json +++ b/dub.json @@ -12,9 +12,9 @@ "xdiff" ], "dependencies": { - "xdiff": { - "repository": "git+https://git.zhirov.kz/dlang/xdiff.git", - "version": "e2396bc172eba813cdcd1a96c494e35d687f576a" + "libxdiff": { + "repository": "git+https://git.zhirov.kz/dlang/libxdiff.git", + "version": "143f39005c0e89cc57d564365e6ea61427928bc3" } } } \ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..c3c6475 --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,6 @@ +{ + "fileVersion": 1, + "versions": { + "libxdiff": {"version":"143f39005c0e89cc57d564365e6ea61427928bc3","repository":"git+https://git.zhirov.kz/dlang/libxdiff.git"} + } +} diff --git a/source/app.d b/source/app.d index 30e9f34..9101d29 100644 --- a/source/app.d +++ b/source/app.d @@ -1,25 +1,17 @@ -import std.stdio : writeln, stderr; -import std.exception : collectException; +import std.stdio; +import libxdiff : MMFile; -import diff; - -int main(string[] args) +void main() { - if (args.length != 3) - { - stderr.writeln("use: ", args[0], " OLD NEW"); - return 1; - } + auto a = MMFile.fromBytes(cast(ubyte[]) "hello world\n"); + auto b = MMFile.fromBytes(cast(ubyte[]) "hello world!\n"); - auto eng = new DiffEngine(3); + auto patch = a.computePatch(b); + writeln("patch size: ", patch.size()); - string result; // сюда положим результат - auto ex = collectException(result = eng.diffFiles(args[1], args[2])); - if (ex !is null) - { - stderr.writeln(ex.msg); - return 2; - } - writeln(result); - return 0; + auto res = a.applyPatch(patch); + writeln("apply success: ", res.success); + writeln(res.patched.asSlice()); // печатаем результат + // печатаем как есть (включая заголовок @@ и строки с '-'/'+' и '\n') + write(cast(string) MMFile.fromBlocksMoved(patch).asSlice()); } diff --git a/source/diff.d b/source/diff.d deleted file mode 100644 index eea1a55..0000000 --- a/source/diff.d +++ /dev/null @@ -1,303 +0,0 @@ -/* ============================================================ - Модуль: diff - Назначение: тонкая ООП-обёртка над libxdiff для вычисления unified-diff - Вход: строки/пути/массивы байт - Выход: единая строка с патчем (включая заголовки ---/+++) - - Краткая схема работы: - - Готовим два mmfile_t (формат libxdiff) из входных байтов. - - Настраиваем callback, который будет собирать текст диффа в буфер. - - Вызываем xdl_diff(..), который сравнивает и «льёт» вывод через callback. - - Склеиваем заголовки (---/+++) и «тело» патча, отдаём как string. - - Замечания: - - Всё в памяти: входы читаются целиком, результат накапливается целиком. - - Для бинарного сравнения лучше использовать bdiff/rabdiff/bpatch (можно добавить). - - Заголовки формируются в этом модуле (libxdiff их сам не печатает). - - При необходимости можно ввести нормализацию CRLF/пробелов флагами xpp/xecfg. - ============================================================ */ - -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; // копирование в буферы libxdiff -import core.stdc.stdlib : malloc, free, realloc; // системный аллокатор для прокидывания в xdiff - -import xdiff; // C-API libxdiff (xdiff.h) - -/* ============================================================ - Аллокатор для libxdiff - ------------------------------------------------------------ - На ряде сборок libxdiff ожидает, что клиент задаст функции - выделения памяти через xdl_set_allocator(). Если этого не - сделать, xdl_mmfile_writeallocate() может возвращать null. - ============================================================ */ - -private extern (C) void* _wrap_malloc(void*, uint size) -{ - // Проксируем к системному malloc - return malloc(size); -} - -private extern (C) void _wrap_free(void*, void* ptr) -{ - // Проксируем к системному free - free(ptr); -} - -private extern (C) void* _wrap_realloc(void*, void* ptr, uint size) -{ - // Проксируем к системному realloc - return realloc(ptr, size); -} - -// Глобальный флаг «аллокатор уже установлен». __gshared — на уровне процесса. -private __gshared bool _allocatorReady = false; - -private void ensureAllocator() -{ - // Важно: вызывать до любых операций с mmfile_t - if (_allocatorReady) - return; - - memallocator_t malt; - malt.priv = null; - malt.malloc = &_wrap_malloc; - malt.free = &_wrap_free; - malt.realloc = &_wrap_realloc; - - // Если вернуть не 0 — значит libxdiff отверг конфигурацию - enforce(xdl_set_allocator(&malt) == 0, "xdl_set_allocator failed"); - _allocatorReady = true; - - /* Потокобезопасность: - Это «ленивая» инициализация без синхронизации. В многопоточной среде - второй параллельный вызов может повторно вызвать xdl_set_allocator(), - что обычно безвредно. Если нужна строгая гарантия — оберните это место - мьютексом/atomic-флагом. */ -} - -/* ============================================================ - RAII для mmfile_t - ------------------------------------------------------------ - Упрощает корректную инициализацию/очистку mmfile_t. Метод - fromBytes() создаёт mmfile, выделяет буфер и копирует данные. - ============================================================ */ - -private struct MmFile -{ - mmfile_t mf; // «ручка» на содержимое в терминах libxdiff - - static MmFile fromBytes(const(ubyte)[] data, - long bsize = 8 * 1024, - ulong flags = XDL_MMF_ATOMIC) - { - // Критично: аллокатор должен быть настроен до init/writeallocate - ensureAllocator(); - - MmFile m; - // Инициализируем внутренние структуры mmfile - enforce(xdl_init_mmfile(&m.mf, bsize, flags) == 0, "xdl_init_mmfile failed"); - - // Копируем полезные данные в непрерывный блок, выделенный libxdiff - if (data.length) - { - auto dst = cast(char*) xdl_mmfile_writeallocate(&m.mf, data.length); - // Если тут null — проверьте flags/bsize; попробуйте flags=0, bsize=32*1024 - enforce(dst !is null, "xdl_mmfile_writeallocate failed"); - memcpy(dst, data.ptr, data.length); - } - return m; - } - - ~this() - { - // Освобождаем ресурсы mmfile_t (всегда вызывается, даже при исключениях) - xdl_free_mmfile(&mf); - } -} - -/* ============================================================ - Синк-коллектор вывода libxdiff - ------------------------------------------------------------ - libxdiff печатает результат через C-callback (outf). Мы даём - свой writeCB(), который складывает куски в Appender!(ubyte[]). - ============================================================ */ - -private class BufferSink -{ - // Appender — дешёвая обёртка над динамическим массивом - Appender!(ubyte[]) outbuf; - - this() - { - outbuf = appender!(ubyte[])(); - } - - // Сигнатура коллбэка задана libxdiff: priv — наш контекст, mb — массив буферов - 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; - // mmbuffer_t.ptr — char*; приводим к ubyte* и аппендим - self.outbuf.put((cast(ubyte*) mb[i].ptr)[0 .. sz]); - } - return 0; // 0 — успех - } - - // Возвращает независимую копию накопленных байт в виде string (UTF-8 предполагается) - string toStringOwned() @trusted - { - // @trusted: мы знаем, что libxdiff генерирует ASCII/UTF-8 - return cast(string) outbuf.data.idup; - } -} - -/* ============================================================ - Публичный движок unified-diff - ------------------------------------------------------------ - Основной API: - - diffText: сравнить две строки - - diffFiles: сравнить два файла (читаются бинарно) - - diffBytes: сравнить два массива байт - Параметры: - - ctxlen: длина контекста (кол-во строк вокруг изменений) - - stripTrailingNewline: при true добавляет '\n', если его нет - ============================================================ */ - -final class DiffEngine -{ - private long _ctxlen; // длина контекста unified-diff - 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") - { - // representation — дешёвый способ получить «сырые» байты из string - 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); - - // Бинарное чтение исключает «сюрпризы» перекодировок/CRLF - 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") - { - // 1) Готовим источники для libxdiff - auto a = MmFile.fromBytes(oldBytes); - auto b = MmFile.fromBytes(newBytes); - - // 2) Готовим приёмник вывода: callback + приватный контекст - auto sink = new BufferSink; - xdemitcb_t ecb; - ecb.priv = cast(void*) sink; // будет возвращено в writeCB через priv - ecb.outf = &BufferSink.writeCB; - - // 3) Параметры xdiff - xpparam_t xpp; - xpp.flags = 0; /* здесь можно задать доп. флаги парсера: - - XDF_* / XDF_INDENT_HEURISTIC и т.п. (см. xdiff.h) - - игнор пробелов/табов и др., если поддерживается */ - - xdemitconf_t xecfg; - xecfg.ctxlen = _ctxlen; // длина контекста в unified-diff - - // 4) Формируем заголовки unified-diff (libxdiff сам их не печатает) - auto pre = formatHeaders(oldLabel, newLabel); - - // 5) Запускаем сравнение — результат «потечёт» в writeCB -> sink.outbuf - auto rc = xdl_diff(&a.mf, &b.mf, &xpp, &xecfg, &ecb); - enforce(rc >= 0, format("xdl_diff failed (rc=%s)", rc)); - - // 6) Склеиваем заголовок и «тело» патча, отдаём как одну строку - return pre ~ sink.toStringOwned(); - } - - /// Заголовки unified-diff в стиле `--- LABEL \t UTC` / `+++ LABEL \t UTC` - static string formatHeaders(string oldLabel, string newLabel) - { - // Ставим ISO-подобный штамп в UTC, чтобы было стабильно и однозначно - auto ts = Clock.currTime(UTC()).toISOExtString(); // YYYY-MM-DDTHH:MM:SS - return "--- " ~ oldLabel ~ "\t" ~ ts ~ "\n" - ~ "+++ " ~ newLabel ~ "\t" ~ ts ~ "\n"; - } - -private: - // Подготовка входной строки к подаче в MmFile: по желанию добавляем завершающий '\n' - const(ubyte)[] _prep(string s) @trusted - { - // Быстрый доступ к байтам строки (без аллокаций/копий) - auto bytes = cast(const(ubyte)[]) s.representation; - - // Если включено «сглаживание» и у строки нет завершающего '\n' — добавим его - if (_stripTrailingNewline && (bytes.length && bytes[$ - 1] != '\n')) - { - // Собираем ubyte[] на 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; - } -} - -/* ============================================================ - ИДЕИ ДЛЯ РАСШИРЕНИЯ (можно реализовать позже) - ------------------------------------------------------------ - 1) Флаги сравнения: - - Пробросить наружу xpp.flags (игнор пробелов, эвристики и т.п.). - - 2) Нормализация перевода строк: - - Добавить опцию для унификации CRLF->LF на входе, чтобы не шуметь. - - 3) Вывод напрямую в FD/файл: - - Реализовать методы diffToFD(int fd)/diffToFile(string path), - где ecb.outf = fdOut, аналогичный твоему CLI примеру. - - 4) Бинарные режимы: - - Обернуть xdl_bdiff/xdl_rabdiff/xdl_bpatch в том же стиле, - если потребуется компактный бинарный патч. - - 5) Потокобезопасность ensureAllocator(): - - Заменить «ленивый флаг» на mutex/atomic, если модуль будет - вызываться конкурентно из нескольких потоков на старте. - ============================================================ */ From e7d16e6969ed05d2f0dc1d7f22faef7b5a39fa96 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Mon, 1 Sep 2025 02:02:13 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=B1=D1=8B=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20diff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dub.json | 6 +-- dub.selections.json | 2 +- source/app.d | 25 ++++++------ source/libxdiff/init.d | 70 +++++++++++++++++++++++++++++++++ source/libxdiff/mmblocks.d | 41 ++++++++++++++++++++ source/libxdiff/mmfile.d | 79 ++++++++++++++++++++++++++++++++++++++ source/libxdiff/package.d | 4 ++ 7 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 source/libxdiff/init.d create mode 100644 source/libxdiff/mmblocks.d create mode 100644 source/libxdiff/mmfile.d create mode 100644 source/libxdiff/package.d diff --git a/dub.json b/dub.json index 33c9c1e..d9c5233 100644 --- a/dub.json +++ b/dub.json @@ -12,9 +12,9 @@ "xdiff" ], "dependencies": { - "libxdiff": { - "repository": "git+https://git.zhirov.kz/dlang/libxdiff.git", - "version": "143f39005c0e89cc57d564365e6ea61427928bc3" + "xdiff": { + "repository": "git+https://git.zhirov.kz/dlang/xdiff.git", + "version": "e2396bc172eba813cdcd1a96c494e35d687f576a" } } } \ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json index c3c6475..c2bab4e 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,6 +1,6 @@ { "fileVersion": 1, "versions": { - "libxdiff": {"version":"143f39005c0e89cc57d564365e6ea61427928bc3","repository":"git+https://git.zhirov.kz/dlang/libxdiff.git"} + "libxdiff": {"version":"e2396bc172eba813cdcd1a96c494e35d687f576a","repository":"git+https://git.zhirov.kz/dlang/xdiff.git"} } } diff --git a/source/app.d b/source/app.d index 9101d29..b1e9a4b 100644 --- a/source/app.d +++ b/source/app.d @@ -1,17 +1,20 @@ -import std.stdio; -import libxdiff : MMFile; +import libxdiff; + +import std.conv : to; +import std.stdio : writefln, writeln; +import std.file : read; void main() { - auto a = MMFile.fromBytes(cast(ubyte[]) "hello world\n"); - auto b = MMFile.fromBytes(cast(ubyte[]) "hello world!\n"); + auto file1 = new MMFile(cast(const(ubyte)[]) read("/tmp/diff1.d")); + auto file2 = new MMFile(cast(const(ubyte)[]) read("/tmp/diff2.d")); - auto patch = a.computePatch(b); - writeln("patch size: ", patch.size()); + auto patch = file1.computePatch(file2); - auto res = a.applyPatch(patch); - writeln("apply success: ", res.success); - writeln(res.patched.asSlice()); // печатаем результат - // печатаем как есть (включая заголовок @@ и строки с '-'/'+' и '\n') - write(cast(string) MMFile.fromBlocksMoved(patch).asSlice()); + writeln(patch); + + writefln( + "file1: %s\nfile2: %s", + file1.size, file2.size + ); } diff --git a/source/libxdiff/init.d b/source/libxdiff/init.d new file mode 100644 index 0000000..546679c --- /dev/null +++ b/source/libxdiff/init.d @@ -0,0 +1,70 @@ +module libxdiff.init; + +import xdiff; + +import core.stdc.stdlib : malloc, free, realloc; +import std.exception : enforce; +import core.stdc.errno : errno; +import core.stdc.string : strerror; +import std.string : fromStringz; +import std.conv : to; + +private __gshared bool _allocatorReady = false; +package(libxdiff) alias ByteArray = const(ubyte)[]; + +extern (C) +{ + private void* wrap_malloc(void*, uint size) + { + return malloc(size); + } + + private void wrap_free(void*, void* p) + { + free(p); + } + + private void* wrap_realloc(void*, void* p, uint size) + { + return realloc(p, size); + } +} + +private void setAllocator() +{ + if (_allocatorReady) + return; + + memallocator_t m = {null, &wrap_malloc, &wrap_free, &wrap_realloc}; + + enforce(xdl_set_allocator(&m) == 0, + "xdl_set_allocator failed: " ~ strerror(errno).fromStringz); + _allocatorReady = true; +} + +mmfile_t initMmfile(size_t len) +{ + setAllocator(); + mmfile_t mf; + enforce(xdl_init_mmfile(&mf, len.to!long, XDL_MMF_ATOMIC) == 0, + "xdl_init_mmfile failed: " ~ strerror(errno).fromStringz); + return mf; +} + +abstract class MM +{ +protected: + mmfile_t _inner; +public: + final @property size_t size() + { + return xdl_mmfile_size(&_inner).to!size_t; + } + + // Проверка «компактности» — содержимое лежит в одном блоке + // (важно для бинарных функций, которым нужно сплошное непрерывное представление). + final bool isCompact() const + { + return xdl_mmfile_iscompact(cast(mmfile_t*)&_inner) != 0; + } +} diff --git a/source/libxdiff/mmblocks.d b/source/libxdiff/mmblocks.d new file mode 100644 index 0000000..8ec975a --- /dev/null +++ b/source/libxdiff/mmblocks.d @@ -0,0 +1,41 @@ +module libxdiff.mmblocks; + +import xdiff; + +import libxdiff.init; + +import std.exception : enforce; +import core.stdc.errno : errno; +import core.stdc.string : strerror; +import std.string : fromStringz; +import std.conv : to; + +private enum DEFAULT_BSIZE = 8 * 1024; + +final class MMBlocks : MM +{ +public: + this() + { + _inner = initMmfile(DEFAULT_BSIZE); + } + + package(libxdiff) mmfile_t* ptr() @trusted + { + return &_inner; + } + + const(ubyte)[] asSlice() @trusted + { + enforce(isCompact(), "MMFile must be compact for asSliceConst"); + auto h = _inner.head; + return h is null || h.size <= 0 ? [] : (cast(const(ubyte)*) h.ptr)[0 .. h.size.to!size_t]; + } + + override string toString() const @trusted + { + enforce(isCompact(), "MMFile must be compact for asSliceConst"); + auto h = _inner.head; + return h is null || h.size <= 0 ? [] : fromStringz(cast(const char*) h.ptr).idup; + } +} diff --git a/source/libxdiff/mmfile.d b/source/libxdiff/mmfile.d new file mode 100644 index 0000000..1bcbca4 --- /dev/null +++ b/source/libxdiff/mmfile.d @@ -0,0 +1,79 @@ +module libxdiff.mmfile; + +import xdiff; + +import libxdiff.init; +import libxdiff.mmblocks; + +import std.exception : enforce; +import core.stdc.errno : errno; +import core.stdc.string : strerror; +import std.string : fromStringz; +import std.conv : to; +import std.string : join, representation; + +final class MMFile : MM +{ +private: + void _write(ByteArray data) + { + _inner = initMmfile(0); + + if (data.length > 0) + { + auto length = data.length.to!long; + auto wrote = xdl_write_mmfile(&_inner, data.ptr, length); + enforce(wrote == length, + "xdl_write_mmfile wrote less than requested: " ~ strerror(errno).fromStringz); + } + } + +public: + this(ByteArray data) + { + _write(data); + } + + this(string data) + { + _write(data.representation); + } + + this(string[] data) + { + _write(data.join('\n').representation); + } + + ~this() + { + if (_inner.head !is null) + xdl_free_mmfile(&_inner); + } + + MMBlocks computePatch(ref MMFile other, long ctxlen = 3) + { + auto patch = new MMBlocks(); + + extern (C) int emitToPatch(void* priv, mmbuffer_t* bufs, int num) + { + auto target = cast(MMBlocks) priv; + foreach (i; 0 .. num) + { + mmbuffer_t* buf = &bufs[i]; + long written = xdl_writem_mmfile(target.ptr(), buf, 1); + if (written < 0 || written != buf.size) + return -1; + } + return 0; + } + + xpparam_t xpp = {0}; + xdemitconf_t xec = {ctxlen}; + xdemitcb_t ecb = {cast(void*) patch, &emitToPatch}; + + enforce(xdl_diff(&_inner, &other._inner, &xpp, &xec, &ecb) == 0, + "xdl_diff failed: " ~ strerror(errno).fromStringz); + + return patch; + } +} diff --git a/source/libxdiff/package.d b/source/libxdiff/package.d new file mode 100644 index 0000000..3e27fe2 --- /dev/null +++ b/source/libxdiff/package.d @@ -0,0 +1,4 @@ +module libxdiff; + +public import libxdiff.mmfile; +public import libxdiff.mmblocks;