diff --git a/include/hyprlang.hpp b/include/hyprlang.hpp index 547fcb8..495270b 100644 --- a/include/hyprlang.hpp +++ b/include/hyprlang.hpp @@ -442,17 +442,17 @@ namespace Hyprlang { } private: - bool m_bCommenced = false; + bool m_bCommenced = false; - CConfigImpl* impl; + CConfigImpl* impl; - CParseResult parseLine(std::string line, bool dynamic = false); - CParseResult configSetValueSafe(const std::string& command, const std::string& value); - CParseResult parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic = false); - void clearState(); - void applyDefaultsToCat(SSpecialCategory& cat); - void retrieveKeysForCat(const char* category, const char*** out, size_t* len); - CParseResult parseRawStream(const std::string& stream); + CParseResult parseLine(std::string line, bool dynamic = false); + std::pair configSetValueSafe(const std::string& command, const std::string& value); + CParseResult parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic = false); + void clearState(); + void applyDefaultsToCat(SSpecialCategory& cat); + void retrieveKeysForCat(const char* category, const char*** out, size_t* len); + CParseResult parseRawStream(const std::string& stream); }; /*! diff --git a/src/config.cpp b/src/config.cpp index 82ce724..0d15c50 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -279,7 +279,8 @@ static std::expected configStringToInt(const std::string& return 0; } -CParseResult CConfig::configSetValueSafe(const std::string& command, const std::string& value) { +// found, result +std::pair CConfig::configSetValueSafe(const std::string& command, const std::string& value) { CParseResult result; std::string valueName; @@ -358,7 +359,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: if (VALUEIT != sc->values.end()) found = true; else if (sc->descriptor->dontErrorOnMissing) - return result; // will return a success, cuz we want to ignore missing + return {true, result}; // will return a success, cuz we want to ignore missing break; } @@ -407,7 +408,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: } else { if (VALUEIT == PCAT->values.end() || VALUEIT->first != sc->key) { result.setError(std::format("special category's first value must be the key. Key for <{}> is <{}>", PCAT->name, PCAT->key)); - return result; + return {true, result}; } impl->currentSpecialKey = value; } @@ -418,7 +419,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: if (!found) { result.setError(std::format("config option <{}> does not exist.", valueName)); - return result; + return {false, result}; } } @@ -428,7 +429,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: const auto INT = configStringToInt(value); if (!INT.has_value()) { result.setError(INT.error()); - return result; + return {true, result}; } VALUEIT->second.setFrom(INT.value()); @@ -440,7 +441,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: VALUEIT->second.setFrom(std::stof(value)); } catch (std::exception& e) { result.setError(std::format("failed parsing a float: {}", e.what())); - return result; + return {true, result}; } break; } @@ -458,7 +459,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: VALUEIT->second.setFrom(SVector2D{.x = std::stof(LHS), .y = std::stof(RHS)}); } catch (std::exception& e) { result.setError(std::format("failed parsing a vec2: {}", e.what())); - return result; + return {true, result}; } break; } @@ -473,19 +474,19 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: if (RESULT.error) { result.setError(RESULT.getError()); - return result; + return {true, result}; } break; } default: { result.setError("internal error: invalid value found (no type?)"); - return result; + return {true, result}; } } VALUEIT->second.m_bSetByUser = true; - return result; + return {true, result}; } CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic) { @@ -791,45 +792,50 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { bool found = false; - for (auto& h : impl->handlers) { - // we want to handle potentially nested keywords and ensure - // we only call the handler if they are scoped correctly, - // unless the keyword is not scoped itself - - const bool UNSCOPED = !h.name.contains(":"); - const auto HANDLERNAME = !h.name.empty() && h.name.at(0) == ':' ? h.name.substr(1) : h.name; - - if (!h.options.allowFlags && !UNSCOPED) { - size_t colon = 0; - size_t idx = 0; - size_t depth = 0; - - while ((colon = HANDLERNAME.find(':', idx)) != std::string::npos && impl->categories.size() > depth) { - auto actual = HANDLERNAME.substr(idx, colon - idx); - - if (actual != impl->categories[depth]) - break; - - idx = colon + 1; - ++depth; - } - - if (depth != impl->categories.size() || HANDLERNAME.substr(idx) != LHS) - continue; - } - - if (UNSCOPED && HANDLERNAME != LHS && !h.options.allowFlags) - continue; - - if (h.options.allowFlags && (!LHS.starts_with(HANDLERNAME) || LHS.contains(':') /* avoid cases where a category is called the same as a handler */)) - continue; - - ret = h.func(LHS.c_str(), RHS.c_str()); - found = true; + if (!impl->configOptions.verifyOnly) { + auto [f, rv] = configSetValueSafe(LHS, RHS); + found = f; + ret = std::move(rv); } - if (!found && !impl->configOptions.verifyOnly) - ret = configSetValueSafe(LHS, RHS); + if (!found) { + for (auto& h : impl->handlers) { + // we want to handle potentially nested keywords and ensure + // we only call the handler if they are scoped correctly, + // unless the keyword is not scoped itself + + const bool UNSCOPED = !h.name.contains(":"); + const auto HANDLERNAME = !h.name.empty() && h.name.at(0) == ':' ? h.name.substr(1) : h.name; + + if (!h.options.allowFlags && !UNSCOPED) { + size_t colon = 0; + size_t idx = 0; + size_t depth = 0; + + while ((colon = HANDLERNAME.find(':', idx)) != std::string::npos && impl->categories.size() > depth) { + auto actual = HANDLERNAME.substr(idx, colon - idx); + + if (actual != impl->categories[depth]) + break; + + idx = colon + 1; + ++depth; + } + + if (depth != impl->categories.size() || HANDLERNAME.substr(idx) != LHS) + continue; + } + + if (UNSCOPED && HANDLERNAME != LHS && !h.options.allowFlags) + continue; + + if (h.options.allowFlags && (!LHS.starts_with(HANDLERNAME) || LHS.contains(':') /* avoid cases where a category is called the same as a handler */)) + continue; + + ret = h.func(LHS.c_str(), RHS.c_str()); + found = true; + } + } if (ret.error) return ret; diff --git a/tests/config/config.conf b/tests/config/config.conf index c2c0b87..1906acb 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -121,6 +121,7 @@ specialGeneric { specialAnonymous { value = 2 + testHandlerDontOverride = true } specialAnonymous { diff --git a/tests/parse/main.cpp b/tests/parse/main.cpp index 8cef06b..ea8870b 100644 --- a/tests/parse/main.cpp +++ b/tests/parse/main.cpp @@ -23,12 +23,13 @@ namespace Colors { } // globals for testing -bool barrelRoll = false; -std::string flagsFound = ""; -Hyprlang::CConfig* pConfig = nullptr; -std::string currentPath = ""; -std::string ignoreKeyword = ""; -std::string useKeyword = ""; +bool barrelRoll = false; +std::string flagsFound = ""; +Hyprlang::CConfig* pConfig = nullptr; +std::string currentPath = ""; +std::string ignoreKeyword = ""; +std::string useKeyword = ""; +bool testHandlerDontOverrideValue = false; static std::vector categoryKeywordActualValues; static Hyprlang::CParseResult handleDoABarrelRoll(const char* COMMAND, const char* VALUE) { @@ -74,6 +75,14 @@ static Hyprlang::CParseResult handleSource(const char* COMMAND, const char* VALU return pConfig->parseFile(PATH.c_str()); } +static Hyprlang::CParseResult handleTestHandlerDontOverride(const char* COMMAND, const char* VALUE) { + testHandlerDontOverrideValue = true; + + Hyprlang::CParseResult result; + return result; +} + + static Hyprlang::CParseResult handleCustomValueSet(const char* VALUE, void** data) { if (!*data) *data = calloc(1, sizeof(int64_t)); @@ -147,12 +156,14 @@ int main(int argc, char** argv, char** envp) { config.registerHandler(&handleTestUseKeyword, ":testUseKeyword", {.allowFlags = false}); config.registerHandler(&handleNoop, "testCategory:testUseKeyword", {.allowFlags = false}); config.registerHandler(&handleCategoryKeyword, "testCategory:categoryKeyword", {.allowFlags = false}); + config.registerHandler(&handleTestHandlerDontOverride, "testHandlerDontOverride", {.allowFlags = false}); config.addSpecialCategory("special", {.key = "key"}); config.addSpecialConfigValue("special", "value", (Hyprlang::INT)0); config.addSpecialCategory("specialAnonymous", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true}); config.addSpecialConfigValue("specialAnonymous", "value", (Hyprlang::INT)0); + config.addSpecialConfigValue("specialAnonymous", "testHandlerDontOverride", (Hyprlang::INT)0); config.addSpecialCategory("specialAnonymousNested", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true}); config.addSpecialConfigValue("specialAnonymousNested", "nested:value1", (Hyprlang::INT)0); @@ -239,6 +250,7 @@ int main(int argc, char** argv, char** envp) { std::cout << " → Testing handlers\n"; EXPECT(barrelRoll, true); EXPECT(flagsFound, std::string{"abc"}); + EXPECT(testHandlerDontOverrideValue, false); EXPECT(categoryKeywordActualValues.at(0), "we are having fun"); EXPECT(categoryKeywordActualValues.at(1), "so much fun"); @@ -315,6 +327,7 @@ int main(int argc, char** argv, char** envp) { EXPECT(config.listKeysForSpecialCategory("specialAnonymous").size(), 2); const auto KEYS = config.listKeysForSpecialCategory("specialAnonymous"); EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymous", "value", KEYS[0].c_str())), 2); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymous", "testHandlerDontOverride", KEYS[0].c_str())), 1); EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymous", "value", KEYS[1].c_str())), 3); // test anonymous nested