diff
This commit is contained in:
		
							parent
							
								
									e9952bacf2
								
							
						
					
					
						commit
						06b2a2a993
					
				
					 2 changed files with 211 additions and 311 deletions
				
			
		
							
								
								
									
										325
									
								
								source/app.d
									
										
									
									
									
								
							
							
						
						
									
										325
									
								
								source/app.d
									
										
									
									
									
								
							| 
						 | 
					@ -1,322 +1,25 @@
 | 
				
			||||||
import std.stdio : writeln, stderr, File;
 | 
					import std.stdio : writeln, stderr;
 | 
				
			||||||
import std.getopt;
 | 
					import std.exception : collectException;
 | 
				
			||||||
import std.file : read, exists;
 | 
					 | 
				
			||||||
import std.exception : enforce, collectException;
 | 
					 | 
				
			||||||
import core.stdc.stdlib : malloc, free, realloc;
 | 
					 | 
				
			||||||
import core.stdc.string : memcpy;
 | 
					 | 
				
			||||||
import core.sys.posix.unistd : posix_write = write;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import xdiff;
 | 
					import diff;
 | 
				
			||||||
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
// RAII для mmfile_t
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
struct MmFile
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	mmfile_t mf;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// фабрика вместо конструктора по умолчанию
 | 
					 | 
				
			||||||
	static MmFile create(long bsize = 8 * 1024, ulong flags = XDL_MMF_ATOMIC)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		MmFile m;
 | 
					 | 
				
			||||||
		enforce(xdl_init_mmfile(&m.mf, bsize, flags) == 0, "xdl_init_mmfile failed");
 | 
					 | 
				
			||||||
		return m;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void deinit()
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		xdl_free_mmfile(&mf);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
// Коллбэки вывода
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
extern (C) int mmfileOut(void* priv, mmbuffer_t* mb, int nbuf)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	auto mmfOut = cast(mmfile_t*) priv;
 | 
					 | 
				
			||||||
	return xdl_writem_mmfile(mmfOut, mb, nbuf) < 0 ? -1 : 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
extern (C) int fdOut(void* priv, mmbuffer_t* mb, int nbuf)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const fd = cast(int) cast(size_t) priv; // 1=stdout, 2=stderr
 | 
					 | 
				
			||||||
	foreach (i; 0 .. nbuf)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		auto ptr = mb[i].ptr;
 | 
					 | 
				
			||||||
		auto left = cast(size_t) mb[i].size;
 | 
					 | 
				
			||||||
		while (left)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			auto n = posix_write(fd, ptr, left);
 | 
					 | 
				
			||||||
			if (n < 0)
 | 
					 | 
				
			||||||
				return -1;
 | 
					 | 
				
			||||||
			ptr += n;
 | 
					 | 
				
			||||||
			left -= cast(size_t) n;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
// Загрузка/выгрузка
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
void loadMmFile(ref MmFile m, string path)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	try
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		enforce(exists(path), "no such file: " ~ path);
 | 
					 | 
				
			||||||
		auto data = cast(const(char)[]) read(path); // читает весь файл
 | 
					 | 
				
			||||||
		auto dst = cast(char*) xdl_mmfile_writeallocate(&m.mf, data.length);
 | 
					 | 
				
			||||||
		enforce(dst !is null, "xdl_mmfile_writeallocate failed (null)");
 | 
					 | 
				
			||||||
		if (data.length)
 | 
					 | 
				
			||||||
			memcpy(dst, data.ptr, data.length);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	catch (Exception e)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// пробрасываем с контекстом пути
 | 
					 | 
				
			||||||
		throw new Exception("loadMmFile(" ~ path ~ "): " ~ e.msg);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void dumpMmFile(const ref MmFile m, string path)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	File f = File(path, "wb");
 | 
					 | 
				
			||||||
	scope (exit)
 | 
					 | 
				
			||||||
		f.close();
 | 
					 | 
				
			||||||
	long sz = 0;
 | 
					 | 
				
			||||||
	auto blk = cast(char*) xdl_mmfile_first(cast(mmfile_t*)&m.mf, &sz);
 | 
					 | 
				
			||||||
	while (blk !is null)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		f.rawWrite(blk[0 .. cast(size_t) sz]);
 | 
					 | 
				
			||||||
		blk = cast(char*) xdl_mmfile_next(cast(mmfile_t*)&m.mf, &sz);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
