diff --git a/include/hyprlang.hpp b/include/hyprlang.hpp index beef2e4..e76fe78 100644 --- a/include/hyprlang.hpp +++ b/include/hyprlang.hpp @@ -50,6 +50,15 @@ namespace Hyprlang { friend class CConfig; }; + /* Generic struct for options for the config parser */ + struct SConfigOptions { + /* Don't throw errors on missing values. */ + bool verifyOnly = false; + + /* Return all errors instead of the first */ + bool throwAllErrors = false; + }; + /* Generic struct for options for handlers */ struct SHandlerOptions { bool allowFlags = false; @@ -142,7 +151,7 @@ namespace Hyprlang { /* Base class for a config file */ class CConfig { public: - CConfig(const char* configPath); + CConfig(const char* configPath, const SConfigOptions& options); ~CConfig(); /* Add a config value, for example myCategory:myValue. diff --git a/src/config.cpp b/src/config.cpp index d0ec8a2..4665ac2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -28,7 +28,7 @@ static std::string removeBeginEndSpacesTabs(std::string str) { return str; } -CConfig::CConfig(const char* path) { +CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options) { impl = new CConfigImpl; try { impl->path = std::filesystem::canonical(path); @@ -46,6 +46,8 @@ CConfig::CConfig(const char* path) { } std::sort(impl->envVariables.begin(), impl->envVariables.end(), [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); }); + + impl->configOptions = options; } CConfig::~CConfig() { @@ -444,7 +446,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { found = true; } - if (!found) + if (!found && !impl->configOptions.verifyOnly) ret = configSetValueSafe(LHS, RHS); if (ret.error) @@ -515,9 +517,11 @@ CParseResult CConfig::parseFile(std::string file) { const auto RET = parseLine(line); - if (RET.error && impl->parseError.empty()) { - impl->parseError = RET.getError(); - result.setError(std::format("Config error in file {} at line {}: {}", file, linenum, RET.errorStdString)); + if (RET.error && (impl->parseError.empty() || impl->configOptions.throwAllErrors)) { + if (!impl->parseError.empty()) + impl->parseError += "\n"; + impl->parseError += std::format("Config error in file {} at line {}: {}", file, linenum, RET.errorStdString); + result.setError(impl->parseError); } ++linenum; @@ -526,7 +530,13 @@ CParseResult CConfig::parseFile(std::string file) { iffile.close(); if (!impl->categories.empty()) { - result.setError("Unclosed category at EOF"); + if (impl->parseError.empty() || impl->configOptions.throwAllErrors) { + if (!impl->parseError.empty()) + impl->parseError += "\n"; + impl->parseError += std::format("Config error in file {}: Unclosed category at EOF", file); + result.setError(impl->parseError); + } + impl->categories.clear(); } diff --git a/src/config.hpp b/src/config.hpp index 2633cec..d5feaac 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -69,4 +69,6 @@ class CConfigImpl { std::string currentSpecialKey = ""; std::string parseError = ""; + + Hyprlang::SConfigOptions configOptions; }; \ No newline at end of file diff --git a/tests/config/error.conf b/tests/config/error.conf new file mode 100644 index 0000000..19fc28c --- /dev/null +++ b/tests/config/error.conf @@ -0,0 +1,6 @@ +# This config houses two errors + +someValue = 123 + +category { + invalid_line \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp index c790441..18048dd 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -75,7 +76,7 @@ int main(int argc, char** argv, char** envp) { std::cout << "Starting test\n"; - Hyprlang::CConfig config("./config/config.conf"); + Hyprlang::CConfig config("./config/config.conf", {}); pConfig = &config; currentPath = std::filesystem::canonical("./config/"); @@ -182,6 +183,16 @@ int main(int argc, char** argv, char** envp) { std::cout << " → Testing custom types\n"; EXPECT(*reinterpret_cast(std::any_cast(config.getConfigValue("customType"))), 1L); + std::cout << " → Testing error.conf\n"; + Hyprlang::CConfig errorConfig("./config/error.conf", {.verifyOnly = true, .throwAllErrors = true}); + + errorConfig.commence(); + const auto ERRORS = errorConfig.parse(); + + EXPECT(ERRORS.error, true); + const auto ERRORSTR = std::string{ERRORS.getError()}; + EXPECT(std::count(ERRORSTR.begin(), ERRORSTR.end(), '\n'), 1); + } catch (const char* e) { std::cout << Colors::RED << "Error: " << Colors::RESET << e << "\n"; return 1;