dxdiff/source/app.d
2025-08-31 00:38:29 +03:00

322 lines
6.8 KiB
D
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 xdiff;
// ------------------------------
// 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)
{
// 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;
}