init
This commit is contained in:
		
						commit
						143f39005c
					
				
					 11 changed files with 734 additions and 0 deletions
				
			
		
							
								
								
									
										16
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					.dub
 | 
				
			||||||
 | 
					docs.json
 | 
				
			||||||
 | 
					__dummy.html
 | 
				
			||||||
 | 
					docs/
 | 
				
			||||||
 | 
					/libxdiff
 | 
				
			||||||
 | 
					libxdiff.so
 | 
				
			||||||
 | 
					libxdiff.dylib
 | 
				
			||||||
 | 
					libxdiff.dll
 | 
				
			||||||
 | 
					libxdiff.a
 | 
				
			||||||
 | 
					libxdiff.lib
 | 
				
			||||||
 | 
					libxdiff-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.
 | 
				
			||||||
							
								
								
									
										39
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					# libxdiff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A D wrapper around the C **libxdiff**: thin bindings ([xdiff.d](https://git.zhirov.kz/dlang/xdiff)) + a small OO layer (`MMFile`, `MMBlocks`) for diff/patch/merge with clear ownership.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **MMFile** — compact in-memory file (single buffer).
 | 
				
			||||||
 | 
					* **MMBlocks** — native libxdiff block chain with compaction.
 | 
				
			||||||
 | 
					* `computePatch`, `applyPatch` (returns accepted + rejected parts).
 | 
				
			||||||
 | 
					* `merge3Raw` (three-way merge) with `nothrow` callbacks.
 | 
				
			||||||
 | 
					* Deep copies where needed, explicit move of ownership, no double-free traps.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Add to `dub.json`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "dependencies": {
 | 
				
			||||||
 | 
					        "libxdiff": "~>0.1.0"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Or to `dub.sdl`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					dependency "libxdiff" version="~>0.1.0"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run `dub build`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Import the module: `import libxdiff;`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 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": "An OO wrapper over xdiff.d (thin libxdiff bindings) with safe ownership and a simple diff/patch/merge API.",
 | 
				
			||||||
 | 
						"license": "BSL-1.0",
 | 
				
			||||||
 | 
						"name": "libxdiff",
 | 
				
			||||||
 | 
						"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"}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								source/libxdiff/init.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								source/libxdiff/init.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					module libxdiff.init;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import core.stdc.stdlib : malloc, free, realloc;
 | 
				
			||||||
 | 
					import std.exception : enforce;
 | 
				
			||||||
 | 
					import xdiff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern (C) private void* wrap_malloc(void*, uint size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return malloc(size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern (C) private void wrap_free(void*, void* p)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						free(p);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern (C) private void* wrap_realloc(void*, void* p, uint size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return realloc(p, size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ensureInit()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						static bool done = false;
 | 
				
			||||||
 | 
						if (done)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						memallocator_t m;
 | 
				
			||||||
 | 
						m.priv = null;
 | 
				
			||||||
 | 
						m.malloc = &wrap_malloc;
 | 
				
			||||||
 | 
						m.free = &wrap_free;
 | 
				
			||||||
 | 
						m.realloc = &wrap_realloc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enforce(xdl_set_allocator(&m) == 0, "xdl_set_allocator failed");
 | 
				
			||||||
 | 
						done = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mmfile_t initMmfile(size_t len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						ensureInit();
 | 
				
			||||||
 | 
						mmfile_t mf;
 | 
				
			||||||
 | 
						enforce(xdl_init_mmfile(&mf, cast(long) len, XDL_MMF_ATOMIC) == 0, "xdl_init_mmfile failed");
 | 
				
			||||||
 | 
						return mf;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										117
									
								
								source/libxdiff/mmblocks.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								source/libxdiff/mmblocks.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,117 @@
 | 
				
			||||||
 | 
					module libxdiff.mmblocks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import std.exception : enforce;
 | 
				
			||||||
 | 
					import libxdiff.init : ensureInit, initMmfile;
 | 
				
			||||||
 | 
					import xdiff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class MMBlocks
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						mmfile_t _inner;
 | 
				
			||||||
 | 
						bool _owned = true;
 | 
				
			||||||
 | 
						enum DEFAULT_BSIZE = 8 * 1024;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static void deepCopyAll(mmfile_t* src, MMBlocks dst)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							enforce(xdl_seek_mmfile(src, 0) == 0, "seek(0) failed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							long sz = 0;
 | 
				
			||||||
 | 
							auto p = xdl_mmfile_first(src, &sz);
 | 
				
			||||||
 | 
							while (p !is null && sz > 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto wrote = xdl_write_mmfile(dst.mmfilePtr(), p, sz);
 | 
				
			||||||
 | 
								enforce(wrote == sz, "write failed during deep copy");
 | 
				
			||||||
 | 
								p = xdl_mmfile_next(src, &sz);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						this()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							_inner = initMmfile(DEFAULT_BSIZE);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static MMBlocks fromBytes(const(ubyte)[] data)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto b = new MMBlocks;
 | 
				
			||||||
 | 
							if (data.length > 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto wrote = xdl_write_mmfile(&b._inner, data.ptr, cast(long) data.length);
 | 
				
			||||||
 | 
								enforce(wrote == cast(long) data.length, "xdl_write_mmfile wrote less than requested");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return b;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						~this()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (_owned && _inner.head !is null)
 | 
				
			||||||
 | 
								xdl_free_mmfile(&_inner);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_inner.head = null;
 | 
				
			||||||
 | 
							_inner.tail = null;
 | 
				
			||||||
 | 
							_inner.rcur = null;
 | 
				
			||||||
 | 
							_inner.wcur = null;
 | 
				
			||||||
 | 
							_inner.fsize = 0;
 | 
				
			||||||
 | 
							_inner.bsize = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mmfile_t* mmfilePtr() @trusted
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return &_inner;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size_t size()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return cast(size_t) xdl_mmfile_size(&_inner);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool isCompact() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return xdl_mmfile_iscompact(cast(mmfile_t*)&_inner) != 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int writeBuf(const(ubyte)[] buf)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto wrote = xdl_write_mmfile(&_inner, buf.ptr, cast(long) buf.length);
 | 
				
			||||||
 | 
							return (wrote == cast(long) buf.length) ? 0 : -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MMBlocks clone() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto dst = new MMBlocks;
 | 
				
			||||||
 | 
							deepCopyAll(cast(mmfile_t*)&_inner, dst);
 | 
				
			||||||
 | 
							return dst;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void toCompact()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (isCompact())
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto dst = new MMBlocks;
 | 
				
			||||||
 | 
							deepCopyAll(&_inner, dst);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (_owned && _inner.head !is null)
 | 
				
			||||||
 | 
								xdl_free_mmfile(&_inner);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_inner = *dst.mmfilePtr();
 | 
				
			||||||
 | 
							dst._owned = false;
 | 
				
			||||||
 | 
							dst._inner.head = null;
 | 
				
			||||||
 | 
							dst._inner.tail = null;
 | 
				
			||||||
 | 
							dst._inner.rcur = null;
 | 
				
			||||||
 | 
							dst._inner.wcur = null;
 | 
				
			||||||
 | 
							dst._inner.fsize = 0;
 | 
				
			||||||
 | 
							dst._inner.bsize = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void disarm()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							_owned = false;
 | 
				
			||||||
 | 
							_inner.head = null;
 | 
				
			||||||
 | 
							_inner.tail = null;
 | 
				
			||||||
 | 
							_inner.rcur = null;
 | 
				
			||||||
 | 
							_inner.wcur = null;
 | 
				
			||||||
 | 
							_inner.fsize = 0;
 | 
				
			||||||
 | 
							_inner.bsize = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										199
									
								
								source/libxdiff/mmfile.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								source/libxdiff/mmfile.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,199 @@
 | 
				
			||||||
 | 
					module libxdiff.mmfile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import std.exception : enforce;
 | 
				
			||||||
 | 
					import libxdiff.init : ensureInit, initMmfile;
 | 
				
			||||||
 | 
					import libxdiff.mmblocks : MMBlocks;
 | 
				
			||||||
 | 
					import xdiff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class MMFile
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						mmfile_t _inner;
 | 
				
			||||||
 | 
						bool _owned = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						this()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							_inner = initMmfile(0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static MMFile fromBytes(const(ubyte)[] data)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto f = new MMFile;
 | 
				
			||||||
 | 
							if (data.length > 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto wrote = xdl_write_mmfile(&f._inner, data.ptr, cast(long) data.length);
 | 
				
			||||||
 | 
								enforce(wrote == cast(long) data.length, "xdl_write_mmfile wrote less than requested");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return f;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						~this()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (_owned && _inner.head !is null)
 | 
				
			||||||
 | 
								xdl_free_mmfile(&_inner);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_inner.head = null;
 | 
				
			||||||
 | 
							_inner.tail = null;
 | 
				
			||||||
 | 
							_inner.rcur = null;
 | 
				
			||||||
 | 
							_inner.wcur = null;
 | 
				
			||||||
 | 
							_inner.fsize = 0;
 | 
				
			||||||
 | 
							_inner.bsize = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size_t size()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return cast(size_t) xdl_mmfile_size(&_inner);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool isCompact() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return xdl_mmfile_iscompact(cast(mmfile_t*)&_inner) != 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const(ubyte)[] asSlice() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							enforce(isCompact(), "MMFile must be compact for asSlice");
 | 
				
			||||||
 | 
							auto h = _inner.head;
 | 
				
			||||||
 | 
							if (h is null || h.size <= 0)
 | 
				
			||||||
 | 
								return (cast(ubyte*) null)[0 .. 0];
 | 
				
			||||||
 | 
							return (cast(const(ubyte)*) h.ptr)[0 .. cast(size_t) h.size];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ubyte[] asSliceMut()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							enforce(isCompact(), "MMFile must be compact for asSliceMut");
 | 
				
			||||||
 | 
							auto h = _inner.head;
 | 
				
			||||||
 | 
							if (h is null || h.size <= 0)
 | 
				
			||||||
 | 
								return (cast(ubyte*) null)[0 .. 0];
 | 
				
			||||||
 | 
							return (cast(ubyte*) h.ptr)[0 .. cast(size_t) h.size];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						alias MMPatch = MMBlocks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MMPatch computePatch(ref MMFile other)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto patch = new MMBlocks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static extern (C) int emitToPatch(void* priv, mmbuffer_t* bufs, int num)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto target = cast(MMBlocks) priv;
 | 
				
			||||||
 | 
								foreach (i; 0 .. num)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									auto b = bufs + i;
 | 
				
			||||||
 | 
									auto wrote = xdl_writem_mmfile(target.mmfilePtr(), b, 1);
 | 
				
			||||||
 | 
									if (wrote != (*b).size)
 | 
				
			||||||
 | 
										return -1;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							xpparam_t xpp;
 | 
				
			||||||
 | 
							xpp.flags = 0;
 | 
				
			||||||
 | 
							xdemitconf_t xec;
 | 
				
			||||||
 | 
							xec.ctxlen = 3;
 | 
				
			||||||
 | 
							xdemitcb_t ecb;
 | 
				
			||||||
 | 
							ecb.priv = cast(void*) patch;
 | 
				
			||||||
 | 
							ecb.outf = &emitToPatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto rc = xdl_diff(&_inner, &other._inner, &xpp, &xec, &ecb);
 | 
				
			||||||
 | 
							enforce(rc == 0, "xdl_diff failed");
 | 
				
			||||||
 | 
							return patch;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct PatchResult
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							bool success;
 | 
				
			||||||
 | 
							MMFile patched;
 | 
				
			||||||
 | 
							MMFile rejected;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PatchResult applyPatch(ref MMPatch patch)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							patch.toCompact();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto acc = new MMBlocks();
 | 
				
			||||||
 | 
							auto rej = new MMBlocks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static extern (C) int emitTo(void* priv, mmbuffer_t* bufs, int num)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto target = cast(MMBlocks) priv;
 | 
				
			||||||
 | 
								long expect = 0;
 | 
				
			||||||
 | 
								foreach (i; 0 .. num)
 | 
				
			||||||
 | 
									expect += (bufs + i).size;
 | 
				
			||||||
 | 
								auto wrote = xdl_writem_mmfile(target.mmfilePtr(), bufs, num);
 | 
				
			||||||
 | 
								return (wrote == expect) ? 0 : -1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							xdemitcb_t accCb;
 | 
				
			||||||
 | 
							accCb.priv = cast(void*) acc;
 | 
				
			||||||
 | 
							accCb.outf = &emitTo;
 | 
				
			||||||
 | 
							xdemitcb_t rejCb;
 | 
				
			||||||
 | 
							rejCb.priv = cast(void*) rej;
 | 
				
			||||||
 | 
							rejCb.outf = &emitTo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto rc = xdl_patch(&_inner, patch.mmfilePtr(), XDL_PATCH_NORMAL, &accCb, &rejCb);
 | 
				
			||||||
 | 
							enforce(rc == 0 || rc == 1, "xdl_patch failed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto accFile = MMFile.fromBlocksMoved(acc);
 | 
				
			||||||
 | 
							auto rejFile = MMFile.fromBlocksMoved(rej);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool ok = (rejFile.size() == 0);
 | 
				
			||||||
 | 
							return PatchResult(ok, accFile, rejFile);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void merge3Raw(
 | 
				
			||||||
 | 
							ref MMFile f1,
 | 
				
			||||||
 | 
							ref MMFile f2,
 | 
				
			||||||
 | 
							scope int delegate(const(ubyte)[]) nothrow acceptCb,
 | 
				
			||||||
 | 
							scope int delegate(const(ubyte)[]) nothrow rejectCb)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							static extern (C) int emitAccept(void* priv, mmbuffer_t* bufs, int num)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto d = cast(int delegate(const(ubyte)[]) nothrow*) priv;
 | 
				
			||||||
 | 
								foreach (i; 0 .. num)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									auto b = bufs + i;
 | 
				
			||||||
 | 
									auto slice = (cast(const(ubyte)*)((*b).ptr))[0 .. cast(size_t)((*b).size)];
 | 
				
			||||||
 | 
									auto r = (*d)(slice);
 | 
				
			||||||
 | 
									if (r != 0)
 | 
				
			||||||
 | 
										return r;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static extern (C) int emitReject(void* priv, mmbuffer_t* bufs, int num)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto d = cast(int delegate(const(ubyte)[]) nothrow*) priv;
 | 
				
			||||||
 | 
								foreach (i; 0 .. num)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									auto b = bufs + i;
 | 
				
			||||||
 | 
									auto slice = (cast(const(ubyte)*)((*b).ptr))[0 .. cast(size_t)((*b).size)];
 | 
				
			||||||
 | 
									auto r = (*d)(slice);
 | 
				
			||||||
 | 
									if (r != 0)
 | 
				
			||||||
 | 
										return r;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto a = acceptCb, r = rejectCb;
 | 
				
			||||||
 | 
							xdemitcb_t accCb;
 | 
				
			||||||
 | 
							accCb.priv = cast(void*)&a;
 | 
				
			||||||
 | 
							accCb.outf = &emitAccept;
 | 
				
			||||||
 | 
							xdemitcb_t rejCb;
 | 
				
			||||||
 | 
							rejCb.priv = cast(void*)&r;
 | 
				
			||||||
 | 
							rejCb.outf = &emitReject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto rc = xdl_merge3(&_inner, &f1._inner, &f2._inner, &accCb, &rejCb);
 | 
				
			||||||
 | 
							enforce(rc == 0, "xdl_merge3 failed");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static MMFile fromBlocksMoved(MMBlocks b)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							b.toCompact();
 | 
				
			||||||
 | 
							auto f = new MMFile;
 | 
				
			||||||
 | 
							f._inner = *b.mmfilePtr();
 | 
				
			||||||
 | 
							b.disarm();
 | 
				
			||||||
 | 
							return f;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								source/libxdiff/package.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								source/libxdiff/package.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					module libxdiff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public import libxdiff.mmblocks;
 | 
				
			||||||
 | 
					public import libxdiff.mmfile;
 | 
				
			||||||
							
								
								
									
										253
									
								
								source/libxdiff/unittests.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								source/libxdiff/unittests.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,253 @@
 | 
				
			||||||
 | 
					module libxdiff.unittests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import std.algorithm : equal;
 | 
				
			||||||
 | 
					import std.array : appender;
 | 
				
			||||||
 | 
					import std.exception : assertThrown;
 | 
				
			||||||
 | 
					import std.range : iota, cycle, take;
 | 
				
			||||||
 | 
					import libxdiff.mmfile : MMFile;
 | 
				
			||||||
 | 
					import libxdiff.mmblocks : MMBlocks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ubyte[] genCycled(size_t n)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto acc = appender!(ubyte[])();
 | 
				
			||||||
 | 
						foreach (v; iota(0, 240).cycle.take(n))
 | 
				
			||||||
 | 
							acc.put(cast(ubyte) v);
 | 
				
			||||||
 | 
						return acc.data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ubyte[] blocksToBytes(const MMBlocks b)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto tmp = b.clone();
 | 
				
			||||||
 | 
						auto mf = MMFile.fromBlocksMoved(tmp);
 | 
				
			||||||
 | 
						return (cast(const(ubyte)[]) mf.asSlice()).dup;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool sameBytes(const MMFile a, const MMFile b)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return a.asSlice().equal(b.asSlice());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto f = new MMFile;
 | 
				
			||||||
 | 
						assert(f.size() == 0);
 | 
				
			||||||
 | 
						assert(f.isCompact());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto data = cast(ubyte[]) "hello world";
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						assert(f.size() == data.length);
 | 
				
			||||||
 | 
						assert(f.isCompact());
 | 
				
			||||||
 | 
						assert(f.asSlice().equal(data));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto data = genCycled(15_000);
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						assert(f.size() == data.length);
 | 
				
			||||||
 | 
						assert(f.isCompact());
 | 
				
			||||||
 | 
						assert(f.asSlice().equal(data));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto data = genCycled(15_000);
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						auto f2 = MMFile.fromBytes(f.asSlice().dup);
 | 
				
			||||||
 | 
						assert(sameBytes(f, f2));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto data = genCycled(15_000);
 | 
				
			||||||
 | 
						auto b = MMBlocks.fromBytes(data);
 | 
				
			||||||
 | 
						auto b2 = b.clone();
 | 
				
			||||||
 | 
						auto bytes = blocksToBytes(b);
 | 
				
			||||||
 | 
						auto bytes2 = blocksToBytes(b2);
 | 
				
			||||||
 | 
						assert(bytes.equal(bytes2));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto data = genCycled(15_000);
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						auto f2 = MMFile.fromBytes(data.dup);
 | 
				
			||||||
 | 
						assert(sameBytes(f, f2));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						assert((new MMFile).asSlice().length == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto data = genCycled(15_000);
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						assert(f.asSlice().equal(data));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto empty = new MMFile;
 | 
				
			||||||
 | 
						assert(empty.asSlice().length == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto data = genCycled(15_000);
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						assert(f.asSlice()[0] == data[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto mut = f.asSliceMut();
 | 
				
			||||||
 | 
						mut[0] = cast(ubyte)(mut[0] + 1);
 | 
				
			||||||
 | 
						assert(f.asSlice()[0] == data[0] + 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto data = cast(ubyte[]) "hello world\n";
 | 
				
			||||||
 | 
						auto data2 = cast(ubyte[]) "hello world!\n";
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(data);
 | 
				
			||||||
 | 
						auto f2 = MMFile.fromBytes(data2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto patch = f.computePatch(f2);
 | 
				
			||||||
 | 
						auto r = f.applyPatch(patch);
 | 
				
			||||||
 | 
						assert(r.success);
 | 
				
			||||||
 | 
						assert(r.patched.asSlice().equal(data2));
 | 
				
			||||||
 | 
						assert(r.rejected.asSlice().length == 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto f = MMFile.fromBytes(cast(ubyte[]) "hello world\n");
 | 
				
			||||||
 | 
						auto f2 = MMFile.fromBytes(cast(ubyte[]) "hello world!\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto p1 = f.computePatch(f2);
 | 
				
			||||||
 | 
						auto r1 = f.applyPatch(p1);
 | 
				
			||||||
 | 
						assert(r1.success);
 | 
				
			||||||
 | 
						assert(r1.patched.asSlice().equal(f2.asSlice()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto m = f2.asSliceMut();
 | 
				
			||||||
 | 
						m[0] = cast(ubyte) 'j';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto p2 = f.computePatch(f2);
 | 
				
			||||||
 | 
						auto r2 = f.applyPatch(p2);
 | 
				
			||||||
 | 
						assert(r2.success);
 | 
				
			||||||
 | 
						assert(r2.patched.asSlice().equal(f2.asSlice()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto base = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
						auto a = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world changed\n");
 | 
				
			||||||
 | 
						auto b = MMFile.fromBytes(cast(ubyte[]) "header_changed\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto lines = appender!(string[])();
 | 
				
			||||||
 | 
						auto rej = appender!(string[])();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int acceptCb(const(ubyte)[] s) nothrow
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							lines.put(cast(string) s.idup);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int rejectCb(const(ubyte)[] s) nothrow
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							rej.put(cast(string) s.idup);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base.merge3Raw(a, b, &acceptCb, &rejectCb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto expected = [
 | 
				
			||||||
 | 
							"header_changed\n",
 | 
				
			||||||
 | 
							"line2\n",
 | 
				
			||||||
 | 
							"line3\n",
 | 
				
			||||||
 | 
							"line4\n",
 | 
				
			||||||
 | 
							"hello world changed\n",
 | 
				
			||||||
 | 
						];
 | 
				
			||||||
 | 
						assert(lines.data.equal(expected));
 | 
				
			||||||
 | 
						assert(rej.data.length == 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto base = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
						auto a = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world changed\n");
 | 
				
			||||||
 | 
						auto b = MMFile.fromBytes(
 | 
				
			||||||
 | 
							cast(ubyte[]) "header\nline2\nline3\nline4\nhello world also changed\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto lines = appender!(string[])();
 | 
				
			||||||
 | 
						auto rej = appender!(string[])();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int acceptCb(const(ubyte)[] s) nothrow
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							lines.put(cast(string) s.idup);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int rejectCb(const(ubyte)[] s) nothrow
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							rej.put(cast(string) s.idup);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base.merge3Raw(a, b, &acceptCb, &rejectCb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(lines.data.length > 0);
 | 
				
			||||||
 | 
						assert(rej.data.length > 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto base = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
						auto a = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world changed\n");
 | 
				
			||||||
 | 
						auto b = MMFile.fromBytes(cast(ubyte[]) "header_changed\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto lines = appender!(string[])();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int acceptCb(const(ubyte)[] s) nothrow
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							static size_t cnt = 0;
 | 
				
			||||||
 | 
							++cnt;
 | 
				
			||||||
 | 
							if (cnt > 3)
 | 
				
			||||||
 | 
								return -1;
 | 
				
			||||||
 | 
							lines.put(cast(string) s.idup);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int rejectCb(const(ubyte)[] s) nothrow
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assertThrown!Exception(base.merge3Raw(a, b, &acceptCb, &rejectCb));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto base = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
						auto want = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world changed\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto patch = base.computePatch(want);
 | 
				
			||||||
 | 
						auto res = base.applyPatch(patch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(res.success);
 | 
				
			||||||
 | 
						assert(sameBytes(res.patched, want));
 | 
				
			||||||
 | 
						assert(res.rejected.asSlice().length == 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unittest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto base = MMFile.fromBytes(cast(ubyte[]) "header\nline2\nline3\nline4\nhello world\n");
 | 
				
			||||||
 | 
						auto want = MMFile.fromBytes(
 | 
				
			||||||
 | 
							cast(ubyte[]) "header changed\nline2\nline3\nline4\nhello world changed\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto patch = base.computePatch(want);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto m = base.asSliceMut();
 | 
				
			||||||
 | 
						m[0] = cast(ubyte) 'b';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto res = base.applyPatch(patch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(!res.success);
 | 
				
			||||||
 | 
						assert(res.rejected.asSlice().length > 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue