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});
 

Reply via email to