diff --git a/src/config.cpp b/src/config.cpp index fdb71db..8d400a9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -695,22 +695,52 @@ CParseResult CConfig::parse() { CParseResult CConfig::parseRawStream(const std::string& stream) { CParseResult result; - std::string line = ""; - int linenum = 1; + std::string rawLine = ""; + int rawLineNum = 0; + std::string line = ""; + int lineNum = 0; std::stringstream str(stream); - while (std::getline(str, line)) { + while (std::getline(str, rawLine)) { + line = rawLine; + lineNum = ++rawLineNum; + + bool mergeWithNextLine = rawLine.length() > 0 && rawLine.at(rawLine.length() - 1) == '\\'; + bool gotWholeLine = true; + + while (mergeWithNextLine) { + const auto lastNonSpace = line.length() < 2 ? -1 : line.find_last_not_of(MULTILINE_SPACE_CHARSET, line.length() - 2); + line = line.substr(0, lastNonSpace + 1); + + if (!std::getline(str, rawLine)) { + if (!impl->parseError.empty()) + impl->parseError += "\n"; + impl->parseError += "Config error: Last line ends with backslash"; + result.setError(impl->parseError); + gotWholeLine = false; + break; + } + + ++rawLineNum; + line += rawLine; + mergeWithNextLine = line.length() > 0 && line.at(line.length() - 1) == '\\'; + } + + if (!gotWholeLine) + break; + const auto RET = parseLine(line); if (RET.error && (impl->parseError.empty() || impl->configOptions.throwAllErrors)) { if (!impl->parseError.empty()) impl->parseError += "\n"; - impl->parseError += std::format("Config error at line {}: {}", linenum, RET.errorStdString); + impl->parseError += std::format("Config error at line {}: {}", lineNum, RET.errorStdString); result.setError(impl->parseError); } - ++linenum; + lineNum = rawLineNum + 1; + line = ""; } if (!impl->categories.empty()) { @@ -736,21 +766,50 @@ CParseResult CConfig::parseFile(const char* file) { return result; } - std::string line = ""; - int linenum = 1; + std::string rawLine = ""; + int rawLineNum = 0; + std::string line = ""; + int lineNum = 0; - while (std::getline(iffile, line)) { + while (std::getline(iffile, rawLine)) { + line = rawLine; + lineNum = ++rawLineNum; + + bool mergeWithNextLine = rawLine.length() > 0 && rawLine.at(rawLine.length() - 1) == '\\'; + bool gotWholeLine = true; + + while (mergeWithNextLine) { + const auto lastNonSpace = line.length() < 2 ? -1 : line.find_last_not_of(MULTILINE_SPACE_CHARSET, line.length() - 2); + line = line.substr(0, lastNonSpace + 1); + + if (!std::getline(iffile, rawLine)) { + if (!impl->parseError.empty()) + impl->parseError += "\n"; + impl->parseError += std::format("Config error in file {}: Last line ends with backslash", file); + result.setError(impl->parseError); + gotWholeLine = false; + break; + } + + ++rawLineNum; + line += rawLine; + mergeWithNextLine = line.length() > 0 && line.at(line.length() - 1) == '\\'; + } + + if (!gotWholeLine) + break; const auto RET = parseLine(line); if (!impl->currentFlags.noError && 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); + impl->parseError += std::format("Config error in file {} at line {}: {}", file, lineNum, RET.errorStdString); result.setError(impl->parseError); } - ++linenum; + lineNum = rawLineNum + 1; + line = ""; } iffile.close(); diff --git a/src/config.hpp b/src/config.hpp index f1011da..f81bc3e 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -4,6 +4,8 @@ #include #include +static const char* MULTILINE_SPACE_CHARSET = " \t"; + struct SHandler { std::string name = ""; Hyprlang::SHandlerOptions options; @@ -93,4 +95,4 @@ class CConfigImpl { struct { bool noError = false; } currentFlags; -}; \ No newline at end of file +}; diff --git a/tests/config/config.conf b/tests/config/config.conf index c8ea739..b7f7b6e 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -90,6 +90,11 @@ flagsStuff { value = 2 } +multiline = \ + very \ + long \ + command + testCategory:testValueHex = 0xFFfFaAbB $RECURSIVE1 = a @@ -103,4 +108,3 @@ doABarrelRoll = woohoo, some, params # Funny! flagsabc = test #doSomethingFunny = 1, 2, 3, 4 # Funnier! #testSpaces = abc , def # many spaces, should be trimmed - diff --git a/tests/parse/main.cpp b/tests/parse/main.cpp index a20d558..0fc05c9 100755 --- a/tests/parse/main.cpp +++ b/tests/parse/main.cpp @@ -145,6 +145,8 @@ int main(int argc, char** argv, char** envp) { config.addSpecialCategory("specialAnonymous", {nullptr, false, true}); config.addSpecialConfigValue("specialAnonymous", "value", (Hyprlang::INT)0); + config.addConfigValue("multiline", ""); + config.commence(); config.addSpecialCategory("specialGeneric:one", {nullptr, true}); @@ -269,6 +271,9 @@ int main(int argc, char** argv, char** envp) { std::cout << " → Testing custom types\n"; EXPECT(*reinterpret_cast(std::any_cast(config.getConfigValue("customType"))), (Hyprlang::INT)1); + // test multiline config + EXPECT(std::any_cast(config.getConfigValue("multiline")), std::string{"very long command"}); + std::cout << " → Testing error.conf\n"; Hyprlang::CConfig errorConfig("./config/error.conf", {.verifyOnly = true, .throwAllErrors = true});