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