Compare commits

...

14 commits
v0.6.4 ... main

8 changed files with 343 additions and 161 deletions

View file

@ -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: |

View file

@ -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

View file

@ -1 +1 @@
0.6.3
0.6.7

View file

@ -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);
};
/*!

View file

@ -78,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(); });
@ -279,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(']');
@ -304,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;
@ -319,6 +328,8 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
applyDefaultsToCat(*PCAT);
PCAT->values[sc->key].setFrom(CATKEY);
overrideSpecialCat = PCAT;
break;
}
}
}
@ -328,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};
}
}
@ -416,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());
@ -428,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;
}
@ -446,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;
}
@ -461,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) {
@ -501,6 +526,16 @@ CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& r
return result;
}
void CConfigImpl::recheckEnv() {
envVariables.clear();
for (char** env = environ; *env; ++env) {
const std::string ENVVAR = *env ? *env : "";
const auto VARIABLE = ENVVAR.substr(0, ENVVAR.find_first_of('='));
const auto VALUE = ENVVAR.substr(ENVVAR.find_first_of('=') + 1);
envVariables.push_back({VARIABLE, VALUE});
}
}
SVariable* CConfigImpl::getVariable(const std::string& name) {
for (auto& v : envVariables) {
if (v.name == name)
@ -536,9 +571,9 @@ std::optional<std::string> CConfigImpl::parseComment(const std::string& comment)
}
if (args[i] == "endif") {
if (!currentFlags.inAnIfBlock)
if (currentFlags.ifDatas.empty())
return "stray endif";
currentFlags.inAnIfBlock = false;
currentFlags.ifDatas.pop_back();
break;
}
@ -549,20 +584,19 @@ std::optional<std::string> CConfigImpl::parseComment(const std::string& comment)
}
if (!ifBlockVariable.empty()) {
if (currentFlags.inAnIfBlock)
return "nested if statements are not allowed";
if (ifBlockVariable.starts_with("!")) {
negated = true;
negated = true;
ifBlockVariable = ifBlockVariable.substr(1);
}
currentFlags.inAnIfBlock = true;
CConfigImpl::SIfBlockData newIfData;
if (const auto VAR = getVariable(ifBlockVariable); VAR)
currentFlags.ifBlockFailed = negated ? VAR->truthy() : !VAR->truthy();
newIfData.failed = negated ? VAR->truthy() : !VAR->truthy();
else
currentFlags.ifBlockFailed = !negated;
newIfData.failed = !negated;
currentFlags.ifDatas.emplace_back(newIfData);
}
return std::nullopt;
@ -632,7 +666,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
return result;
}
if (impl->currentFlags.inAnIfBlock && impl->currentFlags.ifBlockFailed)
if (!impl->currentFlags.ifDatas.empty() && impl->currentFlags.ifDatas.back().failed)
return result;
size_t lastHashPos = 0;
@ -780,45 +814,51 @@ CParseResult CConfig::parseLine(std::string line, bool 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;
}
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 (!impl->configOptions.verifyOnly) {
auto [f, rv] = configSetValueSafe(LHS, RHS);
found = f;
ret = std::move(rv);
ret.errorString = ret.errorStdString.c_str();
}
if (!found && !impl->configOptions.verifyOnly)
ret = configSetValueSafe(LHS, RHS);
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;
@ -836,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("{")) {
@ -892,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;
@ -995,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; });
}

View file

@ -102,10 +102,15 @@ class CConfigImpl {
std::optional<std::string> parseComment(const std::string& comment);
std::expected<float, std::string> parseExpression(const std::string& s);
SVariable* getVariable(const std::string& name);
void recheckEnv();
struct SIfBlockData {
bool failed = false;
};
struct {
bool noError = false;
bool inAnIfBlock = false;
bool ifBlockFailed = false;
bool noError = false;
std::vector<SIfBlockData> ifDatas;
} currentFlags;
};

View file

@ -32,6 +32,7 @@ $DYNAMIC_EXPRESSION = moved: {{$MOVING_VAR / 2}} expr: \{{$MOVING_VAR / 2}}
testDynamicEscapedExpression = \{{ $DYNAMIC_EXPRESSION }}
testEnv = $SHELL
testEnv2 = $TEST_ENV
source = ./colors.conf
@ -39,10 +40,20 @@ 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
@ -111,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
}
@ -139,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
}
}

View file

@ -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", {});
@ -121,6 +138,7 @@ int main(int argc, char** argv, char** envp) {
config.addConfigValue("testString", "");
config.addConfigValue("testStringColon", "");
config.addConfigValue("testEnv", "");
config.addConfigValue("testEnv2", "");
config.addConfigValue("testVar", (Hyprlang::INT)0);
config.addConfigValue("categoryKeyword", (Hyprlang::STRING) "");
config.addConfigValue("testStringQuotes", "");
@ -147,17 +165,34 @@ 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});
@ -196,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");
@ -233,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");
@ -279,6 +321,7 @@ int main(int argc, char** argv, char** envp) {
// test env variables
std::cout << " → Testing env variables\n";
EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv")), std::string{getenv("SHELL")});
EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv2")), std::string{"1"});
// test special categories
std::cout << " → Testing special categories\n";
@ -288,6 +331,17 @@ 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);
@ -302,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);
@ -317,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});