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