diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 30eafc0897..61809ab065 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1131,3 +1131,64 @@ int string_truncate_lines(const char *s, size_t n_lines, char **ret) { *ret = copy; return truncation_applied; } + +int string_extract_line(const char *s, size_t i, char **ret) { + const char *p = s; + size_t c = 0; + + /* Extract the i'nth line from the specified string. Returns > 0 if there are more lines after that, + * and == 0 if we are looking at the last line or already beyond the last line. As special + * optimization, if the first line is requested and the string only consists of one line we return + * NULL, indicating the input string should be used as is, and avoid a memory allocation for a very + * common case. */ + + for (;;) { + const char *q; + + q = strchr(p, '\n'); + if (i == c) { + /* The line we are looking for! */ + + if (q) { + char *m; + + m = strndup(p, q - p); + if (!m) + return -ENOMEM; + + *ret = m; + return !isempty(q + 1); /* more coming? */ + } else { + if (p == s) + *ret = NULL; /* Just use the input string */ + else { + char *m; + + m = strdup(p); + if (!m) + return -ENOMEM; + + *ret = m; + } + + return 0; /* The end */ + } + } + + if (!q) { + char *m; + + /* No more lines, return empty line */ + + m = strdup(""); + if (!m) + return -ENOMEM; + + *ret = m; + return 0; /* The end */ + } + + p = q + 1; + c++; + } +} diff --git a/src/basic/string-util.h b/src/basic/string-util.h index ebab694b93..f98fbdddda 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -282,3 +282,4 @@ static inline char* str_realloc(char **p) { char* string_erase(char *x); int string_truncate_lines(const char *s, size_t n_lines, char **ret); +int string_extract_line(const char *s, size_t i, char **ret); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 7a65ff83c4..13936f6d25 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -634,6 +634,82 @@ static void test_string_truncate_lines(void) { test_string_truncate_lines_one("\n\nx\n", 3, "\n\nx", false); } +static void test_string_extract_lines_one(const char *input, size_t i, const char *output, bool more) { + _cleanup_free_ char *b = NULL; + int k; + + assert_se((k = string_extract_line(input, i, &b)) >= 0); + assert_se(streq(b ?: input, output)); + assert_se(!!k == more); +} + +static void test_string_extract_line(void) { + test_string_extract_lines_one("", 0, "", false); + test_string_extract_lines_one("", 1, "", false); + test_string_extract_lines_one("", 2, "", false); + test_string_extract_lines_one("", 3, "", false); + + test_string_extract_lines_one("x", 0, "x", false); + test_string_extract_lines_one("x", 1, "", false); + test_string_extract_lines_one("x", 2, "", false); + test_string_extract_lines_one("x", 3, "", false); + + test_string_extract_lines_one("x\n", 0, "x", false); + test_string_extract_lines_one("x\n", 1, "", false); + test_string_extract_lines_one("x\n", 2, "", false); + test_string_extract_lines_one("x\n", 3, "", false); + + test_string_extract_lines_one("x\ny", 0, "x", true); + test_string_extract_lines_one("x\ny", 1, "y", false); + test_string_extract_lines_one("x\ny", 2, "", false); + test_string_extract_lines_one("x\ny", 3, "", false); + + test_string_extract_lines_one("x\ny\n", 0, "x", true); + test_string_extract_lines_one("x\ny\n", 1, "y", false); + test_string_extract_lines_one("x\ny\n", 2, "", false); + test_string_extract_lines_one("x\ny\n", 3, "", false); + + test_string_extract_lines_one("x\ny\nz", 0, "x", true); + test_string_extract_lines_one("x\ny\nz", 1, "y", true); + test_string_extract_lines_one("x\ny\nz", 2, "z", false); + test_string_extract_lines_one("x\ny\nz", 3, "", false); + + test_string_extract_lines_one("\n", 0, "", false); + test_string_extract_lines_one("\n", 1, "", false); + test_string_extract_lines_one("\n", 2, "", false); + test_string_extract_lines_one("\n", 3, "", false); + + test_string_extract_lines_one("\n\n", 0, "", true); + test_string_extract_lines_one("\n\n", 1, "", false); + test_string_extract_lines_one("\n\n", 2, "", false); + test_string_extract_lines_one("\n\n", 3, "", false); + + test_string_extract_lines_one("\n\n\n", 0, "", true); + test_string_extract_lines_one("\n\n\n", 1, "", true); + test_string_extract_lines_one("\n\n\n", 2, "", false); + test_string_extract_lines_one("\n\n\n", 3, "", false); + + test_string_extract_lines_one("\n\n\n\n", 0, "", true); + test_string_extract_lines_one("\n\n\n\n", 1, "", true); + test_string_extract_lines_one("\n\n\n\n", 2, "", true); + test_string_extract_lines_one("\n\n\n\n", 3, "", false); + + test_string_extract_lines_one("\nx\n\n\n", 0, "", true); + test_string_extract_lines_one("\nx\n\n\n", 1, "x", true); + test_string_extract_lines_one("\nx\n\n\n", 2, "", true); + test_string_extract_lines_one("\nx\n\n\n", 3, "", false); + + test_string_extract_lines_one("\n\nx\n\n", 0, "", true); + test_string_extract_lines_one("\n\nx\n\n", 1, "", true); + test_string_extract_lines_one("\n\nx\n\n", 2, "x", true); + test_string_extract_lines_one("\n\nx\n\n", 3, "", false); + + test_string_extract_lines_one("\n\n\nx\n", 0, "", true); + test_string_extract_lines_one("\n\n\nx\n", 1, "", true); + test_string_extract_lines_one("\n\n\nx\n", 2, "", true); + test_string_extract_lines_one("\n\n\nx\n", 3, "x", false); +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -667,6 +743,7 @@ int main(int argc, char *argv[]) { test_memory_startswith(); test_memory_startswith_no_case(); test_string_truncate_lines(); + test_string_extract_line(); return 0; }