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 deleted file mode 100644 index c3c6475..0000000 --- a/dub.selections.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "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 9101d29..7b3a7ed 100644 --- a/source/app.d +++ b/source/app.d @@ -1,17 +1,322 @@ -import std.stdio; -import libxdiff : MMFile; +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; -void main() +import xdiff; + +// ------------------------------ +// RAII для mmfile_t +// ------------------------------ +struct MmFile { - auto a = MMFile.fromBytes(cast(ubyte[]) "hello world\n"); - auto b = MMFile.fromBytes(cast(ubyte[]) "hello world!\n"); + mmfile_t mf; - auto patch = a.computePatch(b); - writeln("patch size: ", patch.size()); + // фабрика вместо конструктора по умолчанию + 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; + } - auto res = a.applyPatch(patch); - writeln("apply success: ", res.success); - writeln(res.patched.asSlice()); // печатаем результат - // печатаем как есть (включая заголовок @@ и строки с '-'/'+' и '\n') - write(cast(string) MMFile.fromBlocksMoved(patch).asSlice()); + 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; }