Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
e7d16e6969 | |||
ef491965f6 | |||
32203fadc0 | |||
06b2a2a993 |
6 changed files with 212 additions and 314 deletions
6
dub.selections.json
Normal file
6
dub.selections.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"libxdiff": {"version":"e2396bc172eba813cdcd1a96c494e35d687f576a","repository":"git+https://git.zhirov.kz/dlang/xdiff.git"}
|
||||
}
|
||||
}
|
326
source/app.d
326
source/app.d
|
@ -1,322 +1,20 @@
|
|||
import std.stdio : writeln, stderr, File;
|
||||
import std.getopt;
|
||||
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 libxdiff;
|
||||
|
||||
import xdiff;
|
||||
import std.conv : to;
|
||||
import std.stdio : writefln, writeln;
|
||||
import std.file : read;
|
||||
|
||||
// ------------------------------
|
||||
// RAII для mmfile_t
|
||||
// ------------------------------
|
||||
struct MmFile
|
||||
void main()
|
||||
{
|
||||
mmfile_t mf;
|
||||
auto file1 = new MMFile(cast(const(ubyte)[]) read("/tmp/diff1.d"));
|
||||
auto file2 = new MMFile(cast(const(ubyte)[]) read("/tmp/diff2.d"));
|
||||
|
||||
// фабрика вместо конструктора по умолчанию
|
||||
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;
|
||||
}
|
||||
auto patch = file1.computePatch(file2);
|
||||
|
||||
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`
|
||||
|
||||
|
||||
writeln(patch);
|
||||
|
||||
writefln(
|
||||
"file1: %s\nfile2: %s",
|
||||
file1.size, file2.size
|
||||
);
|
||||
enforce(!helpWanted, "help");
|
||||
|
||||
Parsed p;
|
||||
p.cmd = cmd;
|
||||
p.ctxlen = ctxlen;
|
||||
p.bsize = bsize;
|
||||
p.positional = optArgs; // здесь уже только пути
|
||||
return p;
|
||||
}
|
||||
|
||||
int main(string[] args)
|
||||
{
|
||||
// 1) Парсим
|
||||
Parsed p;
|
||||
try
|
||||
{
|
||||
p = parseArgs(args);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 4) Запускаем действие
|
||||
int rc = 0;
|
||||
final switch (p.cmd)
|
||||
{
|
||||
case Cmd.diff:
|
||||
rc = doDiff(mf1, mf2, p.ctxlen);
|
||||
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;
|
||||
}
|
||||
|
|
70
source/libxdiff/init.d
Normal file
70
source/libxdiff/init.d
Normal file
|
@ -0,0 +1,70 @@
|
|||
module libxdiff.init;
|
||||
|
||||
import xdiff;
|
||||
|
||||
import core.stdc.stdlib : malloc, free, realloc;
|
||||
import std.exception : enforce;
|
||||
import core.stdc.errno : errno;
|
||||
import core.stdc.string : strerror;
|
||||
import std.string : fromStringz;
|
||||
import std.conv : to;
|
||||
|
||||
private __gshared bool _allocatorReady = false;
|
||||
package(libxdiff) alias ByteArray = const(ubyte)[];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAllocator()
|
||||
{
|
||||
if (_allocatorReady)
|
||||
return;
|
||||
|
||||
memallocator_t m = {null, &wrap_malloc, &wrap_free, &wrap_realloc};
|
||||
|
||||
enforce(xdl_set_allocator(&m) == 0,
|
||||
"xdl_set_allocator failed: " ~ strerror(errno).fromStringz);
|
||||
_allocatorReady = true;
|
||||
}
|
||||
|
||||
mmfile_t initMmfile(size_t len)
|
||||
{
|
||||
setAllocator();
|
||||
mmfile_t mf;
|
||||
enforce(xdl_init_mmfile(&mf, len.to!long, XDL_MMF_ATOMIC) == 0,
|
||||
"xdl_init_mmfile failed: " ~ strerror(errno).fromStringz);
|
||||
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;
|
||||
}
|
||||
}
|
41
source/libxdiff/mmblocks.d
Normal file
41
source/libxdiff/mmblocks.d
Normal file
|
@ -0,0 +1,41 @@
|
|||
module libxdiff.mmblocks;
|
||||
|
||||
import xdiff;
|
||||
|
||||
import libxdiff.init;
|
||||
|
||||
import std.exception : enforce;
|
||||
import core.stdc.errno : errno;
|
||||
import core.stdc.string : strerror;
|
||||
import std.string : fromStringz;
|
||||
import std.conv : to;
|
||||
|
||||
private enum DEFAULT_BSIZE = 8 * 1024;
|
||||
|
||||
final class MMBlocks : MM
|
||||
{
|
||||
public:
|
||||
this()
|
||||
{
|
||||
_inner = initMmfile(DEFAULT_BSIZE);
|
||||
}
|
||||
|
||||
package(libxdiff) mmfile_t* ptr() @trusted
|
||||
{
|
||||
return &_inner;
|
||||
}
|
||||
|
||||
const(ubyte)[] asSlice() @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
|
||||
{
|
||||
enforce(isCompact(), "MMFile must be compact for asSliceConst");
|
||||
auto h = _inner.head;
|
||||
return h is null || h.size <= 0 ? [] : fromStringz(cast(const char*) h.ptr).idup;
|
||||
}
|
||||
}
|
79
source/libxdiff/mmfile.d
Normal file
79
source/libxdiff/mmfile.d
Normal file
|
@ -0,0 +1,79 @@
|
|||
module libxdiff.mmfile;
|
||||
|
||||
import xdiff;
|
||||
|
||||
import libxdiff.init;
|
||||
import libxdiff.mmblocks;
|
||||
|
||||
import std.exception : enforce;
|
||||
import core.stdc.errno : errno;
|
||||
import core.stdc.string : strerror;
|
||||
import std.string : fromStringz;
|
||||
import std.conv : to;
|
||||
import std.string : join, representation;
|
||||
|
||||
final class MMFile : MM
|
||||
{
|
||||
private:
|
||||
void _write(ByteArray 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: " ~ strerror(errno).fromStringz);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
this(ByteArray 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 computePatch(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)
|
||||
{
|
||||
mmbuffer_t* buf = &bufs[i];
|
||||
long written = xdl_writem_mmfile(target.ptr(), buf, 1);
|
||||
if (written < 0 || written != buf.size)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
xpparam_t xpp = {0};
|
||||
xdemitconf_t xec = {ctxlen};
|
||||
xdemitcb_t ecb = {cast(void*) patch, &emitToPatch};
|
||||
|
||||
enforce(xdl_diff(&_inner, &other._inner, &xpp, &xec, &ecb) == 0,
|
||||
"xdl_diff failed: " ~ strerror(errno).fromStringz);
|
||||
|
||||
return patch;
|
||||
}
|
||||
}
|
4
source/libxdiff/package.d
Normal file
4
source/libxdiff/package.d
Normal file
|
@ -0,0 +1,4 @@
|
|||
module libxdiff;
|
||||
|
||||
public import libxdiff.mmfile;
|
||||
public import libxdiff.mmblocks;
|
Loading…
Add table
Add a link
Reference in a new issue