Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement multiline support #51

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <fstream>
#include <string>
#include <format>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <expected>
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if line.find_last_not_of(MULTILINE_SPACE_CHARSET) returns std::string::npos


auto commentPos = line.find('#');

Expand All @@ -496,34 +498,38 @@ 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);
commentPos = line.find('#', lastHashPos);
}
}

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;
}

Expand Down Expand Up @@ -562,6 +568,35 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
if (ISVARIABLE)
return parseVariable(LHS, RHS, dynamic);

auto lastChar = RHS[RHS.size() - 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.at

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;
}

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)
Expand Down
13 changes: 12 additions & 1 deletion src/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <string>
#include <vector>

static const char* MULTILINE_SPACE_CHARSET = " \t";

struct SHandler {
std::string name = "";
Hyprlang::SHandlerOptions options;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 = "";

Expand All @@ -93,4 +104,4 @@ class CConfigImpl {
struct {
bool noError = false;
} currentFlags;
};
};
45 changes: 45 additions & 0 deletions tests/config/config.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions tests/parse/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down Expand Up @@ -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";
Expand Down