diff --git a/include/hyprutils/string/String.hpp b/include/hyprutils/string/String.hpp index cdc5c39..3bcf8c9 100644 --- a/include/hyprutils/string/String.hpp +++ b/include/hyprutils/string/String.hpp @@ -4,8 +4,10 @@ namespace Hyprutils { namespace String { // trims beginning and end of whitespace characters - std::string trim(const std::string& in); - bool isNumber(const std::string& str, bool allowfloat = false); - void replaceInString(std::string& string, const std::string& what, const std::string& to); + 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); }; }; \ No newline at end of file diff --git a/include/hyprutils/string/VarList2.hpp b/include/hyprutils/string/VarList2.hpp new file mode 100644 index 0000000..fb6113b --- /dev/null +++ b/include/hyprutils/string/VarList2.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include + +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 = false); + + ~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::const_iterator begin() const { + return m_args.begin(); + } + std::vector::const_iterator end() const { + return m_args.end(); + } + + private: + std::string m_inString; + std::vector m_copyStrings; + std::vector m_args; + }; + } +} diff --git a/src/string/ConstVarList.cpp b/src/string/ConstVarList.cpp index 5f43e7e..76a0928 100644 --- a/src/string/ConstVarList.cpp +++ b/src/string/ConstVarList.cpp @@ -1,26 +1,10 @@ #include #include +#include #include 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()})); } } diff --git a/src/string/String.cpp b/src/string/String.cpp index 3018128..db8dcc1 100644 --- a/src/string/String.cpp +++ b/src/string/String.cpp @@ -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; diff --git a/src/string/VarList.cpp b/src/string/VarList.cpp index 8a93559..48508ba 100644 --- a/src/string/VarList.cpp +++ b/src/string/VarList.cpp @@ -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()})); } } diff --git a/src/string/VarList2.cpp b/src/string/VarList2.cpp new file mode 100644 index 0000000..751f212 --- /dev/null +++ b/src/string/VarList2.cpp @@ -0,0 +1,112 @@ +#include + +#include +#include + +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 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; }); +} diff --git a/tests/string.cpp b/tests/string.cpp index fb94800..1732875 100644 --- a/tests/string.cpp +++ b/tests/string.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "shared.hpp" @@ -47,6 +48,45 @@ 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?"); + std::string hello = "hello world!"; replaceInString(hello, "hello", "hi"); EXPECT(hello, "hi world!");