string-util: add helper for extracting n'th line of a string

This commit is contained in:
Lennart Poettering 2020-01-13 16:20:27 +01:00
parent 8dd6491ef9
commit f6857fa601
3 changed files with 139 additions and 0 deletions

View File

@ -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++;
}
}

View File

@ -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);

View File

@ -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;
}