From e15cfdcd97ee18613de32e1c3a41ca9b171d5d14 Mon Sep 17 00:00:00 2001 From: Aurelien Brabant Date: Sat, 6 Jul 2024 16:37:33 +0200 Subject: [PATCH 1/3] feat: implement multiline support --- src/config.cpp | 50 +++++++++++++++++++++++++++++++++------- src/config.hpp | 13 ++++++++++- tests/config/config.conf | 45 ++++++++++++++++++++++++++++++++++++ tests/parse/main.cpp | 17 ++++++++++++++ 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index a295650..eccfa7e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -473,8 +474,9 @@ void CConfigImpl::parseComment(const std::string& comment) { CParseResult CConfig::parseLine(std::string line, bool dynamic) { CParseResult result; + bool shouldPreverseLeadingWhitespace = impl->multiline.delimiter == '\\'; - line = trim(line); + line = shouldPreverseLeadingWhitespace ? line.substr(0, line.find_last_not_of(MULTILINE_SPACE_CHARSET) + 1) : trim(line); auto commentPos = line.find('#'); @@ -496,6 +498,8 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { if (!escaped) { line = line.substr(0, commentPos); + // there might be trailing whitespaces after the comment that weren't previous trimmed + line = line.substr(0, line.find_last_not_of(MULTILINE_SPACE_CHARSET) + 1); break; } else { line = line.substr(0, commentPos + 1) + line.substr(commentPos + 2); @@ -503,27 +507,29 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { } } - line = trim(line); + if (line.empty()) { + if (impl->multiline.active) + result.setError("Found empty line while parsing multiline value"); - if (line.empty()) return result; + } auto equalsPos = line.find('='); - if (equalsPos == std::string::npos && !line.ends_with("{") && line != "}") { + if (equalsPos == std::string::npos && !line.ends_with("{") && line != "}" && !impl->multiline.active) { // invalid line result.setError("Invalid config line"); return result; } - if (equalsPos != std::string::npos) { + if (equalsPos != std::string::npos || impl->multiline.active) { // set value or call handler CParseResult ret; - auto LHS = trim(line.substr(0, equalsPos)); - auto RHS = trim(line.substr(equalsPos + 1)); + auto LHS = impl->multiline.active ? impl->multiline.lhs : trim(line.substr(0, equalsPos)); + auto RHS = impl->multiline.active ? line : trim(line.substr(equalsPos + 1)); if (LHS.empty()) { - result.setError("Empty lhs."); + result.setError("Empty lhs"); return result; } @@ -562,6 +568,34 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { if (ISVARIABLE) return parseVariable(LHS, RHS, dynamic); + auto lastChar = RHS[RHS.size() - 1]; + bool isMultilineContinuation = lastChar == '\\' || lastChar == '>'; + + if (isMultilineContinuation && impl->multiline.active && impl->multiline.delimiter != lastChar) { + result.setError("Multiline continuation character mismatch. Make sure you are not mixing \\ and >"); + + return result; + } + + if (impl->multiline.buffer.size() > 0 && impl->multiline.delimiter == '>') + impl->multiline.buffer += " "; + + impl->multiline.active = isMultilineContinuation; + + if (isMultilineContinuation) { + impl->multiline.lhs = LHS; + impl->multiline.delimiter = lastChar; + RHS.erase(RHS.size() - 1); + impl->multiline.buffer += RHS.substr(0, RHS.find_last_not_of(MULTILINE_SPACE_CHARSET) + 1); + + return CParseResult{}; + } + + if (!impl->multiline.buffer.empty()) { + RHS = impl->multiline.buffer + RHS; + impl->multiline.buffer.clear(); + } + bool found = false; for (auto& h : impl->handlers) { if (!h.options.allowFlags && h.name != LHS) diff --git a/src/config.hpp b/src/config.hpp index f1011da..ca9fa9b 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; @@ -33,6 +35,13 @@ enum eDataType { CONFIGDATATYPE_CUSTOM, }; +struct SMultiLine { + std::string buffer; + char delimiter = 0; + bool active = false; + std::string lhs; +}; + // CUSTOM is stored as STR!! struct SConfigDefaultValue { std::any data; @@ -83,6 +92,8 @@ class CConfigImpl { std::vector categories; std::string currentSpecialKey = ""; SSpecialCategory* currentSpecialCategory = nullptr; // if applicable + bool isSpecialCategory = false; + SMultiLine multiline; std::string parseError = ""; @@ -93,4 +104,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 45a7b8d..e379a4c 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -79,6 +79,51 @@ flagsStuff { value = 2 } + +# '\' POSIX shell-like multiline syntax +# Leading spaces are preserved while trailing spaces and linebreaks are discarded +multilineSimple = I use C++ because \ + I hate Java + +multilineTrim = I use Javascript because \ # Spaces should be trimmed before and after the delimiter + I hate Python # here also. + +multilineVar = $SPECIALVAL1 \ + $SPECIALVAL1 \ + $SPECIALVAL1 \ + $SPECIALVAL1 + +$NAME = multiline + +multilineBreakWord = Hello $NAME, how are you to\ +day? + +multilineMultiBreakWord = oui \ + oui \ + b \ +a \ +g \ +u \ +e \ +t \ +t \ +e + +# Another syntax for multiline. +# Ignores leading and trailing whitespaces. Linebreaks are turned into spaces. + +multilineCategory { + indentedMultiline = Hello > + world > + this is another syntax for > + multiline that trims all spaces + + multilineUneven = Hello > + world > + this is another syntax for > + multiline that trims all spaces +} + testCategory:testValueHex = 0xFFfFaAbB $RECURSIVE1 = a diff --git a/tests/parse/main.cpp b/tests/parse/main.cpp index 6970386..94e0044 100644 --- a/tests/parse/main.cpp +++ b/tests/parse/main.cpp @@ -103,6 +103,14 @@ int main(int argc, char** argv, char** envp) { config.addConfigValue("myColors:green", (Hyprlang::INT)0); config.addConfigValue("myColors:random", (Hyprlang::INT)0); config.addConfigValue("customType", {Hyprlang::CConfigCustomValueType{&handleCustomValueSet, &handleCustomValueDestroy, "def"}}); + config.addConfigValue("multilineSimple", (Hyprlang::STRING) ""); + config.addConfigValue("multilineTrim", (Hyprlang::STRING) ""); + config.addConfigValue("multilineVar", (Hyprlang::STRING) ""); + config.addConfigValue("multilineTrim", (Hyprlang::STRING) ""); + config.addConfigValue("multilineBreakWord", (Hyprlang::STRING) ""); + config.addConfigValue("multilineMultiBreakWord", (Hyprlang::STRING) ""); + config.addConfigValue("multilineCategory:indentedMultiline", (Hyprlang::STRING) ""); + config.addConfigValue("multilineCategory:multilineUneven", (Hyprlang::STRING) ""); config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {false}); config.registerHandler(&handleFlagsTest, "flags", {true}); @@ -149,6 +157,15 @@ int main(int argc, char** argv, char** envp) { EXPECT(std::any_cast(config.getConfigValue("testCategory:testColor2")), (Hyprlang::INT)0xFF000000); EXPECT(std::any_cast(config.getConfigValue("testCategory:testColor3")), (Hyprlang::INT)0x22ffeeff); EXPECT(std::any_cast(config.getConfigValue("testStringColon")), std::string{"ee:ee:ee"}); + EXPECT(std::any_cast(config.getConfigValue("multilineSimple")), std::string{"I use C++ because I hate Java"}); + EXPECT(std::any_cast(config.getConfigValue("multilineTrim")), std::string{"I use Javascript because I hate Python"}); + EXPECT(std::any_cast(config.getConfigValue("multilineVar")), std::string{"1 1 1 1"}); + EXPECT(std::any_cast(config.getConfigValue("multilineBreakWord")), std::string{"Hello multiline, how are you today?"}); + EXPECT(std::any_cast(config.getConfigValue("multilineMultiBreakWord")), std::string{"oui oui baguette"}); + EXPECT(std::any_cast(config.getConfigValue("multilineCategory:indentedMultiline")), + std::string{"Hello world this is another syntax for multiline that trims all spaces"}); + EXPECT(std::any_cast(config.getConfigValue("multilineCategory:multilineUneven")), + std::string{"Hello world this is another syntax for multiline that trims all spaces"}); // test static values std::cout << " → Testing static values\n"; From b386643974f8ebd42bc9fb01c82178150b7c6e9a Mon Sep 17 00:00:00 2001 From: Aurelien Brabant Date: Sat, 6 Jul 2024 16:53:31 +0200 Subject: [PATCH 2/3] fix: deactivate multiline if an error occurs --- src/config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.cpp b/src/config.cpp index eccfa7e..f949f30 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -572,6 +572,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) { bool isMultilineContinuation = lastChar == '\\' || lastChar == '>'; if (isMultilineContinuation && impl->multiline.active && impl->multiline.delimiter != lastChar) { + impl->multiline.active = false; result.setError("Multiline continuation character mismatch. Make sure you are not mixing \\ and >"); return result; From cf68b93791e57745568403b8bec237aa3e7babed Mon Sep 17 00:00:00 2001 From: Aurelien Brabant Date: Sat, 6 Jul 2024 16:55:56 +0200 Subject: [PATCH 3/3] chore: SMultiLine -> SMultiline --- src/config.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.hpp b/src/config.hpp index ca9fa9b..724a5ee 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -35,7 +35,7 @@ enum eDataType { CONFIGDATATYPE_CUSTOM, }; -struct SMultiLine { +struct SMultiline { std::string buffer; char delimiter = 0; bool active = false; @@ -93,7 +93,7 @@ class CConfigImpl { std::string currentSpecialKey = ""; SSpecialCategory* currentSpecialCategory = nullptr; // if applicable bool isSpecialCategory = false; - SMultiLine multiline; + SMultiline multiline; std::string parseError = "";