src/util.cpp
browsing at commit = 55fa1ac0b398c748c7e2421b855a1606790cafc9
#include "util.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
namespace util {
namespace {
constexpr int MIN_OID_LEN = 4;
constexpr int MAX_OID_LEN = 40;
} // namespace
bool is_hex_oid(const std::string &s) {
if (s.length() < MIN_OID_LEN || s.length() > MAX_OID_LEN)
return false;
for (char c : s) {
const bool is_digit = c >= '0' && c <= '9';
const bool is_lower_hex = c >= 'a' && c <= 'f';
if (!is_digit && !is_lower_hex)
return false;
}
return true;
}
std::string human_size(uint64_t bytes) {
constexpr uint64_t unit = 1024;
const std::array<const char *, 5> suffixes{"B", "KiB", "MiB", "GiB", "TiB"};
if (bytes < unit)
return std::to_string(bytes) + " B";
double value = static_cast<double>(bytes);
size_t i = 0;
while (value >= unit && i + 1 < suffixes.size()) {
value /= unit;
++i;
}
std::array<char, 32> buf{};
std::snprintf(buf.data(), buf.size(), "%.1f %s", value, suffixes[i]);
return std::string(buf.data());
}
std::string format_time(std::time_t t) {
std::tm tm{};
gmtime_r(&t, &tm);
std::array<char, 32> buf{};
std::strftime(buf.data(), buf.size(), "%Y-%m-%d %H:%M", &tm);
return std::string(buf.data());
}
std::string basename(const std::string &path) {
const size_t pos = path.find_last_of('/');
if (pos == std::string::npos)
return path;
return path.substr(pos + 1);
}
std::string parent_path(const std::string &path) {
const size_t pos = path.find_last_of('/');
if (pos == std::string::npos)
return "";
return path.substr(0, pos);
}
std::string trim_slashes(const std::string &path) {
size_t start = 0;
size_t end = path.length();
while (start < end && path[start] == '/')
++start;
while (end > start && path[end - 1] == '/')
--end;
return path.substr(start, end - start);
}
std::string sanitize_filename(const std::string &name) {
std::string out = name;
for (char &c : out) {
const bool is_digit = c >= '0' && c <= '9';
const bool is_upper = c >= 'A' && c <= 'Z';
const bool is_lower = c >= 'a' && c <= 'z';
const bool is_safe_punct = c == '.' || c == '_' || c == '-';
if (!is_digit && !is_upper && !is_lower && !is_safe_punct)
c = '_';
}
return out;
}
bool is_markdown_path(const std::string &path) {
const size_t dot = path.find_last_of('.');
if (dot == std::string::npos)
return false;
std::string ext = path.substr(dot + 1);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return ext == "md" || ext == "markdown";
}
std::string image_mime_type(const std::string &path) {
const size_t dot = path.find_last_of('.');
if (dot == std::string::npos)
return "";
std::string ext = path.substr(dot + 1);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "png")
return "image/png";
if (ext == "jpg" || ext == "jpeg")
return "image/jpeg";
if (ext == "gif")
return "image/gif";
if (ext == "webp")
return "image/webp";
if (ext == "svg")
return "image/svg+xml";
if (ext == "bmp")
return "image/bmp";
if (ext == "ico")
return "image/x-icon";
if (ext == "avif")
return "image/avif";
return "";
}
namespace {
// Splits a remote URL into host and path ("owner/repo[.git]"). Returns false
// when the URL has no recognisable host/path split.
bool split_remote(const std::string &url, std::string &host, std::string &path) {
const std::string scheme_sep = "://";
const size_t scheme = url.find(scheme_sep);
if (scheme != std::string::npos) {
// scheme://[user@]host[:port]/owner/repo
std::string rest = url.substr(scheme + scheme_sep.length());
const size_t at = rest.find('@');
if (at != std::string::npos)
rest = rest.substr(at + 1);
const size_t slash = rest.find('/');
if (slash == std::string::npos)
return false;
host = rest.substr(0, slash);
path = rest.substr(slash + 1);
const size_t port = host.find(':');
if (port != std::string::npos)
host = host.substr(0, port);
return true;
}
// scp-like: [user@]host:owner/repo
const size_t colon = url.find(':');
if (colon == std::string::npos)
return false;
std::string host_part = url.substr(0, colon);
path = url.substr(colon + 1);
const size_t at = host_part.find('@');
host = at == std::string::npos ? host_part : host_part.substr(at + 1);
return true;
}
bool is_github_host(std::string host, const std::vector<std::string> &enterprise_hosts) {
std::transform(host.begin(), host.end(), host.begin(), ::tolower);
if (host == "github.com")
return true;
for (const std::string &configured : enterprise_hosts) {
std::string h = configured;
std::transform(h.begin(), h.end(), h.begin(), ::tolower);
if (host == h)
return true;
}
return false;
}
} // namespace
GitHubClone github_clone_urls(const std::string &remote_url,
const std::vector<std::string> &enterprise_hosts) {
GitHubClone result;
std::string host;
std::string path;
if (!split_remote(remote_url, host, path))
return result;
if (!is_github_host(host, enterprise_hosts))
return result;
while (path.length() > 0 && path.front() == '/')
path.erase(path.begin());
while (path.length() > 0 && path.back() == '/')
path.pop_back();
const std::string git_suffix = ".git";
if (path.length() >= git_suffix.length() &&
path.compare(path.length() - git_suffix.length(), git_suffix.length(),
git_suffix) == 0)
path = path.substr(0, path.length() - git_suffix.length());
if (path.empty())
return result;
result.ssh = "git@" + host + ":" + path + ".git";
result.https = "https://" + host + "/" + path + ".git";
return result;
}
bool show_emails() {
static const bool value = [] {
const char *flag = std::getenv("SHOW_EMAILS");
return flag && std::string(flag) == "1";
}();
return value;
}
} // namespace util