From ef491965f63798e939f9f763a6614861b608538f Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 31 Aug 2025 16:47:55 +0300 Subject: [PATCH] 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, если модуль будет - вызываться конкурентно из нескольких потоков на старте. - ============================================================ */