init
This commit is contained in:
commit
e9952bacf2
6 changed files with 392 additions and 0 deletions
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
.dub
|
||||
docs.json
|
||||
__dummy.html
|
||||
docs/
|
||||
/dxdiff
|
||||
dxdiff.so
|
||||
dxdiff.dylib
|
||||
dxdiff.dll
|
||||
dxdiff.a
|
||||
dxdiff.lib
|
||||
dxdiff-test-*
|
||||
*.exe
|
||||
*.pdb
|
||||
*.o
|
||||
*.obj
|
||||
*.lst
|
||||
/bin
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// Используйте IntelliSense, чтобы узнать о возможных атрибутах.
|
||||
// Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов.
|
||||
// Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "code-d",
|
||||
"request": "launch",
|
||||
"dubBuild": true,
|
||||
"name": "Build & Debug DUB project",
|
||||
"cwd": "${command:dubWorkingDirectory}",
|
||||
"program": "bin/${command:dubTarget}",
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
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
|
||||
}
|
11
README.md
Normal file
11
README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# dxdiff
|
||||
|
||||
Тестовый пример использования библиотеки LibXDiff.
|
||||
|
||||
```
|
||||
use: ./bin/dxdiff diff [-C N] FROM TO
|
||||
./bin/dxdiff patch ORIG PATCH
|
||||
./bin/dxdiff bdiff [-B N] FROM TO
|
||||
./bin/dxdiff rabdiff FROM TO
|
||||
./bin/dxdiff bpatch ORIG PATCH
|
||||
```
|
20
dub.json
Normal file
20
dub.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"authors": [
|
||||
"alexander"
|
||||
],
|
||||
"copyright": "Copyright © 2025, alexander",
|
||||
"description": "A minimal D application.",
|
||||
"license": "proprietary",
|
||||
"name": "dxdiff",
|
||||
"targetPath": "bin",
|
||||
"targetType": "executable",
|
||||
"libs": [
|
||||
"xdiff"
|
||||
],
|
||||
"dependencies": {
|
||||
"xdiff": {
|
||||
"repository": "git+https://git.zhirov.kz/dlang/xdiff.git",
|
||||
"version": "e2396bc172eba813cdcd1a96c494e35d687f576a"
|
||||
}
|
||||
}
|
||||
}
|
322
source/app.d
Normal file
322
source/app.d
Normal file
|
@ -0,0 +1,322 @@
|
|||
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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue