server/src/quit_hotkey_parse.cpp
Rama main · réplica de lectura del repositorio original.
/**
* @file src/quit_hotkey_parse.cpp
* @brief The parser, which must never be the reason a server fails to start.
*
* Nothing here throws and nothing here logs. A rejected value comes back as a result carrying a
* sentence, and the decision about what to do about it - fall back and carry on - belongs to the
* caller, where the config and the log are.
*/
#include "src/quit_hotkey_parse.h"
#include <algorithm>
#include <cctype>
#include <string>
#include <vector>
namespace quit_hotkey {
namespace {
std::string trim(const std::string &value) {
const auto first = value.find_first_not_of(" \t\r\n");
if (first == std::string::npos) {
return {};
}
const auto last = value.find_last_not_of(" \t\r\n");
return value.substr(first, last - first + 1);
}
std::string upper(const std::string &value) {
std::string out = value;
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) {
return static_cast<char>(std::toupper(c));
});
return out;
}
std::vector<std::string> split_on_plus(const std::string &value) {
std::vector<std::string> parts;
std::string current;
for (const char c : value) {
if (c == '+') {
parts.push_back(current);
current.clear();
} else {
current += c;
}
}
parts.push_back(current);
return parts;
}
} // namespace
std::string combo_t::default_value() {
return "Ctrl+Alt+Shift+Q";
}
parse_result_t parse(const std::string &value) {
parse_result_t result;
const std::string trimmed = trim(value);
if (trimmed.empty()) {
result.error = "the value is empty";
return result;
}
bool have_key = false;
for (const auto &raw_part : split_on_plus(trimmed)) {
const std::string part = upper(trim(raw_part));
if (part.empty()) {
result.error = "there is an empty part between two '+' signs";
return result;
}
if (part == "CTRL") {
result.ctrl = true;
continue;
}
if (part == "ALT") {
result.alt = true;
continue;
}
if (part == "SHIFT") {
result.shift = true;
continue;
}
// Everything else has to be the key itself, and there can only be one.
if (have_key) {
result.error = "'" + part + "' is a second key; a combination has exactly one";
return result;
}
if (part.size() == 1 && (std::isupper(static_cast<unsigned char>(part[0])) || std::isdigit(static_cast<unsigned char>(part[0])))) {
result.key = part[0];
have_key = true;
continue;
}
// A function key: F followed by 1-24. "F" on its own is the letter F, not a function key,
// which is why this needs the digits to be present and in range.
if (part.size() > 1 && part[0] == 'F') {
const std::string digits = part.substr(1);
if (std::all_of(digits.begin(), digits.end(), [](unsigned char c) { return std::isdigit(c); })) {
const int number = std::stoi(digits);
if (number >= 1 && number <= 24) {
result.function_key = number;
have_key = true;
continue;
}
result.error = "'" + part + "' is not a function key; this accepts F1 to F24";
return result;
}
}
result.error = "'" + part + "' is not a key this can register";
return result;
}
if (!have_key) {
result.error = "there is no key, only modifiers; that combination can never be pressed";
return result;
}
result.ok = true;
return result;
}
combo_t to_combo(const parse_result_t &parsed) {
combo_t combo;
combo.key = parsed.key;
combo.function_key = parsed.function_key;
combo.ctrl = parsed.ctrl;
combo.alt = parsed.alt;
combo.shift = parsed.shift;
return combo;
}
} // namespace quit_hotkey