From e9952bacf2c8a76e3cd8b0370ea4124e29215fe8 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sun, 31 Aug 2025 00:38:29 +0300 Subject: [PATCH] init --- .gitignore | 17 +++ .vscode/launch.json | 17 +++ .vscode/settings.json | 5 + README.md | 11 ++ dub.json | 20 +++ source/app.d | 322 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 392 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 dub.json create mode 100644 source/app.d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc1f6c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.dub +docs.json +__dummy.html +docs/ +/dxdiff +dxdiff.so +dxdiff.dylib +dxdiff.dll +dxdiff.a +dxdiff.lib +dxdiff-test-* +*.exe +*.pdb +*.o +*.obj +*.lst +/bin diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..70e0918 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Используйте IntelliSense, чтобы узнать о возможных атрибутах. + // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов. + // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "code-d", + "request": "launch", + "dubBuild": true, + "name": "Build & Debug DUB project", + "cwd": "${command:dubWorkingDirectory}", + "program": "bin/${command:dubTarget}", + "args": [] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d1c022f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.detectIndentation": false +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..758f959 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# dxdiff + +Тестовый пример использования библиотеки LibXDiff. + +``` +use: ./bin/dxdiff diff [-C N] FROM TO + ./bin/dxdiff patch ORIG PATCH + ./bin/dxdiff bdiff [-B N] FROM TO + ./bin/dxdiff rabdiff FROM TO + ./bin/dxdiff bpatch ORIG PATCH +``` diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..d9c5233 --- /dev/null +++ b/dub.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "alexander" + ], + "copyright": "Copyright © 2025, alexander", + "description": "A minimal D application.", + "license": "proprietary", + "name": "dxdiff", + "targetPath": "bin", + "targetType": "executable", + "libs": [ + "xdiff" + ], + "dependencies": { + "xdiff": { + "repository": "git+https://git.zhirov.kz/dlang/xdiff.git", + "version": "e2396bc172eba813cdcd1a96c494e35d687f576a" + } + } +} \ No newline at end of file diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..7b3a7ed --- /dev/null +++ b/source/app.d @@ -0,0 +1,322 @@ +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 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; +} + +int main(string[] args) +{ + // 1) Парсим + Parsed p; + try + { + 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); + return 1; + } + + // 4) Запускаем действие + int rc = 0; + final switch (p.cmd) + { + 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; + } + return rc < 0 ? 2 : 0; +}