feat: implement multiline support

This commit is contained in:
Aurelien Brabant 2024-07-06 16:37:33 +02:00
parent ec6938c662
commit e15cfdcd97
No known key found for this signature in database
GPG key ID: 5F90C2579402FCFA
4 changed files with 116 additions and 9 deletions

View file

@ -3,6 +3,7 @@
#include <fstream>
#include <string>
#include <format>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <expected>
@ -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)

View file

@ -4,6 +4,8 @@
#include <string>
#include <vector>
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<std::string> 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;
};
};

View file

@ -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

View file

@ -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<int64_t>(config.getConfigValue("testCategory:testColor2")), (Hyprlang::INT)0xFF000000);
EXPECT(std::any_cast<int64_t>(config.getConfigValue("testCategory:testColor3")), (Hyprlang::INT)0x22ffeeff);
EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringColon")), std::string{"ee:ee:ee"});
EXPECT(std::any_cast<const char*>(config.getConfigValue("multilineSimple")), std::string{"I use C++ because I hate Java"});
EXPECT(std::any_cast<const char*>(config.getConfigValue("multilineTrim")), std::string{"I use Javascript because I hate Python"});
EXPECT(std::any_cast<const char*>(config.getConfigValue("multilineVar")), std::string{"1 1 1 1"});
EXPECT(std::any_cast<const char*>(config.getConfigValue("multilineBreakWord")), std::string{"Hello multiline, how are you today?"});
EXPECT(std::any_cast<const char*>(config.getConfigValue("multilineMultiBreakWord")), std::string{"oui oui baguette"});
EXPECT(std::any_cast<const char*>(config.getConfigValue("multilineCategory:indentedMultiline")),
std::string{"Hello world this is another syntax for multiline that trims all spaces"});
EXPECT(std::any_cast<const char*>(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";