commit 8c388b112360a9f5bf89b9aa12b1977b8967b219 Author: Alexander Zhirov Date: Mon Sep 1 03:21:54 2025 +0300 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c49b07f --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.dub +docs.json +__dummy.html +docs/ +/sdiff +sdiff.so +sdiff.dylib +sdiff.dll +sdiff.a +sdiff.lib +sdiff-test-* +*.exe +*.pdb +*.o +*.obj +*.lst 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/LICENSE b/LICENSE new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7644d85 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Simply Diff + +A minimal D wrapper over [xdiff.d](https://git.zhirov.kz/dlang/xdiff) for computing file differences and generating patches. + +## Features + +* Create in-memory files (`MMFile`) from strings, string arrays, or raw bytes. +* Compute diffs between two in-memory files. +* Collect results as `MMBlocks`, convertible to a string or byte slice. + +## Installation + +Add to `dub.json`: + +```json +{ + "dependencies": { + "sdiff": "~>0.1.0" + } +} +``` + +Or + +```json +{ + "dependencies": { + "sdiff": { + "repository": "git+https://git.zhirov.kz/dlang/sdiff.git", + "version": "" + } + } +} +``` + +Run `dub build`. + +## Example + +```d +import sdiff; +import std.stdio : writeln; + +void main() +{ + auto a = new MMFile("hello world\n"); + auto b = new MMFile("hello world!\n"); + + auto patch = a.diff(b); + + writeln(patch); +} +``` + +Output: + +```diff +@@ -1,1 +1,1 @@ +-hello world ++hello world! +``` + +## License + +Boost Software License 1.0. See [LICENSE](LICENSE). diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..d7390e8 --- /dev/null +++ b/dub.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "Alexander Zhirov" + ], + "copyright": "Copyright © 2025, Alexander Zhirov", + "description": "A minimal D wrapper over xdiff.d for computing file differences and generating patches.", + "license": "BSL-1.0", + "name": "sdiff", + "targetType": "library", + "dependencies": { + "xdiff": { + "repository": "git+https://git.zhirov.kz/dlang/xdiff.git", + "version": "e2396bc172eba813cdcd1a96c494e35d687f576a" + } + }, + "buildTypes": { + "unittest": { + "dflags": [ + "-unittest", + "-g" + ], + "libs": [ + "xdiff" + ] + } + } + +} \ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..0b7884b --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,6 @@ +{ + "fileVersion": 1, + "versions": { + "xdiff": {"version":"e2396bc172eba813cdcd1a96c494e35d687f576a","repository":"git+https://git.zhirov.kz/dlang/xdiff.git"} + } +} diff --git a/source/sdiff/init.d b/source/sdiff/init.d new file mode 100644 index 0000000..0ab1389 --- /dev/null +++ b/source/sdiff/init.d @@ -0,0 +1,56 @@ +module sdiff.init; + +import xdiff; + +import core.stdc.stdlib : malloc, free, realloc; +import std.exception : enforce; +import std.conv : to; + +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); + } +} + +shared static this() +{ + memallocator_t m = {null, &wrap_malloc, &wrap_free, &wrap_realloc}; + auto rc = xdl_set_allocator(&m); + enforce(rc == 0, "xdl_set_allocator failed (rc=" ~ rc.to!string ~ ")"); +} + +mmfile_t initMmfile(size_t blockSize) +{ + mmfile_t mf; + auto rc = xdl_init_mmfile(&mf, blockSize.to!long, XDL_MMF_ATOMIC); + enforce(rc == 0, "xdl_init_mmfile failed (rc=" ~ rc.to!string ~ ")"); + 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/sdiff/mmblocks.d b/source/sdiff/mmblocks.d new file mode 100644 index 0000000..1d25433 --- /dev/null +++ b/source/sdiff/mmblocks.d @@ -0,0 +1,42 @@ +module sdiff.mmblocks; + +import xdiff; + +import sdiff.init; + +import std.exception : enforce; +import std.conv : to; + +private enum DEFAULT_BSIZE = 64 * 1024; + +final class MMBlocks : MM +{ +public: + this() + { + _inner = initMmfile(DEFAULT_BSIZE); + } + + ~this() + { + if (_inner.head !is null) + xdl_free_mmfile(&_inner); + } + + package(sdiff) mmfile_t* ptr() @trusted + { + return &_inner; + } + + const(ubyte)[] slice() const @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 + { + return cast(string) slice(); + } +} diff --git a/source/sdiff/mmfile.d b/source/sdiff/mmfile.d new file mode 100644 index 0000000..a1e91a0 --- /dev/null +++ b/source/sdiff/mmfile.d @@ -0,0 +1,79 @@ +module sdiff.mmfile; + +import xdiff; + +import sdiff.init; +import sdiff.mmblocks; + +import std.exception : enforce; +import std.conv : to; +import std.string : join, representation; + +final class MMFile : MM +{ +private: + void _write(const(ubyte)[] 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 (rc=" ~ wrote.to!string ~ ")"); + } + } + +public: + this(const(ubyte)[] 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 diff(const scope 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) + { + auto buf = &bufs[i]; + auto rc = xdl_writem_mmfile(target.ptr(), buf, 1); + if (rc < 0 || rc != buf.size) + return -1; + } + return 0; + } + + xpparam_t xpp = xpparam_t.init; + + xdemitconf_t xec = xdemitconf_t.init; + xec.ctxlen = ctxlen; + + xdemitcb_t ecb = {cast(void*) patch, &emitToPatch}; + + auto rc = xdl_diff(&_inner, cast(mmfile_t*)&other._inner, &xpp, &xec, &ecb); + enforce(rc == 0, "xdl_diff failed (rc=" ~ rc.to!string ~ ")"); + + return patch; + } +} diff --git a/source/sdiff/package.d b/source/sdiff/package.d new file mode 100644 index 0000000..bbd012f --- /dev/null +++ b/source/sdiff/package.d @@ -0,0 +1,4 @@ +module sdiff; + +public import sdiff.mmfile; +public import sdiff.mmblocks; diff --git a/source/sdiff/unittests.d b/source/sdiff/unittests.d new file mode 100644 index 0000000..6ed5650 --- /dev/null +++ b/source/sdiff/unittests.d @@ -0,0 +1,23 @@ +module sdiff.unittests; + +import sdiff; + +import std.conv : to; +import std.stdio; + +unittest +{ + auto data = new MMFile("hello world\n"); + auto data2 = new MMFile("hello world!\n"); + + auto patch = data.diff(data2); + + writeln(patch); + + assert( + patch.to!string == + "@@ -1,1 +1,1 @@\n" ~ + "-hello world\n" ~ + "+hello world!\n" + ); +}