commit f95c5aeddbde6c7753efdf256f72b8bf935bfd59 Author: William Dillon Date: Sun Nov 30 17:56:18 2025 -0500 first commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af41116 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +CXXFLAGS:=-std=c++23 -Wall -Wextra -pthread -g + +gostrings_test: gostrings.hpp + $(CXX) $(CXXFLAGS) -o gostrings_test gostrings_test.cpp -lpthread -lgtest + +clean: + rm -f gostrings_test \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..31f7c8f --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# unimplemented functions: +* func Clone(s string) string +* func EqualFold(s, t string) bool +* func FieldsFuncSeq(s string, f func(rune) bool) iter.Seq[string] +* func FieldsSeq(s string) iter.Seq[string] +* func Lines(s string) iter.Seq[string] +* func Map(mapping func(rune) rune, s string) string +* func SplitAfterSeq(s, sep string) iter.Seq[string] +* func SplitSeq(s, sep string) iter.Seq[string] +* func Title(s string) stringdeprecated +* func ToLowerSpecial(c unicode.SpecialCase, s string) string +* func ToTitle(s string) string +* func ToTitleSpecial(c unicode.SpecialCase, s string) string +* func ToUpperSpecial(c unicode.SpecialCase, s string) string +* func ToValidUTF8(s, replacement string) string \ No newline at end of file diff --git a/gostrings.hpp b/gostrings.hpp new file mode 100644 index 0000000..909db66 --- /dev/null +++ b/gostrings.hpp @@ -0,0 +1,347 @@ +#ifndef __GOSTRINGS_HPP__ +#define __GOSTRINGS_HPP__ + +#include +#include + +namespace gostrings { + +inline int Compare(const std::string &a, const std::string &b) { + if (a == b) return 0; + return (a < b) ? -1 : 1; +} + +inline bool Contains(const std::string &s, const std::string &substr) { + return s.find(substr) != std::string::npos; +} + +inline bool ContainsAny(const std::string &s, const std::string &chars) { + return s.find_first_of(chars) != std::string::npos; +} + +inline bool ContainsFunc(const std::string &s, bool (*f)(char)) { + for (char c : s) { + if (f(c)) { + return true; + } + } + return false; +} + +inline int Count(const std::string &s, const std::string &substr) { + if (substr.empty()) { + return static_cast(s.size()) + 1; + } + int count = 0; + size_t pos = 0; + while ((pos = s.find(substr, pos)) != std::string::npos) { + ++count; + pos += substr.length(); + } + return count; +} + +struct CutPrefixResult { + std::string after; + bool found; +}; + +struct CutSuffixResult { + std::string before; + bool found; +}; + +struct CutResult { + CutPrefixResult prefix; + CutSuffixResult suffix; + inline bool found() const { + return prefix.found && suffix.found; + } +}; + +inline CutResult Cut(const std::string &s, const std::string &sep) { + size_t pos = s.find(sep); + if (pos == std::string::npos) { + return {{"", false}, {"", false}}; + } + return { {s.substr(pos + sep.length()), true}, {s.substr(0, pos), true} }; +} + +inline CutPrefixResult CutPrefix(const std::string &s, const std::string &prefix) { + if (s.substr(0, prefix.length()) == prefix) { + return {s.substr(prefix.length()), true}; + } + return {"", false}; +} + +inline CutSuffixResult CutSuffix(const std::string &s, const std::string &suffix) { + if (s.length() >= suffix.length() && + s.substr(s.length() - suffix.length()) == suffix) { + return {s.substr(0, s.length() - suffix.length()), true}; + } + return {"", false}; +} + +inline std::vector Fields(const std::string &s) { + std::vector result; + size_t start = s.find_first_not_of(" \t\n\r\f\v"); + while (start != std::string::npos) { + size_t end = s.find_first_of(" \t\n\r\f\v", start); + result.push_back(s.substr(start, end - start)); + start = s.find_first_not_of(" \t\n\r\f\v", end); + } + return result; +} + +inline std::vector FieldsFunc(const std::string &s, bool (*f)(char)) { + std::vector result; + size_t start = 0; + while (start < s.length()) { + while (start < s.length() && f(s[start])) { + ++start; + } + if (start >= s.length()) break; + size_t end = start; + while (end < s.length() && !f(s[end])) { + ++end; + } + result.push_back(s.substr(start, end - start)); + start = end; + } + return result; +} + +inline bool HasPrefix(const std::string &s, const std::string &prefix) { + return s.substr(0, prefix.length()) == prefix; +} + +inline bool HasSuffix(const std::string &s, const std::string &suffix) { + return s.length() >= suffix.length() && + s.substr(s.length() - suffix.length()) == suffix; +} + +inline size_t Index(const std::string &s, const std::string &substr) { + size_t pos = s.find(substr); + return (pos == std::string::npos) ? -1 : pos; +} + +inline size_t IndexAny(const std::string &s, const std::string &chars) { + size_t pos = s.find_first_of(chars); + return (pos == std::string::npos) ? -1 : pos; +} + +inline size_t IndexByte(const std::string &s, char c) { + size_t pos = s.find(c); + return (pos == std::string::npos) ? -1 : pos; +} + +inline size_t IndexFunc(const std::string &s, bool (*f)(char)) { + for (size_t i = 0; i < s.length(); ++i) { + if (f(s[i])) { + return i; + } + } + return -1; +} + +inline std::string Join(const std::vector &elems, const std::string &sep) { + std::string result; + for (size_t i = 0; i < elems.size(); ++i) { + result += elems[i]; + if (i < elems.size() - 1) { + result += sep; + } + } + return result; +} + +inline size_t LastIndex(const std::string &s, const std::string &substr) { + size_t pos = s.rfind(substr); + return (pos == std::string::npos) ? -1 : pos; +} + +inline size_t LastIndexAny(const std::string &s, const std::string &chars) { + size_t pos = s.find_last_of(chars); + return (pos == std::string::npos) ? -1 : pos; +} + +inline size_t LastIndexByte(const std::string &s, char c) { + size_t pos = s.rfind(c); + return (pos == std::string::npos) ? -1 : pos; +} + +inline size_t LastIndexFunc(const std::string &s, bool (*f)(char)) { + for (size_t i = s.length(); i-- > 0;) { + if (f(s[i])) { + return i; + } + } + return -1; +} + +inline std::string Repeat(const std::string &s, int count) { + if (count <= 0) return ""; + std::string result; + for (int i = 0; i < count; ++i) { + result += s; + } + return result; +} + +inline std::string Replace(const std::string &s, const std::string &old, const std::string &newstr, int n) { + std::string result = s; + size_t pos = 0; + int count = 0; + while ((pos = result.find(old, pos)) != std::string::npos) { + if (n != -1 && count >= n) break; + result.replace(pos, old.length(), newstr); + pos += newstr.length(); + ++count; + } + return result; +} + +inline std::string ReplaceAll(const std::string &s, const std::string &old, const std::string &newstr) { + return Replace(s, old, newstr, -1); +} + +inline std::vector Split(const std::string &s, const std::string &sep) { + std::vector result; + size_t start = 0; + size_t end; + while ((end = s.find(sep, start)) != std::string::npos) { + result.push_back(s.substr(start, end - start)); + start = end + sep.length(); + } + result.push_back(s.substr(start)); + return result; +} + +inline std::vector SplitAfter(const std::string &s, const std::string &sep) { + std::vector result; + size_t start = 0; + size_t end; + while ((end = s.find(sep, start)) != std::string::npos) { + result.push_back(s.substr(start, end + sep.length() - start)); + start = end + sep.length(); + } + result.push_back(s.substr(start)); + return result; +} + +inline std::vector SplitAfterN(const std::string &s, const std::string &sep, int n) { + std::vector result; + size_t start = 0; + size_t end; + int count = 0; + while (count < n - 1 && (end = s.find(sep, start)) != std::string::npos) { + result.push_back(s.substr(start, end + sep.length() - start)); + start = end + sep.length(); + ++count; + } + result.push_back(s.substr(start)); + return result; +} + +inline std::vector SplitN(const std::string &s, const std::string &sep, int n) { + std::vector result; + size_t start = 0; + size_t end; + int count = 0; + while (count < n - 1 && (end = s.find(sep, start)) != std::string::npos) { + result.push_back(s.substr(start, end - start)); + start = end + sep.length(); + ++count; + } + result.push_back(s.substr(start)); + return result; +} + +inline std::string ToLower(const std::string &s) { + std::string result = s; + for (char &c : result) { + c = static_cast(tolower(static_cast(c))); + } + return result; +} + +inline std::string ToUpper(const std::string &s) { + std::string result = s; + for (char &c : result) { + c = static_cast(toupper(static_cast(c))); + } + return result; +} + +inline std::string Trim(const std::string &s, const std::string &cutset) { + size_t start = s.find_first_not_of(cutset); + if (start == std::string::npos) return ""; + size_t end = s.find_last_not_of(cutset); + return s.substr(start, end - start + 1); +} + +inline std::string TrimFunc(const std::string &s, bool (*f)(char)) { + size_t start = 0; + while (start < s.length() && f(s[start])) { + ++start; + } + if (start == s.length()) return ""; + size_t end = s.length() - 1; + while (end > start && f(s[end])) { + --end; + } + return s.substr(start, end - start + 1); +} + +inline std::string TrimLeft(const std::string &s, const std::string &cutset) { + size_t start = s.find_first_not_of(cutset); + if (start == std::string::npos) return ""; + return s.substr(start); +} + +inline std::string TrimLeftFunc(const std::string &s, bool (*f)(char)) { + size_t start = 0; + while (start < s.length() && f(s[start])) { + ++start; + } + return s.substr(start); +} + +inline std::string TrimPrefix(const std::string &s, const std::string &prefix) { + if (s.substr(0, prefix.length()) == prefix) { + return s.substr(prefix.length()); + } + return s; +} + +inline std::string TrimRight(const std::string &s, const std::string &cutset) { + size_t end = s.find_last_not_of(cutset); + if (end == std::string::npos) return ""; + return s.substr(0, end + 1); +} + +inline std::string TrimRightFunc(const std::string &s, bool (*f)(char)) { + size_t end = s.length(); + while (end > 0 && f(s[end - 1])) { + --end; + } + return s.substr(0, end); +} + +inline std::string TrimSpace(const std::string &s) { + return Trim(s, " \t\n\r\f\v"); +} + +inline std::string TrimSuffix(const std::string &s, const std::string &suffix) { + if (s.length() >= suffix.length() && + s.substr(s.length() - suffix.length()) == suffix) { + return s.substr(0, s.length() - suffix.length()); + } + return s; +} + +} // namespace gostrings + + + +#endif // __GOSTRINGS_HPP__ \ No newline at end of file diff --git a/gostrings_test.cpp b/gostrings_test.cpp new file mode 100644 index 0000000..0ac6272 --- /dev/null +++ b/gostrings_test.cpp @@ -0,0 +1,246 @@ +#include "gostrings.hpp" +#include + +TEST(GoStringsTest, Compare) { + EXPECT_EQ(gostrings::Compare("apple", "apple"), 0); + EXPECT_EQ(gostrings::Compare("apple", "banana"), -1); + EXPECT_EQ(gostrings::Compare("banana", "apple"), 1); +} + +TEST(GoStringsTest, Contains) { + EXPECT_TRUE(gostrings::Contains("hello world", "world")); + EXPECT_FALSE(gostrings::Contains("hello world", "mars")); +} + +TEST(GoStringsTest, ContainsFunc) { + auto isVowel = [](char c) { + c = tolower(c); + return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; + }; + EXPECT_TRUE(gostrings::ContainsFunc("hello", isVowel)); + EXPECT_FALSE(gostrings::ContainsFunc("rhythm", isVowel)); +} + +TEST(GoStringsTest, Count) { + EXPECT_EQ(gostrings::Count("ababab", "ab"), 3); + EXPECT_EQ(gostrings::Count("aaaa", "aa"), 2); + EXPECT_EQ(gostrings::Count("hello", "world"), 0); + EXPECT_EQ(gostrings::Count("hello", ""), 6); +} + +TEST(GoStringsTest, Cut) { + auto result = gostrings::Cut("hello world", " "); + EXPECT_TRUE(result.found()); + EXPECT_EQ(result.prefix.after, "world"); + EXPECT_EQ(result.suffix.before, "hello"); + + result = gostrings::Cut("helloworld", " "); + EXPECT_FALSE(result.found()); +} + +TEST(GoStringsTest, CutPrefix) { + auto result = gostrings::CutPrefix("hello world", "hello "); + EXPECT_TRUE(result.found); + EXPECT_EQ(result.after, "world"); + + result = gostrings::CutPrefix("hello world", "world "); + EXPECT_FALSE(result.found); + +} + +TEST(GoStringsTest, CutSuffix) { + auto result = gostrings::CutSuffix("hello world", " world"); + EXPECT_TRUE(result.found); + EXPECT_EQ(result.before, "hello"); + + result = gostrings::CutSuffix("hello world", " hello"); + EXPECT_FALSE(result.found); +} + +TEST(GoStringsTest, Fields) { + auto result = gostrings::Fields(" hello world "); + ASSERT_EQ(result.size(), 2); + EXPECT_EQ(result[0], "hello"); + EXPECT_EQ(result[1], "world"); +} + +TEST(GoStringsTest, FieldsFunc) { + auto isSpace = [](char c) { + return c == ' ' || c == '\t' || c == '\n'; + }; + auto result = gostrings::FieldsFunc(" hello\tworld\n", isSpace); + ASSERT_EQ(result.size(), 2); + EXPECT_EQ(result[0], "hello"); + EXPECT_EQ(result[1], "world"); +} + +TEST(GoStringsTest, HasPrefix) { + EXPECT_TRUE(gostrings::HasPrefix("hello world", "hello")); + EXPECT_FALSE(gostrings::HasPrefix("hello world", "world")); +} + +TEST(GoStringsTest, Index) { + EXPECT_EQ(gostrings::Index("hello world", "world"), 6); + EXPECT_EQ(gostrings::Index("hello world", "mars"), -1); +} + +TEST(GoStringsTest, IndexAny) { + EXPECT_EQ(gostrings::IndexAny("hello world", "aeiou"), 1); + EXPECT_EQ(gostrings::IndexAny("rhythm", "aeiou"), -1); +} + +TEST(GoStringsTest, IndexByte) { + EXPECT_EQ(gostrings::IndexByte("hello world", 'o'), 4); + EXPECT_EQ(gostrings::IndexByte("hello world", 'z'), -1); +} + +TEST(GoStringsTest, IndexFunc) { + auto isVowel = [](char c) { + c = tolower(c); + return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; + }; + EXPECT_EQ(gostrings::IndexFunc("hello", isVowel), 1); + EXPECT_EQ(gostrings::IndexFunc("rhythm", isVowel), -1); +} + +TEST(GoStringsTest, Join) { + std::vector elems = {"hello", "world"}; + EXPECT_EQ(gostrings::Join(elems, " "), "hello world"); +} + +TEST(GoStringsTest, LastIndex) { + EXPECT_EQ(gostrings::LastIndex("ababcabc", "abc"), 5); + EXPECT_EQ(gostrings::LastIndex("hello world", "mars"), -1); +} + +TEST(GoStringsTest, LastIndexAny) { + EXPECT_EQ(gostrings::LastIndexAny("hello world", "aeiou"), 7); + EXPECT_EQ(gostrings::LastIndexAny("rhythm", "aeiou"), -1); +} + +TEST(GoStringsTest, LastIndexByte) { + EXPECT_EQ(gostrings::LastIndexByte("hello world", 'o'), 7); + EXPECT_EQ(gostrings::LastIndexByte("hello world", 'z'), -1); +} + +TEST(GoStringsTest, LastIndexFunc) { + auto isVowel = [](char c) { + c = tolower(c); + return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; + }; + EXPECT_EQ(gostrings::LastIndexFunc("hello", isVowel), 4); + EXPECT_EQ(gostrings::LastIndexFunc("rhythm", isVowel), -1); +} + +TEST(GoStringsTest, Repeat) { + EXPECT_EQ(gostrings::Repeat("ab", 3), "ababab"); + EXPECT_EQ(gostrings::Repeat("xyz", 0), ""); +} + +TEST(GoStringsTest, Replace) { + EXPECT_EQ(gostrings::Replace("ababab", "ab", "xy", 2), "xyxyab"); + EXPECT_EQ(gostrings::Replace("hello world", "world", "mars", -1), "hello mars"); +} + +TEST(GoStringsTest, ReplaceAll) { + EXPECT_EQ(gostrings::ReplaceAll("ababab", "ab", "xy"), "xyxyxy"); + EXPECT_EQ(gostrings::ReplaceAll("hello world", "world", "mars"), "hello mars"); +} + +TEST(GoStringsTest, Split) { + auto result = gostrings::Split("a,b,c", ","); + ASSERT_EQ(result.size(), 3); + EXPECT_EQ(result[0], "a"); + EXPECT_EQ(result[1], "b"); + EXPECT_EQ(result[2], "c"); +} + +TEST(GoStringsTest, SplitAfter) { + auto result = gostrings::SplitAfter("a,b,c", ","); + ASSERT_EQ(result.size(), 3); + EXPECT_EQ(result[0], "a,"); + EXPECT_EQ(result[1], "b,"); + EXPECT_EQ(result[2], "c"); +} + +TEST(GoStringsTest, SplitAfterN) { + auto result = gostrings::SplitAfterN("a,b,c,d", ",", 3); + ASSERT_EQ(result.size(), 3); + EXPECT_EQ(result[0], "a,"); + EXPECT_EQ(result[1], "b,"); + EXPECT_EQ(result[2], "c,d"); +} + +TEST(GoStringsTest, SplitN) { + auto result = gostrings::SplitN("a,b,c,d", ",", 3); + ASSERT_EQ(result.size(), 3); + EXPECT_EQ(result[0], "a"); + EXPECT_EQ(result[1], "b"); + EXPECT_EQ(result[2], "c,d"); +} + +TEST(GoStringsTest, ToLower) { + EXPECT_EQ(gostrings::ToLower("HeLLo WoRLD"), "hello world"); +} + +TEST(GoStringsTest, ToUpper) { + EXPECT_EQ(gostrings::ToUpper("HeLLo WoRLD"), "HELLO WORLD"); +} + +TEST(GoStringsTest, Trim) { + EXPECT_EQ(gostrings::Trim(" hello world ", " "), "hello world"); +} + +TEST(GoStringsTest, TrimFunc) { + auto isSpace = [](char c) { + return c == ' ' || c == '\t' || c == '\n'; + }; + EXPECT_EQ(gostrings::TrimFunc(" \thello world\n ", isSpace), "hello world"); +} + +TEST(GoStringsTest, TrimLeft) { + EXPECT_EQ(gostrings::TrimLeft(" hello world ", " "), "hello world "); +} + +TEST(GoStringsTest, TrimLeftFunc) { + auto isSpace = [](char c) { + return c == ' ' || c == '\t' || c == '\n'; + }; + EXPECT_EQ(gostrings::TrimLeftFunc(" \thello world\n ", isSpace), "hello world\n "); +} + +TEST(GoStringsTest, TrimPrefix) { + auto result = gostrings::TrimPrefix("hello world", "hello "); + EXPECT_EQ(result, "world"); + + result = gostrings::TrimPrefix("hello world", "world "); + EXPECT_EQ(result, "hello world"); +} + +TEST(GoStringsTest, TrimRight) { + EXPECT_EQ(gostrings::TrimRight(" hello world ", " "), " hello world"); +} + +TEST(GoStringsTest, TrimRightFunc) { + auto isSpace = [](char c) { + return c == ' ' || c == '\t' || c == '\n'; + }; + EXPECT_EQ(gostrings::TrimRightFunc(" \thello world\n ", isSpace), " \thello world"); +} + +TEST(GoStringsTest, TrimSpace) { + EXPECT_EQ(gostrings::TrimSpace(" \thello world\n "), "hello world"); +} + +TEST(GoStringsTest, TrimSuffix) { + auto result = gostrings::TrimSuffix("hello world", " world"); + EXPECT_EQ(result, "hello"); + + result = gostrings::TrimSuffix("hello world", " hello"); + EXPECT_EQ(result, "hello world"); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file