Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
ef491965f6 | |||
32203fadc0 | |||
06b2a2a993 |
3 changed files with 21 additions and 320 deletions
6
dub.json
6
dub.json
|
@ -12,9 +12,9 @@
|
|||
"xdiff"
|
||||
],
|
||||
"dependencies": {
|
||||
"xdiff": {
|
||||
"repository": "git+https://git.zhirov.kz/dlang/xdiff.git",
|
||||
"version": "e2396bc172eba813cdcd1a96c494e35d687f576a"
|
||||
"libxdiff": {
|
||||
"repository": "git+https://git.zhirov.kz/dlang/libxdiff.git",
|
||||
"version": "143f39005c0e89cc57d564365e6ea61427928bc3"
|
||||
}
|
||||
}
|
||||
}
|
6
dub.selections.json
Normal file
6
dub.selections.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"libxdiff": {"version":"143f39005c0e89cc57d564365e6ea61427928bc3","repository":"git+https://git.zhirov.kz/dlang/libxdiff.git"}
|
||||
}
|
||||
}
|
331
source/app.d
331
source/app.d
|
@ -1,322 +1,17 @@
|
|||
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 std.stdio;
|
||||
import libxdiff : MMFile;
|
||||
|
||||
import xdiff;
|
||||
void main()
|
||||
{
|
||||
auto a = MMFile.fromBytes(cast(ubyte[]) "hello world\n");
|
||||
auto b = MMFile.fromBytes(cast(ubyte[]) "hello world!\n");
|
||||
|
||||
// ------------------------------
|
||||
// RAII для mmfile_t
|
||||
// ------------------------------
|
||||
struct MmFile
|
||||
{
|
||||
mmfile_t mf;
|
||||
auto patch = a.computePatch(b);
|
||||
writeln("patch size: ", patch.size());
|
||||
|
||||
// фабрика вместо конструктора по умолчанию
|
||||
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;
|
||||
auto res = a.applyPatch(patch);
|
||||
writeln("apply success: ", res.success);
|
||||
writeln(res.patched.asSlice()); // печатаем результат
|
||||
// печатаем как есть (включая заголовок @@ и строки с '-'/'+' и '\n')
|
||||
write(cast(string) MMFile.fromBlocksMoved(patch).asSlice());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue