Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package hyprlang for openSUSE:Factory checked in at 2025-09-15 19:53:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/hyprlang (Old) and /work/SRC/openSUSE:Factory/.hyprlang.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "hyprlang" Mon Sep 15 19:53:19 2025 rev:7 rq:1304801 version:0.6.4 Changes: -------- --- /work/SRC/openSUSE:Factory/hyprlang/hyprlang.changes 2025-01-12 11:28:39.162211761 +0100 +++ /work/SRC/openSUSE:Factory/.hyprlang.new.1977/hyprlang.changes 2025-09-15 19:57:24.514444652 +0200 @@ -1,0 +2,16 @@ +Wed Sep 10 20:10:57 UTC 2025 - Marcus Rueckert <[email protected]> + +- Update to version 0.6.4: + - core: add support for conditional statements + - Add ability to escape {{EXPRESSION}} syntax from #75 by + @JonathanSteininger in #76 + +------------------------------------------------------------------- +Mon May 19 01:26:01 UTC 2025 - Soc Virnyl Estela <[email protected]> + +- Update to version 0.6.3: + * parser: change arithmetic syntax + * parser: change expression syntax to avoid bash clashes + * parser: add support for basic arithmetic + +------------------------------------------------------------------- Old: ---- hyprlang-0.6.0.tar.gz New: ---- hyprlang-0.6.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ hyprlang.spec ++++++ --- /var/tmp/diff_new_pack.H0T3Fu/_old 2025-09-15 19:57:24.986464471 +0200 +++ /var/tmp/diff_new_pack.H0T3Fu/_new 2025-09-15 19:57:24.990464639 +0200 @@ -1,7 +1,7 @@ # # spec file for package hyprlang # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2025 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %define sover 2 Name: hyprlang -Version: 0.6.0 +Version: 0.6.4 License: LGPL-3.0-only Release: 0 Summary: A configuration language for Linux applications @@ -75,6 +75,5 @@ %{_libdir}/pkgconfig/%{name}.pc %files -n lib%{name}%{sover} -%{_libdir}/lib%{name}.so.%{sover} -%{_libdir}/lib%{name}.so.%{version} +%{_libdir}/lib%{name}.so.* ++++++ hyprlang-0.6.0.tar.gz -> hyprlang-0.6.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/.clang-tidy new/hyprlang-0.6.4/.clang-tidy --- old/hyprlang-0.6.0/.clang-tidy 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprlang-0.6.4/.clang-tidy 2025-07-25 21:26:39.000000000 +0200 @@ -0,0 +1,101 @@ +WarningsAsErrors: '*' +HeaderFilterRegex: '.*\.hpp' +FormatStyle: 'file' +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-forward-declaration-namespace, + -bugprone-forward-declaration-namespace, + -bugprone-macro-parentheses, + -bugprone-narrowing-conversions, + -bugprone-branch-clone, + -bugprone-assignment-in-if-condition, + concurrency-*, + -concurrency-mt-unsafe, + cppcoreguidelines-*, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-explicit-virtual-functions, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-narrowing-conversions, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-macro-to-enum, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-pro-type-reinterpret-cast, + google-global-names-in-headers, + -google-readability-casting, + google-runtime-operator, + misc-*, + -misc-unused-parameters, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + -misc-include-cleaner, + -misc-use-anonymous-namespace, + -misc-const-correctness, + modernize-*, + -modernize-return-braced-init-list, + -modernize-use-trailing-return-type, + -modernize-use-using, + -modernize-use-override, + -modernize-avoid-c-arrays, + -modernize-macro-to-enum, + -modernize-loop-convert, + -modernize-use-nodiscard, + -modernize-pass-by-value, + -modernize-use-auto, + performance-*, + -performance-avoid-endl, + -performance-unnecessary-value-param, + portability-std-allocator-const, + readability-*, + -readability-function-cognitive-complexity, + -readability-function-size, + -readability-identifier-length, + -readability-magic-numbers, + -readability-uppercase-literal-suffix, + -readability-braces-around-statements, + -readability-redundant-access-specifiers, + -readability-else-after-return, + -readability-container-data-pointer, + -readability-implicit-bool-conversion, + -readability-avoid-nested-conditional-operator, + -readability-redundant-member-init, + -readability-redundant-string-init, + -readability-avoid-const-params-in-decls, + -readability-named-parameter, + -readability-convert-member-functions-to-static, + -readability-qualified-auto, + -readability-make-member-function-const, + -readability-isolate-declaration, + -readability-inconsistent-declaration-parameter-name, + -clang-diagnostic-error, + +CheckOptions: + performance-for-range-copy.WarnOnAllAutoCopies: true + performance-inefficient-string-concatenation.StrictMode: true + readability-braces-around-statements.ShortStatementLines: 0 + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.ClassIgnoredRegexp: I.* + readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? + readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumPrefix: e + readability-identifier-naming.EnumConstantCase: UPPER_CASE + readability-identifier-naming.FunctionCase: camelBack + readability-identifier-naming.NamespaceCase: CamelCase + readability-identifier-naming.NamespacePrefix: N + readability-identifier-naming.StructPrefix: S + readability-identifier-naming.StructCase: CamelCase diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/.github/workflows/nix.yml new/hyprlang-0.6.4/.github/workflows/nix.yml --- old/hyprlang-0.6.0/.github/workflows/nix.yml 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/.github/workflows/nix.yml 2025-07-25 21:26:39.000000000 +0200 @@ -13,8 +13,35 @@ steps: - uses: actions/checkout@v3 - - uses: DeterminateSystems/nix-installer-action@main - - uses: DeterminateSystems/magic-nix-cache-action@main + - name: Install Nix + uses: nixbuild/nix-quick-install-action@v31 + with: + nix_conf: | + keep-env-derivations = true + keep-outputs = true + + - name: Restore and save Nix store + uses: nix-community/cache-nix-action@v6 + with: + # restore and save a cache using this key + primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + # if there's no cache hit, restore a cache by this prefix + restore-prefixes-first-match: nix-${{ runner.os }}- + # collect garbage until the Nix store size (in bytes) is at most this number + # before trying to save a new cache + # 1G = 1073741824 + gc-max-store-size-linux: 1G + # do purge caches + purge: true + # purge all versions of the cache + purge-prefixes: nix-${{ runner.os }}- + # created more than this number of seconds ago + purge-created: 0 + # or, last accessed more than this number of seconds ago + # relative to the start of the `Post Restore and save Nix store` phase + purge-last-accessed: 0 + # except any version with the key that is the same as the `primary-key` + purge-primary-key: never # not needed (yet) # - uses: cachix/cachix-action@v12 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/CMakeLists.txt new/hyprlang-0.6.4/CMakeLists.txt --- old/hyprlang-0.6.0/CMakeLists.txt 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/CMakeLists.txt 2025-07-25 21:26:39.000000000 +0200 @@ -28,9 +28,16 @@ add_compile_definitions(HYPRLANG_INTERNAL) set(CMAKE_CXX_STANDARD 23) +add_compile_options( + -Wall + -Wextra + -Wpedantic + -Wno-unused-parameter + -Wno-missing-field-initializers) +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprutils>=0.1.1) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprutils>=0.7.1) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/hyprlang.hpp") @@ -47,15 +54,6 @@ target_link_libraries(hyprlang PkgConfig::deps) -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # for std::expected. probably evil. Arch's clang is very outdated tho... - target_compile_options(hyprlang PUBLIC -std=gnu++2b -D__cpp_concepts=202002L - -Wno-macro-redefined) - add_compile_options(-stdlib=libc++) - add_link_options(-stdlib=libc++) - message(STATUS "Using clang++ to compile hyprlang") -endif() - add_library(hypr::hyprlang ALIAS hyprlang) install(TARGETS hyprlang) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/README.md new/hyprlang-0.6.4/README.md --- old/hyprlang-0.6.0/README.md 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/README.md 2025-07-25 21:26:39.000000000 +0200 @@ -54,4 +54,4 @@ ### Example implementation -For an example implmentation, take a look at the `tests/` directory. +For an example implementation, take a look at the `tests/` directory. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/VERSION new/hyprlang-0.6.4/VERSION --- old/hyprlang-0.6.0/VERSION 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/VERSION 2025-07-25 21:26:39.000000000 +0200 @@ -1 +1 @@ -0.6.0 +0.6.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/flake.lock new/hyprlang-0.6.4/flake.lock --- old/hyprlang-0.6.0/flake.lock 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/flake.lock 2025-07-25 21:26:39.000000000 +0200 @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1721324102, - "narHash": "sha256-WAZ0X6yJW1hFG6otkHBfyJDKRpNP5stsRqdEuHrFRpk=", + "lastModified": 1749135356, + "narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "962582a090bc233c4de9d9897f46794280288989", + "rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb", "type": "github" }, "original": { @@ -25,11 +25,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1721138476, - "narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=", + "lastModified": 1748929857, + "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ad0b5eed1b6031efaed382844806550c3dcb4206", + "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", "type": "github" }, "original": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/flake.nix new/hyprlang-0.6.4/flake.nix --- old/hyprlang-0.6.0/flake.nix 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/flake.nix 2025-07-25 21:26:39.000000000 +0200 @@ -39,7 +39,7 @@ inputs.hyprutils.overlays.default (final: prev: { hyprlang = final.callPackage ./nix/default.nix { - stdenv = final.gcc13Stdenv; + stdenv = final.gcc15Stdenv; version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); }; hyprlang-with-tests = final.hyprlang.override {doCheck = true;}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/include/hyprlang.hpp new/hyprlang-0.6.4/include/hyprlang.hpp --- old/hyprlang-0.6.0/include/hyprlang.hpp 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/include/hyprlang.hpp 2025-07-25 21:26:39.000000000 +0200 @@ -50,7 +50,7 @@ typedef CConfigCustomValueType CUSTOMTYPE; /*! - A very simple vector type + A very simple vector type */ struct SVector2D { float x = 0, y = 0; @@ -95,12 +95,12 @@ Generic struct for options for the config parser */ struct SConfigOptions { - /*! + /*! Don't throw errors on missing values. */ int verifyOnly = false; - /*! + /*! Return all errors instead of just the first */ int throwAllErrors = false; @@ -175,11 +175,11 @@ typedef void (*PCONFIGCUSTOMVALUEDESTRUCTOR)(void** data); /*! - Container for a custom config value type + Container for a custom config value type When creating, pass your handler. Handler will receive a void** that points to a void* that you can set to your own thing. Pass a dtor to free whatever you allocated when the custom value type is being released. - data may always be pointing to a nullptr. + data may always be pointing to a nullptr. */ class CConfigCustomValueType { public: @@ -254,7 +254,7 @@ /*! Get the contained value as an std::any. For strings, this is a const char*. - For custom data types, this is a CConfigCustomValueType*. + For custom data types, this is a void* representing the data ptr stored by it. */ std::any getValue() const { switch (m_eType) { @@ -271,7 +271,7 @@ /*! \since 0.3.0 - + a flag to notify whether this value has been set explicitly by the user, or not. */ @@ -305,7 +305,7 @@ ~CConfig(); /*! - Add a config value, for example myCategory:myValue. + Add a config value, for example myCategory:myValue. This has to be done before commence() Value provided becomes default. */ @@ -319,8 +319,8 @@ /*! \since 0.3.0 - - Unregister a handler. + + Unregister a handler. */ void unregisterHandler(const char* name); @@ -362,14 +362,14 @@ CParseResult parse(); /*! - Same as parse(), but parse a specific file, without any refreshing. + Same as parse(), but parse a specific file, without any refreshing. recommended to use for stuff like source = path.conf */ CParseResult parseFile(const char* file); /*! - Parse a single "line", dynamically. - Values set by this are temporary and will be overwritten + Parse a single "line", dynamically. + Values set by this are temporary and will be overwritten by default / config on the next parse() */ CParseResult parseDynamic(const char* line); @@ -377,14 +377,14 @@ /*! Get a config's value ptr. These are static. - nullptr on fail + nullptr on fail */ CConfigValue* getConfigValuePtr(const char* name); /*! Get a special category's config value ptr. These are only static for static (key-less) categories. - key can be nullptr for static categories. Cannot be nullptr for id-based categories. + key can be nullptr for static categories. Cannot be nullptr for id-based categories. nullptr on fail. */ CConfigValue* getSpecialConfigValuePtr(const char* category, const char* name, const char* key = nullptr); @@ -541,4 +541,4 @@ #undef HYPRLANG_END_MAGIC #endif -#endif \ No newline at end of file +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/src/common.cpp new/hyprlang-0.6.4/src/common.cpp --- old/hyprlang-0.6.0/src/common.cpp 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/src/common.cpp 2025-07-25 21:26:39.000000000 +0200 @@ -1,6 +1,6 @@ #include "public.hpp" #include "config.hpp" -#include <string.h> +#include <cstring> using namespace Hyprlang; @@ -30,38 +30,28 @@ } } -CConfigValue::CConfigValue(const int64_t value) { - m_pData = new int64_t; +CConfigValue::CConfigValue(const int64_t value) : m_eType(CONFIGDATATYPE_INT), m_pData(new int64_t) { *reinterpret_cast<int64_t*>(m_pData) = value; - m_eType = CONFIGDATATYPE_INT; } -CConfigValue::CConfigValue(const float value) { - m_pData = new float; +CConfigValue::CConfigValue(const float value) : m_eType(CONFIGDATATYPE_FLOAT), m_pData(new float) { *reinterpret_cast<float*>(m_pData) = value; - m_eType = CONFIGDATATYPE_FLOAT; } -CConfigValue::CConfigValue(const SVector2D value) { - m_pData = new SVector2D; +CConfigValue::CConfigValue(const SVector2D value) : m_eType(CONFIGDATATYPE_VEC2), m_pData(new SVector2D) { *reinterpret_cast<SVector2D*>(m_pData) = value; - m_eType = CONFIGDATATYPE_VEC2; } -CConfigValue::CConfigValue(const char* value) { - m_pData = new char[strlen(value) + 1]; +CConfigValue::CConfigValue(const char* value) : m_eType(CONFIGDATATYPE_STR), m_pData(new char[strlen(value) + 1]) { strncpy((char*)m_pData, value, strlen(value)); ((char*)m_pData)[strlen(value)] = '\0'; - m_eType = CONFIGDATATYPE_STR; } -CConfigValue::CConfigValue(CConfigCustomValueType&& value) { - m_pData = new CConfigCustomValueType(value); - m_eType = CONFIGDATATYPE_CUSTOM; +CConfigValue::CConfigValue(CConfigCustomValueType&& value) : m_eType(CONFIGDATATYPE_CUSTOM), m_pData(new CConfigCustomValueType(value)) { + ; } -CConfigValue::CConfigValue(const CConfigValue& other) { - m_eType = other.m_eType; +CConfigValue::CConfigValue(const CConfigValue& other) : m_eType(other.m_eType) { setFrom(&other); } @@ -77,11 +67,9 @@ return &m_pData; } -CConfigCustomValueType::CConfigCustomValueType(PCONFIGCUSTOMVALUEHANDLERFUNC handler_, PCONFIGCUSTOMVALUEDESTRUCTOR dtor_, const char* def) { - handler = handler_; - dtor = dtor_; - defaultVal = def; - lastVal = def; +CConfigCustomValueType::CConfigCustomValueType(PCONFIGCUSTOMVALUEHANDLERFUNC handler_, PCONFIGCUSTOMVALUEDESTRUCTOR dtor_, const char* def) : + handler(handler_), dtor(dtor_), defaultVal(def), lastVal(def) { + ; } CConfigCustomValueType::~CConfigCustomValueType() { @@ -216,4 +204,4 @@ throw "bad defaultFrom type"; } } -} \ No newline at end of file +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/src/config.cpp new/hyprlang-0.6.4/src/config.cpp --- old/hyprlang-0.6.0/src/config.cpp 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/src/config.cpp 2025-07-25 21:26:39.000000000 +0200 @@ -1,7 +1,9 @@ #include "config.hpp" +#include <array> #include <exception> #include <filesystem> #include <fstream> +#include <iostream> #include <stdexcept> #include <string> #include <format> @@ -12,6 +14,7 @@ #include <cstring> #include <hyprutils/string/VarList.hpp> #include <hyprutils/string/String.hpp> +#include <hyprutils/string/ConstVarList.hpp> using namespace Hyprlang; using namespace Hyprutils::String; @@ -20,28 +23,51 @@ #include <crt_externs.h> #define environ (*_NSGetEnviron()) #else +// NOLINTNEXTLINE extern "C" char** environ; #endif // defines -inline constexpr const char* ANONYMOUS_KEY = "__hyprlang_internal_anonymous_key"; +inline constexpr const char* ANONYMOUS_KEY = "__hyprlang_internal_anonymous_key"; +inline constexpr const char* MULTILINE_SPACE_CHARSET = " \t"; // static size_t seekABIStructSize(const void* begin, size_t startOffset, size_t maxSize) { for (size_t off = startOffset; off < maxSize; off += 4) { - if (*(int*)((unsigned char*)begin + off) == int{HYPRLANG_END_MAGIC}) + if (*(int*)((unsigned char*)begin + off) == HYPRLANG_END_MAGIC) return off; } return 0; } -CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) { +static std::expected<std::string, eGetNextLineFailure> getNextLine(std::istream& str, int& rawLineNum, int& lineNum) { + std::string line = ""; + std::string nextLine = ""; + + if (!std::getline(str, line)) + return std::unexpected(GETNEXTLINEFAILURE_EOF); + + lineNum = ++rawLineNum; + + while (line.length() > 0 && line.at(line.length() - 1) == '\\') { + const auto lastNonSpace = line.length() < 2 ? -1 : line.find_last_not_of(MULTILINE_SPACE_CHARSET, line.length() - 2); + line = line.substr(0, lastNonSpace + 1); + + if (!std::getline(str, nextLine)) + return std::unexpected(GETNEXTLINEFAILURE_BACKSLASH); + + ++rawLineNum; + line += nextLine; + } + + return line; +} + +CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) : impl(new CConfigImpl) { SConfigOptions options; std::memcpy(&options, &options_, seekABIStructSize(&options_, 16, sizeof(SConfigOptions))); - impl = new CConfigImpl; - if (options.pathIsStream) impl->rawConfigString = path; else @@ -60,7 +86,7 @@ impl->envVariables.push_back({VARIABLE, VALUE}); } - std::sort(impl->envVariables.begin(), impl->envVariables.end(), [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); }); + std::ranges::sort(impl->envVariables, [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); }); impl->configOptions = options; } @@ -74,40 +100,42 @@ throw "Cannot addConfigValue after commence()"; if ((eDataType)value.m_eType != CONFIGDATATYPE_CUSTOM && (eDataType)value.m_eType != CONFIGDATATYPE_STR) - impl->defaultValues.emplace(name, SConfigDefaultValue{value.getValue(), (eDataType)value.m_eType}); + impl->defaultValues.emplace(name, SConfigDefaultValue{.data = value.getValue(), .type = (eDataType)value.m_eType}); else if ((eDataType)value.m_eType == CONFIGDATATYPE_STR) - impl->defaultValues.emplace(name, SConfigDefaultValue{std::string{std::any_cast<const char*>(value.getValue())}, (eDataType)value.m_eType}); + impl->defaultValues.emplace(name, SConfigDefaultValue{.data = std::string{std::any_cast<const char*>(value.getValue())}, .type = (eDataType)value.m_eType}); else impl->defaultValues.emplace(name, - SConfigDefaultValue{reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal, (eDataType)value.m_eType, - reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler, - reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor}); + SConfigDefaultValue{.data = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal, + .type = (eDataType)value.m_eType, + .handler = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler, + .dtor = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor}); } void CConfig::addSpecialConfigValue(const char* cat, const char* name, const CConfigValue& value) { - const auto IT = std::find_if(impl->specialCategoryDescriptors.begin(), impl->specialCategoryDescriptors.end(), [&](const auto& other) { return other->name == cat; }); + const auto IT = std::ranges::find_if(impl->specialCategoryDescriptors, [&](const auto& other) { return other->name == cat; }); if (IT == impl->specialCategoryDescriptors.end()) throw "No such category"; if ((eDataType)value.m_eType != CONFIGDATATYPE_CUSTOM && (eDataType)value.m_eType != CONFIGDATATYPE_STR) - IT->get()->defaultValues.emplace(name, SConfigDefaultValue{value.getValue(), (eDataType)value.m_eType}); + IT->get()->defaultValues.emplace(name, SConfigDefaultValue{.data = value.getValue(), .type = (eDataType)value.m_eType}); else if ((eDataType)value.m_eType == CONFIGDATATYPE_STR) - IT->get()->defaultValues.emplace(name, SConfigDefaultValue{std::string{std::any_cast<const char*>(value.getValue())}, (eDataType)value.m_eType}); + IT->get()->defaultValues.emplace(name, SConfigDefaultValue{.data = std::string{std::any_cast<const char*>(value.getValue())}, .type = (eDataType)value.m_eType}); else IT->get()->defaultValues.emplace(name, - SConfigDefaultValue{reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal, (eDataType)value.m_eType, - reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler, - reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor}); + SConfigDefaultValue{.data = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal, + .type = (eDataType)value.m_eType, + .handler = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler, + .dtor = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor}); - const auto CAT = std::find_if(impl->specialCategories.begin(), impl->specialCategories.end(), [cat, name](const auto& other) { return other->name == cat && other->isStatic; }); + const auto CAT = std::ranges::find_if(impl->specialCategories, [cat](const auto& other) { return other->name == cat && other->isStatic; }); if (CAT != impl->specialCategories.end()) CAT->get()->values[name].defaultFrom(IT->get()->defaultValues[name]); } void CConfig::removeSpecialConfigValue(const char* cat, const char* name) { - const auto IT = std::find_if(impl->specialCategoryDescriptors.begin(), impl->specialCategoryDescriptors.end(), [&](const auto& other) { return other->name == cat; }); + const auto IT = std::ranges::find_if(impl->specialCategoryDescriptors, [&](const auto& other) { return other->name == cat; }); if (IT == impl->specialCategoryDescriptors.end()) throw "No such category"; @@ -138,9 +166,8 @@ } // sort longest to shortest - std::sort(impl->specialCategories.begin(), impl->specialCategories.end(), [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); }); - std::sort(impl->specialCategoryDescriptors.begin(), impl->specialCategoryDescriptors.end(), - [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); }); + std::ranges::sort(impl->specialCategories, [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); }); + std::ranges::sort(impl->specialCategoryDescriptors, [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); }); } void CConfig::removeSpecialCategory(const char* name) { @@ -195,7 +222,7 @@ if (!r.has_value() || !g.has_value() || !b.has_value()) return std::unexpected("failed parsing " + VALUEWITHOUTFUNC); - return a * (Hyprlang::INT)0x1000000 + r.value() * (Hyprlang::INT)0x10000 + g.value() * (Hyprlang::INT)0x100 + b.value(); + return (a * (Hyprlang::INT)0x1000000) + (r.value() * (Hyprlang::INT)0x10000) + (g.value() * (Hyprlang::INT)0x100) + b.value(); } else if (VALUEWITHOUTFUNC.length() == 8) { const auto RGBA = parseHex(VALUEWITHOUTFUNC); @@ -203,7 +230,7 @@ return RGBA; // now we need to RGBA -> ARGB. The config holds ARGB only. - return (RGBA.value() >> 8) + 0x1000000 * (RGBA.value() & 0xFF); + return (RGBA.value() >> 8) + (0x1000000 * (RGBA.value() & 0xFF)); } return std::unexpected("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values"); @@ -224,7 +251,7 @@ if (!r.has_value() || !g.has_value() || !b.has_value()) return std::unexpected("failed parsing " + VALUEWITHOUTFUNC); - return (Hyprlang::INT)0xFF000000 + r.value() * (Hyprlang::INT)0x10000 + g.value() * (Hyprlang::INT)0x100 + b.value(); + return (Hyprlang::INT)0xFF000000 + (r.value() * (Hyprlang::INT)0x10000) + (g.value() * (Hyprlang::INT)0x100) + b.value(); } else if (VALUEWITHOUTFUNC.length() == 6) { const auto RGB = parseHex(VALUEWITHOUTFUNC); @@ -357,8 +384,7 @@ // find suitable key size_t biggest = 0; for (auto& catt : impl->specialCategories) { - if (catt->anonymousID > biggest) - biggest = catt->anonymousID; + biggest = std::max(catt->anonymousID, biggest); } biggest++; @@ -417,7 +443,7 @@ if (LHS.contains(" ") || RHS.contains(" ")) throw std::runtime_error("too many args"); - VALUEIT->second.setFrom(SVector2D{std::stof(LHS), std::stof(RHS)}); + 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; @@ -451,14 +477,14 @@ } CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic) { - auto IT = std::find_if(impl->variables.begin(), impl->variables.end(), [&](const auto& v) { return v.name == lhs.substr(1); }); + auto IT = std::ranges::find_if(impl->variables, [&](const auto& v) { return v.name == lhs.substr(1); }); if (IT != impl->variables.end()) IT->value = rhs; else { 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(); }); - IT = std::find_if(impl->variables.begin(), impl->variables.end(), [&](const auto& v) { return v.name == lhs.substr(1); }); + std::ranges::sort(impl->variables, [](const auto& lhs, const auto& rhs) { return lhs.name.length() > rhs.name.length(); }); + IT = std::ranges::find_if(impl->variables, [&](const auto& v) { return v.name == lhs.substr(1); }); } if (dynamic) { @@ -475,16 +501,121 @@ return result; } -void CConfigImpl::parseComment(const std::string& comment) { +SVariable* CConfigImpl::getVariable(const std::string& name) { + for (auto& v : envVariables) { + if (v.name == name) + return &v; + } + + for (auto& v : variables) { + if (v.name == name) + return &v; + } + + return nullptr; +} + +std::optional<std::string> CConfigImpl::parseComment(const std::string& comment) { const auto COMMENT = trim(comment); if (!COMMENT.starts_with("hyprlang")) - return; + return std::nullopt; + + CConstVarList args(COMMENT, 0, 's', true); - CVarList args(COMMENT, 0, 's', true); + bool negated = false; + std::string ifBlockVariable = ""; - if (args[1] == "noerror") - currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set"; + for (size_t i = 1; i < args.size(); ++i) { + if (args[i] == "noerror") { + if (negated) + currentFlags.noError = false; + else + currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set" || args[2].empty(); + break; + } + + if (args[i] == "endif") { + if (!currentFlags.inAnIfBlock) + return "stray endif"; + currentFlags.inAnIfBlock = false; + break; + } + + if (args[i] == "if") { + ifBlockVariable = args[++i]; + break; + } + } + + if (!ifBlockVariable.empty()) { + if (currentFlags.inAnIfBlock) + return "nested if statements are not allowed"; + + if (ifBlockVariable.starts_with("!")) { + negated = true; + ifBlockVariable = ifBlockVariable.substr(1); + } + + currentFlags.inAnIfBlock = true; + + if (const auto VAR = getVariable(ifBlockVariable); VAR) + currentFlags.ifBlockFailed = negated ? VAR->truthy() : !VAR->truthy(); + else + currentFlags.ifBlockFailed = !negated; + } + + return std::nullopt; +} + +std::expected<float, std::string> CConfigImpl::parseExpression(const std::string& s) { + // for now, we only support very basic expressions. + // + - * / and only one per $() + // TODO: something better + + if (s.empty()) + return std::unexpected("Expression is empty"); + + CConstVarList args(s, 0, 's', true); + + if (args[1] != "+" && args[1] != "-" && args[1] != "*" && args[1] != "/") + return std::unexpected("Invalid expression type: supported +, -, *, /"); + + auto LHS_VAR = std::ranges::find_if(variables, [&](const auto& v) { return v.name == args[0]; }); + auto RHS_VAR = std::ranges::find_if(variables, [&](const auto& v) { return v.name == args[2]; }); + + float left = 0; + float right = 0; + + if (LHS_VAR != variables.end()) { + try { + left = std::stof(LHS_VAR->value); + } catch (...) { return std::unexpected("Failed to parse expression: value 1 holds a variable that does not look like a number"); } + } else { + try { + left = std::stof(std::string{args[0]}); + } catch (...) { return std::unexpected("Failed to parse expression: value 1 does not look like a number or the variable doesn't exist"); } + } + + if (RHS_VAR != variables.end()) { + try { + right = std::stof(RHS_VAR->value); + } catch (...) { return std::unexpected("Failed to parse expression: value 1 holds a variable that does not look like a number"); } + } else { + try { + right = std::stof(std::string{args[2]}); + } catch (...) { return std::unexpected("Failed to parse expression: value 1 does not look like a number or the variable doesn't exist"); } + } + + switch (args[1][0]) { + case '+': return left + right; + case '-': return left - right; + case '*': return left * right; + case '/': return left / right; + default: break; + } + + return std::unexpected("Unknown error while parsing expression"); } CParseResult CConfig::parseLine(std::string line, bool dynamic) { @@ -495,10 +626,15 @@ auto commentPos = line.find('#'); if (commentPos == 0) { - impl->parseComment(line.substr(1)); + const auto COMMENT_RESULT = impl->parseComment(line.substr(1)); + if (COMMENT_RESULT.has_value()) + result.setError(*COMMENT_RESULT); return result; } + if (impl->currentFlags.inAnIfBlock && impl->currentFlags.ifBlockFailed) + return result; + size_t lastHashPos = 0; while (commentPos != std::string::npos) { @@ -548,6 +684,8 @@ // limit unwrapping iterations to 100. if exceeds, raise error for (size_t i = 0; i < 100; ++i) { bool anyMatch = false; + + // parse variables for (auto& var : impl->variables) { // don't parse LHS variables if this is a variable... const auto LHSIT = ISVARIABLE ? std::string::npos : LHS.find("$" + var.name); @@ -566,6 +704,47 @@ anyMatch = true; } + // parse expressions {{somevar + 2}} + // We only support single expressions for now + while (RHS.contains("{{")) { + auto firstUnescaped = RHS.find("{{"); + // Keep searching until non-escaped expression start is found + while (firstUnescaped > 0) { + // Special check to avoid undefined behaviour with std::basic_string::find_last_not_of + auto amountSkipped = 0; + for (int i = firstUnescaped - 1; i >= 0; i--) { + if (RHS.at(i) != '\\') + break; + amountSkipped++; + } + // No escape chars, or even escape chars. means they escaped themselves. + if (amountSkipped % 2 == 0) + break; + // Continue searching for next valid expression start. + firstUnescaped = RHS.find("{{", firstUnescaped + 1); + // Break if the next match is never found + if (firstUnescaped == std::string::npos) + break; + } + // Real match was never found. + if (firstUnescaped == std::string::npos) + break; + const auto BEGIN_EXPR = firstUnescaped; + // "}}" doesnt need escaping. Would be invalid expression anyways. + const auto END_EXPR = RHS.find("}}", BEGIN_EXPR + 2); + if (END_EXPR != std::string::npos) { + // try to parse the expression + const auto RESULT = impl->parseExpression(RHS.substr(BEGIN_EXPR + 2, END_EXPR - BEGIN_EXPR - 2)); + if (!RESULT.has_value()) { + result.setError(RESULT.error()); + return result; + } + + RHS = RHS.substr(0, BEGIN_EXPR) + std::format("{}", RESULT.value()) + RHS.substr(END_EXPR + 2); + } else + break; + } + if (!anyMatch) break; @@ -578,6 +757,27 @@ if (ISVARIABLE) return parseVariable(LHS, RHS, dynamic); + // Removing escape chars. -- in the future, maybe map all the chars that can be escaped. + // Right now only expression parsing has escapeable chars + const char ESCAPE_CHAR = '\\'; + const std::array<char, 2> ESCAPE_SET{'{', '}'}; + for (size_t i = 0; RHS.length() != 0 && i < RHS.length() - 1; i++) { + if (RHS.at(i) != ESCAPE_CHAR) + continue; + //if escaping an escape, remove and skip the next char + if (RHS.at(i + 1) == ESCAPE_CHAR) { + RHS.erase(i, 1); + continue; + } + //checks if any of the chars were escapable. + for (const auto& ESCAPABLE_CHAR : ESCAPE_SET) { + if (RHS.at(i + 1) != ESCAPABLE_CHAR) + continue; + RHS.erase(i--, 1); + break; + } + } + bool found = false; for (auto& h : impl->handlers) { @@ -593,7 +793,7 @@ size_t idx = 0; size_t depth = 0; - while ((colon = HANDLERNAME.find(":", idx)) != std::string::npos && impl->categories.size() > depth) { + while ((colon = HANDLERNAME.find(':', idx)) != std::string::npos && impl->categories.size() > depth) { auto actual = HANDLERNAME.substr(idx, colon - idx); if (actual != impl->categories[depth]) @@ -695,22 +895,35 @@ CParseResult CConfig::parseRawStream(const std::string& stream) { CParseResult result; - std::string line = ""; - int linenum = 1; + int rawLineNum = 0; + int lineNum = 0; std::stringstream str(stream); - while (std::getline(str, line)) { - const auto RET = parseLine(line); + while (true) { + const auto line = getNextLine(str, rawLineNum, lineNum); + + if (!line) { + switch (line.error()) { + case GETNEXTLINEFAILURE_EOF: break; + case GETNEXTLINEFAILURE_BACKSLASH: + if (!impl->parseError.empty()) + impl->parseError += "\n"; + impl->parseError += std::format("Config error: Last line ends with backslash"); + result.setError(impl->parseError); + break; + } + break; + } + + const auto RET = parseLine(line.value()); if (RET.error && (impl->parseError.empty() || impl->configOptions.throwAllErrors)) { if (!impl->parseError.empty()) impl->parseError += "\n"; - impl->parseError += std::format("Config error at line {}: {}", linenum, RET.errorStdString); + impl->parseError += std::format("Config error at line {}: {}", lineNum, RET.errorStdString); result.setError(impl->parseError); } - - ++linenum; } if (!impl->categories.empty()) { @@ -736,21 +949,33 @@ return result; } - std::string line = ""; - int linenum = 1; + int rawLineNum = 0; + int lineNum = 0; - while (std::getline(iffile, line)) { + while (true) { + const auto line = getNextLine(iffile, rawLineNum, lineNum); - const auto RET = parseLine(line); + if (!line) { + switch (line.error()) { + case GETNEXTLINEFAILURE_EOF: break; + case GETNEXTLINEFAILURE_BACKSLASH: + if (!impl->parseError.empty()) + impl->parseError += "\n"; + impl->parseError += std::format("Config error in file {}: Last line ends with backslash", file); + result.setError(impl->parseError); + break; + } + break; + } + + const auto RET = parseLine(line.value()); if (!impl->currentFlags.noError && RET.error && (impl->parseError.empty() || impl->configOptions.throwAllErrors)) { if (!impl->parseError.empty()) impl->parseError += "\n"; - impl->parseError += std::format("Config error in file {} at line {}: {}", file, linenum, RET.errorStdString); + impl->parseError += std::format("Config error in file {} at line {}: {}", file, lineNum, RET.errorStdString); result.setError(impl->parseError); } - - ++linenum; } iffile.close(); @@ -811,7 +1036,7 @@ void CConfig::registerHandler(PCONFIGHANDLERFUNC func, const char* name, SHandlerOptions options_) { SHandlerOptions options; std::memcpy(&options, &options_, seekABIStructSize(&options_, 0, sizeof(SHandlerOptions))); - impl->handlers.push_back(SHandler{name, options, func}); + impl->handlers.push_back(SHandler{.name = name, .options = options, .func = func}); } void CConfig::unregisterHandler(const char* name) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/src/config.hpp new/hyprlang-0.6.4/src/config.hpp --- old/hyprlang-0.6.0/src/config.hpp 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/src/config.hpp 2025-07-25 21:26:39.000000000 +0200 @@ -4,6 +4,7 @@ #include <string> #include <vector> #include <memory> +#include <expected> struct SHandler { std::string name = ""; @@ -22,6 +23,10 @@ }; std::vector<SVarLine> linesContainingVar; // for dynamic updates + + bool truthy() { + return value.length() > 0; + } }; // remember to also edit CConfigValue if editing @@ -65,6 +70,11 @@ size_t anonymousID = 0; }; +enum eGetNextLineFailure : uint8_t { + GETNEXTLINEFAILURE_EOF = 0, + GETNEXTLINEFAILURE_BACKSLASH, +}; + class CConfigImpl { public: std::string path = ""; @@ -89,9 +99,13 @@ Hyprlang::SConfigOptions configOptions; - void parseComment(const std::string& comment); + 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); struct { - bool noError = false; + bool noError = false; + bool inAnIfBlock = false; + bool ifBlockFailed = false; } currentFlags; -}; \ No newline at end of file +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/tests/config/config.conf new/hyprlang-0.6.4/tests/config/config.conf --- old/hyprlang-0.6.0/tests/config/config.conf 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/tests/config/config.conf 2025-07-25 21:26:39.000000000 +0200 @@ -14,23 +14,54 @@ $MY_VAR_2 = $MY_VAR testVar = $MY_VAR$MY_VAR_2 +$EXPR_VAR = {{MY_VAR + 2}} +testExpr = {{EXPR_VAR - 4}} + +testEscapedExpr = \{{testInt + 7}} +testEscapedExpr2 = {\{testInt + 7}} +testEscapedExpr3 = \{\{3 + 8}} +testEscapedEscape = \\{{10 - 5}} +testMixedEscapedExpression = {{8 - 10}} \{{ \{{50 + 50}} / \{{10 * 5}} }} +testMixedEscapedExpression2 = {\{8\\{{10 + 3}}}} should equal "\{{8\13}}" + +$ESCAPED_TEXT = \{{10 + 10}} +testImbeddedEscapedExpression = $ESCAPED_TEXT + +$MOVING_VAR = 1000 +$DYNAMIC_EXPRESSION = moved: {{$MOVING_VAR / 2}} expr: \{{$MOVING_VAR / 2}} +testDynamicEscapedExpression = \{{ $DYNAMIC_EXPRESSION }} + testEnv = $SHELL source = ./colors.conf customType = abc +# hyprlang if !NONEXISTENT_VAR + testStringColon = ee:ee:ee +# hyprlang endif + # hyprlang noerror true errorVariable = true # hyprlang noerror false +# hyprlang if NONEXISTENT_VAR + +customType = bcd + +# hyprlang endif + +# hyprlang if MY_VAR + categoryKeyword = oops, this one shouldn't call the handler, not fun testUseKeyword = yes +# hyprlang endif + testCategory { testValueInt = 123456 testValueHex = 0xF @@ -90,6 +121,11 @@ value = 2 } +multiline = \ + very \ + long \ + command + testCategory:testValueHex = 0xFFfFaAbB $RECURSIVE1 = a @@ -103,4 +139,3 @@ flagsabc = test #doSomethingFunny = 1, 2, 3, 4 # Funnier! #testSpaces = abc , def # many spaces, should be trimmed - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/tests/config/multiline-errors.conf new/hyprlang-0.6.4/tests/config/multiline-errors.conf --- old/hyprlang-0.6.0/tests/config/multiline-errors.conf 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprlang-0.6.4/tests/config/multiline-errors.conf 2025-07-25 21:26:39.000000000 +0200 @@ -0,0 +1,20 @@ +# Careful when modifying this file. Line numbers are part of the test. + +multiline = \ + one \ + two \ + three + +# Line numbers reported in errors should match the actual line numbers of the source file +# even after multi-line configs. Any errors reported should use the line number of the +# first line of any multi-line config. + +this \ + should \ + cause \ + error \ + on \ + line \ + 12 + +# A config file cannot end with a bashslash because we are expecting another line! Even in a comment! \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/tests/fuzz/main.cpp new/hyprlang-0.6.4/tests/fuzz/main.cpp --- old/hyprlang-0.6.0/tests/fuzz/main.cpp 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/tests/fuzz/main.cpp 2025-07-25 21:26:39.000000000 +0200 @@ -11,7 +11,7 @@ std::string chars; for (int i = 0; i < len; ++i) { - chars += rand() % 254 + 1; + chars += std::to_string((rand() % 254) + 1); } return chars; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprlang-0.6.0/tests/parse/main.cpp new/hyprlang-0.6.4/tests/parse/main.cpp --- old/hyprlang-0.6.0/tests/parse/main.cpp 2024-12-13 21:48:31.000000000 +0100 +++ new/hyprlang-0.6.4/tests/parse/main.cpp 2025-07-25 21:26:39.000000000 +0200 @@ -48,7 +48,7 @@ } static Hyprlang::CParseResult handleCategoryKeyword(const char* COMMAND, const char* VALUE) { - categoryKeywordActualValues.push_back(VALUE); + categoryKeywordActualValues.emplace_back(VALUE); return Hyprlang::CParseResult(); } @@ -107,8 +107,17 @@ // setup config config.addConfigValue("testInt", (Hyprlang::INT)0); + config.addConfigValue("testExpr", (Hyprlang::INT)0); + config.addConfigValue("testEscapedExpr", ""); + config.addConfigValue("testEscapedExpr2", ""); + config.addConfigValue("testEscapedExpr3", ""); + config.addConfigValue("testEscapedEscape", ""); + config.addConfigValue("testMixedEscapedExpression", ""); + config.addConfigValue("testMixedEscapedExpression2", ""); + config.addConfigValue("testImbeddedEscapedExpression", ""); + config.addConfigValue("testDynamicEscapedExpression", ""); config.addConfigValue("testFloat", 0.F); - config.addConfigValue("testVec", Hyprlang::SVector2D{69, 420}); + config.addConfigValue("testVec", Hyprlang::SVector2D{.x = 69, .y = 420}); config.addConfigValue("testString", ""); config.addConfigValue("testStringColon", ""); config.addConfigValue("testEnv", ""); @@ -131,25 +140,27 @@ config.addConfigValue("myColors:random", (Hyprlang::INT)0); config.addConfigValue("customType", {Hyprlang::CConfigCustomValueType{&handleCustomValueSet, &handleCustomValueDestroy, "def"}}); - config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {false}); - config.registerHandler(&handleFlagsTest, "flags", {true}); - config.registerHandler(&handleSource, "source", {false}); - config.registerHandler(&handleTestIgnoreKeyword, "testIgnoreKeyword", {false}); - config.registerHandler(&handleTestUseKeyword, ":testUseKeyword", {false}); - config.registerHandler(&handleNoop, "testCategory:testUseKeyword", {false}); - config.registerHandler(&handleCategoryKeyword, "testCategory:categoryKeyword", {false}); + config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {.allowFlags = false}); + config.registerHandler(&handleFlagsTest, "flags", {.allowFlags = true}); + config.registerHandler(&handleSource, "source", {.allowFlags = false}); + config.registerHandler(&handleTestIgnoreKeyword, "testIgnoreKeyword", {.allowFlags = false}); + config.registerHandler(&handleTestUseKeyword, ":testUseKeyword", {.allowFlags = false}); + config.registerHandler(&handleNoop, "testCategory:testUseKeyword", {.allowFlags = false}); + config.registerHandler(&handleCategoryKeyword, "testCategory:categoryKeyword", {.allowFlags = false}); - config.addSpecialCategory("special", {"key"}); + config.addSpecialCategory("special", {.key = "key"}); config.addSpecialConfigValue("special", "value", (Hyprlang::INT)0); - config.addSpecialCategory("specialAnonymous", {nullptr, false, true}); + config.addSpecialCategory("specialAnonymous", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true}); config.addSpecialConfigValue("specialAnonymous", "value", (Hyprlang::INT)0); + config.addConfigValue("multiline", ""); + config.commence(); - config.addSpecialCategory("specialGeneric:one", {nullptr, true}); + config.addSpecialCategory("specialGeneric:one", {.key = nullptr, .ignoreMissing = true}); config.addSpecialConfigValue("specialGeneric:one", "value", (Hyprlang::INT)0); - config.addSpecialCategory("specialGeneric:two", {nullptr, true}); + config.addSpecialCategory("specialGeneric:two", {.key = nullptr, .ignoreMissing = true}); config.addSpecialConfigValue("specialGeneric:two", "value", (Hyprlang::INT)0); const Hyprlang::CConfigValue copyTest = {(Hyprlang::INT)1}; @@ -167,7 +178,7 @@ std::cout << " → Testing values\n"; EXPECT(std::any_cast<int64_t>(config.getConfigValue("testInt")), 123); EXPECT(std::any_cast<float>(config.getConfigValue("testFloat")), 123.456f); - auto EXP = Hyprlang::SVector2D{69, 420}; + auto EXP = Hyprlang::SVector2D{.x = 69, .y = 420}; EXPECT(std::any_cast<Hyprlang::SVector2D>(config.getConfigValue("testVec")), EXP); EXPECT(std::any_cast<const char*>(config.getConfigValue("testString")), std::string{"Hello World! # This is not a comment!"}); EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringQuotes")), std::string{"\"Hello World!\""}); @@ -195,6 +206,22 @@ EXPECT(*T3, EXP); EXPECT(*T4, "Hello World! # This is not a comment!"); + // test expressions + std::cout << " → Testing expressions\n"; + EXPECT(std::any_cast<int64_t>(config.getConfigValue("testExpr")), 1335); + + // test expression escape + std::cout << " → Testing expression escapes\n"; + EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedExpr")), std::string{"{{testInt + 7}}"}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedExpr2")), std::string{"{{testInt + 7}}"}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedExpr3")), std::string{"{{3 + 8}}"}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedEscape")), std::string{"\\5"}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testMixedEscapedExpression")), std::string{"-2 {{ {{50 + 50}} / {{10 * 5}} }}"}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testMixedEscapedExpression2")), std::string{"{{8\\13}} should equal \"{{8\\13}}\""}); + + EXPECT(std::any_cast<const char*>(config.getConfigValue("testImbeddedEscapedExpression")), std::string{"{{10 + 10}}"}); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testDynamicEscapedExpression")), std::string{"{{ moved: 500 expr: {{1000 / 2}} }}"}); + // test static values std::cout << " → Testing static values\n"; static auto* const PTESTINT = config.getConfigValuePtr("testInt")->getDataStaticPtr(); @@ -241,6 +268,14 @@ EXPECT(config.parseDynamic("$RECURSIVE1 = d").error, false); EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringRecursive")), std::string{"dbc"}); + // test expression escape with dynamic vars + EXPECT(config.parseDynamic("$MOVING_VAR = 500").error, false); + EXPECT(std::any_cast<const char*>(config.getConfigValue("testDynamicEscapedExpression")), std::string{"{{ moved: 250 expr: {{500 / 2}} }}"}); + + // test dynamic exprs + EXPECT(config.parseDynamic("testExpr = {{EXPR_VAR * 2}}").error, false); + EXPECT(std::any_cast<int64_t>(config.getConfigValue("testExpr")), 1339L * 2); + // test env variables std::cout << " → Testing env variables\n"; EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv")), std::string{getenv("SHELL")}); @@ -279,6 +314,9 @@ std::cout << " → Testing custom types\n"; EXPECT(*reinterpret_cast<int64_t*>(std::any_cast<void*>(config.getConfigValue("customType"))), (Hyprlang::INT)1); + // test multiline config + EXPECT(std::any_cast<const char*>(config.getConfigValue("multiline")), std::string{"very long command"}); + std::cout << " → Testing error.conf\n"; Hyprlang::CConfig errorConfig("./config/error.conf", {.verifyOnly = true, .throwAllErrors = true}); @@ -307,6 +345,17 @@ EXPECT(ERRORS2.error, true); const auto ERRORSTR2 = std::string{ERRORS2.getError()}; EXPECT(std::count(ERRORSTR2.begin(), ERRORSTR2.end(), '\n'), 9 - 1); + + Hyprlang::CConfig multilineErrorConfig("./config/multiline-errors.conf", {.verifyOnly = true, .throwAllErrors = true}); + multilineErrorConfig.commence(); + const auto ERRORS3 = multilineErrorConfig.parse(); + EXPECT(ERRORS3.error, true); + const auto ERRORSTR3 = std::string{ERRORS3.getError()}; + + // Error on line 12 + EXPECT(ERRORSTR3.contains("12"), true); + // Backslash at end of file + EXPECT(ERRORSTR3.contains("backslash"), true); } catch (const char* e) { std::cout << Colors::RED << "Error: " << Colors::RESET << e << "\n"; return 1;