// Аллокатор
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
extern (C) void* wrap_malloc(void*, uint size)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return malloc(size);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
extern (C) void wrap_free(void*, void* ptr)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	free(ptr);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
extern (C) void* wrap_realloc(void*, void* ptr, uint size)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return realloc(ptr, size);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void installAllocator()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	memallocator_t malt;
 | 
					 | 
				
			||||||
	malt.priv = null;
 | 
					 | 
				
			||||||
	malt.malloc = &wrap_malloc;
 | 
					 | 
				
			||||||
	malt.free = &wrap_free;
 | 
					 | 
				
			||||||
	malt.realloc = &wrap_realloc;
 | 
					 | 
				
			||||||
	enforce(xdl_set_allocator(&malt) == 0, "xdl_set_allocator failed");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
// Команды
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
int doDiff(ref MmFile a, ref MmFile b, long ctxlen)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	xpparam_t xpp;
 | 
					 | 
				
			||||||
	xpp.flags = 0;
 | 
					 | 
				
			||||||
	xdemitconf_t xecfg;
 | 
					 | 
				
			||||||
	xecfg.ctxlen = ctxlen;
 | 
					 | 
				
			||||||
	xdemitcb_t ecb;
 | 
					 | 
				
			||||||
	ecb.priv = cast(void*) cast(size_t) 1;
 | 
					 | 
				
			||||||
	ecb.outf = &fdOut;
 | 
					 | 
				
			||||||
	return xdl_diff(&a.mf, &b.mf, &xpp, &xecfg, &ecb);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int doPatch(ref MmFile orig, ref MmFile patch, int mode)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	xdemitcb_t outcb;
 | 
					 | 
				
			||||||
	outcb.priv = cast(void*) cast(size_t) 1;
 | 
					 | 
				
			||||||
	outcb.outf = &fdOut;
 | 
					 | 
				
			||||||
	xdemitcb_t rejcb;
 | 
					 | 
				
			||||||
	rejcb.priv = cast(void*) cast(size_t) 2;
 | 
					 | 
				
			||||||
	rejcb.outf = &fdOut;
 | 
					 | 
				
			||||||
	return xdl_patch(&orig.mf, &patch.mf, mode, &outcb, &rejcb);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int doBDiff(ref MmFile a, ref MmFile b, long bsize)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	bdiffparam_t bdp;
 | 
					 | 
				
			||||||
	bdp.bsize = bsize;
 | 
					 | 
				
			||||||
	xdemitcb_t ecb;
 | 
					 | 
				
			||||||
	ecb.priv = cast(void*) cast(size_t) 1;
 | 
					 | 
				
			||||||
	ecb.outf = &fdOut;
 | 
					 | 
				
			||||||
	return xdl_bdiff(&a.mf, &b.mf, &bdp, &ecb);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int doRaBDiff(ref MmFile a, ref MmFile b)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	xdemitcb_t ecb;
 | 
					 | 
				
			||||||
	ecb.priv = cast(void*) cast(size_t) 1;
 | 
					 | 
				
			||||||
	ecb.outf = &fdOut;
 | 
					 | 
				
			||||||
	return xdl_rabdiff(&a.mf, &b.mf, &ecb);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int doBPatch(ref MmFile base, ref MmFile binPatch)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	xdemitcb_t ecb;
 | 
					 | 
				
			||||||
	ecb.priv = cast(void*) cast(size_t) 1;
 | 
					 | 
				
			||||||
	ecb.outf = &fdOut;
 | 
					 | 
				
			||||||
	return xdl_bpatch(&base.mf, &binPatch.mf, &ecb);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
// CLI
 | 
					 | 
				
			||||||
// ------------------------------
 | 
					 | 
				
			||||||
enum Cmd
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	diff,
 | 
					 | 
				
			||||||
	patch,
 | 
					 | 
				
			||||||
	bdiff,
 | 
					 | 
				
			||||||
	rabdiff,
 | 
					 | 
				
			||||||
	bpatch
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private Cmd parseCmd(string s)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	final switch (s)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
	case "diff":
 | 
					 | 
				
			||||||
		return Cmd.diff;
 | 
					 | 
				
			||||||
	case "patch":
 | 
					 | 
				
			||||||
		return Cmd.patch;
 | 
					 | 
				
			||||||
	case "bdiff":
 | 
					 | 
				
			||||||
		return Cmd.bdiff;
 | 
					 | 
				
			||||||
	case "rabdiff":
 | 
					 | 
				
			||||||
		return Cmd.rabdiff;
 | 
					 | 
				
			||||||
	case "bpatch":
 | 
					 | 
				
			||||||
		return Cmd.bpatch;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Короткая справка
 | 
					 | 
				
			||||||
