diff --git a/src/config.cpp b/src/config.cpp index 4ac3e1b..bbe7f4d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -80,6 +80,16 @@ static bool isNumber(const std::string& str, bool allowfloat) { return true; } +static void replaceAll(std::string& str, const std::string& from, const std::string& to) { + if (from.empty()) + return; + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) { + str.replace(pos, from.length(), to); + pos += to.length(); + } +} + static int64_t configStringToInt(const std::string& VALUE) { if (VALUE.starts_with("0x")) { // Values with 0x are hex @@ -155,7 +165,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: const auto VALUEIT = impl->values.find(valueName); if (VALUEIT == impl->values.end()) { - result.setError("config option doesn't exist"); + result.setError(std::format("config option <{}> does not exist.", valueName)); return result; } @@ -209,6 +219,13 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: return result; } +CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& rhs) { + impl->variables.push_back({lhs.substr(1), rhs}); + std::sort(impl->variables.begin(), impl->variables.end(), [](const auto& lhs, const auto& rhs) { return lhs.name.length() > rhs.name.length(); }); + CParseResult result; + return result; +} + CParseResult CConfig::parseLine(std::string line) { CParseResult result; @@ -245,10 +262,45 @@ CParseResult CConfig::parseLine(std::string line) { if (equalsPos != std::string::npos) { // set value or call handler CParseResult ret; - const auto LHS = removeBeginEndSpacesTabs(line.substr(0, equalsPos)); - const auto RHS = removeBeginEndSpacesTabs(line.substr(equalsPos + 1)); + auto LHS = removeBeginEndSpacesTabs(line.substr(0, equalsPos)); + auto RHS = removeBeginEndSpacesTabs(line.substr(equalsPos + 1)); - bool found = false; + if (LHS.empty()) { + result.setError("Empty lhs."); + return result; + } + + if (*LHS.begin() == '$') + return parseVariable(LHS, RHS); + + // limit unwrapping iterations to 100. if exceeds, raise error + for (size_t i = 0; i < 100; ++i) { + bool anyMatch = false; + for (auto& var : impl->variables) { + const auto LHSIT = LHS.find("$" + var.name); + const auto RHSIT = RHS.find("$" + var.name); + + if (LHSIT != std::string::npos) + replaceAll(LHS, "$" + var.name, var.value); + if (RHSIT != std::string::npos) + replaceAll(RHS, "$" + var.name, var.value); + + if (RHSIT == std::string::npos && LHSIT == std::string::npos) + break; + + anyMatch = true; + } + + if (!anyMatch) + break; + + if (i == 99) { + result.setError("Expanding variables exceeded max iteration limit"); + return result; + } + } + + bool found = false; for (auto& h : impl->handlers) { if (!h.options.allowFlags && h.name != LHS) continue; diff --git a/src/config.hpp b/src/config.hpp index 24a376c..5acfe80 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -10,6 +10,11 @@ struct SHandler { Hyprlang::PCONFIGHANDLERFUNC func = nullptr; }; +struct SVariable { + std::string name = ""; + std::string value = ""; +}; + class CConfigImpl { public: std::string path = ""; @@ -17,6 +22,7 @@ class CConfigImpl { std::unordered_map values; std::unordered_map defaultValues; std::vector handlers; + std::vector variables; std::vector categories; diff --git a/src/public.hpp b/src/public.hpp index 58e41df..da5dd20 100644 --- a/src/public.hpp +++ b/src/public.hpp @@ -139,6 +139,7 @@ namespace Hyprlang { CParseResult parseLine(std::string line); CParseResult configSetValueSafe(const std::string& command, const std::string& value); + CParseResult parseVariable(const std::string& lhs, const std::string& rhs); void clearState(); }; }; \ No newline at end of file diff --git a/tests/config/config.conf b/tests/config/config.conf index 28402b4..d2b7448 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -5,6 +5,10 @@ testInt = 123 testFloat = 123.456 testString = Hello World! ## This is not a comment! # This is! +$MY_VAR = 1337 +$MY_VAR_2 = $MY_VAR +testVar = $MY_VAR$MY_VAR_2 + testCategory { testValueInt = 123456 testValueHex = 0xF diff --git a/tests/main.cpp b/tests/main.cpp index e38a4e6..46b6db9 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -53,6 +53,7 @@ int main(int argc, char** argv, char** envp) { config.addConfigValue("testFloat", 0.F); config.addConfigValue("testVec", Hyprlang::SVector2D{69, 420}); config.addConfigValue("testString", ""); + config.addConfigValue("testVar", 0L); config.addConfigValue("testStringQuotes", ""); config.addConfigValue("testCategory:testValueInt", 0L); config.addConfigValue("testCategory:testValueHex", 0xAL); @@ -106,6 +107,10 @@ int main(int argc, char** argv, char** envp) { EXPECT(config.parseDynamic("testCategory:testValueHex", "0xaabbccdd").error, false); EXPECT(std::any_cast(config.getConfigValue("testCategory:testValueHex")), 0xAABBCCDDL); + // test variables + std::cout << " → Testing variables\n"; + EXPECT(std::any_cast(config.getConfigValue("testVar")), 13371337); + } catch (const char* e) { std::cout << Colors::RED << "Error: " << Colors::RESET << e << "\n"; return 1;