init
This commit is contained in:
commit
8c388b1123
11 changed files with 347 additions and 0 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"editor.insertSpaces": false,
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.detectIndentation": false
|
||||||
|
}
|
23
LICENSE
Normal file
23
LICENSE
Normal 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
65
README.md
Normal 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
28
dub.json
Normal 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
6
dub.selections.json
Normal 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
56
source/sdiff/init.d
Normal 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
42
source/sdiff/mmblocks.d
Normal 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
79
source/sdiff/mmfile.d
Normal 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
4
source/sdiff/package.d
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module sdiff;
|
||||||
|
|
||||||
|
public import sdiff.mmfile;
|
||||||
|
public import sdiff.mmblocks;
|
23
source/sdiff/unittests.d
Normal file
23
source/sdiff/unittests.d
Normal 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"
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue