tags 1106521 + patch thanks Dear Maintainer,
I'm uploading an NMU for hyprlang 0.6.4-0.1 to upgrade this to the latest upstream version. I've already committed the changes into VCS at https://salsa.debian.org/Debian/hyprlang. Attached is the diff relative to the previous upload. I will be uploading this to DELAYED/2 shortly. Please reach out if you'd like me to cancel the upload within the next 2 days. -- Kind regards, Loong Jin
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..224864f
--- /dev/null
+++ b/.clang-tidy
@@ -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 --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml
index 5889c5e..772cd1c 100644
--- a/.github/workflows/nix.yml
+++ b/.github/workflows/nix.yml
@@ -13,8 +13,35 @@ jobs:
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 --git a/CMakeLists.txt b/CMakeLists.txt
index aa8cdf9..ccb2053 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,9 +28,16 @@ endif()
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 @@ set_target_properties(
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 --git a/README.md b/README.md
index 3946498..58b0ded 100644
--- a/README.md
+++ b/README.md
@@ -54,4 +54,4 @@ Visit [hyprland.org/hyprlang](https://hyprland.org/hyprlang) to see the document
### 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 --git a/VERSION b/VERSION
index a918a2a..844f6a9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.6.0
+0.6.3
diff --git a/debian/changelog b/debian/changelog
index 69fc49b..45849b4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+hyprlang (0.6.4-0.1) unstable; urgency=medium
+
+ * Non-maintainer upload
+ [ Carl Keinath ]
+ * add gbp conf
+ * simplified watch file
+ * cleanup d/u/metadata
+ * added ci config
+ * cleanup d/rules
+ * folded package deps in d/control
+ * updated copyright
+ * removed trailing whitespaces
+
+ [ Chow Loong Jin ]
+ * New upstream version 0.6.4 (Closes: #1106521)
+
+ -- Chow Loong Jin <[email protected]> Mon, 29 Sep 2025 11:36:27 +0800
+
hyprlang (0.6.0-1) unstable; urgency=medium
* New upstream version 0.6.0
diff --git a/debian/control b/debian/control
index 5bac359..0133a8a 100644
--- a/debian/control
+++ b/debian/control
@@ -16,7 +16,9 @@ Rules-Requires-Root: no
Package: libhyprlang-dev
Section: libdevel
Architecture: any
-Depends: ${misc:Depends}, libhyprlang2 (= ${binary:Version})
+Depends:
+ ${misc:Depends},
+ libhyprlang2 (= ${binary:Version}),
Description: Fast and user-friendly configuration language (dev files)
The hypr configuration language is an extremely efficient, yet easy to
work with, configuration language for Linux applications.
@@ -27,7 +29,9 @@ Description: Fast and user-friendly configuration language (dev files)
Package: libhyprlang2
Architecture: any
-Depends: ${misc:Depends}, ${shlibs:Depends}
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
Description: Fast and user-friendly configuration language (library files)
The hypr configuration language is an extremely efficient, yet easy to
work with, configuration language for Linux applications.
diff --git a/debian/copyright b/debian/copyright
index 477bcaa..5c686d3 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -8,8 +8,9 @@ Copyright: 2023 - 2024, Hypr Development <[email protected]>
License: LGPL-3
Files: debian/*
-Copyright: 2024, Mo Zhou <[email protected]>
- 2024 Alan M Varghese <[email protected]>
+Copyright: 2024 Mo Zhou <[email protected]>
+ 2024-2025 Alan M Varghese <[email protected]>
+ 2025 Carl Keinath <[email protected]>
License: LGPL-3
License: LGPL-3
diff --git a/debian/gbp.conf b/debian/gbp.conf
new file mode 100644
index 0000000..6f440a2
--- /dev/null
+++ b/debian/gbp.conf
@@ -0,0 +1,4 @@
+[DEFAULT]
+debian-branch = debian/latest
+upstream-branch = upstream/latest
+pristine-tar = True
\ No newline at end of file
diff --git a/debian/rules b/debian/rules
index f7f1e0a..d8309f6 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,8 +1,6 @@
#!/usr/bin/make -f
-#export DH_VERBOSE = 1
+
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
-#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
-#export DEB_LDFLAGS_MAINT_APPEND = -Wl,-O1
%:
dh $@
diff --git a/debian/salsa-ci.yml b/debian/salsa-ci.yml
new file mode 100644
index 0000000..84d19ed
--- /dev/null
+++ b/debian/salsa-ci.yml
@@ -0,0 +1,3 @@
+---
+include:
+ - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml
\ No newline at end of file
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
index 586d5d6..35f9264 100644
--- a/debian/upstream/metadata
+++ b/debian/upstream/metadata
@@ -1,7 +1,3 @@
-#
-# DEP-12: Per-package machine-readable metadata about Upstream
-# Please check * https://dep-team.pages.debian.net/deps/dep12/
-# * https://wiki.debian.org/UpstreamMetadata
Bug-Database: https://github.com/hyprwm/hyprlang/issues
Bug-Submit: https://github.com/hyprwm/hyprlang/issues/new
Repository: https://github.com/hyprwm/hyprlang
diff --git a/debian/watch b/debian/watch
index 2433a19..3d725c7 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,4 +1,4 @@
version=4
-opts="searchmode=html" \
- https://github.com/hyprwm/hyprlang/tags \
- https://github.com/hyprwm/hyprlang/archive/refs/tags/v(\d+.\d+.\d+)\.tar\.gz
+opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%" \
+ https://github.com/hyprwm/hyprlang/tags \
+ (?:.*?/)?v?(\d[\d.]*)\.tar\.gz
diff --git a/flake.lock b/flake.lock
index 5abff22..8798ba3 100644
--- a/flake.lock
+++ b/flake.lock
@@ -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 --git a/flake.nix b/flake.nix
index 959d3cf..3b63d6f 100644
--- a/flake.nix
+++ b/flake.nix
@@ -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 --git a/include/hyprlang.hpp b/include/hyprlang.hpp
index 863b277..547fcb8 100644
--- a/include/hyprlang.hpp
+++ b/include/hyprlang.hpp
@@ -50,7 +50,7 @@ namespace Hyprlang {
typedef CConfigCustomValueType CUSTOMTYPE;
/*!
- A very simple vector type
+ A very simple vector type
*/
struct SVector2D {
float x = 0, y = 0;
@@ -95,12 +95,12 @@ namespace Hyprlang {
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 @@ namespace Hyprlang {
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 @@ namespace Hyprlang {
/*!
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 @@ namespace Hyprlang {
/*!
\since 0.3.0
-
+
a flag to notify whether this value has been set explicitly by the user,
or not.
*/
@@ -305,7 +305,7 @@ namespace Hyprlang {
~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 @@ namespace Hyprlang {
/*!
\since 0.3.0
-
- Unregister a handler.
+
+ Unregister a handler.
*/
void unregisterHandler(const char* name);
@@ -362,14 +362,14 @@ namespace Hyprlang {
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 @@ namespace Hyprlang {
/*!
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 @@ namespace Hyprlang {
#undef HYPRLANG_END_MAGIC
#endif
-#endif
\ No newline at end of file
+#endif
diff --git a/src/common.cpp b/src/common.cpp
index cfe43bb..a1c1504 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -1,6 +1,6 @@
#include "public.hpp"
#include "config.hpp"
-#include <string.h>
+#include <cstring>
using namespace Hyprlang;
@@ -30,38 +30,28 @@ CConfigValue::~CConfigValue() {
}
}
-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 @@ void* const* CConfigValue::getDataStaticPtr() const {
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 @@ void CConfigValue::setFrom(std::any ref) {
throw "bad defaultFrom type";
}
}
-}
\ No newline at end of file
+}
diff --git a/src/config.cpp b/src/config.cpp
index fdb71db..560fd8f 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -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 @@ using namespace Hyprutils::String;
#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 @@ CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) {
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 @@ void CConfig::addConfigValue(const char* name, const CConfigValue& value) {
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 @@ void CConfig::addSpecialCategory(const char* name, SSpecialCategoryOptions optio
}
// 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 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
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 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
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 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
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 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
// 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 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
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::configSetValueSafe(const std::string& command, const std::
}
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 @@ CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& r
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;
- CVarList args(COMMENT, 0, 's', true);
+ CConstVarList args(COMMENT, 0, 's', true);
- if (args[1] == "noerror")
- currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set";
+ bool negated = false;
+ std::string ifBlockVariable = "";
+
+ 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 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
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 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
// 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 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
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 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
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 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
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::parse() {
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 @@ CParseResult CConfig::parseFile(const char* file) {
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 @@ CConfigValue* CConfig::getSpecialConfigValuePtr(const char* category, const char
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 --git a/src/config.hpp b/src/config.hpp
index 46efaf5..63d9840 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -4,6 +4,7 @@
#include <string>
#include <vector>
#include <memory>
+#include <expected>
struct SHandler {
std::string name = "";
@@ -22,6 +23,10 @@ struct SVariable {
};
std::vector<SVarLine> linesContainingVar; // for dynamic updates
+
+ bool truthy() {
+ return value.length() > 0;
+ }
};
// remember to also edit CConfigValue if editing
@@ -65,6 +70,11 @@ struct SSpecialCategory {
size_t anonymousID = 0;
};
+enum eGetNextLineFailure : uint8_t {
+ GETNEXTLINEFAILURE_EOF = 0,
+ GETNEXTLINEFAILURE_BACKSLASH,
+};
+
class CConfigImpl {
public:
std::string path = "";
@@ -89,9 +99,13 @@ class CConfigImpl {
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 --git a/tests/config/config.conf b/tests/config/config.conf
index c8ea739..e3c372f 100644
--- a/tests/config/config.conf
+++ b/tests/config/config.conf
@@ -14,23 +14,54 @@ $MY_VAR = 1337
$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 @@ flagsStuff {
value = 2
}
+multiline = \
+ very \
+ long \
+ command
+
testCategory:testValueHex = 0xFFfFaAbB
$RECURSIVE1 = a
@@ -103,4 +139,3 @@ doABarrelRoll = woohoo, some, params # Funny!
flagsabc = test
#doSomethingFunny = 1, 2, 3, 4 # Funnier!
#testSpaces = abc , def # many spaces, should be trimmed
-
diff --git a/tests/config/multiline-errors.conf b/tests/config/multiline-errors.conf
new file mode 100644
index 0000000..9ea2c28
--- /dev/null
+++ b/tests/config/multiline-errors.conf
@@ -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 --git a/tests/fuzz/main.cpp b/tests/fuzz/main.cpp
index 3f6effd..b0bfe00 100644
--- a/tests/fuzz/main.cpp
+++ b/tests/fuzz/main.cpp
@@ -11,7 +11,7 @@ std::string garbage() {
std::string chars;
for (int i = 0; i < len; ++i) {
- chars += rand() % 254 + 1;
+ chars += std::to_string((rand() % 254) + 1);
}
return chars;
diff --git a/tests/parse/main.cpp b/tests/parse/main.cpp
old mode 100755
new mode 100644
index 56b40d8..dbf5e70
--- a/tests/parse/main.cpp
+++ b/tests/parse/main.cpp
@@ -48,7 +48,7 @@ static Hyprlang::CParseResult handleFlagsTest(const char* COMMAND, const char* V
}
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 @@ int main(int argc, char** argv, char** envp) {
// 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 @@ int main(int argc, char** argv, char** envp) {
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 @@ int main(int argc, char** argv, char** envp) {
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 @@ int main(int argc, char** argv, char** envp) {
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 @@ int main(int argc, char** argv, char** envp) {
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 @@ int main(int argc, char** argv, char** envp) {
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 @@ int main(int argc, char** argv, char** envp) {
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;
signature.asc
Description: PGP signature

