#ifndef TK_STRINGS_H #define TK_STRINGS_H /** * @file tk_strings.h * @brief Single-header string utilities. * * Define TK_STRINGS_IMPLEMENTATION in exactly one translation unit before * including this file to include the implementation. */ #include #include #ifdef __cplusplus extern "C" { #endif /** * @brief Count occurrences of a character in a C string. * @param str Null-terminated input string. Must not be NULL. * @param c Character to count. * @return Number of times c appears in str. */ size_t tk_str_count_occurrences(const char* str, char c); /** * @brief Count occurrences of a substring in a C string. * @param str Null-terminated input string. Must not be NULL. * @param substr Null-terminated substring to count. Must not be NULL or empty. * @return Number of times substr appears in str. */ size_t tk_str_count_occurrences_of_substring(const char* str, const char* substr); /** * @brief Split a string by a delimiter character. * * This copies all parts. The caller is responsible for freeing each string in * result and the result array itself. * * @param str Null-terminated input string. Must not be NULL. * @param delimiter Delimiter character. * @param[out] result On success, set to a malloc'd array of char* parts. * @return Number of parts. An empty string returns one empty part. If the * result array allocation fails, returns 0. If a part allocation fails, returns * the number of parts allocated so far and result points to that partial array. */ size_t tk_str_split_by_char(const char* str, char delimiter, char*** result); /** * @brief Split a string by a delimiter substring. * * This copies all parts. The caller is responsible for freeing each string in * result and the result array itself. * * @param str Null-terminated input string. Must not be NULL. * @param substr Null-terminated delimiter substring. Must not be NULL or empty. * @param[out] result On success, set to a malloc'd array of char* parts. * @return Number of parts. If the result array allocation fails, returns 0. If * a part allocation fails, returns the number of parts allocated so far and * result points to that partial array. */ size_t tk_str_split_by_substring(const char* str, const char* substr, char*** result); /** * @brief Convert all lowercase ASCII letters in a string to uppercase. * @param str Null-terminated string to modify in place. Must not be NULL. */ void tk_str_to_upper(char* str); /** * @brief Convert all uppercase ASCII letters in a string to lowercase. * @param str Null-terminated string to modify in place. Must not be NULL. */ void tk_str_to_lower(char* str); /** * @brief Concatenate two null-terminated strings into a newly allocated string. * @param str1 First input string. Must not be NULL. * @param str2 Second input string. Must not be NULL. * @return Newly allocated concatenated string, or NULL on allocation error. */ char* tk_str_concat(const char* str1, const char* str2); /** * @brief Trim leading and trailing whitespace from a string. * @param str Null-terminated input string. Must not be NULL, empty, or all whitespace. * @return Newly allocated trimmed string, or NULL on allocation error. */ char* tk_str_trim(const char* str); /** * @brief Check whether a string contains a substring. * @param str Null-terminated input string. Must not be NULL. * @param substr Null-terminated substring to search for. Must not be NULL. * @return 1 if str contains substr, 0 otherwise. */ uint8_t tk_str_contains(const char* str, const char* substr); #ifdef __cplusplus } #endif #endif /* TK_STRINGS_H */ #ifdef TK_STRINGS_IMPLEMENTATION #include #include #include inline size_t tk_str_count_occurrences(const char* str, const char c) { size_t i = 0; size_t count = 0; while (str[i] != '\0') { if (str[i] == c) { count++; } i++; } return count; } inline size_t tk_str_count_occurrences_of_substring(const char* str, const char* substr) { size_t i = 0; size_t count = 0; while (str[i] != '\0') { if (strstr(&str[i], substr) == &str[i]) { count++; } i++; } return count; } inline size_t tk_str_split_by_char(const char* str, const char delimiter, char*** result) { const size_t size = tk_str_count_occurrences(str, delimiter); char** parts = malloc((size + 1) * sizeof(char*)); if (parts == NULL) { *result = NULL; return 0; } size_t curr_part_idx = 0; size_t last_part_end = 0; size_t idx = 0; while (str[idx] != '\0') { if (str[idx] == delimiter) { const size_t len = idx - last_part_end; char* part = malloc(sizeof(char) * (len + 1)); if (part == NULL) { *result = parts; return curr_part_idx; } memcpy(part, &str[last_part_end], len); part[idx - last_part_end] = '\0'; parts[curr_part_idx++] = part; last_part_end = idx; last_part_end++; } idx++; } char* part = malloc(sizeof(char) * (idx - last_part_end + 1)); if (part == NULL) { *result = parts; return curr_part_idx; } memcpy(part, &str[last_part_end], idx - last_part_end); part[idx - last_part_end] = '\0'; parts[curr_part_idx++] = part; *result = parts; return curr_part_idx; } inline size_t tk_str_split_by_substring(const char* str, const char* substr, char*** result) { const size_t size = tk_str_count_occurrences_of_substring(str, substr); char** parts = malloc((size + 1) * sizeof(char*)); if (parts == NULL) { *result = NULL; return 0; } size_t curr_part_idx = 0; size_t last_part_end = 0; size_t idx = 0; while (str[idx] != '\0') { if (strstr(&str[idx], substr) == &str[idx]) { const size_t len = idx - last_part_end; char* part = malloc(sizeof(char) * (len + 1)); if (part == NULL) { *result = parts; return curr_part_idx; } memcpy(part, &str[last_part_end], len); part[len] = '\0'; parts[curr_part_idx++] = part; last_part_end = idx; last_part_end += strlen(substr); } idx++; } const size_t len = idx - last_part_end; char* part = malloc(sizeof(char) * (len + 1)); if (part == NULL) { *result = parts; return curr_part_idx; } memcpy(part, &str[last_part_end], len); part[len] = '\0'; parts[curr_part_idx++] = part; *result = parts; return curr_part_idx; } inline void tk_str_to_upper(char* str) { for (size_t i = 0; str[i] != '\0'; i++) { if (str[i] >= 'a' && str[i] <= 'z') { str[i] -= 'a' - 'A'; } } } inline void tk_str_to_lower(char* str) { for (size_t i = 0; str[i] != '\0'; i++) { if (str[i] >= 'A' && str[i] <= 'Z') { str[i] += 'a' - 'A'; } } } inline char* tk_str_concat(const char* str1, const char* str2) { const size_t len1 = strlen(str1); const size_t len2 = strlen(str2); char* result = malloc(sizeof(char) * (len1 + len2 + 1)); if (result == NULL) { return NULL; } memcpy(result, str1, len1); memcpy(&result[len1], str2, len2); result[len1 + len2] = '\0'; return result; } static inline bool tk_internal_str_char_is_rubbish(const char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } inline char* tk_str_trim(const char* str) { size_t start = 0; size_t end = strlen(str) - 1; while (tk_internal_str_char_is_rubbish(str[start])) { start++; } while (tk_internal_str_char_is_rubbish(str[end])) { end--; } char* new_str = malloc(sizeof(char) * (end - start + 2)); if (new_str == NULL) { return NULL; } memcpy(new_str, &str[start], end - start + 1); new_str[end - start + 1] = '\0'; return new_str; } inline uint8_t tk_str_contains(const char* str, const char* substr) { return strstr(str, substr) != NULL; } #endif /* TK_STRINGS_IMPLEMENTATION */