commits
tags
#include "torlinkc/util/format.hpp"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <map>
#include <regex>
#include <vector>
namespace torlinkc {
std::string formatBytes(double bytes) {
if (!std::isfinite(bytes) || bytes <= 0) return "0 B";
static const char* units[] = {"B", "KB", "MB", "GB", "TB"};
int i = 0;
while (bytes >= 1024.0 && i < 4) {
bytes /= 1024.0;
i++;
}
char buf[64];
std::snprintf(buf, sizeof(buf), i == 0 ? "%.0f %s" : "%.2f %s", bytes, units[i]);
return buf;
}
std::string formatCount(double n) {
if (!std::isfinite(n) || n <= 0) return "0";
if (n < 10000) return std::to_string(static_cast<long long>(std::lround(n)));
const double k = std::round(n / 1000.0);
if (k < 1000) return std::to_string(static_cast<long long>(k)) + "k";
const double m = n / 1'000'000.0;
char buf[32];
if (m < 10) {
std::snprintf(buf, sizeof(buf), "%.1f", m);
std::string s = buf;
if (s.size() >= 2 && s[s.size() - 2] == '.' && s.back() == '0') s.resize(s.size() - 2);
return s + "m";
}
std::snprintf(buf, sizeof(buf), "%.0fm", std::round(m));
return buf;
}
std::int64_t parseSize(const std::string& s) {
static const std::map<std::string, double> kUnits = {
{"B", 1}, {"KIB", 1024}, {"MIB", 1024.0 * 1024}, {"GIB", 1024.0 * 1024 * 1024},
{"TIB", 1024.0 * 1024 * 1024 * 1024}, {"KB", 1000}, {"MB", 1e6}, {"GB", 1e9}, {"TB", 1e12},
};
static const std::regex kSizeRe(R"(([\d.]+)\s*([KMGT]?I?B))", std::regex::icase);
std::smatch m;
if (!std::regex_search(s, m, kSizeRe)) return 0;
std::string unit = m[2].str();
std::transform(unit.begin(), unit.end(), unit.begin(), [](unsigned char c) { return std::toupper(c); });
double multiplier = 1;
if (auto it = kUnits.find(unit); it != kUnits.end()) multiplier = it->second;
try {
return static_cast<std::int64_t>(std::llround(std::stod(m[1].str()) * multiplier));
} catch (...) {
return 0;
}
}
std::string stripControl(const std::string& s) {
std::string out;
out.reserve(s.size());
std::size_t i = 0;
while (i < s.size()) {
const unsigned char c = static_cast<unsigned char>(s[i]);
std::size_t len = 1;
if ((c & 0xE0) == 0xC0) len = 2;
else if ((c & 0xF0) == 0xE0) len = 3;
else if ((c & 0xF8) == 0xF0) len = 4;
len = std::min(len, s.size() - i);
if (len > 1) {
// A valid multi-byte lead byte is always >= 0xC2, outside every
// control range below -- only a lone byte can be one, so a whole
// sequence is always kept together, never inspected byte-by-byte
// (which would treat a continuation byte in 0x80-0x9F as a C1
// control and corrupt the encoding -- the bug this used to have).
out.append(s, i, len);
} else {
const bool isControl = c <= 0x1f || c == 0x7f || (c >= 0x80 && c <= 0x9f);
if (!isControl) out += static_cast<char>(c);
}
i += len;
}
return out;
}
std::string truncate(const std::string& s, int maxWidth) {
std::vector<std::size_t> starts;
std::size_t i = 0;
while (i < s.size()) {
starts.push_back(i);
const unsigned char c = static_cast<unsigned char>(s[i]);
std::size_t len = 1;
if ((c & 0xE0) == 0xC0) len = 2;
else if ((c & 0xF0) == 0xE0) len = 3;
else if ((c & 0xF8) == 0xF0) len = 4;
i += std::min(len, s.size() - i);
}
starts.push_back(s.size());
const int total = static_cast<int>(starts.size()) - 1;
if (maxWidth <= 1) {
const int keep = std::max(0, maxWidth);
return keep >= total ? s : s.substr(0, starts[static_cast<std::size_t>(keep)]);
}
if (total <= maxWidth) return s;
return s.substr(0, starts[static_cast<std::size_t>(maxWidth - 1)]) + "\xE2\x80\xA6";
}
} // namespace torlinkc