diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..c2bab4e --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,6 @@ +{ + "fileVersion": 1, + "versions": { + "libxdiff": {"version":"e2396bc172eba813cdcd1a96c494e35d687f576a","repository":"git+https://git.zhirov.kz/dlang/xdiff.git"} + } +} diff --git a/source/app.d b/source/app.d index 7b3a7ed..b1e9a4b 100644 --- a/source/app.d +++ b/source/app.d @@ -1,322 +1,20 @@ -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 libxdiff; -import xdiff; +import std.conv : to; +import std.stdio : writefln, writeln; +import std.file : read; -// ------------------------------ -// RAII для mmfile_t -// ------------------------------ -struct MmFile +void main() { - mmfile_t mf; + auto file1 = new MMFile(cast(const(ubyte)[]) read("/tmp/diff1.d")); + auto file2 = new MMFile(cast(const(ubyte)[]) read("/tmp/diff2.d")); - // фабрика вместо конструктора по умолчанию - 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 patch = file1.computePatch(file2); - 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` - - + writeln(patch); + writefln( + "file1: %s\nfile2: %s", + file1.size, file2.size ); - 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; } 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;