mirror of
https://github.com/aristocratos/btop.git
synced 2024-06-02 02:24:54 +12:00
Get rid of string-based option access
This commit is contained in:
parent
4569eaae9a
commit
13b66edbf2
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
1179
src/btop_menu.cpp
1179
src/btop_menu.cpp
File diff suppressed because it is too large
Load diff
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue