commit ea03bfa556eeaf3f10a455e6b5f529257bee028b from: Evan Burkey date: Sat Mar 14 06:16:11 2026 UTC add str_trim, str_join, str_starts_with, str_ends_with, str_replace, str_to_upper, str_to_lower commit - 5256e868ed7f0d40ccaf480bd903ef722df4a052 commit + ea03bfa556eeaf3f10a455e6b5f529257bee028b blob - 8afb905eb12229c1215e032fca1f8fd5d3e47465 blob + 1e49819533239f64b0c2f0d005cdc4802c9cfd2d --- include/lfstring.h +++ include/lfstring.h @@ -7,4 +7,18 @@ int find_substrings(const char* haystack, const char* char* substr(const char* str, size_t idx, size_t len); +char *str_trim(const char *str); + +char *str_join(const char **strs, size_t count, const char *sep); + +int str_starts_with(const char *str, const char *prefix); + +int str_ends_with(const char *str, const char *suffix); + +char *str_replace(const char *str, const char *old, const char *new); + +char *str_to_upper(const char *str); + +char *str_to_lower(const char *str); + #endif // LIBFLINT_H_STRING blob - 2cb1d2572d1af0dac58eb454a6b5905d9111c579 blob + a39bd8b1795b4f994bca76be4054f8820f1fa1c0 --- src/string.c +++ src/string.c @@ -1,5 +1,6 @@ #include #include +#include #include "lfstring.h" @@ -56,3 +57,130 @@ char* substr(const char* str, size_t idx, size_t len) return substr; } + +char *str_trim(const char *str) { + const char *start = str; + while (*start && isspace((unsigned char)*start)) { + start++; + } + const char *end = str + strlen(str); + while (end > start && isspace((unsigned char)*(end - 1))) { + end--; + } + size_t len = end - start; + char *out = malloc(len + 1); + if (out == NULL) { + return NULL; + } + memcpy(out, start, len); + out[len] = '\0'; + return out; +} + +char *str_join(const char **strs, size_t count, const char *sep) { + if (count == 0) { + char *out = malloc(1); + if (out != NULL) { + out[0] = '\0'; + } + return out; + } + size_t sep_len = strlen(sep); + size_t total = 0; + for (size_t i = 0; i < count; i++) { + total += strlen(strs[i]); + } + total += sep_len * (count - 1); + + char *out = malloc(total + 1); + if (out == NULL) { + return NULL; + } + char *p = out; + for (size_t i = 0; i < count; i++) { + size_t len = strlen(strs[i]); + memcpy(p, strs[i], len); + p += len; + if (i < count - 1) { + memcpy(p, sep, sep_len); + p += sep_len; + } + } + *p = '\0'; + return out; +} + +int str_starts_with(const char *str, const char *prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +int str_ends_with(const char *str, const char *suffix) { + size_t str_len = strlen(str); + size_t suf_len = strlen(suffix); + if (suf_len > str_len) { + return 0; + } + return strcmp(str + str_len - suf_len, suffix) == 0; +} + +char *str_replace(const char *str, const char *old, const char *new) { + size_t old_len = strlen(old); + size_t new_len = strlen(new); + if (old_len == 0) { + return strdup(str); + } + + /* count occurrences */ + size_t count = 0; + const char *p = str; + while ((p = strstr(p, old)) != NULL) { + count++; + p += old_len; + } + + size_t out_len = strlen(str) + count * (new_len - old_len); + char *out = malloc(out_len + 1); + if (out == NULL) { + return NULL; + } + + char *w = out; + p = str; + while (*p) { + if (strncmp(p, old, old_len) == 0) { + memcpy(w, new, new_len); + w += new_len; + p += old_len; + } else { + *w++ = *p++; + } + } + *w = '\0'; + return out; +} + +char *str_to_upper(const char *str) { + size_t len = strlen(str); + char *out = malloc(len + 1); + if (out == NULL) { + return NULL; + } + for (size_t i = 0; i < len; i++) { + out[i] = toupper((unsigned char)str[i]); + } + out[len] = '\0'; + return out; +} + +char *str_to_lower(const char *str) { + size_t len = strlen(str); + char *out = malloc(len + 1); + if (out == NULL) { + return NULL; + } + for (size_t i = 0; i < len; i++) { + out[i] = tolower((unsigned char)str[i]); + } + out[len] = '\0'; + return out; +} blob - ba720d22e8bd1104d7cf1fc2952c5df00d8f4b50 blob + 8e29b506d9e134285c55909bb2bf72a0651d9280 --- tests/test_string.c +++ tests/test_string.c @@ -5,6 +5,7 @@ #include "lfstring.h" int main() { + /* existing find_substrings tests */ const char *haystack = "Test one two one and also maybe two but not Gabe's least favorite number, which is not one."; const char *needles[] = { @@ -48,5 +49,79 @@ int main() { free(subs); subs = NULL; + /* str_trim */ + s = str_trim(" hello world "); + ASSERT_STR_EQ(s, "hello world"); + free(s); + + s = str_trim("no_spaces"); + ASSERT_STR_EQ(s, "no_spaces"); + free(s); + + s = str_trim(" "); + ASSERT_STR_EQ(s, ""); + free(s); + + s = str_trim("\t\n hi \r\n"); + ASSERT_STR_EQ(s, "hi"); + free(s); + + /* str_join */ + const char *parts[] = {"foo", "bar", "baz"}; + s = str_join(parts, 3, ", "); + ASSERT_STR_EQ(s, "foo, bar, baz"); + free(s); + + s = str_join(parts, 1, ", "); + ASSERT_STR_EQ(s, "foo"); + free(s); + + s = str_join(parts, 0, ", "); + ASSERT_STR_EQ(s, ""); + free(s); + + s = str_join(parts, 3, ""); + ASSERT_STR_EQ(s, "foobarbaz"); + free(s); + + /* str_starts_with */ + ASSERT_TRUE(str_starts_with("hello world", "hello")); + ASSERT_TRUE(str_starts_with("hello", "hello")); + ASSERT_FALSE(str_starts_with("hello", "world")); + ASSERT_TRUE(str_starts_with("hello", "")); + + /* str_ends_with */ + ASSERT_TRUE(str_ends_with("hello world", "world")); + ASSERT_TRUE(str_ends_with("hello", "hello")); + ASSERT_FALSE(str_ends_with("hello", "world")); + ASSERT_TRUE(str_ends_with("hello", "")); + ASSERT_FALSE(str_ends_with("hi", "longer")); + + /* str_replace */ + s = str_replace("foo bar foo baz foo", "foo", "qux"); + ASSERT_STR_EQ(s, "qux bar qux baz qux"); + free(s); + + s = str_replace("aaa", "a", "bb"); + ASSERT_STR_EQ(s, "bbbbbb"); + free(s); + + s = str_replace("hello", "xyz", "abc"); + ASSERT_STR_EQ(s, "hello"); + free(s); + + s = str_replace("abcabc", "abc", ""); + ASSERT_STR_EQ(s, ""); + free(s); + + /* str_to_upper / str_to_lower */ + s = str_to_upper("hello World 123"); + ASSERT_STR_EQ(s, "HELLO WORLD 123"); + free(s); + + s = str_to_lower("HELLO World 123"); + ASSERT_STR_EQ(s, "hello world 123"); + free(s); + TEST_REPORT(); }