string: add VarList2 (#84)

reworks how varList works, mixes best of both ConstVarList and regular VarList. Deprecates both.
This commit is contained in:
Vaxry 2025-11-09 15:10:58 +00:00 committed by GitHub
parent 968f881222
commit 9a9745d7aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 236 additions and 22 deletions

View file

@ -4,7 +4,9 @@
namespace Hyprutils {
namespace String {
// trims beginning and end of whitespace characters
std::string trim(const char* in);
std::string trim(const std::string& in);
std::string_view trim(const std::string_view& in);
bool isNumber(const std::string& str, bool allowfloat = false);
void replaceInString(std::string& string, const std::string& what, const std::string& to);
};

View file

@ -0,0 +1,50 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
namespace Hyprutils {
namespace String {
class CVarList2 {
public:
/** Split string into arg list
Prefer this over CConstVarList / CVarList, this is better.
@param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args
@param delim if delimiter is 's', use std::isspace
@param removeEmpty remove empty args from argv
@param allowEscape whether to allow escaping the delimiter
*/
CVarList2(std::string&& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false, const bool allowEscape = true);
~CVarList2() = default;
size_t size() const {
return m_args.size();
}
std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
void append(std::string&& arg);
bool contains(const std::string& el);
std::string_view operator[](const size_t& idx) const {
if (idx >= m_args.size())
return "";
return m_args[idx];
}
// for range-based loops
std::vector<std::string_view>::const_iterator begin() const {
return m_args.begin();
}
std::vector<std::string_view>::const_iterator end() const {
return m_args.end();
}
private:
std::string m_inString;
std::vector<std::string> m_copyStrings;
std::vector<std::string_view> m_args;
};
}
}

View file

@ -1,26 +1,10 @@
#include <ranges>
#include <algorithm>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/ConstVarList.hpp>
using namespace Hyprutils::String;
static std::string_view trim(const std::string_view& sv) {
if (sv.empty())
return sv;
size_t countBefore = 0;
while (countBefore < sv.length() && std::isspace(sv.at(countBefore))) {
countBefore++;
}
size_t countAfter = 0;
while (countAfter < sv.length() - countBefore && std::isspace(sv.at(sv.length() - countAfter - 1))) {
countAfter++;
}
return sv.substr(countBefore, sv.length() - countBefore - countAfter);
}
CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) : m_str(in) {
if (in.empty())
return;
@ -37,7 +21,7 @@ CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, cons
break;
}
pos += s.size() + 1;
m_args.emplace_back(trim(s.data()));
m_args.emplace_back(trim(std::string_view{s.data()}));
}
}

View file

@ -22,6 +22,27 @@ std::string Hyprutils::String::trim(const std::string& in) {
return result;
}
std::string_view Hyprutils::String::trim(const std::string_view& sv) {
if (sv.empty())
return sv;
size_t countBefore = 0;
while (countBefore < sv.length() && std::isspace(sv.at(countBefore))) {
countBefore++;
}
size_t countAfter = 0;
while (countAfter < sv.length() - countBefore && std::isspace(sv.at(sv.length() - countAfter - 1))) {
countAfter++;
}
return sv.substr(countBefore, sv.length() - countBefore - countAfter);
}
std::string Hyprutils::String::trim(const char* in) {
return trim(std::string{in});
}
bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
if (str.empty())
return false;

View file

@ -22,7 +22,7 @@ Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastAr
break;
}
pos += s.size() + 1;
m_vArgs.emplace_back(trim(s.data()));
m_vArgs.emplace_back(trim(std::string{s.data()}));
}
}

112
src/string/VarList2.cpp Normal file
View file

