mirror of
https://github.com/hyprwm/hyprlang.git
synced 2025-12-20 12:00:03 +01:00
Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d00dc1189 | |||
|
|
eb5be96aa0 | ||
| 3d66ec7c29 | |||
| a318deec0c | |||
| 3d3057837c | |||
| deea98d5b6 | |||
| 8b3da759ec | |||
| b0d7b375a9 | |||
| 4302343ce7 | |||
| 771e915f59 | |||
| 995db114b8 | |||
|
|
4dafa28d4f | ||
| 3d63fb4a42 | |||
|
|
23f0debd20 | ||
| 12cb0e19e3 | |||
| 13865735fe | |||
| 235ce61cba | |||
| cee01452bc | |||
|
|
1bfb84f54d | ||
|
|
163c83b3db | ||
| 557241780c | |||
| a59e86a3da |
11 changed files with 548 additions and 173 deletions
8
.github/workflows/arch.yml
vendored
8
.github/workflows/arch.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
run: |
|
||||
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman gtest
|
||||
|
||||
- name: Get hyprutils-git
|
||||
run: |
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
run: |
|
||||
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman gtest
|
||||
|
||||
- name: Get hyprutils-git
|
||||
run: |
|
||||
|
|
@ -79,7 +79,7 @@ jobs:
|
|||
run: |
|
||||
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang git pixman gtest
|
||||
|
||||
- name: Get hyprutils-git
|
||||
run: |
|
||||
|
|
@ -110,7 +110,7 @@ jobs:
|
|||
run: |
|
||||
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ git pixman
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ git pixman gtest
|
||||
|
||||
- name: Get hyprutils-git
|
||||
run: |
|
||||
|
|
|
|||
30
.github/workflows/nix.yml
vendored
30
.github/workflows/nix.yml
vendored
|
|
@ -13,7 +13,35 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DeterminateSystems/nix-installer-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
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ add_baker = Koichi, 18, Morioh
|
|||
|
||||
## Docs
|
||||
|
||||
Visit [hyprland.org/hyprlang](https://hyprland.org/hyprlang) to see the documentation.
|
||||
Visit [wiki.hypr.land/Hypr-Ecosystem/hyprlang/](https://wiki.hypr.land/Hypr-Ecosystem/hyprlang/) to see the documentation.
|
||||
|
||||
### Example implementation
|
||||
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.6.2
|
||||
0.6.7
|
||||
|
|
|
|||
12
flake.lock
generated
12
flake.lock
generated
|
|
@ -10,11 +10,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1746635225,
|
||||
"narHash": "sha256-W9G9bb0zRYDBRseHbVez0J8qVpD5QbizX67H/vsudhM=",
|
||||
"lastModified": 1749135356,
|
||||
"narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "674ea57373f08b7609ce93baff131117a0dfe70d",
|
||||
"rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -25,11 +25,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1746461020,
|
||||
"narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
|
||||
"lastModified": 1748929857,
|
||||
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
|
||||
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
inputs.hyprutils.overlays.default
|
||||
(final: prev: {
|
||||
hyprlang = final.callPackage ./nix/default.nix {
|
||||
stdenv = final.gcc14Stdenv;
|
||||
stdenv = final.gcc15Stdenv;
|
||||
version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
};
|
||||
hyprlang-with-tests = final.hyprlang.override {doCheck = true;};
|
||||
|
|
|
|||
|
|
@ -441,18 +441,25 @@ namespace Hyprlang {
|
|||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Change the root path of the config
|
||||
|
||||
\since 0.6.7
|
||||
*/
|
||||
void changeRootPath(const char* path);
|
||||
|
||||
private:
|
||||
bool m_bCommenced = false;
|
||||
bool m_bCommenced = false;
|
||||
|
||||
CConfigImpl* impl;
|
||||
CConfigImpl* impl;
|
||||
|
||||
CParseResult parseLine(std::string line, bool dynamic = false);
|
||||
CParseResult configSetValueSafe(const std::string& command, const std::string& value);
|
||||
CParseResult parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic = false);
|
||||
void clearState();
|
||||
void applyDefaultsToCat(SSpecialCategory& cat);
|
||||
void retrieveKeysForCat(const char* category, const char*** out, size_t* len);
|
||||
CParseResult parseRawStream(const std::string& stream);
|
||||
CParseResult parseLine(std::string line, bool dynamic = false);
|
||||
std::pair<bool, CParseResult> configSetValueSafe(const std::string& command, const std::string& value);
|
||||
CParseResult parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic = false);
|
||||
void clearState();
|
||||
void applyDefaultsToCat(SSpecialCategory& cat);
|
||||
void retrieveKeysForCat(const char* category, const char*** out, size_t* len);
|
||||
CParseResult parseRawStream(const std::string& stream);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
|
|
|||
436
src/config.cpp
436
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>
|
||||
|
|
@ -76,13 +78,7 @@ CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) : i
|
|||
throw "File does not exist";
|
||||
}
|
||||
|
||||
impl->envVariables.clear();
|
||||
for (char** env = environ; *env; ++env) {
|
||||
const std::string ENVVAR = *env ? *env : "";
|
||||
const auto VARIABLE = ENVVAR.substr(0, ENVVAR.find_first_of('='));
|
||||
const auto VALUE = ENVVAR.substr(ENVVAR.find_first_of('=') + 1);
|
||||
impl->envVariables.push_back({VARIABLE, VALUE});
|
||||
}
|
||||
impl->recheckEnv();
|
||||
|
||||
std::ranges::sort(impl->envVariables, [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); });
|
||||
|
||||
|
|
@ -277,21 +273,21 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
|
|||
return 0;
|
||||
}
|
||||
|
||||
CParseResult CConfig::configSetValueSafe(const std::string& command, const std::string& value) {
|
||||
// found, result
|
||||
std::pair<bool, CParseResult> CConfig::configSetValueSafe(const std::string& command, const std::string& value) {
|
||||
CParseResult result;
|
||||
|
||||
std::string valueName;
|
||||
std::string catPrefix;
|
||||
for (auto& c : impl->categories) {
|
||||
valueName += c + ':';
|
||||
catPrefix += c + ':';
|
||||
}
|
||||
|
||||
valueName += command;
|
||||
|
||||
const auto VALUEONLYNAME = command.starts_with(catPrefix) ? command.substr(catPrefix.length()) : command;
|
||||
// TODO: all this sucks xD
|
||||
|
||||
SSpecialCategory* overrideSpecialCat = nullptr;
|
||||
|
||||
// FIXME: this will bug with nested.
|
||||
if (valueName.contains('[') && valueName.contains(']')) {
|
||||
const auto L = valueName.find_first_of('[');
|
||||
const auto R = valueName.find_last_of(']');
|
||||
|
|
@ -302,12 +298,27 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
|
||||
valueName = valueName.substr(0, L) + valueName.substr(R + 1);
|
||||
|
||||
// if it doesn't exist, make it
|
||||
for (auto& sc : impl->specialCategoryDescriptors) {
|
||||
if (sc->key.empty() || !valueName.starts_with(sc->name))
|
||||
if (sc->key.empty() || !valueName.starts_with(sc->name + ":"))
|
||||
continue;
|
||||
|
||||
// bingo
|
||||
bool keyExists = false;
|
||||
for (const auto& specialCat : impl->specialCategories) {
|
||||
if (specialCat->key != sc->key || specialCat->name != sc->name)
|
||||
continue;
|
||||
|
||||
if (CATKEY != std::string_view{std::any_cast<const char*>(specialCat->values[sc->key].getValue())})
|
||||
continue;
|
||||
|
||||
// existing special
|
||||
keyExists = true;
|
||||
overrideSpecialCat = specialCat.get();
|
||||
}
|
||||
|
||||
if (keyExists)
|
||||
break;
|
||||
|
||||
// if it doesn't exist, make it
|
||||
const auto PCAT = impl->specialCategories.emplace_back(std::make_unique<SSpecialCategory>()).get();
|
||||
PCAT->descriptor = sc.get();
|
||||
PCAT->name = sc->name;
|
||||
|
|
@ -317,6 +328,8 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
applyDefaultsToCat(*PCAT);
|
||||
|
||||
PCAT->values[sc->key].setFrom(CATKEY);
|
||||
overrideSpecialCat = PCAT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -326,85 +339,99 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
// it might be in a special category
|
||||
bool found = false;
|
||||
|
||||
if (impl->currentSpecialCategory && valueName.starts_with(impl->currentSpecialCategory->name)) {
|
||||
VALUEIT = impl->currentSpecialCategory->values.find(valueName.substr(impl->currentSpecialCategory->name.length() + 1));
|
||||
if (overrideSpecialCat) {
|
||||
VALUEIT = overrideSpecialCat->values.find(valueName.substr(overrideSpecialCat->name.length() + 1));
|
||||
|
||||
if (VALUEIT != impl->currentSpecialCategory->values.end())
|
||||
if (VALUEIT != overrideSpecialCat->values.end())
|
||||
found = true;
|
||||
}
|
||||
} else {
|
||||
if (impl->currentSpecialCategory && valueName.starts_with(impl->currentSpecialCategory->name)) {
|
||||
VALUEIT = impl->currentSpecialCategory->values.find(valueName.substr(impl->currentSpecialCategory->name.length() + 1));
|
||||
|
||||
if (!found) {
|
||||
for (auto& sc : impl->specialCategories) {
|
||||
if (!valueName.starts_with(sc->name))
|
||||
continue;
|
||||
|
||||
if (!sc->isStatic && std::string{std::any_cast<const char*>(sc->values[sc->key].getValue())} != impl->currentSpecialKey)
|
||||
continue;
|
||||
|
||||
VALUEIT = sc->values.find(valueName.substr(sc->name.length() + 1));
|
||||
impl->currentSpecialCategory = sc.get();
|
||||
|
||||
if (VALUEIT != sc->values.end())
|
||||
if (VALUEIT != impl->currentSpecialCategory->values.end())
|
||||
found = true;
|
||||
else if (sc->descriptor->dontErrorOnMissing)
|
||||
return result; // will return a success, cuz we want to ignore missing
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// could be a dynamic category that doesnt exist yet
|
||||
for (auto& sc : impl->specialCategoryDescriptors) {
|
||||
if (sc->key.empty() || !valueName.starts_with(sc->name))
|
||||
continue;
|
||||
// probably a handler
|
||||
if (!valueName.contains(":"))
|
||||
return {false, result};
|
||||
|
||||
if (!found) {
|
||||
for (auto& sc : impl->specialCategories) {
|
||||
if (!valueName.starts_with(sc->name + ":"))
|
||||
continue;
|
||||
|
||||
if (!sc->isStatic && std::string{std::any_cast<const char*>(sc->values[sc->key].getValue())} != impl->currentSpecialKey)
|
||||
continue;
|
||||
|
||||
VALUEIT = sc->values.find(valueName.substr(sc->name.length() + 1));
|
||||
impl->currentSpecialCategory = sc.get();
|
||||
|
||||
if (VALUEIT != sc->values.end())
|
||||
found = true;
|
||||
else if (sc->descriptor->dontErrorOnMissing)
|
||||
return {false, result}; // will return a success, cuz we want to ignore missing
|
||||
|
||||
// category does exist, check if value exists
|
||||
if (!sc->defaultValues.contains(VALUEONLYNAME) && VALUEONLYNAME != sc->key)
|
||||
break;
|
||||
|
||||
// bingo
|
||||
const auto PCAT = impl->specialCategories.emplace_back(std::make_unique<SSpecialCategory>()).get();
|
||||
PCAT->descriptor = sc.get();
|
||||
PCAT->name = sc->name;
|
||||
PCAT->key = sc->key;
|
||||
addSpecialConfigValue(sc->name.c_str(), sc->key.c_str(), CConfigValue("0"));
|
||||
|
||||
applyDefaultsToCat(*PCAT);
|
||||
|
||||
VALUEIT = PCAT->values.find(valueName.substr(sc->name.length() + 1));
|
||||
impl->currentSpecialCategory = PCAT;
|
||||
|
||||
if (VALUEIT != PCAT->values.end())
|
||||
found = true;
|
||||
|
||||
if (sc->anonymous) {
|
||||
// find suitable key
|
||||
size_t biggest = 0;
|
||||
for (auto& catt : impl->specialCategories) {
|
||||
biggest = std::max(catt->anonymousID, biggest);
|
||||
}
|
||||
|
||||
biggest++;
|
||||
|
||||
PCAT->values[ANONYMOUS_KEY].setFrom(std::to_string(biggest));
|
||||
impl->currentSpecialKey = std::to_string(biggest);
|
||||
PCAT->anonymousID = biggest;
|
||||
} else {
|
||||
if (VALUEIT == PCAT->values.end() || VALUEIT->first != sc->key) {
|
||||
result.setError(std::format("special category's first value must be the key. Key for <{}> is <{}>", PCAT->name, PCAT->key));
|
||||
return result;
|
||||
}
|
||||
impl->currentSpecialKey = value;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
if (!found) {
|
||||
// could be a dynamic category that doesnt exist yet
|
||||
for (auto& sc : impl->specialCategoryDescriptors) {
|
||||
if (sc->key.empty() || !valueName.starts_with(sc->name + ":"))
|
||||
continue;
|
||||
|
||||
// found value root to be a special category, get the trunk
|
||||
const auto VALUETRUNK = valueName.substr(sc->name.length() + 1);
|
||||
|
||||
// check if trunk is a value within the special category
|
||||
if (!sc->defaultValues.contains(VALUETRUNK) && VALUETRUNK != sc->key)
|
||||
break;
|
||||
|
||||
// bingo
|
||||
const auto PCAT = impl->specialCategories.emplace_back(std::make_unique<SSpecialCategory>()).get();
|
||||
PCAT->descriptor = sc.get();
|
||||
PCAT->name = sc->name;
|
||||
PCAT->key = sc->key;
|
||||
addSpecialConfigValue(sc->name.c_str(), sc->key.c_str(), CConfigValue("0"));
|
||||
|
||||
applyDefaultsToCat(*PCAT);
|
||||
|
||||
VALUEIT = PCAT->values.find(valueName.substr(sc->name.length() + 1));
|
||||
impl->currentSpecialCategory = PCAT;
|
||||
|
||||
if (VALUEIT != PCAT->values.end())
|
||||
found = true;
|
||||
|
||||
if (sc->anonymous) {
|
||||
// find suitable key
|
||||
size_t biggest = 0;
|
||||
for (auto& catt : impl->specialCategories) {
|
||||
biggest = std::max(catt->anonymousID, biggest);
|
||||
}
|
||||
|
||||
biggest++;
|
||||
|
||||
PCAT->values[ANONYMOUS_KEY].setFrom(std::to_string(biggest));
|
||||
impl->currentSpecialKey = std::to_string(biggest);
|
||||
PCAT->anonymousID = biggest;
|
||||
} else {
|
||||
if (VALUEIT == PCAT->values.end() || VALUEIT->first != sc->key) {
|
||||
result.setError(std::format("special category's first value must be the key. Key for <{}> is <{}>", PCAT->name, PCAT->key));
|
||||
return {true, result};
|
||||
}
|
||||
impl->currentSpecialKey = value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
result.setError(std::format("config option <{}> does not exist.", valueName));
|
||||
return result;
|
||||
return {false, result};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -414,7 +441,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
const auto INT = configStringToInt(value);
|
||||
if (!INT.has_value()) {
|
||||
result.setError(INT.error());
|
||||
return result;
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
VALUEIT->second.setFrom(INT.value());
|
||||
|
|
@ -426,7 +453,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
VALUEIT->second.setFrom(std::stof(value));
|
||||
} catch (std::exception& e) {
|
||||
result.setError(std::format("failed parsing a float: {}", e.what()));
|
||||
return result;
|
||||
return {true, result};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -444,7 +471,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
VALUEIT->second.setFrom(SVector2D{.x = std::stof(LHS), .y = std::stof(RHS)});
|
||||
} catch (std::exception& e) {
|
||||
result.setError(std::format("failed parsing a vec2: {}", e.what()));
|
||||
return result;
|
||||
return {true, result};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -459,19 +486,19 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
|
|||
|
||||
if (RESULT.error) {
|
||||
result.setError(RESULT.getError());
|
||||
return result;
|
||||
return {true, result};
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result.setError("internal error: invalid value found (no type?)");
|
||||
return result;
|
||||
return {true, result};
|
||||
}
|
||||
}
|
||||
|
||||
VALUEIT->second.m_bSetByUser = true;
|
||||
|
||||
return result;
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic) {
|
||||
|
|
@ -499,16 +526,80 @@ CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& r
|
|||
return result;
|
||||
}
|
||||
|
||||
void CConfigImpl::parseComment(const std::string& comment) {
|
||||
void CConfigImpl::recheckEnv() {
|
||||
envVariables.clear();
|
||||
for (char** env = environ; *env; ++env) {
|
||||
const std::string ENVVAR = *env ? *env : "";
|
||||
const auto VARIABLE = ENVVAR.substr(0, ENVVAR.find_first_of('='));
|
||||
const auto VALUE = ENVVAR.substr(ENVVAR.find_first_of('=') + 1);
|
||||
envVariables.push_back({VARIABLE, VALUE});
|
||||
}
|
||||
}
|
||||
|
||||
SVariable* CConfigImpl::getVariable(const std::string& name) {
|
||||
for (auto& v : envVariables) {
|
||||
if (v.name == name)
|
||||
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.ifDatas.empty())
|
||||
return "stray endif";
|
||||
currentFlags.ifDatas.pop_back();
|
||||
break;
|
||||
}
|
||||
|
||||
if (args[i] == "if") {
|
||||
ifBlockVariable = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ifBlockVariable.empty()) {
|
||||
if (ifBlockVariable.starts_with("!")) {
|
||||
negated = true;
|
||||
ifBlockVariable = ifBlockVariable.substr(1);
|
||||
}
|
||||
|
||||
CConfigImpl::SIfBlockData newIfData;
|
||||
|
||||
if (const auto VAR = getVariable(ifBlockVariable); VAR)
|
||||
newIfData.failed = negated ? VAR->truthy() : !VAR->truthy();
|
||||
else
|
||||
newIfData.failed = !negated;
|
||||
|
||||
currentFlags.ifDatas.emplace_back(newIfData);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::expected<float, std::string> CConfigImpl::parseExpression(const std::string& s) {
|
||||
|
|
@ -569,10 +660,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.ifDatas.empty() && impl->currentFlags.ifDatas.back().failed)
|
||||
return result;
|
||||
|
||||
size_t lastHashPos = 0;
|
||||
|
||||
while (commentPos != std::string::npos) {
|
||||
|
|
@ -642,11 +738,34 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
|
|||
anyMatch = true;
|
||||
}
|
||||
|
||||
// parse expressions $(somevar + 2)
|
||||
// parse expressions {{somevar + 2}}
|
||||
// We only support single expressions for now
|
||||
while (RHS.contains("$(")) {
|
||||
const auto BEGIN_EXPR = RHS.find("$(");
|
||||
const auto END_EXPR = RHS.find(')', BEGIN_EXPR + 2);
|
||||
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));
|
||||
|
|
@ -655,7 +774,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
|
|||
return result;
|
||||
}
|
||||
|
||||
RHS = RHS.substr(0, BEGIN_EXPR) + std::format("{}", RESULT.value()) + RHS.substr(END_EXPR + 1);
|
||||
RHS = RHS.substr(0, BEGIN_EXPR) + std::format("{}", RESULT.value()) + RHS.substr(END_EXPR + 2);
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
|
@ -672,47 +791,74 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
|
|||
if (ISVARIABLE)
|
||||
return parseVariable(LHS, RHS, dynamic);
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (auto& h : impl->handlers) {
|
||||
// we want to handle potentially nested keywords and ensure
|
||||
// we only call the handler if they are scoped correctly,
|
||||
// unless the keyword is not scoped itself
|
||||
|
||||
const bool UNSCOPED = !h.name.contains(":");
|
||||
const auto HANDLERNAME = !h.name.empty() && h.name.at(0) == ':' ? h.name.substr(1) : h.name;
|
||||
|
||||
if (!h.options.allowFlags && !UNSCOPED) {
|
||||
size_t colon = 0;
|
||||
size_t idx = 0;
|
||||
size_t depth = 0;
|
||||
|
||||
while ((colon = HANDLERNAME.find(':', idx)) != std::string::npos && impl->categories.size() > depth) {
|
||||
auto actual = HANDLERNAME.substr(idx, colon - idx);
|
||||
|
||||
if (actual != impl->categories[depth])
|
||||
break;
|
||||
|
||||
idx = colon + 1;
|
||||
++depth;
|
||||
}
|
||||
|
||||
if (depth != impl->categories.size() || HANDLERNAME.substr(idx) != LHS)
|
||||
continue;
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (UNSCOPED && HANDLERNAME != LHS && !h.options.allowFlags)
|
||||
continue;
|
||||
|
||||
if (h.options.allowFlags && (!LHS.starts_with(HANDLERNAME) || LHS.contains(':') /* avoid cases where a category is called the same as a handler */))
|
||||
continue;
|
||||
|
||||
ret = h.func(LHS.c_str(), RHS.c_str());
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found && !impl->configOptions.verifyOnly)
|
||||
ret = configSetValueSafe(LHS, RHS);
|
||||
bool found = false;
|
||||
|
||||
if (!impl->configOptions.verifyOnly) {
|
||||
auto [f, rv] = configSetValueSafe(LHS, RHS);
|
||||
found = f;
|
||||
ret = std::move(rv);
|
||||
ret.errorString = ret.errorStdString.c_str();
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
for (auto& h : impl->handlers) {
|
||||
// we want to handle potentially nested keywords and ensure
|
||||
// we only call the handler if they are scoped correctly,
|
||||
// unless the keyword is not scoped itself
|
||||
|
||||
const bool UNSCOPED = !h.name.contains(":");
|
||||
const auto HANDLERNAME = !h.name.empty() && h.name.at(0) == ':' ? h.name.substr(1) : h.name;
|
||||
|
||||
if (!h.options.allowFlags && !UNSCOPED) {
|
||||
size_t colon = 0;
|
||||
size_t idx = 0;
|
||||
size_t depth = 0;
|
||||
|
||||
while ((colon = HANDLERNAME.find(':', idx)) != std::string::npos && impl->categories.size() > depth) {
|
||||
auto actual = HANDLERNAME.substr(idx, colon - idx);
|
||||
|
||||
if (actual != impl->categories[depth])
|
||||
break;
|
||||
|
||||
idx = colon + 1;
|
||||
++depth;
|
||||
}
|
||||
|
||||
if (depth != impl->categories.size() || HANDLERNAME.substr(idx) != LHS)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (UNSCOPED && HANDLERNAME != LHS && !h.options.allowFlags)
|
||||
continue;
|
||||
|
||||
if (h.options.allowFlags && (!LHS.starts_with(HANDLERNAME) || LHS.contains(':') /* avoid cases where a category is called the same as a handler */))
|
||||
continue;
|
||||
|
||||
ret = h.func(LHS.c_str(), RHS.c_str());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret.error)
|
||||
return ret;
|
||||
|
|
@ -730,9 +876,12 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
|
|||
return result;
|
||||
}
|
||||
|
||||
impl->currentSpecialKey = "";
|
||||
impl->currentSpecialCategory = nullptr;
|
||||
impl->categories.pop_back();
|
||||
|
||||
if (impl->categories.empty()) {
|
||||
impl->currentSpecialKey = "";
|
||||
impl->currentSpecialCategory = nullptr;
|
||||
}
|
||||
} else {
|
||||
// open a category.
|
||||
if (!line.ends_with("{")) {
|
||||
|
|
@ -786,6 +935,10 @@ CParseResult CConfig::parse() {
|
|||
return fileParseResult;
|
||||
}
|
||||
|
||||
void CConfig::changeRootPath(const char* path) {
|
||||
impl->path = path;
|
||||
}
|
||||
|
||||
CParseResult CConfig::parseRawStream(const std::string& stream) {
|
||||
CParseResult result;
|
||||
|
||||
|
|
@ -889,17 +1042,22 @@ CParseResult CConfig::parseFile(const char* file) {
|
|||
}
|
||||
|
||||
CParseResult CConfig::parseDynamic(const char* line) {
|
||||
return parseLine(line, true);
|
||||
auto ret = parseLine(line, true);
|
||||
impl->currentSpecialCategory = nullptr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
CParseResult CConfig::parseDynamic(const char* command, const char* value) {
|
||||
return parseLine(std::string{command} + "=" + std::string{value}, true);
|
||||
auto ret = parseLine(std::string{command} + "=" + std::string{value}, true);
|
||||
impl->currentSpecialCategory = nullptr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CConfig::clearState() {
|
||||
impl->categories.clear();
|
||||
impl->parseError = "";
|
||||
impl->variables = impl->envVariables;
|
||||
impl->recheckEnv();
|
||||
impl->variables = impl->envVariables;
|
||||
std::erase_if(impl->specialCategories, [](const auto& e) { return !e->isStatic; });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,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
|
||||
|
|
@ -95,10 +99,18 @@ 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);
|
||||
void recheckEnv();
|
||||
|
||||
struct SIfBlockData {
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
struct {
|
||||
bool noError = false;
|
||||
bool noError = false;
|
||||
|
||||
std::vector<SIfBlockData> ifDatas;
|
||||
} currentFlags;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,26 +14,65 @@ $MY_VAR = 1337
|
|||
$MY_VAR_2 = $MY_VAR
|
||||
testVar = $MY_VAR$MY_VAR_2
|
||||
|
||||
$EXPR_VAR = $(MY_VAR + 2)
|
||||
testExpr = $(EXPR_VAR - 4)
|
||||
$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
|
||||
testEnv2 = $TEST_ENV
|
||||
|
||||
source = ./colors.conf
|
||||
|
||||
customType = abc
|
||||
|
||||
# hyprlang if !NONEXISTENT_VAR
|
||||
|
||||
# hyprlang if !NONEXISTENT_VAR_2
|
||||
|
||||
testStringColon = ee:ee:ee
|
||||
|
||||
# hyprlang endif
|
||||
|
||||
# hyprlang if NONEXISTENT_VAR
|
||||
|
||||
testStringColon = ee:ee:ee:22
|
||||
|
||||
# hyprlang endif
|
||||
|
||||
# hyprlang endif
|
||||
|
||||
# hyprlang noerror true
|
||||
|
||||
errorVariable = true
|
||||
|
||||
# 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
|
||||
|
|
@ -83,12 +122,34 @@ specialGeneric {
|
|||
|
||||
specialAnonymous {
|
||||
value = 2
|
||||
testHandlerDontOverride = true
|
||||
}
|
||||
|
||||
specialAnonymous {
|
||||
value = 3
|
||||
}
|
||||
|
||||
specialAnonymousNested {
|
||||
nested:value1 = 1
|
||||
nested:value2 = 2
|
||||
nested1:nested2:value1 = 10
|
||||
nested1:nested2:value2 = 11
|
||||
}
|
||||
|
||||
specialAnonymousNested {
|
||||
nested {
|
||||
value1 = 3
|
||||
value2 = 4
|
||||
}
|
||||
|
||||
nested1 {
|
||||
nested2 {
|
||||
value1 = 12
|
||||
value2 = 13
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flagsStuff {
|
||||
value = 2
|
||||
}
|
||||
|
|
@ -111,3 +172,14 @@ doABarrelRoll = woohoo, some, params # Funny!
|
|||
flagsabc = test
|
||||
#doSomethingFunny = 1, 2, 3, 4 # Funnier!
|
||||
#testSpaces = abc , def # many spaces, should be trimmed
|
||||
|
||||
sameKeywordSpecialCat = pablo
|
||||
|
||||
sameKeywordSpecialCat:two:hola = rose
|
||||
|
||||
sameKeywordSpecialCat {
|
||||
one {
|
||||
some_size = 44
|
||||
some_radius = 7.6
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ namespace Colors {
|
|||
}
|
||||
|
||||
// globals for testing
|
||||
bool barrelRoll = false;
|
||||
std::string flagsFound = "";
|
||||
Hyprlang::CConfig* pConfig = nullptr;
|
||||
std::string currentPath = "";
|
||||
std::string ignoreKeyword = "";
|
||||
std::string useKeyword = "";
|
||||
bool barrelRoll = false;
|
||||
std::string flagsFound = "";
|
||||
Hyprlang::CConfig* pConfig = nullptr;
|
||||
std::string currentPath = "";
|
||||
std::string ignoreKeyword = "";
|
||||
std::string useKeyword = "";
|
||||
std::string sameKeywordSpecialCat = "";
|
||||
bool testHandlerDontOverrideValue = false;
|
||||
static std::vector<std::string> categoryKeywordActualValues;
|
||||
|
||||
static Hyprlang::CParseResult handleDoABarrelRoll(const char* COMMAND, const char* VALUE) {
|
||||
|
|
@ -74,6 +76,19 @@ static Hyprlang::CParseResult handleSource(const char* COMMAND, const char* VALU
|
|||
return pConfig->parseFile(PATH.c_str());
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleSameKeywordSpecialCat(const char* COMMAND, const char* VALUE) {
|
||||
sameKeywordSpecialCat = VALUE;
|
||||
|
||||
return Hyprlang::CParseResult();
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleTestHandlerDontOverride(const char* COMMAND, const char* VALUE) {
|
||||
testHandlerDontOverrideValue = true;
|
||||
|
||||
Hyprlang::CParseResult result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleCustomValueSet(const char* VALUE, void** data) {
|
||||
if (!*data)
|
||||
*data = calloc(1, sizeof(int64_t));
|
||||
|
|
@ -99,6 +114,8 @@ int main(int argc, char** argv, char** envp) {
|
|||
if (!getenv("SHELL"))
|
||||
setenv("SHELL", "/bin/sh", true);
|
||||
|
||||
setenv("TEST_ENV", "1", true);
|
||||
|
||||
std::cout << "Starting test\n";
|
||||
|
||||
Hyprlang::CConfig config("./config/config.conf", {});
|
||||
|
|
@ -108,11 +125,20 @@ 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{.x = 69, .y = 420});
|
||||
config.addConfigValue("testString", "");
|
||||
config.addConfigValue("testStringColon", "");
|
||||
config.addConfigValue("testEnv", "");
|
||||
config.addConfigValue("testEnv2", "");
|
||||
config.addConfigValue("testVar", (Hyprlang::INT)0);
|
||||
config.addConfigValue("categoryKeyword", (Hyprlang::STRING) "");
|
||||
config.addConfigValue("testStringQuotes", "");
|
||||
|
|
@ -139,17 +165,34 @@ int main(int argc, char** argv, char** envp) {
|
|||
config.registerHandler(&handleTestUseKeyword, ":testUseKeyword", {.allowFlags = false});
|
||||
config.registerHandler(&handleNoop, "testCategory:testUseKeyword", {.allowFlags = false});
|
||||
config.registerHandler(&handleCategoryKeyword, "testCategory:categoryKeyword", {.allowFlags = false});
|
||||
config.registerHandler(&handleTestHandlerDontOverride, "testHandlerDontOverride", {.allowFlags = false});
|
||||
|
||||
config.addSpecialCategory("special", {.key = "key"});
|
||||
config.addSpecialConfigValue("special", "value", (Hyprlang::INT)0);
|
||||
|
||||
config.addSpecialCategory("specialAnonymous", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true});
|
||||
config.addSpecialConfigValue("specialAnonymous", "value", (Hyprlang::INT)0);
|
||||
config.addSpecialConfigValue("specialAnonymous", "testHandlerDontOverride", (Hyprlang::INT)0);
|
||||
|
||||
config.addSpecialCategory("specialAnonymousNested", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true});
|
||||
config.addSpecialConfigValue("specialAnonymousNested", "nested:value1", (Hyprlang::INT)0);
|
||||
config.addSpecialConfigValue("specialAnonymousNested", "nested:value2", (Hyprlang::INT)0);
|
||||
config.addSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", (Hyprlang::INT)0);
|
||||
config.addSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", (Hyprlang::INT)0);
|
||||
|
||||
config.addConfigValue("multiline", "");
|
||||
|
||||
config.registerHandler(&handleSameKeywordSpecialCat, "sameKeywordSpecialCat", {.allowFlags = false});
|
||||
config.addSpecialCategory("sameKeywordSpecialCat", {.key = nullptr, .ignoreMissing = true, .anonymousKeyBased = false});
|
||||
|
||||
config.commence();
|
||||
|
||||
config.addSpecialCategory("sameKeywordSpecialCat:one", {.key = nullptr, .ignoreMissing = true});
|
||||
config.addSpecialConfigValue("sameKeywordSpecialCat:one", "some_size", (Hyprlang::INT)10);
|
||||
config.addSpecialConfigValue("sameKeywordSpecialCat:one", "some_radius", (Hyprlang::FLOAT)0.0);
|
||||
config.addSpecialCategory("sameKeywordSpecialCat:two", {.key = nullptr, .ignoreMissing = true});
|
||||
config.addSpecialConfigValue("sameKeywordSpecialCat:two", "hola", "");
|
||||
|
||||
config.addSpecialCategory("specialGeneric:one", {.key = nullptr, .ignoreMissing = true});
|
||||
config.addSpecialConfigValue("specialGeneric:one", "value", (Hyprlang::INT)0);
|
||||
config.addSpecialCategory("specialGeneric:two", {.key = nullptr, .ignoreMissing = true});
|
||||
|
|
@ -188,6 +231,12 @@ int main(int argc, char** argv, char** envp) {
|
|||
EXPECT(ignoreKeyword, "aaa");
|
||||
EXPECT(useKeyword, "yes");
|
||||
|
||||
// test special category with same name as a keyword
|
||||
EXPECT(sameKeywordSpecialCat, std::string_view{"pablo"});
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("sameKeywordSpecialCat:one", "some_size")), (Hyprlang::INT)44);
|
||||
EXPECT(std::any_cast<float>(config.getSpecialConfigValue("sameKeywordSpecialCat:one", "some_radius")), (Hyprlang::FLOAT)7.6);
|
||||
EXPECT(std::any_cast<const char*>(config.getSpecialConfigValue("sameKeywordSpecialCat:two", "hola")), std::string_view{"rose"});
|
||||
|
||||
// Test templated wrapper
|
||||
auto T1 = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(&config, "testInt");
|
||||
auto T2 = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(&config, "testFloat");
|
||||
|
|
@ -202,6 +251,18 @@ int main(int argc, char** argv, char** envp) {
|
|||
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();
|
||||
|
|
@ -213,6 +274,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
std::cout << " → Testing handlers\n";
|
||||
EXPECT(barrelRoll, true);
|
||||
EXPECT(flagsFound, std::string{"abc"});
|
||||
EXPECT(testHandlerDontOverrideValue, false);
|
||||
|
||||
EXPECT(categoryKeywordActualValues.at(0), "we are having fun");
|
||||
EXPECT(categoryKeywordActualValues.at(1), "so much fun");
|
||||
|
|
@ -248,13 +310,18 @@ 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(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")});
|
||||
EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv2")), std::string{"1"});
|
||||
|
||||
// test special categories
|
||||
std::cout << " → Testing special categories\n";
|
||||
|
|
@ -264,6 +331,17 @@ int main(int argc, char** argv, char** envp) {
|
|||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialGeneric:two", "value")), 2);
|
||||
EXPECT(config.parseDynamic("special[b]:value = 3").error, false);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("special", "value", "b")), 3);
|
||||
EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value1 = 4").error, false);
|
||||
EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value2 = 5").error, false);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", "c")), (Hyprlang::INT)4);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", "c")), (Hyprlang::INT)5);
|
||||
EXPECT(config.parseDynamic("specialAnonymousNested[c]:nested:value2 = 6").error, false);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", "c")), (Hyprlang::INT)6);
|
||||
|
||||
EXPECT(config.parseDynamic("special[a]:value = 69").error, false);
|
||||
EXPECT(config.parseDynamic("special[b]:value = 420").error, false);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("special", "value", "a")), 69);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("special", "value", "b")), 420);
|
||||
|
||||
// test dynamic special variable
|
||||
EXPECT(config.parseDynamic("$SPECIALVAL1 = 2").error, false);
|
||||
|
|
@ -278,8 +356,22 @@ int main(int argc, char** argv, char** envp) {
|
|||
// test anonymous
|
||||
EXPECT(config.listKeysForSpecialCategory("specialAnonymous").size(), 2);
|
||||
const auto KEYS = config.listKeysForSpecialCategory("specialAnonymous");
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymous", "value", KEYS[0].c_str())), 2);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymous", "testHandlerDontOverride", KEYS[0].c_str())), 1);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymous", "value", KEYS[1].c_str())), 3);
|
||||
|
||||
// test anonymous nested
|
||||
EXPECT(config.listKeysForSpecialCategory("specialAnonymousNested").size(), 2 + /*from dynamic*/ 1);
|
||||
const auto KEYS2 = config.listKeysForSpecialCategory("specialAnonymousNested");
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", KEYS2[0].c_str())), 1);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", KEYS2[0].c_str())), 2);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value1", KEYS2[1].c_str())), 3);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested:value2", KEYS2[1].c_str())), 4);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", KEYS2[0].c_str())), 10);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", KEYS2[0].c_str())), 11);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value1", KEYS2[1].c_str())), 12);
|
||||
EXPECT(std::any_cast<int64_t>(config.getSpecialConfigValue("specialAnonymousNested", "nested1:nested2:value2", KEYS2[1].c_str())), 13);
|
||||
|
||||
// test sourcing
|
||||
std::cout << " → Testing sourcing\n";
|
||||
EXPECT(std::any_cast<int64_t>(config.getConfigValue("myColors:pink")), (Hyprlang::INT)0xFFc800c8);
|
||||
|
|
@ -293,6 +385,12 @@ int main(int argc, char** argv, char** envp) {
|
|||
// test multiline config
|
||||
EXPECT(std::any_cast<const char*>(config.getConfigValue("multiline")), std::string{"very long command"});
|
||||
|
||||
// test dynamic env
|
||||
setenv("TEST_ENV", "2", true);
|
||||
config.parse();
|
||||
std::cout << " → Testing dynamic env variables\n";
|
||||
EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv2")), std::string{"2"});
|
||||
|
||||
std::cout << " → Testing error.conf\n";
|
||||
Hyprlang::CConfig errorConfig("./config/error.conf", {.verifyOnly = true, .throwAllErrors = true});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue