Get rid of string-based option access

This commit is contained in:
lvxnull 2023-10-21 21:30:27 +02:00
parent 4569eaae9a
commit 13b66edbf2
5 changed files with 788 additions and 730 deletions

View file

@ -21,6 +21,7 @@ tab-size = 4
#include <exception>
#include <fstream>
#include <ranges>
#include <stdexcept>
#include <string_view>
#include <fmt/core.h>
@ -44,7 +45,7 @@ namespace Config {
atomic<bool> locked (false);
atomic<bool> writelock (false);
bool write_new;
robin_hood::unordered_flat_set<std::string_view> cached{};
unordered_flat_map<uint32_t, ConfigType> cached{};
ConfigSet current = ConfigSet::with_defaults();
ConfigSet cache{};
@ -150,91 +151,21 @@ namespace Config {
string validError;
bool intValid(const std::string_view name, const string& value) {
int i_value;
bool to_int(const string& value, int& result) {
try {
i_value = stoi(value);
}
catch (const std::invalid_argument&) {
result = stoi(value);
} catch (const std::invalid_argument&) {
validError = "Invalid numerical value!";
return false;
}
catch (const std::out_of_range&) {
} catch (const std::out_of_range&) {
validError = "Value out of range!";
return false;
}
catch (const std::exception& e) {
return false;
} catch (const std::exception& e) {
validError = string{e.what()};
return false;
}
if (name == "update_ms" and i_value < 100)
validError = "Config value update_ms set too low (<100).";
else if (name == "update_ms" and i_value > 86400000)
validError = "Config value update_ms set too high (>86400000).";
else
return true;
return false;
}
bool stringValid(const std::string_view name, const string& value) {
if (name == "log_level" and not v_contains(Logger::log_levels, value))
validError = "Invalid log_level: " + value;
else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value))
validError = "Invalid graph symbol identifier: " + value;
else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value)))
validError = fmt::format("Invalid graph symbol identifier for {}: {}", name, value);
else if (name == "shown_boxes" and not value.empty() and not check_boxes(value))
validError = "Invalid box name(s) in shown_boxes!";
else if (name == "presets" and not presetsValid(value))
return false;
else if (name == "cpu_core_map") {
const auto maps = ssplit(value);
bool all_good = true;
for (const auto& map : maps) {
const auto map_split = ssplit(map, ':');
if (map_split.size() != 2)
all_good = false;
else if (not isint(map_split.at(0)) or not isint(map_split.at(1)))
all_good = false;
if (not all_good) {
validError = "Invalid formatting of cpu_core_map!";
return false;
}
}
return true;
}
else if (name == "io_graph_speeds") {
const auto maps = ssplit(value);
bool all_good = true;
for (const auto& map : maps) {
const auto map_split = ssplit(map, ':');
if (map_split.size() != 2)
all_good = false;
else if (map_split.at(0).empty() or not isint(map_split.at(1)))
all_good = false;
if (not all_good) {
validError = "Invalid formatting of io_graph_speeds!";
return false;
}
}
return true;
}
else
return true;
return false;
return true;
}
template<typename T>
@ -242,18 +173,16 @@ namespace Config {
dynamic_set<T>(dest, offset, dynamic_get<T>(src, offset));
}
string getAsString(const std::string_view name) {
auto& entry = parse_table.at(name);
switch(entry.type) {
case ConfigType::BOOL:
return dynamic_get<bool>(current, entry.offset) ? "True" : "False";
case ConfigType::INT:
return to_string(dynamic_get<int>(current, entry.offset));
case ConfigType::STRING:
return dynamic_get<string>(current, entry.offset);
default:
throw std::logic_error("Not implemented");
string getAsString(const size_t offset) {
switch(offset) {
#define X(T, name, v, desc) \
case CONFIG_OFFSET(name): \
return details::to_string<T>(current, offset);
OPTIONS_LIST
#undef X
}
return "";
}
void unlock() {
@ -270,17 +199,15 @@ namespace Config {
}
for(auto& item: cached) {
auto& data = parse_table.at(item);
switch(data.type) {
switch(item.second) {
case ConfigType::STRING:
copy_field<string>(current, cache, data.offset);
copy_field<string>(current, cache, item.first);
break;
case ConfigType::INT:
copy_field<int>(current, cache, data.offset);
copy_field<int>(current, cache, item.first);
break;
case ConfigType::BOOL:
copy_field<bool>(current, cache, data.offset);
copy_field<bool>(current, cache, item.first);
break;
}
}
@ -350,23 +277,23 @@ namespace Config {
}
cread >> std::ws;
int intValue;
switch(found->second.type) {
case ConfigType::BOOL:
cread >> value;
if (not isbool(value))
load_warnings.push_back("Got an invalid bool value for config name: " + name);
else
try {
dynamic_set(set, found->second.offset, stobool(value));
} catch(std::invalid_argument&) {
load_warnings.push_back("Got an invalid bool value for config name: " + name);
}
break;
case ConfigType::INT:
cread >> value;
if (not isint(value))
load_warnings.push_back("Got an invalid integer value for config name: " + name);
else if (not intValid(name, value)) {
if(to_int(value, intValue) and validate(found->second.offset, intValue)) {
dynamic_set(set, found->second.offset, intValue);
} else {
load_warnings.push_back(validError);
}
else
dynamic_set(set, found->second.offset, stoi(value));
break;
case ConfigType::STRING:
if (cread.peek() == '"') {
@ -375,10 +302,11 @@ namespace Config {
}
else cread >> value;
if (not stringValid(name, value))
load_warnings.push_back(validError);
else
if (validate(found->second.offset, value)) {
dynamic_set(set, found->second.offset, value);
} else {
load_warnings.push_back(validError);
}
break;
}
@ -391,34 +319,37 @@ namespace Config {
}
}
template<typename T>
inline void write_field(std::ofstream& stream, const std::string_view name, const T& value, const std::string_view description) {
stream << "\n\n";
if(not description.empty()) {
stream << description << '\n';
}
stream << name << " = ";
if constexpr(std::is_same_v<T, bool>) {
stream << (value ? "True" : "False");
}
if constexpr(std::is_same_v<T, int>) {
stream << value;
}
if constexpr(std::is_same_v<T, string>) {
stream << '"' << value << '"';
}
}
void write() {
if (conf_file.empty() or not write_new) return;
Logger::debug("Writing new config file");
if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return;
std::ofstream cwrite(conf_file, std::ios::trunc);
auto write_field = [&](const std::string_view name, const std::string_view description) {
cwrite << "\n\n";
if(not description.empty()) {
cwrite << description << "\n";
}
cwrite << name << " = ";
auto& data = parse_table.at(name);
switch(data.type) {
case ConfigType::BOOL:
cwrite << (dynamic_get<bool>(current, data.offset) ? "True" : "False");
break;
case ConfigType::INT:
cwrite << dynamic_get<int>(current, data.offset);
break;
case ConfigType::STRING:
cwrite << '"' << dynamic_get<string>(current, data.offset) << '"';
break;
}
};
if (cwrite.good()) {
cwrite << "#? Config file for btop v. " << Global::Version;
#define X(T, name, v, desc) write_field(#name, desc);
#define X(T, name, v, desc) write_field(cwrite, #name, current.name, desc);
OPTIONS_WRITABLE_LIST
#undef X
}

View file

@ -18,6 +18,7 @@ tab-size = 4
#pragma once
#include "btop_tools.hpp"
#include <string>
#include <vector>
#include <filesystem>
@ -196,9 +197,10 @@ using robin_hood::unordered_flat_map;
#define OPTIONS_LIST OPTIONS_WRITABLE_LIST OPTIONS_INTERNAL_LIST
#define CONFIG_OFFSET(name) offsetof(Config::ConfigSet, name)
#define CONFIG_SET(name, v) Config::set< \
decltype(std::declval<Config::ConfigSet>().name) \
>(offsetof(Config::ConfigSet, name), #name, v, Config::is_writable(#name))
>(CONFIG_OFFSET(name), v, Config::is_writable(#name))
//* Functions and variables for reading and writing the btop config file
namespace Config {
@ -226,12 +228,6 @@ namespace Config {
};
}
struct ParseData {
// Offset will be 32bit to save space/make this fit in a register
uint32_t offset;
ConfigType type;
};
struct ConfigSet {
#define X(T, name, v, desc) T name;
OPTIONS_LIST
@ -246,10 +242,21 @@ namespace Config {
}
};
struct ParseData {
// Offset will be 32bit to save space/make this fit in a register
uint32_t offset;
ConfigType type;
};
template<typename T>
constexpr inline ConfigType type_enum() {
return details::TypeToEnum<T>::value;
}
const unordered_flat_map<std::string_view, ParseData> parse_table = {
#define X(T, name, v, desc) { #name, { \
.offset = offsetof(ConfigSet, name), \
.type = Config::details::TypeToEnum<decltype(std::declval<ConfigSet>().name)>::value, \
.offset = CONFIG_OFFSET(name), \
.type = Config::type_enum<decltype(std::declval<ConfigSet>().name)>(), \
} },
OPTIONS_LIST
#undef X
@ -267,7 +274,7 @@ namespace Config {
extern vector<string> preset_list;
extern vector<string> available_batteries;
extern int current_preset;
extern robin_hood::unordered_flat_set<std::string_view> cached;
extern unordered_flat_map<uint32_t, ConfigType> cached;
constexpr static bool is_writable(const std::string_view option) {
#define X(T, name, v, desc) if(option == #name) return false;
@ -297,7 +304,7 @@ namespace Config {
bool _locked();
string getAsString(const std::string_view name);
string getAsString(const size_t offset);
template<typename T>
inline const T& dynamic_get(const ConfigSet& src, size_t offset) {
@ -310,18 +317,103 @@ namespace Config {
}
template<typename T>
void set(const size_t offset, const std::string_view name, const T& value, const bool writable) {
void set(const size_t offset, const T& value, const bool writable) {
bool locked = _locked();
auto& set = get_mut(locked, writable);
if(locked)
cached.insert(name);
cached[offset] = type_enum<T>();
dynamic_set(set, offset, value);
}
namespace details {
template<typename T>
inline string to_string(const ConfigSet& set, const size_t offset);
template<>
inline string to_string<bool>(const ConfigSet& set, const size_t offset) {
return dynamic_get<bool>(set, offset) ? "True" : "False";
}
template<>
inline string to_string<int>(const ConfigSet& set, const size_t offset) {
return std::to_string(dynamic_get<int>(set, offset));
}
template<>
inline string to_string<string>(const ConfigSet& set, const size_t offset) {
return dynamic_get<string>(set, offset);
}
}
extern string validError;
bool intValid(const std::string_view name, const string& value);
bool stringValid(const std::string_view name, const string& value);
//* Converts string to int. True on success, false on error.
bool to_int(const string& value, int& result);
template<typename T>
bool validate(const size_t offset, const T& value, const bool setError = true) {
using namespace Tools;
bool isValid = true;
#define VALIDATE(name, cond, err) case CONFIG_OFFSET(name): \
isValid = (cond); \
if(setError and not isValid) { \
validError = (err); \
} \
break
if constexpr(std::is_same_v<T, int>) {
switch(offset) {
VALIDATE(update_ms, value >= 100 && value <= 86400000,
"Config value update_ms out of range (100 <= x <= 86400000)");
}
}
if constexpr(std::is_same_v<T, string>) {
switch(offset) {
VALIDATE(log_level, Tools::v_contains(Logger::log_levels, value),
"Invalid log level: " + value);
VALIDATE(graph_symbol, Tools::v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier: " + value);
VALIDATE(graph_symbol_proc, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_proc: " + value);
VALIDATE(graph_symbol_cpu, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_cpu: " + value);
VALIDATE(graph_symbol_mem, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_mem: " + value);
VALIDATE(graph_symbol_net, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_net: " + value);
VALIDATE(shown_boxes, value.empty() || check_boxes(value),
"Invalid box_name(s) in shown_boxes");
VALIDATE(cpu_core_map, [&]{
const auto maps = ssplit(value);
for (const auto& map : maps) {
const auto map_split = ssplit(map, ':');
if (map_split.size() != 2 || not isint(map_split.at(0)) or not isint(map_split.at(1)))
return false;
}
return true;
}(), "Invalid formatting of cpu_core_map!");
VALIDATE(io_graph_speeds, [&]{
const auto maps = ssplit(value);
for (const auto& map : maps) {
const auto map_split = ssplit(map, ':');
if (map_split.size() != 2 || map_split.at(0).empty() or not isint(map_split.at(1)))
return false;
}
return true;
}(), "Invalid formatting of io_graph_speeds!");
case CONFIG_OFFSET(presets):
return presetsValid(value);
}
}
#undef VALIDATE
return isValid;
}
//* Lock config and cache changes until unlocked
void lock();

File diff suppressed because it is too large Load diff

View file

@ -258,14 +258,6 @@ namespace Tools {
return str;
}
string s_replace(const string& str, const string& from, const string& to) {
string out = str;
for (size_t start_pos = out.find(from); start_pos != std::string::npos; start_pos = out.find(from)) {
out.replace(start_pos, from.length(), to);
}
return out;
}
string ltrim(const string& str, const string& t_str) {
std::string_view str_v{str};
while (str_v.starts_with(t_str))

View file

@ -166,7 +166,14 @@ namespace Tools {
string luresize(const string str, const size_t len, bool wide = false);
//* Replace <from> in <str> with <to> and return new string
string s_replace(const string& str, const string& from, const string& to);
template<typename T>
string s_replace(const T& str, const std::string_view from, const std::string_view to) {
string out = std::string(str);
for (size_t start_pos = out.find(from); start_pos != std::string::npos; start_pos = out.find(from)) {
out.replace(start_pos, from.length(), to);
}
return out;
}
//* Capatilize <str>
inline string capitalize(string str) {
@ -235,14 +242,17 @@ namespace Tools {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
//* Check if a string is a valid bool value
inline bool isbool(const string& str) {
return is_in(str, "true", "false", "True", "False");
}
//* Convert string to bool, returning any value not equal to "true" or "True" as false
//* Convert string to bool, or throws if string cannot be converted
inline bool stobool(const string& str) {
return is_in(str, "true", "True");
if(is_in(str, "true", "True")) {
return true;
}
if(is_in(str, "false", "False")) {
return false;
}
throw std::invalid_argument("Argument cannot be convert to bool");
}
//* Check if a string is a valid integer value (only postive)