Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package hyprlang for openSUSE:Factory checked in at 2026-01-06 17:45:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/hyprlang (Old) and /work/SRC/openSUSE:Factory/.hyprlang.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "hyprlang" Tue Jan 6 17:45:27 2026 rev:8 rq:1325573 version:0.6.7 Changes: -------- --- /work/SRC/openSUSE:Factory/hyprlang/hyprlang.changes 2025-09-15 19:57:24.514444652 +0200 +++ /work/SRC/openSUSE:Factory/.hyprlang.new.1928/hyprlang.changes 2026-01-06 17:46:57.307478374 +0100 @@ -1,0 +2,20 @@ +Mon Dec 29 21:22:54 UTC 2025 - Florian "sp1rit" <[email protected]> + +- Update to version 0.6.7: + + core: add changeRootPath for CConfig + + core: fix dynamic env changes + + core: fix crash with same name special category and keyword + +- Changes from version 0.6.6: + + config/parser: don't return found on dontErrorOnMissing in + special + +- Changes from version 0.6.5: + + config: allow nesting if statements + + config: try variables before handlers if possible + + config/parser: fix invalid ptr after move + + parser: fix lingering currentSpecialCat after dynamic calls + + core: support nesting with special categories and fix explicit + key + nested + +------------------------------------------------------------------- Old: ---- hyprlang-0.6.4.tar.gz New: ---- hyprlang-0.6.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ hyprlang.spec ++++++ --- /var/tmp/diff_new_pack.rwRP1y/_old 2026-01-06 17:46:57.803498780 +0100 +++ /var/tmp/diff_new_pack.rwRP1y/_new 2026-01-06 17:46:57.807498944 +0100 @@ -19,7 +19,7 @@ %define sover 2 Name: hyprlang -Version: 0.6.4 +Version: 0.6.7 License: LGPL-3.0-only Release: 0 Summary: A configuration language for Linux applications ++++++ hyprlang-0.6.4.tar.gz -> hyprlang-0.6.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/.github/workflows/arch.yml new/hyprlang-0.6.7/.github/workflows/arch.yml --- old/hyprlang-0.6.4/.github/workflows/arch.yml 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/.github/workflows/arch.yml 2025-12-01 19:07:10.000000000 +0100 @@ -17,7 +17,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman gtest - name: Get hyprutils-git run: | @@ -48,7 +48,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman gtest - name: Get hyprutils-git run: | @@ -79,7 +79,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman gtest - name: Get hyprutils-git run: | @@ -110,7 +110,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ git pixman + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ git pixman gtest - name: Get hyprutils-git run: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/README.md new/hyprlang-0.6.7/README.md --- old/hyprlang-0.6.4/README.md 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/README.md 2025-12-01 19:07:10.000000000 +0100 @@ -50,7 +50,7 @@ ## Docs -Visit [hyprland.org/hyprlang](https://hyprland.org/hyprlang) to see the documentation. +Visit [wiki.hypr.land/Hypr-Ecosystem/hyprlang/](https://wiki.hypr.land/Hypr-Ecosystem/hyprlang/) to see the documentation. ### Example implementation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/VERSION new/hyprlang-0.6.7/VERSION --- old/hyprlang-0.6.4/VERSION 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/VERSION 2025-12-01 19:07:10.000000000 +0100 @@ -1 +1 @@ -0.6.3 +0.6.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/include/hyprlang.hpp new/hyprlang-0.6.7/include/hyprlang.hpp --- old/hyprlang-0.6.4/include/hyprlang.hpp 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/include/hyprlang.hpp 2025-12-01 19:07:10.000000000 +0100 @@ -441,18 +441,25 @@ return result; } + /*! + Change the root path of the config + + \since 0.6.7 + */ + void changeRootPath(const char* path); + 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<bool, 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); }; /*! diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/src/config.cpp new/hyprlang-0.6.7/src/config.cpp --- old/hyprlang-0.6.4/src/config.cpp 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/src/config.cpp 2025-12-01 19:07:10.000000000 +0100 @@ -78,13 +78,7 @@ throw "File does not exist"; } - impl->envVariables.clear(); - for (char** env = environ; *env; ++env) { - const std::string ENVVAR = *env ? *env : ""; - const auto VARIABLE = ENVVAR.substr(0, ENVVAR.find_first_of('=')); - const auto VALUE = ENVVAR.substr(ENVVAR.find_first_of('=') + 1); - impl->envVariables.push_back({VARIABLE, VALUE}); - } + impl->recheckEnv(); std::ranges::sort(impl->envVariables, [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); }); @@ -279,21 +273,21 @@ return 0; } -CParseResult CConfig::configSetValueSafe(const std::string& command, const std::string& value) { +// found, result +std::pair<bool, CParseResult> CConfig::configSetValueSafe(const std::string& command, const std::string& value) { 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; + // TODO: all this sucks xD + + SSpecialCategory* overrideSpecialCat = nullptr; - // 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 +298,27 @@ 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<const char*>(specialCat->values[sc->key].getValue())}) + continue; + + // existing special + keyExists = true; + overrideSpecialCat = specialCat.get(); + } + + if (keyExists) + break; + + // if it doesn't exist, make it const auto PCAT = impl->specialCategories.emplace_back(std::make_unique<SSpecialCategory>()).get(); PCAT->descriptor = sc.get(); PCAT->name = sc->name; @@ -319,6 +328,8 @@ applyDefaultsToCat(*PCAT); PCAT->values[sc->key].setFrom(CATKEY); + overrideSpecialCat = PCAT; + break; } } } @@ -328,85 +339,99 @@ // it might be in a special category bool found = false; - if (impl->currentSpecialCategory && valueName.starts_with(impl->currentSpecialCategory->name)) { - VALUEIT = impl->currentSpecialCategory->values.find(valueName.substr(impl->currentSpecialCategory->name.length() + 1)); + if (overrideSpecialCat) { + VALUEIT = overrideSpecialCat->values.find(valueName.substr(overrideSpecialCat->name.length() + 1)); - if (VALUEIT != impl->currentSpecialCategory->values.end()) + if (VALUEIT != overrideSpecialCat->values.end()) found = true; - } - - if (!found) { - for (auto& sc : impl->specialCategories) { - if (!valueName.starts_with(sc->name)) - continue; - - if (!sc->isStatic && std::string{std::any_cast<const char*>(sc->values[sc->key].getValue())} != impl->currentSpecialKey) - continue; - - VALUEIT = sc->values.find(valueName.substr(sc->name.length() + 1)); - impl->currentSpecialCategory = sc.get(); + } else { + if (impl->currentSpecialCategory && valueName.starts_with(impl->currentSpecialCategory->name)) { + VALUEIT = impl->currentSpecialCategory->values.find(valueName.substr(impl->currentSpecialCategory->name.length() + 1)); - if (VALUEIT != sc->values.end()) + if (VALUEIT != impl->currentSpecialCategory->values.end()) found = true; - else if (sc->descriptor->dontErrorOnMissing) - return result; // will return a success, cuz we want to ignore missing - - break; } - } - 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)) - continue; + // probably a handler + if (!valueName.contains(":")) + return {false, result}; + + if (!found) { + for (auto& sc : impl->specialCategories) { + if (!valueName.starts_with(sc->name + ":")) + continue; + + if (!sc->isStatic && std::string{std::any_cast<const char*>(sc->values[sc->key].getValue())} != impl->currentSpecialKey) + continue; + + VALUEIT = sc->values.find(valueName.substr(sc->name.length() + 1)); + impl->currentSpecialCategory = sc.get(); + + if (VALUEIT != sc->values.end()) + found = true; + else if (sc->descriptor->dontErrorOnMissing) + return {false, result}; // will return a success, cuz we want to ignore missing - // category does exist, check if value exists - if (!sc->defaultValues.contains(VALUEONLYNAME) && VALUEONLYNAME != sc->key) break; + } + } - // bingo - const auto PCAT = impl->specialCategories.emplace_back(std::make_unique<SSpecialCategory>()).get(); - PCAT->descriptor = sc.get(); - PCAT->name = sc->name; - PCAT->key = sc->key; - addSpecialConfigValue(sc->name.c_str(), sc->key.c_str(), CConfigValue("0")); - - applyDefaultsToCat(*PCAT); + 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 + ":")) + continue; - VALUEIT = PCAT->values.find(valueName.substr(sc->name.length() + 1)); - impl->currentSpecialCategory = PCAT; + // found value root to be a special category, get the trunk + const auto VALUETRUNK = valueName.substr(sc->name.length() + 1); - if (VALUEIT != PCAT->values.end()) - found = true; + // check if trunk is a value within the special category + if (!sc->defaultValues.contains(VALUETRUNK) && VALUETRUNK != sc->key) + break; - if (sc->anonymous) { - // find suitable key - size_t biggest = 0; - for (auto& catt : impl->specialCategories) { - biggest = std::max(catt->anonymousID, biggest); + // bingo + const auto PCAT = impl->specialCategories.emplace_back(std::make_unique<SSpecialCategory>()).get(); + PCAT->descriptor = sc.get(); + PCAT->name = sc->name; + PCAT->key = sc->key; + addSpecialConfigValue(sc->name.c_str(), sc->key.c_str(), CConfigValue("0")); + + applyDefaultsToCat(*PCAT); + + VALUEIT = PCAT->values.find(valueName.substr(sc->name.length() + 1)); + impl->currentSpecialCategory = PCAT; + + if (VALUEIT != PCAT->values.end()) + found = true; + + if (sc->anonymous) { + // find suitable key + size_t biggest = 0; + for (auto& catt : impl->specialCategories) { + biggest = std::max(catt->anonymousID, biggest); + } + + biggest++; + + PCAT->values[ANONYMOUS_KEY].setFrom(std::to_string(biggest)); + impl->currentSpecialKey = std::to_string(biggest); + PCAT->anonymousID = biggest; + } 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 {true, result}; + } + impl->currentSpecialKey = value; } - biggest++; - - PCAT->values[ANONYMOUS_KEY].setFrom(std::to_string(biggest)); - impl->currentSpecialKey = std::to_string(biggest); - PCAT->anonymousID = biggest; - } 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; - } - impl->currentSpecialKey = value; + break; } - - break; } } if (!found) { result.setError(std::format("config option <{}> does not exist.", valueName)); - return result; + return {false, result}; } } @@ -416,7 +441,7 @@ const auto INT = configStringToInt(value); if (!INT.has_value()) { result.setError(INT.error()); - return result; + return {true, result}; } VALUEIT->second.setFrom(INT.value()); @@ -428,7 +453,7 @@ 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; } @@ -446,7 +471,7 @@ 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; } @@ -461,19 +486,19 @@ 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) { @@ -501,6 +526,16 @@ return result; } +void CConfigImpl::recheckEnv() { + envVariables.clear(); + for (char** env = environ; *env; ++env) { + const std::string ENVVAR = *env ? *env : ""; + const auto VARIABLE = ENVVAR.substr(0, ENVVAR.find_first_of('=')); + const auto VALUE = ENVVAR.substr(ENVVAR.find_first_of('=') + 1); + envVariables.push_back({VARIABLE, VALUE}); + } +} + SVariable* CConfigImpl::getVariable(const std::string& name) { for (auto& v : envVariables) { if (v.name == name) @@ -536,9 +571,9 @@ } if (args[i] == "endif") { - if (!currentFlags.inAnIfBlock) + if (currentFlags.ifDatas.empty()) return "stray endif"; - currentFlags.inAnIfBlock = false; + currentFlags.ifDatas.pop_back(); break; } @@ -549,20 +584,19 @@ } if (!ifBlockVariable.empty()) { - if (currentFlags.inAnIfBlock) - return "nested if statements are not allowed"; - if (ifBlockVariable.starts_with("!")) { - negated = true; + negated = true; ifBlockVariable = ifBlockVariable.substr(1); } - currentFlags.inAnIfBlock = true; + CConfigImpl::SIfBlockData newIfData; if (const auto VAR = getVariable(ifBlockVariable); VAR) - currentFlags.ifBlockFailed = negated ? VAR->truthy() : !VAR->truthy(); + newIfData.failed = negated ? VAR->truthy() : !VAR->truthy(); else - currentFlags.ifBlockFailed = !negated; + newIfData.failed = !negated; + + currentFlags.ifDatas.emplace_back(newIfData); } return std::nullopt; @@ -632,7 +666,7 @@ return result; } - if (impl->currentFlags.inAnIfBlock && impl->currentFlags.ifBlockFailed) + if (!impl->currentFlags.ifDatas.empty() && impl->currentFlags.ifDatas.back().failed) return result; size_t lastHashPos = 0; @@ -780,46 +814,52 @@ 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; + if (!impl->configOptions.verifyOnly) { + auto [f, rv] = configSetValueSafe(LHS, RHS); + found = f; + ret = std::move(rv); + ret.errorString = ret.errorStdString.c_str(); + } + + 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); + 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; + if (actual != impl->categories[depth]) + break; + + idx = colon + 1; + ++depth; + } - idx = colon + 1; - ++depth; + if (depth != impl->categories.size() || HANDLERNAME.substr(idx) != LHS) + continue; } - if (depth != impl->categories.size() || HANDLERNAME.substr(idx) != LHS) + if (UNSCOPED && HANDLERNAME != LHS && !h.options.allowFlags) 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; + 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; + ret = h.func(LHS.c_str(), RHS.c_str()); + found = true; + } } - if (!found && !impl->configOptions.verifyOnly) - ret = configSetValueSafe(LHS, RHS); - if (ret.error) return ret; } else { @@ -836,9 +876,12 @@ 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("{")) { @@ -892,6 +935,10 @@ return fileParseResult; } +void CConfig::changeRootPath(const char* path) { + impl->path = path; +} + CParseResult CConfig::parseRawStream(const std::string& stream) { CParseResult result; @@ -995,17 +1042,22 @@ } CParseResult CConfig::parseDynamic(const char* line) { - return parseLine(line, true); + auto ret = parseLine(line, true); + impl->currentSpecialCategory = nullptr; + return ret; } CParseResult CConfig::parseDynamic(const char* command, const char* value) { - return parseLine(std::string{command} + "=" + std::string{value}, true); + auto ret = parseLine(std::string{command} + "=" + std::string{value}, true); + impl->currentSpecialCategory = nullptr; + return ret; } void CConfig::clearState() { impl->categories.clear(); impl->parseError = ""; - impl->variables = impl->envVariables; + impl->recheckEnv(); + impl->variables = impl->envVariables; std::erase_if(impl->specialCategories, [](const auto& e) { return !e->isStatic; }); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/src/config.hpp new/hyprlang-0.6.7/src/config.hpp --- old/hyprlang-0.6.4/src/config.hpp 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/src/config.hpp 2025-12-01 19:07:10.000000000 +0100 @@ -102,10 +102,15 @@ std::optional<std::string> parseComment(const std::string& comment); std::expected<float, std::string> parseExpression(const std::string& s); SVariable* getVariable(const std::string& name); + void recheckEnv(); + + struct SIfBlockData { + bool failed = false; + }; struct { - bool noError = false; - bool inAnIfBlock = false; - bool ifBlockFailed = false; + bool noError = false; + + std::vector<SIfBlockData> ifDatas; } currentFlags; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/tests/config/config.conf new/hyprlang-0.6.7/tests/config/config.conf --- old/hyprlang-0.6.4/tests/config/config.conf 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/tests/config/config.conf 2025-12-01 19:07:10.000000000 +0100 @@ -32,6 +32,7 @@ testDynamicEscapedExpression = \{{ $DYNAMIC_EXPRESSION }} testEnv = $SHELL +testEnv2 = $TEST_ENV source = ./colors.conf @@ -39,10 +40,20 @@ # hyprlang if !NONEXISTENT_VAR +# hyprlang if !NONEXISTENT_VAR_2 + testStringColon = ee:ee:ee # hyprlang endif +# hyprlang if NONEXISTENT_VAR + +testStringColon = ee:ee:ee:22 + +# hyprlang endif + +# hyprlang endif + # hyprlang noerror true errorVariable = true @@ -111,12 +122,34 @@ specialAnonymous { value = 2 + testHandlerDontOverride = true } 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 } @@ -139,3 +172,14 @@ flagsabc = test #doSomethingFunny = 1, 2, 3, 4 # Funnier! #testSpaces = abc , def # many spaces, should be trimmed + +sameKeywordSpecialCat = pablo + +sameKeywordSpecialCat:two:hola = rose + +sameKeywordSpecialCat { + one { + some_size = 44 + some_radius = 7.6 + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.4/tests/parse/main.cpp new/hyprlang-0.6.7/tests/parse/main.cpp --- old/hyprlang-0.6.4/tests/parse/main.cpp 2025-07-25 21:26:39.000000000 +0200 +++ new/hyprlang-0.6.7/tests/parse/main.cpp 2025-12-01 19:07:10.000000000 +0100 @@ -23,12 +23,14 @@ } // 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 = ""; +std::string sameKeywordSpecialCat = ""; +bool testHandlerDontOverrideValue = false; static std::vector<std::string> categoryKeywordActualValues; static Hyprlang::CParseResult handleDoABarrelRoll(const char* COMMAND, const char* VALUE) { @@ -74,6 +76,19 @@ return pConfig->parseFile(PATH.c_str()); } +static Hyprlang::CParseResult handleSameKeywordSpecialCat(const char* COMMAND, const char* VALUE) { + sameKeywordSpecialCat = VALUE; + + return Hyprlang::CParseResult(); +} + +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)); @@ -99,6 +114,8 @@ if (!getenv("SHELL")) setenv("SHELL", "/bin/sh", true); + setenv("TEST_ENV", "1", true); + std::cout << "Starting test\n"; Hyprlang::CConfig config("./config/config.conf", {}); @@ -121,6 +138,7 @@ config.addConfigValue("testString", ""); config.addConfigValue("testStringColon", ""); config.addConfigValue("testEnv", ""); + config.addConfigValue("testEnv2", ""); config.addConfigValue("testVar", (Hyprlang::INT)0); config.addConfigValue("categoryKeyword", (Hyprlang::STRING) ""); config.addConfigValue("testStringQuotes", ""); @@ -147,17 +165,34 @@ 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); + 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.registerHandler(&handleSameKeywordSpecialCat, "sameKeywordSpecialCat", {.allowFlags = false}); + config.addSpecialCategory("sameKeywordSpecialCat", {.key = nullptr, .ignoreMissing = true, .anonymousKeyBased = false}); + config.commence(); + config.addSpecialCategory("sameKeywordSpecialCat:one", {.key = nullptr, .ignoreMissing = true}); + config.addSpecialConfigValue("sameKeywordSpecialCat:one", "some_size", (Hyprlang::INT)10); + config.addSpecialConfigValue("sameKeywordSpecialCat:one", "some_radius", (Hyprlang::FLOAT)0.0); + config.addSpecialCategory("sameKeywordSpecialCat:two", {.key = nullptr, .ignoreMissing = true}); + config.addSpecialConfigValue("sameKeywordSpecialCat:two", "hola", ""); + config.addSpecialCategory("specialGeneric:one", {.key = nullptr, .ignoreMissing = true}); config.addSpecialConfigValue("specialGeneric:one", "value", (Hyprlang::INT)0); config.addSpecialCategory("specialGeneric:two", {.key = nullptr, .ignoreMissing = true}); @@ -196,6 +231,12 @@ EXPECT(ignoreKeyword, "aaa"); EXPECT(useKeyword, "yes"); + // test special category with same name as a keyword + EXPECT(sameKeywordSpecialCat, std::string_view{"pablo"}); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("sameKeywordSpecialCat:one", "some_size")), (Hyprlang::INT)44); + EXPECT(std::any_cast<float>(config.getSpecialConfigValue("sameKeywordSpecialCat:one", "some_radius")), (Hyprlang::FLOAT)7.6); + EXPECT(std::any_cast<const char*>(config.getSpecialConfigValue("sameKeywordSpecialCat:two", "hola")), std::string_view{"rose"}); + // Test templated wrapper auto T1 = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(&config, "testInt"); auto T2 = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(&config, "testFloat"); @@ -233,6 +274,7 @@ 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"); @@ -279,6 +321,7 @@ // test env variables std::cout << " → Testing env variables\n"; EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv")), std::string{getenv("SHELL")}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv2")), std::string{"1"}); // test special categories std::cout << " → Testing special categories\n"; @@ -288,6 +331,17 @@ EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialGeneric:two", "value")), 2); EXPECT(config.parseDynamic("special[b]:value = 3").error, false); EXPECT(std::any_cast<int64_t>(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<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", "c")), (Hyprlang::INT)4); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", "c")), (Hyprlang::INT)5); + EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value2 = 6").error, false); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", "c")), (Hyprlang::INT)6); + + EXPECT(config.parseDynamic("special[a]:value = 69").error, false); + EXPECT(config.parseDynamic("special[b]:value = 420").error, false); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("special", "value", "a")), 69); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("special", "value", "b")), 420); // test dynamic special variable EXPECT(config.parseDynamic("$SPECIALVAL1 = 2").error, false); @@ -302,8 +356,22 @@ // test anonymous EXPECT(config.listKeysForSpecialCategory("specialAnonymous").size(), 2); const auto KEYS = config.listKeysForSpecialCategory("specialAnonymous"); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymous", "value", KEYS[0].c_str())), 2); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymous", "testHandlerDontOverride", KEYS[0].c_str())), 1); EXPECT(std::any_cast<int64_t>(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<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", KEYS2[0].c_str())), 1); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", KEYS2[0].c_str())), 2); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", KEYS2[1].c_str())), 3); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", KEYS2[1].c_str())), 4); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", KEYS2[0].c_str())), 10); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", KEYS2[0].c_str())), 11); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", KEYS2[1].c_str())), 12); + EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", KEYS2[1].c_str())), 13); + // test sourcing std::cout << " → Testing sourcing\n"; EXPECT(std::any_cast<int64_t>(config.getConfigValue("myColors:pink")), (Hyprlang::INT)0xFFc800c8); @@ -317,6 +385,12 @@ // test multiline config EXPECT(std::any_cast<const char*>(config.getConfigValue("multiline")), std::string{"very long command"}); + // test dynamic env + setenv("TEST_ENV", "2", true); + config.parse(); + std::cout << " → Testing dynamic env variables\n"; + EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv2")), std::string{"2"}); + std::cout << " → Testing error.conf\n"; Hyprlang::CConfig errorConfig("./config/error.conf", {.verifyOnly = true, .throwAllErrors = true});
