diff --git a/src/config.cpp b/src/config.cpp index 216f924..2e5dfeb 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -59,6 +59,38 @@ void CConfig::addConfigValue(const char* name, const CConfigValue value) { impl->defaultValues[std::string{name}] = value; } +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; }); + + if (IT == impl->specialCategoryDescriptors.end()) + throw "No such category"; + + IT->get()->defaultValues[std::string{name}] = value; +} + +void CConfig::addSpecialCategory(const char* name, SSpecialCategoryOptions options) { + const auto PDESC = impl->specialCategoryDescriptors.emplace_back(std::make_unique()).get(); + PDESC->name = name; + PDESC->key = options.key ? options.key : ""; + PDESC->dontErrorOnMissing = options.ignoreMissing; + + if (!options.key) { + const auto PCAT = impl->specialCategories.emplace_back(std::make_unique()).get(); + PCAT->descriptor = PDESC; + PCAT->name = name; + PCAT->key = options.key ? options.key : ""; + PCAT->isStatic = true; + if (!PCAT->key.empty()) + addSpecialConfigValue(name, options.key, CConfigValue("0")); + } +} + +void SSpecialCategory::applyDefaults() { + for (auto& [k, v] : descriptor->defaultValues) { + values[k] = v; + } +} + void CConfig::commence() { m_bCommenced = true; for (auto& [k, v] : impl->defaultValues) { @@ -174,10 +206,62 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std:: valueName += command; - const auto VALUEIT = impl->values.find(valueName); + auto VALUEIT = impl->values.find(valueName); if (VALUEIT == impl->values.end()) { - result.setError(std::format("config option <{}> does not exist.", valueName)); - return result; + // it might be in a special category + bool found = false; + for (auto& sc : impl->specialCategories) { + if (!valueName.starts_with(sc->name) || valueName.substr(sc->name.length() + 1).contains(":")) + continue; + + if (!sc->isStatic && std::string{std::any_cast(sc->values[sc->key].getValue())} != impl->currentSpecialKey) + continue; + + VALUEIT = sc->values.find(valueName.substr(sc->name.length() + 1)); + + if (VALUEIT != sc->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) || valueName.substr(sc->name.length() + 1).contains(":")) + continue; + + // bingo + const auto PCAT = impl->specialCategories.emplace_back(std::make_unique()).get(); + PCAT->descriptor = sc.get(); + PCAT->name = sc->name; + PCAT->key = sc->key; + addSpecialConfigValue(sc->name.c_str(), sc->key.c_str(), CConfigValue("0")); + + PCAT->applyDefaults(); + + VALUEIT = PCAT->values.find(valueName.substr(sc->name.length() + 1)); + + if (VALUEIT != PCAT->values.end()) + found = true; + + 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) { + result.setError(std::format("config option <{}> does not exist.", valueName)); + return result; + } } switch (VALUEIT->second.m_eType) { @@ -359,6 +443,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { return result; } + impl->currentSpecialKey = ""; impl->categories.pop_back(); } else { // open a category. @@ -385,6 +470,9 @@ CParseResult CConfig::parse() { for (auto& [k, v] : impl->defaultValues) { impl->values.at(k) = v; } + for (auto& sc : impl->specialCategories) { + sc->applyDefaults(); + } std::ifstream iffile(impl->path); if (!iffile.good()) @@ -430,6 +518,7 @@ void CConfig::clearState() { impl->categories.clear(); impl->parseError = ""; impl->variables = impl->envVariables; + std::erase_if(impl->specialCategories, [](const auto& e) { return !e->isStatic; }); } CConfigValue* CConfig::getConfigValuePtr(const char* name) { @@ -437,6 +526,25 @@ CConfigValue* CConfig::getConfigValuePtr(const char* name) { return IT == impl->values.end() ? nullptr : &IT->second; } +CConfigValue* CConfig::getSpecialConfigValuePtr(const char* category, const char* name, const char* key) { + const std::string CAT = category; + const std::string NAME = name; + const std::string KEY = key ? key : ""; + + for (auto& sc : impl->specialCategories) { + if (sc->name != CAT || (!sc->isStatic && std::string{std::any_cast(sc->values[sc->key].getValue())} != KEY)) + continue; + + const auto IT = sc->values.find(NAME); + if (IT == sc->values.end()) + return nullptr; + + return &IT->second; + } + + return nullptr; +} + void CConfig::registerHandler(PCONFIGHANDLERFUNC func, const char* name, SHandlerOptions options) { impl->handlers.push_back(SHandler{name, options, func}); } \ No newline at end of file diff --git a/src/config.hpp b/src/config.hpp index 4026dc5..40c6f66 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -16,17 +16,37 @@ struct SVariable { std::vector linesContainingVar; // for dynamic updates }; +struct SSpecialCategoryDescriptor { + std::string name = ""; + std::string key = ""; + std::unordered_map defaultValues; + bool dontErrorOnMissing = false; +}; + +struct SSpecialCategory { + SSpecialCategoryDescriptor* descriptor = nullptr; + std::string name = ""; + std::string key = ""; // empty means no key + std::unordered_map values; + bool isStatic = false; + + void applyDefaults(); +}; + class CConfigImpl { public: - std::string path = ""; + std::string path = ""; - std::unordered_map values; - std::unordered_map defaultValues; - std::vector handlers; - std::vector variables; - std::vector envVariables; + std::unordered_map values; + std::unordered_map defaultValues; + std::vector handlers; + std::vector variables; + std::vector envVariables; + std::vector> specialCategories; + std::vector> specialCategoryDescriptors; - std::vector categories; + std::vector categories; + std::string currentSpecialKey = ""; - std::string parseError = ""; + std::string parseError = ""; }; \ No newline at end of file diff --git a/src/public.hpp b/src/public.hpp index eab2ec1..1620784 100644 --- a/src/public.hpp +++ b/src/public.hpp @@ -44,10 +44,22 @@ namespace Hyprlang { friend class CConfig; }; + /* Generic struct for options for handlers */ struct SHandlerOptions { bool allowFlags = false; }; + /* Generic struct for options for special categories */ + struct SSpecialCategoryOptions { + /* a key is the name of a value that will be the identifier of a special category + can be left null for no key, aka a generic one + keys are always strings. Default key value is "0" */ + const char* key = nullptr; + + /* don't pop up an error if the config value is missing */ + bool ignoreMissing = false; + }; + /* typedefs */ typedef CParseResult (*PCONFIGHANDLERFUNC)(const char* COMMAND, const char* VALUE); @@ -111,6 +123,12 @@ namespace Hyprlang { no new values may be added or removed. Required for parsing. */ void commence(); + /* Add a special category. Can be done dynamically. */ + void addSpecialCategory(const char* name, SSpecialCategoryOptions options); + + /* Add a config value to a special category */ + void addSpecialConfigValue(const char* cat, const char* name, const CConfigValue value); + /* Parse the config. Refresh the values. */ CParseResult parse(); @@ -124,6 +142,12 @@ namespace Hyprlang { 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, unless a new variable is added via addSpecialConfigValue. + 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); + /* Get a config value's stored value. Empty on fail*/ std::any getConfigValue(const char* name) { CConfigValue* val = getConfigValuePtr(name); @@ -132,6 +156,14 @@ namespace Hyprlang { return val->getValue(); } + /* Get a special config value's stored value. Empty on fail. */ + std::any getSpecialConfigValue(const char* category, const char* name, const char* key = nullptr) { + CConfigValue* val = getSpecialConfigValuePtr(category, name, key); + if (!val) + return {}; + return val->getValue(); + } + private: bool m_bCommenced = false; diff --git a/tests/config/config.conf b/tests/config/config.conf index f519f27..77f1316 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -27,6 +27,27 @@ testCategory { } } +special { + key = a + value = 1 +} + +special { + key = b + value = 2 +} + +specialGeneric { + one { + value = 1 + } + + two { + value = 2 + nonexistent = abc + } +} + testCategory:testValueHex = 0xFFfFaAbB testStringQuotes = "Hello World!" diff --git a/tests/main.cpp b/tests/main.cpp index 4fcafe5..018230f 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -68,8 +68,16 @@ int main(int argc, char** argv, char** envp) { config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {false}); config.registerHandler(&handleFlagsTest, "flags", {true}); + config.addSpecialCategory("special", {"key"}); + config.addSpecialConfigValue("special", "value", 0L); + config.commence(); + config.addSpecialCategory("specialGeneric:one", {nullptr, true}); + config.addSpecialConfigValue("specialGeneric:one", "value", 0L); + config.addSpecialCategory("specialGeneric:two", {nullptr, true}); + config.addSpecialConfigValue("specialGeneric:two", "value", 0L); + const auto PARSERESULT = config.parse(); if (PARSERESULT.error) { std::cout << "Parse error: " << PARSERESULT.getError() << "\n"; @@ -118,8 +126,16 @@ int main(int argc, char** argv, char** envp) { EXPECT(std::any_cast(config.getConfigValue("testVar")), 1337420); // test env variables + std::cout << " → Testing env variables\n"; EXPECT(std::any_cast(config.getConfigValue("testEnv")), std::string{getenv("SHELL")}); + // test special categories + std::cout << " → Testing special categories\n"; + EXPECT(std::any_cast(config.getSpecialConfigValue("special", "value", "a")), 1); + EXPECT(std::any_cast(config.getSpecialConfigValue("special", "value", "b")), 2); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialGeneric:one", "value")), 1); + EXPECT(std::any_cast(config.getSpecialConfigValue("specialGeneric:two", "value")), 2); + } catch (const char* e) { std::cout << Colors::RED << "Error: " << Colors::RESET << e << "\n"; return 1;