This commit is contained in:
Alexander Zhirov 2025-09-01 03:21:54 +03:00
commit 8c388b1123
Signed by: alexander
GPG key ID: C8D8BE544A27C511
11 changed files with 347 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -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

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.detectIndentation": false
}

23
LICENSE Normal file
View file

@ -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.

65
README.md Normal file
View file

@ -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": "<hash from git>"
}
}
}
```
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).

28
dub.json Normal file
View file

@ -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"
]
}
}
}

6
dub.selections.json Normal file
View file

@ -0,0 +1,6 @@
{
"fileVersion": 1,
"versions": {
"xdiff": {"version":"e2396bc172eba813cdcd1a96c494e35d687f576a","repository":"git+https://git.zhirov.kz/dlang/xdiff.git"}
}
}

56
source/sdiff/init.d Normal file
View file

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

42
source/sdiff/mmblocks.d Normal file
View file

@ -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();
}
}

79
source/sdiff/mmfile.d Normal file
View file

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

4
source/sdiff/package.d Normal file
View file

@ -0,0 +1,4 @@
module sdiff;
public import sdiff.mmfile;
public import sdiff.mmblocks;

23
source/sdiff/unittests.d Normal file
View file

@ -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"
);
}