From 4dafa28d4f79877d67a7d1a654cddccf8ebf15da Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Fri, 26 Sep 2025 23:05:02 +0000 Subject: [PATCH] core: support nesting with special categories and fix explicit key + nested (#82) --- src/config.cpp | 43 +++++++++++++++++++++++++++------------- tests/config/config.conf | 21 ++++++++++++++++++++ tests/parse/main.cpp | 25 +++++++++++++++++++++++ 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index f2a2fc7..82ce724 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -283,17 +283,12 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: CParseResult result; std::string valueName; - std::string catPrefix; for (auto& c : impl->categories) { valueName += c + ':'; - catPrefix += c + ':'; } valueName += command; - const auto VALUEONLYNAME = command.starts_with(catPrefix) ? command.substr(catPrefix.length()) : command; - - // FIXME: this will bug with nested. if (valueName.contains('[') && valueName.contains(']')) { const auto L = valueName.find_first_of('['); const auto R = valueName.find_last_of(']'); @@ -304,12 +299,26 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: valueName = valueName.substr(0, L) + valueName.substr(R + 1); - // if it doesn't exist, make it for (auto& sc : impl->specialCategoryDescriptors) { - if (sc->key.empty() || !valueName.starts_with(sc->name)) + if (sc->key.empty() || !valueName.starts_with(sc->name + ":")) continue; - // bingo + bool keyExists = false; + for (const auto& specialCat : impl->specialCategories) { + if (specialCat->key != sc->key || specialCat->name != sc->name) + continue; + + if (CATKEY != std::string_view{std::any_cast(specialCat->values[sc->key].getValue())}) + continue; + + // existing special + keyExists = true; + } + + if (keyExists) + break; + + // if it doesn't exist, make it const auto PCAT = impl->specialCategories.emplace_back(std::make_unique()).get(); PCAT->descriptor = sc.get(); PCAT->name = sc->name; @@ -358,11 +367,14 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: if (!found) { // could be a dynamic category that doesnt exist yet for (auto& sc : impl->specialCategoryDescriptors) { - if (sc->key.empty() || !valueName.starts_with(sc->name)) + if (sc->key.empty() || !valueName.starts_with(sc->name + ":")) continue; - // category does exist, check if value exists - if (!sc->defaultValues.contains(VALUEONLYNAME) && VALUEONLYNAME != sc->key) + // found value root to be a special category, get the trunk + const auto VALUETRUNK = valueName.substr(sc->name.length() + 1); + + // check if trunk is a value within the special category + if (!sc->defaultValues.contains(VALUETRUNK) && VALUETRUNK != sc->key) break; // bingo @@ -550,7 +562,7 @@ std::optional CConfigImpl::parseComment(const std::string& comment) if (!ifBlockVariable.empty()) { if (ifBlockVariable.starts_with("!")) { - negated = true; + negated = true; ifBlockVariable = ifBlockVariable.substr(1); } @@ -835,9 +847,12 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { return result; } - impl->currentSpecialKey = ""; - impl->currentSpecialCategory = nullptr; impl->categories.pop_back(); + + if (impl->categories.empty()) { + impl->currentSpecialKey = ""; + impl->currentSpecialCategory = nullptr; + } } else { // open a category. if (!line.ends_with("{")) { diff --git a/tests/config/config.conf b/tests/config/config.conf index 54e71bf..c2c0b87 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -127,6 +127,27 @@ specialAnonymous { value = 3 } +specialAnonymousNested { + nested:value1 = 1 + nested:value2 = 2 + nested1:nested2:value1 = 10 + nested1:nested2:value2 = 11 +} + +specialAnonymousNested { + nested { + value1 = 3 + value2 = 4 + } + + nested1 { + nested2 { + value1 = 12 + value2 = 13 + } + } +} + flagsStuff { value = 2 } diff --git a/tests/parse/main.cpp b/tests/parse/main.cpp index dbf5e70..8cef06b 100644 --- a/tests/parse/main.cpp +++ b/tests/parse/main.cpp @@ -154,6 +154,12 @@ int main(int argc, char** argv, char** envp) { config.addSpecialCategory("specialAnonymous", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true}); config.addSpecialConfigValue("specialAnonymous", "value", (Hyprlang::INT)0); + config.addSpecialCategory("specialAnonymousNested", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true}); + config.addSpecialConfigValue("specialAnonymousNested", "nested:value1", (Hyprlang::INT)0); + config.addSpecialConfigValue("specialAnonymousNested", "nested:value2", (Hyprlang::INT)0); + config.addSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", (Hyprlang::INT)0); + config.addSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", (Hyprlang::INT)0); + config.addConfigValue("multiline", ""); config.commence(); @@ -288,6 +294,12 @@ int main(int argc, char** argv, char** envp) { EXPECT(std::any_cast(config.getSpecialConfigValue("specialGeneric:two", "value")), 2); EXPECT(config.parseDynamic("special[b]:value = 3").error, false); EXPECT(std::any_cast(config.getSpecialConfigValue("special", "value", "b")), 3); + EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value1 = 4").error, false); + EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value2 = 5").error, false); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", "c")), (Hyprlang::INT)4); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", "c")), (Hyprlang::INT)5); + EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value2 = 6").error, false); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", "c")), (Hyprlang::INT)6); // test dynamic special variable EXPECT(config.parseDynamic("$SPECIALVAL1 = 2").error, false); @@ -302,8 +314,21 @@ int main(int argc, char** argv, char** envp) { // test anonymous 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", "value", KEYS[1].c_str())), 3); + // test anonymous nested + EXPECT(config.listKeysForSpecialCategory("specialAnonymousNested").size(), 2 + /*from dynamic*/ 1); + const auto KEYS2 = config.listKeysForSpecialCategory("specialAnonymousNested"); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", KEYS2[0].c_str())), 1); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", KEYS2[0].c_str())), 2); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", KEYS2[1].c_str())), 3); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", KEYS2[1].c_str())), 4); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", KEYS2[0].c_str())), 10); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", KEYS2[0].c_str())), 11); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", KEYS2[1].c_str())), 12); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", KEYS2[1].c_str())), 13); + // test sourcing std::cout << " → Testing sourcing\n"; EXPECT(std::any_cast(config.getConfigValue("myColors:pink")), (Hyprlang::INT)0xFFc800c8);