Files
bantam-cpp/util.cpp
T

374 lines
13 KiB
C++
Raw Normal View History

2026-10-14 00:01:03 +03:00
/* util.cpp — string building, UTF-8, filtering, color, IDs, MD5, misc. */
#include "bantam.hpp"
#include <array>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
/* ----------------------------- formatting ----------------------------- */
static std::string vfmt(const char *fmt, va_list ap) {
va_list cp;
va_copy(cp, ap);
int need = std::vsnprintf(nullptr, 0, fmt, cp);
va_end(cp);
if (need < 0) return {};
std::string s;
s.resize((size_t)need);
std::vsnprintf(s.data(), (size_t)need + 1, fmt, ap);
return s;
}
std::string sfmt(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
std::string s = vfmt(fmt, ap);
va_end(ap);
return s;
}
void str_appendf(std::string &s, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
std::string more = vfmt(fmt, ap);
va_end(ap);
s += more;
}
/* ----------------------------- UTF-8 ---------------------------------- */
int utf8_decode(const unsigned char *p, size_t avail, size_t *plen) {
if (avail == 0) { *plen = 0; return 0xFFFD; }
unsigned char b0 = p[0];
if (b0 < 0x80) { *plen = 1; return b0; }
if ((b0 & 0xE0) == 0xC0) {
if (avail < 2 || (p[1] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; }
int cp = ((b0 & 0x1F) << 6) | (p[1] & 0x3F);
if (cp < 0x80) { *plen = 1; return 0xFFFD; }
*plen = 2; return cp;
}
if ((b0 & 0xF0) == 0xE0) {
if (avail < 3 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; }
int cp = ((b0 & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF)) { *plen = 1; return 0xFFFD; }
*plen = 3; return cp;
}
if ((b0 & 0xF8) == 0xF0) {
if (avail < 4 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; }
int cp = ((b0 & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F);
if (cp < 0x10000 || cp > 0x10FFFF) { *plen = 1; return 0xFFFD; }
*plen = 4; return cp;
}
*plen = 1; return 0xFFFD;
}
std::string_view utf8_truncate(std::string_view s, size_t max_bytes) {
if (s.size() <= max_bytes) return s;
const unsigned char *p = (const unsigned char *)s.data();
size_t pos = 0, avail = s.size();
while (avail > 0) {
size_t pl;
utf8_decode(p + pos, avail, &pl);
if (pl == 0 || pos + pl > max_bytes) break;
pos += pl;
avail -= pl;
}
return s.substr(0, pos);
}
/* ----------------------------- filtering ------------------------------ */
static bool kept_rune(int r) {
if (r == ' ' || r == '\t' || r == '\n') return true;
if (r < 0x20) return false; /* C0 controls */
if (r == 0x7f) return false; /* DEL */
if (r >= 0x80 && r <= 0x9f) return false; /* C1 controls */
switch (r) {
case 0x00a0: case 0x00ad:
case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004:
case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009:
case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f:
case 0x3000:
case 0x200b: case 0x200c: case 0x200d: case 0x2060: case 0xfeff:
case 0x200e: case 0x200f:
case 0x202a: case 0x202b: case 0x202c: case 0x202d: case 0x202e:
return false;
}
return true;
}
std::string filter_text(std::string_view s) {
std::string out;
out.reserve(s.size());
const unsigned char *p = (const unsigned char *)s.data();
size_t avail = s.size();
while (avail > 0) {
size_t pl;
int cp = utf8_decode(p, avail, &pl);
if (cp == 0xFFFD && pl == 1) { p++; avail--; continue; }
if (kept_rune(cp)) out.append((const char *)p, pl);
p += pl; avail -= pl;
}
return out;
}
/* ----------------------------- color ---------------------------------- */
int g_col = 0;
volatile int g_interrupted = 0;
int visible_len(std::string_view s) {
int n = 0;
const unsigned char *p = (const unsigned char *)s.data();
size_t avail = s.size();
while (avail > 0) {
if (p[0] == 0x1b) {
size_t i = 1;
if (i < avail && p[1] == '[') {
i++;
while (i < avail && !(p[i] >= 0x40 && p[i] <= 0x7e)) i++;
if (i < avail) i++;
} else {
i = 1;
}
p += i; avail -= i;
continue;
}
if (p[0] == 0x01 || p[0] == 0x02) { p++; avail--; continue; }
size_t pl;
int cp = utf8_decode(p, avail, &pl);
n += rune_width(cp);
p += pl; avail -= pl;
}
return n;
}
/* Display width of a single Unicode code point. Most code points occupy one
* terminal column, but East-Asian Wide/Fullwidth characters (notably CJK) and
* many emoji occupy two. Combining marks and zero-width characters occupy
* none. Without this, tables containing Chinese text were misaligned because
* visible_len counted every rune as a single column. */
int rune_width(int cp) {
if (cp < 0x20) return 0;
if (cp == 0x7f) return 0;
/* combining marks / zero-width */
if ((cp >= 0x0300 && cp <= 0x036f) || (cp >= 0x0483 && cp <= 0x0489) ||
(cp >= 0x0591 && cp <= 0x05bd) || (cp >= 0x0610 && cp <= 0x061a) ||
(cp >= 0x064b && cp <= 0x065f) || (cp >= 0x0670 && cp <= 0x0670) ||
(cp >= 0x06d6 && cp <= 0x06dc) || (cp >= 0x0e31 && cp <= 0x0e3a) ||
(cp >= 0x0e47 && cp <= 0x0e4e) || (cp >= 0x0eb1 && cp <= 0x0eb9) ||
(cp >= 0x0ebb && cp <= 0x0ebd) || (cp >= 0x0ec8 && cp <= 0x0ecd) ||
(cp >= 0x200b && cp <= 0x200f) || (cp >= 0x202a && cp <= 0x202e) ||
(cp >= 0x2060 && cp <= 0x2064) || (cp >= 0xfe00 && cp <= 0xfe0f) ||
(cp == 0xfeff) || (cp >= 0xfff9 && cp <= 0xfffb) ||
(cp >= 0x1d173 && cp <= 0x1d17a))
return 0;
/* East Asian Wide / Fullwidth */
if (cp == 0x3000) return 2;
if (cp >= 0x1100 && cp <= 0x115f) return 2;
if (cp >= 0x2e80 && cp <= 0x303e) return 2;
if (cp >= 0x3041 && cp <= 0x33ff) return 2;
if (cp >= 0x3400 && cp <= 0x4dbf) return 2;
if (cp >= 0x4e00 && cp <= 0x9fff) return 2;
if (cp >= 0xa000 && cp <= 0xa4cf) return 2;
if (cp >= 0xac00 && cp <= 0xd7a3) return 2;
if (cp >= 0xf900 && cp <= 0xfaff) return 2;
if (cp >= 0xfe30 && cp <= 0xfe4f) return 2;
if (cp >= 0xff00 && cp <= 0xff60) return 2;
if (cp >= 0xffe0 && cp <= 0xffe6) return 2;
if (cp >= 0x1f300 && cp <= 0x1faff) return 2;
if (cp >= 0x20000 && cp <= 0x3fffd) return 2;
return 1;
}
/* ----------------------------- paths ---------------------------------- */
void mkdir_p(std::string_view dir) {
if (dir.empty()) return;
std::string path(dir);
for (size_t i = 1; i < path.size(); i++) {
if (path[i] == '/') {
path[i] = '\0';
mkdir(path.c_str(), 0755);
path[i] = '/';
}
}
mkdir(path.c_str(), 0755);
}
2026-10-14 12:19:11 +03:00
std::string bantam_tmp_dir(std::string_view sess_id) {
2026-10-14 00:01:03 +03:00
const char *tmp = std::getenv("TMPDIR");
if (!tmp || !*tmp) tmp = "/tmp";
2026-10-14 12:19:11 +03:00
std::string base(tmp);
/* Drop a single trailing slash so we never build a "//bantam" path (which
* some platforms treat as a special "super-root"); keep "/" itself intact. */
if (base.size() > 1 && base.back() == '/') base.pop_back();
std::string p = base;
if (p != "/") p += "/";
p += "bantam";
/* Segregate temporary artifacts per session so different projects/sessions
* never share or collide on scratch files. */
std::string sid(sess_id);
if (sid.empty()) sid = project_id();
if (!sid.empty()) { p += "/"; p += sid; }
2026-10-14 00:01:03 +03:00
mkdir_p(p);
return p;
}
2026-10-14 12:19:11 +03:00
std::string tool_res_dir(std::string_view sess_id) {
std::string base = bantam_tmp_dir(sess_id);
2026-10-14 00:01:03 +03:00
std::string p = base + "/toolres";
mkdir_p(p);
return p;
}
std::string project_id() {
char cwd[4096];
if (!getcwd(cwd, sizeof cwd)) std::strcpy(cwd, ".");
char *abs = realpath(cwd, nullptr);
std::string src = abs ? abs : cwd;
if (abs) free(abs);
return md5_hex(src);
}
/* ----------------------------- MD5 ------------------------------------ */
#define MD5_ROTLEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c))))
struct MD5_CTX { uint32_t state[4]; uint32_t count[2]; unsigned char buf[64]; };
static void md5_transform(uint32_t state[4], const unsigned char block[64]);
static void md5_init(MD5_CTX *c) {
c->state[0] = 0x67452301; c->state[1] = 0xefcdab89;
c->state[2] = 0x98badcfe; c->state[3] = 0x10325476;
c->count[0] = c->count[1] = 0;
}
static void md5_update(MD5_CTX *c, const unsigned char *in, size_t len) {
uint32_t bits = (uint32_t)(c->count[0] >> 3) & 0x3F;
if ((c->count[0] += (uint32_t)(len << 3)) < (uint32_t)(len << 3)) c->count[1]++;
c->count[1] += (uint32_t)(len >> 29);
while (len--) {
c->buf[bits++] = *in++;
if (bits == 64) { md5_transform(c->state, c->buf); bits = 0; }
}
}
static void md5_final(unsigned char digest[16], MD5_CTX *c) {
unsigned char bits[8];
for (int i = 0; i < 8; i++) bits[i] = (unsigned char)((c->count[i >> 2] >> ((i & 3) * 8)) & 0xFF);
unsigned char pad = (unsigned char)((c->count[0] >> 3) & 0x3F);
unsigned char padlen = (pad < 56) ? (56 - pad) : (120 - pad);
unsigned char p = 0x80;
md5_update(c, &p, 1);
unsigned char zero = 0;
for (int i = 0; i < padlen - 1; i++) md5_update(c, &zero, 1);
md5_update(c, bits, 8);
for (int i = 0; i < 4; i++) {
digest[i * 4 + 0] = (unsigned char)(c->state[i] & 0xFF);
digest[i * 4 + 1] = (unsigned char)((c->state[i] >> 8) & 0xFF);
digest[i * 4 + 2] = (unsigned char)((c->state[i] >> 16) & 0xFF);
digest[i * 4 + 3] = (unsigned char)((c->state[i] >> 24) & 0xFF);
}
}
static void md5_transform(uint32_t state[4], const unsigned char b[64]) {
uint32_t a = state[0], d = state[3], c = state[2], e = state[1];
uint32_t x[16];
for (int i = 0, j = 0; i < 16; i++, j += 4)
x[i] = (uint32_t)b[j] | ((uint32_t)b[j+1] << 8) | ((uint32_t)b[j+2] << 16) | ((uint32_t)b[j+3] << 24);
static const uint32_t K[64] = {
0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821,
0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391};
static const int S[64] = {
7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
5,9,14,20, 5,9,14,20, 5,9,14,20, 5,9,14,20,
4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21};
for (int i = 0; i < 64; i++) {
uint32_t f; int g;
if (i < 16) { f = (e & c) | (~e & d); g = i; }
else if (i < 32) { f = (d & e) | (~d & c); g = (5 * i + 1) % 16; }
else if (i < 48) { f = e ^ c ^ d; g = (3 * i + 5) % 16; }
else { f = c ^ (e | (~d)); g = (7 * i) % 16; }
f = f + a + K[i] + x[g];
a = d; d = c; c = e;
e = e + MD5_ROTLEFT(f, S[i]);
}
state[0] += a; state[1] += e; state[2] += c; state[3] += d;
}
std::string md5_hex(std::string_view data) {
MD5_CTX ctx; md5_init(&ctx);
md5_update(&ctx, (const unsigned char *)data.data(), data.size());
unsigned char dig[16];
md5_final(dig, &ctx);
std::string out;
out.reserve(33);
static const char *hx = "0123456789abcdef";
for (int i = 0; i < 16; i++) {
out += hx[dig[i] >> 4];
out += hx[dig[i] & 0xF];
}
return out;
}
/* ----------------------------- misc ----------------------------------- */
int to_int_val(std::string_view s, int def) {
if (s.empty()) return def;
std::string tmp(s);
char *end;
long v = std::strtol(tmp.c_str(), &end, 10);
if (end == tmp.c_str() || *end != '\0') return def;
return (int)v;
}
bool read_file_str(std::string_view path, std::string &out) {
out.clear();
std::string p(path);
FILE *f = std::fopen(p.c_str(), "rb");
if (!f) return false;
char buf[4096];
size_t n;
while ((n = std::fread(buf, 1, sizeof buf, f)) > 0) {
out.append(buf, n);
}
bool ok = (std::ferror(f) == 0);
std::fclose(f);
return ok;
}
bool write_file_str(std::string_view path, std::string_view data) {
std::string p(path);
FILE *f = std::fopen(p.c_str(), "wb");
if (!f) return false;
size_t written = 0;
while (written < data.size()) {
size_t n = std::fwrite(data.data() + written, 1, data.size() - written, f);
if (n == 0) {
std::fclose(f);
return false;
}
written += n;
}
return std::fclose(f) == 0;
}
2026-10-14 00:01:03 +03:00
/* ----------------------------- signal --------------------------------- */
static void sigint_handler(int sig) {
(void)sig;
g_interrupted = 1;
}
void setup_signal_handler() {
struct sigaction sa;
std::memset(&sa, 0, sizeof sa);
sa.sa_handler = sigint_handler;
sigaction(SIGINT, &sa, nullptr);
}
int color_enabled(const Cfg &cfg) {
const char *nc = std::getenv("NO_COLOR");
if (nc && *nc) return 0;
const char *bnc = std::getenv("BANTAM_NO_COLOR");
if (bnc && *bnc) return 0;
const std::string &c = cfg.color;
if (c == "always") return 1;
if (c == "never") return 0;
return is_terminal(STDOUT_FILENO);
}