@ -0,0 +1,112 @@
#include <algorithm>
#include <hyprutils/string/VarList2.hpp>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) : m_inString(std::move(in)) {
if (!removeEmpty && m_inString.empty())
return;
auto isDelimiter = [&delim](const char& c) { return delim == 's' ? std::isspace(c) : delim == c; };
size_t argBegin = 0;
std::vector<size_t> escapedIndices; // local to the current arg
for (size_t i = 0; i < m_inString.size(); ++i) {
const char& c = m_inString[i];
if (!isDelimiter(c))
continue;
if (allowEscape) {
// we allow escape, so this might be escaped. Check first
if (i - argBegin == 0)
continue; // can't be
const char& previousC = m_inString[i - 1];
if (i - argBegin == 1) {
if (previousC == '\\') {
escapedIndices.emplace_back(i - argBegin - 1);
continue; // escaped
}
// fall to breaking, not escaped
} else {
const char& prevPreviousC = m_inString[i - 2];
if (previousC == '\\') {
// whether or not escaped, pop char
escapedIndices.emplace_back(i - argBegin - 1);
if (prevPreviousC != '\\') {
// escaped
continue;
}
}
// fall to breaking, not escaped, but mark the \\ to be popped
}
}
// here we found a delimiter and need to break up the string (not escaped)
if (escapedIndices.empty()) {
// we didn't escape anything, so we can use inString
const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, i - argBegin));
if (!ARG.empty() || !removeEmpty)
m_args.emplace_back(ARG);
} else {
// we escaped something, fixup the string, add to copies, then emplace
std::string cpy = m_inString.substr(argBegin, i - argBegin);
for (size_t i = 0; i < escapedIndices.size(); ++i) {
cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
}
m_copyStrings.emplace_back(std::move(cpy));
m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
}
// update next argBegin
argBegin = i + 1;
escapedIndices.clear();
}
// append anything left
if (argBegin < m_inString.size()) {
if (escapedIndices.empty()) {
// we didn't escape anything, so we can use inString
const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, m_inString.size() - argBegin));
if (!ARG.empty() || !removeEmpty)
m_args.emplace_back(ARG);
} else {
// we escaped something, fixup the string, add to copies, then emplace
std::string cpy = m_inString.substr(argBegin, m_inString.size() - argBegin);
for (size_t i = 0; i < escapedIndices.size(); ++i) {
cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
}
m_copyStrings.emplace_back(std::move(cpy));
m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
}
}
}
std::string CVarList2::join(const std::string& joiner, size_t from, size_t to) const {
if (to == 0 || to <= from)
return "";
std::string roll;
for (size_t i = from; i < to; ++i) {
roll += m_args[i];
if (i + 1 < to)
roll += joiner;
}
return roll;
}
void CVarList2::append(std::string&& arg) {
m_copyStrings.emplace_back(std::move(arg));
m_args.emplace_back(m_copyStrings.back());
}
bool CVarList2::contains(const std::string& el) {
return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; });
}

View file

@ -1,5 +1,6 @@
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/VarList2.hpp>
#include <hyprutils/string/ConstVarList.hpp>
#include "shared.hpp"
@ -47,6 +48,50 @@ int main(int argc, char** argv, char** envp) {
EXPECT(listConst2[0], "0");
EXPECT(listConst2[1], "set");
CVarList2 varList2("0 set", 2, ' ');
EXPECT(varList2[0], "0");
EXPECT(varList2[1], "set");
varList2.append("Hello");
EXPECT(varList2[1], "set");
EXPECT(varList2[2], "Hello");
CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true);
EXPECT(varList2B[0], "hello");
EXPECT(varList2B[1], "world, ok?");
CVarList2 varList2C("hello, , ok?", 0, ',', true, true);
EXPECT(varList2C[0], "hello");
EXPECT(varList2C[1], "ok?");
CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true);
EXPECT(varList2D[0], "\\");
EXPECT(varList2D[1], "ok?");
CVarList2 varList2E("\\, , ok?", 0, ',', true, true);
EXPECT(varList2E[0], ",");
EXPECT(varList2E[1], "ok?");
CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true);
EXPECT(varList2F[0], "Hello");
EXPECT(varList2F[1], "world\\");
EXPECT(varList2F[2], "ok?");
CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true);
EXPECT(varList2G[0], "Hello");
EXPECT(varList2G[1], ", ok?");
CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true);
EXPECT(varList2H[0], "Hello");
EXPECT(varList2H[1], "\\");
EXPECT(varList2H[2], "ok?");
CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false);
EXPECT(varList2I[0], "Hello");
EXPECT(varList2I[1], "\\");
EXPECT(varList2I[2], "ok?");
std::string hello = "hello world!";
replaceInString(hello, "hello", "hi");
EXPECT(hello, "hi world!");