void printUsage(string prg)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	stderr.writefln(
 | 
					 | 
				
			||||||
		"use: %1$s diff   [-C N]  FROM TO\n" ~
 | 
					 | 
				
			||||||
			"     %1$s patch           ORIG PATCH\n" ~
 | 
					 | 
				
			||||||
			"     %1$s bdiff  [-B N]   FROM TO\n" ~
 | 
					 | 
				
			||||||
			"     %1$s rabdiff         FROM TO\n" ~
 | 
					 | 
				
			||||||
			"     %1$s bpatch          ORIG PATCH\n",
 | 
					 | 
				
			||||||
			prg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Парсинг подкоманды + её опций. Возвращаем уже «очищенный» список позиционных.
 | 
					 | 
				
			||||||
struct Parsed
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	Cmd cmd;
 | 
					 | 
				
			||||||
	long ctxlen = 3; // для diff
 | 
					 | 
				
			||||||
	long bsize = 16; // для bdiff
 | 
					 | 
				
			||||||
	string[] positional; // оставшиеся позиционные (пути)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Parsed parseArgs(string[] args)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	enforce(args.length >= 2, "no subcommand");
 | 
					 | 
				
			||||||
	auto sub = args[1];
 | 
					 | 
				
			||||||
	auto cmd = parseCmd(sub);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	long ctxlen = 3;
 | 
					 | 
				
			||||||
	long bsize = 16;
 | 
					 | 
				
			||||||
	bool helpWanted = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Опции и позиционные ТОЛЬКО после подкоманды
 | 
					 | 
				
			||||||
	string[] optArgs = args.length > 2 ? args[2 .. $].dup : [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// getopt выкинет опции из optArgs и оставит только позиционные
 | 
					 | 
				
			||||||
	auto r = getopt(optArgs,
 | 
					 | 
				
			||||||
		"h|help", &helpWanted,
 | 
					 | 
				
			||||||
		"C", &ctxlen, // актуально для `diff`
 | 
					 | 
				
			||||||
		"B", &bsize // актуально для `bdiff`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
	enforce(!helpWanted, "help");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Parsed p;
 | 
					 | 
				
			||||||
	p.cmd = cmd;
 | 
					 | 
				
			||||||
	p.ctxlen = ctxlen;
 | 
					 | 
				
			||||||
	p.bsize = bsize;
 | 
					 | 
				
			||||||
	p.positional = optArgs; // здесь уже только пути
 | 
					 | 
				
			||||||
	return p;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
int main(string[] args)
 | 
					int main(string[] args)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	// 1) Парсим
 | 
						if (args.length != 3)
 | 
				
			||||||
	Parsed p;
 | 
					 | 
				
			||||||
	try
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		p = parseArgs(args);
 | 
							stderr.writeln("use: ", args[0], " OLD NEW");
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	catch (Exception)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		printUsage(args[0]);
 | 
					 | 
				
			||||||
		return 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (p.positional.length != 2)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// каждая подкоманда требует ровно 2 пути
 | 
					 | 
				
			||||||
		printUsage(args[0]);
 | 
					 | 
				
			||||||
		return 1;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	auto p1 = p.positional[0];
 | 
					 | 
				
			||||||
	auto p2 = p.positional[1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 2) Аллокатор libxdiff
 | 
					 | 
				
			||||||
	installAllocator();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 3) Готовим входы
 | 
					 | 
				
			||||||
	auto mf1 = MmFile.create();
 | 
					 | 
				
			||||||
	scope (exit)
 | 
					 | 
				
			||||||
		mf1.deinit();
 | 
					 | 
				
			||||||
	auto mf2 = MmFile.create();
 | 
					 | 
				
			||||||
	scope (exit)
 | 
					 | 
				
			||||||
		mf2.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		loadMmFile(mf1, p1);
 | 
					 | 
				
			||||||
		loadMmFile(mf2, p2);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	catch (Exception e)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		stderr.writeln(e.msg);
 | 
					 | 
				
			||||||
		return 1;
 | 
							return 1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4) Запускаем действие
 | 
						auto eng = new DiffEngine(3);
 | 
				
			||||||
	int rc = 0;
 | 
					
 | 
				
			||||||
	final switch (p.cmd)
 | 
						string result; // сюда положим результат
 | 
				
			||||||
 | 
						auto ex = collectException(result = eng.diffFiles(args[1], args[2]));
 | 
				
			||||||
 | 
						if (ex !is null)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
	case Cmd.diff:
 | 
							stderr.writeln(ex.msg);
 | 
				
			||||||
		rc = doDiff(mf1, mf2, p.ctxlen);
 | 
							return 2;
 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	case Cmd.patch:
 | 
					 | 
				
			||||||
		rc = doPatch(mf1, mf2, XDL_PATCH_NORMAL);
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	case Cmd.bdiff:
 | 
					 | 
				
			||||||
		rc = doBDiff(mf1, mf2, p.bsize);
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	case Cmd.rabdiff:
 | 
					 | 
				
			||||||
		rc = doRaBDiff(mf1, mf2);
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	case Cmd.bpatch:
 | 
					 | 
				
			||||||
		rc = doBPatch(mf1, mf2);
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return rc < 0 ? 2 : 0;
 | 
						writeln(result);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										197
									
								
								source/diff.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								source/diff.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,197 @@
 | 
				
			||||||
 | 
					module diff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import std.exception : enforce;
 | 
				
			||||||
 | 
					import std.file : read, exists; // бинарное чтение
 | 
				
			||||||
 | 
					import std.array : Appender, appender;
 | 
				
			||||||
 | 
					import std.string : representation, format;
 | 
				
			||||||
 | 
					import std.datetime : Clock, UTC;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import core.stdc.string : memcpy;
 | 
				
			||||||
 | 
					import core.stdc.stdlib : malloc, free, realloc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import xdiff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					// Аллокатор для libxdiff (обязателен на ряде сборок)
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					private extern (C) void* _wrap_malloc(void*, uint size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return malloc(size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private extern (C) void _wrap_free(void*, void* ptr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						free(ptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private extern (C) void* _wrap_realloc(void*, void* ptr, uint size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return realloc(ptr, size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private __gshared bool _allocatorReady = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private void ensureAllocator()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (_allocatorReady)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						memallocator_t malt;
 | 
				
			||||||
 | 
						malt.priv = null;
 | 
				
			||||||
 | 
						malt.malloc = &_wrap_malloc;
 | 
				
			||||||
 | 
						malt.free = &_wrap_free;
 | 
				
			||||||
 | 
						malt.realloc = &_wrap_realloc;
 | 
				
			||||||
 | 
						enforce(xdl_set_allocator(&malt) == 0, "xdl_set_allocator failed");
 | 
				
			||||||
 | 
						_allocatorReady = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					// RAII для mmfile_t
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					private struct MmFile
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						mmfile_t mf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static MmFile fromBytes(const(ubyte)[] data,
 | 
				
			||||||
 | 
							long bsize = 8 * 1024,
 | 
				
			||||||
 | 
							ulong flags = XDL_MMF_ATOMIC)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							ensureAllocator(); // критично: до init/writeallocate
 | 
				
			||||||
 | 
							MmFile m;
 | 
				
			||||||
 | 
							enforce(xdl_init_mmfile(&m.mf, bsize, flags) == 0, "xdl_init_mmfile failed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (data.length)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto dst = cast(char*) xdl_mmfile_writeallocate(&m.mf, data.length);
 | 
				
			||||||
 | 
								enforce(dst !is null, "xdl_mmfile_writeallocate failed");
 | 
				
			||||||
 | 
								memcpy(dst, data.ptr, data.length);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return m;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						~this()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							xdl_free_mmfile(&mf);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					// Синк-коллектор вывода libxdiff
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					private class BufferSink
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Appender!(ubyte[]) outbuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							outbuf = appender!(ubyte[])();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						extern (C) static int writeCB(void* priv, mmbuffer_t* mb, int nbuf)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto self = cast(BufferSink) priv;
 | 
				
			||||||
 | 
							foreach (i; 0 .. nbuf)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								size_t sz = cast(size_t) mb[i].size;
 | 
				
			||||||
 | 
								if (sz == 0)
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								self.outbuf.put((cast(ubyte*) mb[i].ptr)[0 .. sz]);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						string toStringOwned() @trusted
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return cast(string) outbuf.data.idup;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					// Публичный движок unified-diff
 | 
				
			||||||
 | 
					//-----------------------------
 | 
				
			||||||
 | 
					final class DiffEngine
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						private long _ctxlen;
 | 
				
			||||||
 | 
						private bool _stripTrailingNewline;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this(long ctxlen = 3, bool stripTrailingNewline = false)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							_ctxlen = ctxlen;
 | 
				
			||||||
 | 
							_stripTrailingNewline = stripTrailingNewline;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Diff двух текстов (UTF-8 строки)
 | 
				
			||||||
 | 
						string diffText(string oldText, string newText,
 | 
				
			||||||
 | 
							string oldLabel = "old", string newLabel = "new")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							const(ubyte)[] a = _prep(oldText);
 | 
				
			||||||
 | 
							const(ubyte)[] b = _prep(newText);
 | 
				
			||||||
 | 
							return diffBytes(a, b, oldLabel, newLabel);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Diff по путям (файлы читаются целиком, бинарно)
 | 
				
			||||||
 | 
						string diffFiles(string oldPath, string newPath,
 | 
				
			||||||
 | 
							string oldLabel = "", string newLabel = "")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							enforce(exists(oldPath), "no such file: " ~ oldPath);
 | 
				
			||||||
 | 
							enforce(exists(newPath), "no such file: " ~ newPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto a = cast(const(ubyte)[]) read(oldPath); // бинарное чтение
 | 
				
			||||||
 | 
							auto b = cast(const(ubyte)[]) read(newPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (oldLabel.length == 0)
 | 
				
			||||||
 | 
								oldLabel = oldPath;
 | 
				
			||||||
 | 
							if (newLabel.length == 0)
 | 
				
			||||||
 | 
								newLabel = newPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return diffBytes(a, b, oldLabel, newLabel);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Базовый метод: diff двух буферов
 | 
				
			||||||
 | 
						string diffBytes(const(ubyte)[] oldBytes, const(ubyte)[] newBytes,
 | 
				
			||||||
 | 
							string oldLabel = "old", string newLabel = "new")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto a = MmFile.fromBytes(oldBytes);
 | 
				
			||||||
 | 
							auto b = MmFile.fromBytes(newBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto sink = new BufferSink;
 | 
				
			||||||
 | 
							xdemitcb_t ecb;
 | 
				
			||||||
 | 
							ecb.priv = cast(void*) sink;
 | 
				
			||||||
 | 
							ecb.outf = &BufferSink.writeCB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							xpparam_t xpp;
 | 
				
			||||||
 | 
							xpp.flags = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							xdemitconf_t xecfg;
 | 
				
			||||||
 | 
							xecfg.ctxlen = _ctxlen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto pre = formatHeaders(oldLabel, newLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto rc = xdl_diff(&a.mf, &b.mf, &xpp, &xecfg, &ecb);
 | 
				
			||||||
 | 
							enforce(rc >= 0, format("xdl_diff failed (rc=%s)", rc));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return pre ~ sink.toStringOwned();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static string formatHeaders(string oldLabel, string newLabel)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto ts = Clock.currTime(UTC()).toISOExtString(); // YYYY-MM-DDTHH:MM:SS
 | 
				
			||||||
 | 
							return "--- " ~ oldLabel ~ "\t" ~ ts ~ "\n"
 | 
				
			||||||
 | 
								~ "+++ " ~ newLabel ~ "\t" ~ ts ~ "\n";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						const(ubyte)[] _prep(string s) @trusted
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto bytes = cast(const(ubyte)[]) s.representation;
 | 
				
			||||||
 | 
							if (_stripTrailingNewline && (bytes.length && bytes[$ - 1] != '\n'))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								ubyte[] tmp;
 | 
				
			||||||
 | 
								tmp.length = bytes.length + 1;
 | 
				
			||||||
 | 
								if (bytes.length)
 | 
				
			||||||
 | 
									memcpy(tmp.ptr, bytes.ptr, bytes.length);
 | 
				
			||||||
 | 
								tmp[$ - 1] = cast(ubyte) '\n';
 | 
				
			||||||
 | 
								return cast(const(ubyte)[]) tmp;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return bytes;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue