config: try variables before handlers if possible

This commit is contained in:
Vaxry 2025-11-11 21:36:52 +00:00
parent 4dafa28d4f
commit 995db114b8
Signed by: vaxry
GPG key ID: 665806380871D640
4 changed files with 82 additions and 62 deletions

View file

@ -442,17 +442,17 @@ namespace Hyprlang {
}
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

@ -279,7 +279,8 @@ 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;
@ -358,7 +359,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
if (VALUEIT != sc->values.end())
found = true;
else if (sc->descriptor->dontErrorOnMissing)
return result; // will return a success, cuz we want to ignore missing
return {true, result}; // will return a success, cuz we want to ignore missing
break;
}
@ -407,7 +408,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
} 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;
return {true, result};
}
impl->currentSpecialKey = value;
}
@ -418,7 +419,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
if (!found) {
result.setError(std::format("config option <{}> does not exist.", valueName));
return result;
return {false, result};
}
}
@ -428,7 +429,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());
@ -440,7 +441,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;
}
@ -458,7 +459,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;
}
@ -473,19 +474,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) {
@ -791,45 +792,50 @@ 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);
}
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;

View file

@ -121,6 +121,7 @@ specialGeneric {
specialAnonymous {
value = 2
testHandlerDontOverride = true
}
specialAnonymous {

View file

@ -23,12 +23,13 @@ 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 = "";
bool testHandlerDontOverrideValue = false;
static std::vector<std::string> categoryKeywordActualValues;
static Hyprlang::CParseResult handleDoABarrelRoll(const char* COMMAND, const char* VALUE) {
@ -74,6 +75,14 @@ static Hyprlang::CParseResult handleSource(const char* COMMAND, const char* VALU
return pConfig->parseFile(PATH.c_str());
}
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));
@ -147,12 +156,14 @@ 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);
@ -239,6 +250,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");
@ -315,6 +327,7 @@ int main(int argc, char** argv, char** envp) {
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