Add line wrapper
Add a tool to wrap lines at words boundaries (spaces) to fit in a specified number of columns, left-indented by a specified number of spaces.
This commit is contained in:
parent
6dba1922c1
commit
9ec3406568
4 changed files with 130 additions and 0 deletions
|
@ -187,6 +187,7 @@ if get_option('buildtype') == 'debug'
|
|||
'tests/test_cli.c',
|
||||
'src/cli.c',
|
||||
'src/options.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_clock', [
|
||||
|
@ -196,6 +197,7 @@ if get_option('buildtype') == 'debug'
|
|||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_device_msg_deserialize', [
|
||||
|
@ -211,6 +213,7 @@ if get_option('buildtype') == 'debug'
|
|||
]],
|
||||
['test_strutil', [
|
||||
'tests/test_strutil.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
]
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#include "str_util.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "util/strbuf.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
|
@ -209,3 +211,81 @@ utf8_from_wide_char(const wchar_t *ws) {
|
|||
}
|
||||
|
||||
#endif
|
||||
|
||||
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
||||
assert(indent < columns);
|
||||
|
||||
struct sc_strbuf buf;
|
||||
|
||||
// The output string should not be much longer than the input string (just
|
||||
// a few '\n' added), so this initial capacity should hopefully almost
|
||||
// always avoid internal realloc() in string buffer
|
||||
size_t cap = strlen(input) * 3 / 2;
|
||||
|
||||
if (!sc_strbuf_init(&buf, cap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
|
||||
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
|
||||
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
|
||||
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
|
||||
|
||||
APPEND_INDENT();
|
||||
|
||||
// The last separator encountered, it must be inserted only conditionnaly,
|
||||
// depending on the next token
|
||||
char pending = 0;
|
||||
|
||||
// col tracks the current column in the current line
|
||||
size_t col = indent;
|
||||
while (*input) {
|
||||
size_t sep_idx = strcspn(input, "\n ");
|
||||
size_t new_col = col + sep_idx;
|
||||
if (pending == ' ') {
|
||||
// The pending space counts
|
||||
++new_col;
|
||||
}
|
||||
bool wrap = new_col > columns;
|
||||
|
||||
char sep = input[sep_idx];
|
||||
if (sep == ' ')
|
||||
sep = ' ';
|
||||
|
||||
if (wrap) {
|
||||
APPEND_CHAR('\n');
|
||||
APPEND_INDENT();
|
||||
col = indent;
|
||||
} else if (pending) {
|
||||
APPEND_CHAR(pending);
|
||||
++col;
|
||||
if (pending == '\n')
|
||||
{
|
||||
APPEND_INDENT();
|
||||
col = indent;
|
||||
}
|
||||
}
|
||||
|
||||
if (sep_idx) {
|
||||
APPEND(input, sep_idx);
|
||||
col += sep_idx;
|
||||
}
|
||||
|
||||
pending = sep;
|
||||
|
||||
input += sep_idx;
|
||||
if (*input != '\0') {
|
||||
// Skip the separator
|
||||
++input;
|
||||
}
|
||||
}
|
||||
|
||||
if (pending)
|
||||
APPEND_CHAR(pending);
|
||||
|
||||
return buf.s;
|
||||
|
||||
error:
|
||||
free(buf.s);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -62,4 +62,12 @@ char *
|
|||
utf8_from_wide_char(const wchar_t *s);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrap input lines to fit in `columns` columns
|
||||
*
|
||||
* Break input lines at word boundaries (spaces) so that they fit in `columns`
|
||||
* columns, left-indented by `indent` spaces.
|
||||
*/
|
||||
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -299,6 +299,44 @@ static void test_strlist_contains(void) {
|
|||
assert(strlist_contains("xyz", '\0', "xyz"));
|
||||
}
|
||||
|
||||
static void test_wrap_lines(void) {
|
||||
const char *s = "This is a text to test line wrapping. The lines must be "
|
||||
"wrapped at a space or a line break.\n"
|
||||
"\n"
|
||||
"This rectangle must remains a rectangle because it is "
|
||||
"drawn in lines having lengths lower than the specified "
|
||||
"number of columns:\n"
|
||||
" +----+\n"
|
||||
" | |\n"
|
||||
" +----+\n";
|
||||
|
||||
// |---- 1 1 2 2|
|
||||
// |0 5 0 5 0 3| <-- 24 columns
|
||||
const char *expected = " This is a text to\n"
|
||||
" test line wrapping.\n"
|
||||
" The lines must be\n"
|
||||
" wrapped at a space\n"
|
||||
" or a line break.\n"
|
||||
" \n"
|
||||
" This rectangle must\n"
|
||||
" remains a rectangle\n"
|
||||
" because it is drawn\n"
|
||||
" in lines having\n"
|
||||
" lengths lower than\n"
|
||||
" the specified number\n"
|
||||
" of columns:\n"
|
||||
" +----+\n"
|
||||
" | |\n"
|
||||
" +----+\n";
|
||||
|
||||
char *formatted = sc_str_wrap_lines(s, 24, 4);
|
||||
assert(formatted);
|
||||
|
||||
assert(!strcmp(formatted, expected));
|
||||
|
||||
free(formatted);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
@ -317,5 +355,6 @@ int main(int argc, char *argv[]) {
|
|||
test_parse_integers();
|
||||
test_parse_integer_with_suffix();
|
||||
test_strlist_contains();
|
||||
test_wrap_lines();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue