Was cooking for more than one month.
bf1831 string: Speed up xvasprintf().
bcc083 string: Add discard feature for for_each_line().
e3868d string: Simplify return value of for_each_line().
d1f0f0 string: Simplify for_each_line().
23b121 string: Clean up for_each_line() and related functions.
6256ed string: Replace the for_each_line_modes enum by a bitmap.
14e689 gui: Don't sleep before executing the status command.
e90c6c gui: Speed up add_spaces().
20e2c6 gui: Rename do_exit().
ef0508 gui: Remove superfluous cmd_died.
d7562b gui: Check stdin for readability.
a44fa6 gui: Discard overlong input lines.
Conflicts:
gui.c
0.?.? (to be announced) "spectral gravity"
------------------------------------------
+ - UTF8 support for para_gui and the mp3 audio format handler.
+ - Scheduler improvements and fixes.
+ - The obsolete gettimeofday() function has been replaced
+ by clock_gettime() on systems which support it.
++ - Speed and usability improvements for para_gui.
+
-----------------------------------------
0.4.12 (2012-12-20) "volatile relativity"
-----------------------------------------
client_ldflags=""
gui_cmdline_objs="add_cmdline(gui)"
- gui_errlist_objs="exec signal string stat ringbuffer fd gui gui_theme"
+ gui_errlist_objs="exec signal string stat ringbuffer fd gui gui_theme time"
gui_objs="$gui_cmdline_objs $gui_errlist_objs"
play_errlist_objs="play fd sched ggo buffer_tree time string net
afh_recv afh_common
AC_MSG_ERROR([fatal: buggy snprintf() detected])
fi])
AX_FUNC_SNPRINTF()
+################################################################## clock_gettime
+clock_gettime_lib=
+AC_CHECK_LIB([c], [clock_gettime], [clock_gettime_lib=c], [
+ AC_CHECK_LIB([rt], [clock_gettime], [clock_gettime_lib=rt], [], [])
+])
+if test -n "$clock_gettime_lib"; then
+ AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [
+ define to 1 if clock_gettime() is supported])
+fi
+if test "$clock_gettime_lib" = "rt"; then
+ AC_SUBST(clock_gettime_ldflags, -lrt)
+fi
########################################################################### osl
have_osl=yes
OLD_CPPFLAGS="$CPPFLAGS"
AC_CHECK_HEADER(curses.h, [], [
have_curses="no"
])
-AC_CHECK_LIB([curses], [initscr], [], [
- have_curses="no"
-])
+gui_ldflags="$curses_libs"
+AC_CHECK_LIB([ncursesw], [initscr],
+ [gui_ldflags="$curses_libs -lncursesw"], [
+ AC_CHECK_LIB([curses], [initscr],
+ [gui_ldflags="$curses_libs -lcurses"],
+ [have_curses="no"]
+ )
+ ]
+)
if test "$have_curses" = "yes"; then
AC_SUBST(curses_cppflags)
- AC_DEFINE(HAVE_NCURSES, 1, [define to 1 to turn on curses support])
extras="$extras gui"
executables="$executables gui"
else
objlist_to_errlist($audioc_errlist_objs), errors used by para_audioc)
AC_SUBST(gui_objs, add_dot_o($gui_objs))
+AC_SUBST(gui_ldflags, $gui_ldflags)
AC_DEFINE_UNQUOTED(INIT_GUI_ERRLISTS,
objlist_to_errlist($gui_errlist_objs), errors used by para_gui)
AC_DEFINE_UNQUOTED(INIT_PLAY_ERRLISTS,
objlist_to_errlist($play_errlist_objs), errors used by para_play)
-AC_MSG_NOTICE(play objs: $play_objs)
-
enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)"
AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS,
enum of supported filters)
#include <signal.h>
#include <sys/types.h>
#include <curses.h>
+#include <locale.h>
+ #include <sys/time.h>
#include "gui.cmdline.h"
#include "para.h"
static unsigned scroll_position;
- static int cmd_died, curses_active;
+ static int curses_active;
static pid_t cmd_pid;
static int command_fds[2];
return file_exists(tmp)? tmp: NULL;
}
- /*
- * print num spaces to curses window
- */
+ /* Print given number of spaces to curses window. */
static void add_spaces(WINDOW* win, unsigned int num)
{
- while (num > 0) {
- num--;
- waddstr(win, " ");
+ char space[] = " ";
+ unsigned sz = sizeof(space);
+
+ while (num >= sz) {
+ waddstr(win, space);
+ num -= sz;
+ }
+ if (num > 0) {
+ assert(num < sz);
+ space[num] = '\0';
+ waddstr(win, space);
}
}
static int align_str(WINDOW* win, char *str, unsigned int len,
unsigned int align)
{
- int i, num; /* of spaces */
+ int ret, i, num; /* of spaces */
+ size_t width;
if (!win || !str)
- return -1;
- num = len - strlen(str);
+ return 0;
+ ret = strwidth(str, &width);
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ width = 0;
+ str[0] = '\0';
+ }
+ num = len - width;
if (num < 0) {
str[len] = '\0';
num = 0;
return RINGBUFFER_SIZE - 1;
}
+/*
+returns number of first visible rbe, *lines is the number of lines drawn.
+ */
static int draw_top_rbe(unsigned *lines)
{
- unsigned len;
- int offset, fvr = first_visible_rbe(lines);
+ int ret, fvr = first_visible_rbe(lines);
struct rb_entry *rbe;
+ size_t bytes_to_skip, cells_to_skip, width;
if (fvr < 0)
return -1;
rbe = ringbuffer_get(bot_win_rb, fvr);
if (!rbe)
return -1;
- len = strlen(rbe->msg);
if (*lines > bot.lines) {
- /* first rbe is only partially visible */
- offset = (*lines - bot.lines) * bot.cols;
- assert(offset <= len);
- } else
- offset = 0;
+ /* rbe is partially visible multi-line */
+ cells_to_skip = (*lines - bot.lines) * bot.cols;
+ ret = skip_cells(rbe->msg, cells_to_skip, &bytes_to_skip);
+ if (ret < 0)
+ return ret;
+ ret = strwidth(rbe->msg + bytes_to_skip, &width);
+ if (ret < 0)
+ return ret;
+ } else {
+ bytes_to_skip = 0;
+ width = rbe->len;
+ }
wattron(bot.win, COLOR_PAIR(rbe->color));
- waddstr(bot.win, rbe->msg + offset);
- *lines = NUM_LINES(len - offset);
+ waddstr(bot.win, rbe->msg + bytes_to_skip);
+ *lines = NUM_LINES(width);
return fvr;
}
static void rb_add_entry(int color, char *msg)
{
- struct rb_entry *old, *new = para_malloc(sizeof(struct rb_entry));
+ struct rb_entry *old, *new;
int x, y;
+ size_t len;
+
+ if (strwidth(msg, &len) < 0)
+ return;
+ new = para_malloc(sizeof(struct rb_entry));
new->color = color;
- new->len = strlen(msg);
+ new->len = len;
new->msg = msg;
old = ringbuffer_add(bot_win_rb, new);
// fprintf(stderr, "added: %s\n", new->msg);
para_sigaction(SIGHUP, SIG_IGN);
}
- __noreturn static void do_exit(int ret)
+ /* kill every process in the process group and exit */
+ __noreturn static void kill_pg_and_die(int ret)
{
para_sigaction(SIGTERM, SIG_IGN);
kill(0, SIGTERM);
__noreturn static void finish(int ret)
{
shutdown_curses();
- do_exit(ret);
+ kill_pg_and_die(ret);
}
/*
va_start(argp, fmt);
vfprintf(outfd, fmt, argp);
va_end(argp);
- do_exit(ret);
+ kill_pg_and_die(ret);
}
static void print_welcome(void)
ret = para_reap_child(&pid);
if (ret <= 0)
return;
- if (pid == cmd_pid) {
+ if (pid == cmd_pid)
cmd_pid = 0;
- cmd_died = 1;
- }
goto reap_next_child;
}
}
}
- static int open_stat_pipe(void)
+ static void status_pre_select(fd_set *rfds, int *max_fileno, struct timeval *tv)
{
- static int init = 1;
+ static struct timeval next_exec, atm, diff;
int ret, fds[3] = {0, 1, 0};
pid_t pid;
- if (init)
- init = 0;
- else
- /*
- * Sleep a bit to avoid a busy loop. As the call to sleep() may
- * be interrupted by SIGCHLD, we simply wait until the call
- * succeeds.
- */
- while (sleep(2))
- ; /* nothing */
+ if (stat_pipe >= 0)
+ goto success;
+ /* Avoid busy loop */
+ gettimeofday(&atm, NULL);
+ if (tv_diff(&next_exec, &atm, &diff) > 0) {
+ if (tv_diff(&diff, tv, NULL) < 0)
+ *tv = diff;
+ return;
+ }
+ next_exec.tv_sec = atm.tv_sec + 2;
ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds);
if (ret < 0)
- return ret;
+ return;
ret = mark_fd_nonblocking(fds[1]);
- if (ret >= 0)
- return fds[1];
- close(fds[1]);
- return ret;
+ if (ret < 0) {
+ close(fds[1]);
+ return;
+ }
+ stat_pipe = fds[1];
+ success:
+ para_fd_set(stat_pipe, rfds, max_fileno);
}
- #define COMMAND_BUF_SIZE 4096
+ #define COMMAND_BUF_SIZE 32768
/*
* This is the core select loop. Besides the (internal) signal
char command_buf[2][COMMAND_BUF_SIZE] = {"", ""};
int cbo[2] = {0, 0}; /* command buf offsets */
struct timeval tv;
+ unsigned flags[2] = {0, 0}; /* for for_each_line() */
repeat:
tv.tv_sec = conf.timeout_arg / 1000;
// ret = refresh_status();
FD_ZERO(&rfds);
max_fileno = 0;
- if (stat_pipe < 0)
- stat_pipe = open_stat_pipe();
- if (stat_pipe >= 0)
- para_fd_set(stat_pipe, &rfds, &max_fileno);
+ status_pre_select(&rfds, &max_fileno, &tv);
/* signal pipe */
para_fd_set(signal_pipe, &rfds, &max_fileno);
/* command pipe only for COMMAND_MODE */
if (command_fds[1] >= 0)
para_fd_set(command_fds[1], &rfds, &max_fileno);
}
+ if (mode == GETCH_MODE || mode == COMMAND_MODE)
+ para_fd_set(STDIN_FILENO, &rfds, &max_fileno);
ret = para_select(max_fileno + 1, &rfds, NULL, &tv);
if (ret <= 0)
goto check_return; /* skip fd checks */
COMMAND_BUF_SIZE - 1 - cbo[i], &rfds, &sz);
cbo[i] += sz;
sz = cbo[i];
- cbo[i] = for_each_line(command_buf[i], cbo[i],
+ cbo[i] = for_each_line(flags[i], command_buf[i], cbo[i],
add_output_line, &i);
- if (sz != cbo[i])
+ if (sz != cbo[i]) { /* at least one line found */
wrefresh(bot.win);
+ flags[i] = 0;
+ }
if (ret < 0) {
PARA_NOTICE_LOG("closing command fd %d: %s",
i, para_strerror(-ret));
close(command_fds[i]);
command_fds[i] = -1;
+ flags[i] = 0;
if (command_fds[!i] < 0) /* both fds closed */
return 0;
}
+ if (cbo[i] == COMMAND_BUF_SIZE - 1) {
+ PARA_NOTICE_LOG("discarding overlong line");
+ cbo[i] = 0;
+ flags[i] = FELF_DISCARD_FIRST;
+ }
}
}
ret = read_stat_pipe(&rfds);
return ret;
break;
case EXTERNAL_MODE:
- if (cmd_died) {
- cmd_died = 0;
+ if (cmd_pid == 0)
return 0;
- }
}
goto repeat;
}
shutdown_curses();
if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
return;
- cmd_died = 0;
do_select(EXTERNAL_MODE);
init_curses();
}
struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
if (!rbe)
break;
- lines += NUM_LINES(strlen(rbe->msg));
+ lines += NUM_LINES(rbe->len);
}
i++;
if (lines > 0 && scroll_position != i) {
struct rb_entry *rbe = ringbuffer_get(bot_win_rb, i);
if (!rbe)
break;
- lines += NUM_LINES(strlen(rbe->msg));
+ lines += NUM_LINES(rbe->len);
}
if (lines) {
scroll_position = i;
top.lines = theme.top_lines_default;
setup_signal_handling();
bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
+ setlocale(LC_CTYPE, "");
initscr(); /* needed only once, always successful */
init_curses();
print_welcome();
/** \file string.c Memory allocation and string handling functions. */
-#include <sys/time.h> /* gettimeofday */
+#define _GNU_SOURCE
+
#include <pwd.h>
#include <sys/utsname.h> /* uname() */
+
#include <string.h>
#include <regex.h>
+#include <langinfo.h>
+#include <wchar.h>
+#include <wctype.h>
+
#include "para.h"
#include "string.h"
#include "error.h"
__printf_2_0 unsigned xvasprintf(char **result, const char *fmt, va_list ap)
{
int ret;
- size_t size;
+ size_t size = 150;
va_list aq;
+ *result = para_malloc(size + 1);
va_copy(aq, ap);
- ret = vsnprintf(NULL, 0, fmt, aq);
+ ret = vsnprintf(*result, size, fmt, aq);
va_end(aq);
assert(ret >= 0);
+ if (ret < size) /* OK */
+ return ret;
size = ret + 1;
- *result = para_malloc(size);
+ *result = para_realloc(*result, size);
va_copy(aq, ap);
ret = vsnprintf(*result, size, fmt, aq);
va_end(aq);
}
/**
- * Used to distinguish between read-only and read-write mode.
+ * Call a custom function for each complete line.
+ *
+ * \param flags Any combination of flags defined in \ref for_each_line_flags.
+ * \param buf The buffer containing data separated by newlines.
+ * \param size The number of bytes in \a buf.
+ * \param line_handler The custom function.
+ * \param private_data Pointer passed to \a line_handler.
+ *
+ * For each complete line in \p buf, \p line_handler is called. The first
+ * argument to \p line_handler is (a copy of) the current line, and \p
+ * private_data is passed as the second argument. If the \p FELF_READ_ONLY
+ * flag is unset, a pointer into \a buf is passed to the line handler,
+ * otherwise a pointer to a copy of the current line is passed instead. This
+ * copy is freed immediately after the line handler returns.
*
- * \sa for_each_line(), for_each_line_ro().
+ * The function returns if \p line_handler returns a negative value or no more
+ * lines are in the buffer. The rest of the buffer (last chunk containing an
+ * incomplete line) is moved to the beginning of the buffer if FELF_READ_ONLY is
+ * unset.
+ *
+ * \return On success this function returns the number of bytes not handled to
+ * \p line_handler. The only possible error is a negative return value from the
+ * line handler. In this case processing stops and the return value of the line
+ * handler is returned to indicate failure.
+ *
+ * \sa \ref for_each_line_flags.
*/
- enum for_each_line_modes{
- /** Activate read-only mode. */
- LINE_MODE_RO,
- /** Activate read-write mode. */
- LINE_MODE_RW
- };
-
- static int for_each_complete_line(enum for_each_line_modes mode, char *buf,
- size_t size, line_handler_t *line_handler, void *private_data)
+ int for_each_line(unsigned flags, char *buf, size_t size,
+ line_handler_t *line_handler, void *private_data)
{
char *start = buf, *end;
int ret, i, num_lines = 0;
} else
end = next_cr;
num_lines++;
- if (!line_handler) {
- start = ++end;
- continue;
- }
- if (mode == LINE_MODE_RO) {
- size_t s = end - start;
- char *b = para_malloc(s + 1);
- memcpy(b, start, s);
- b[s] = '\0';
- // PARA_NOTICE_LOG("b: %s, start: %s\n", b, start);
- ret = line_handler(b, private_data);
- free(b);
- } else {
- *end = '\0';
- ret = line_handler(start, private_data);
+ if (!(flags & FELF_DISCARD_FIRST) || start != buf) {
+ if (flags & FELF_READ_ONLY) {
+ size_t s = end - start;
+ char *b = para_malloc(s + 1);
+ memcpy(b, start, s);
+ b[s] = '\0';
+ ret = line_handler(b, private_data);
+ free(b);
+ } else {
+ *end = '\0';
+ ret = line_handler(start, private_data);
+ }
+ if (ret < 0)
+ return ret;
}
- if (ret < 0)
- return ret;
start = ++end;
}
- if (!line_handler || mode == LINE_MODE_RO)
- return num_lines;
i = buf + size - start;
- if (i && i != size)
+ if (i && i != size && !(flags & FELF_READ_ONLY))
memmove(buf, start, i);
return i;
}
- /**
- * Call a custom function for each complete line.
- *
- * \param buf The buffer containing data separated by newlines.
- * \param size The number of bytes in \a buf.
- * \param line_handler The custom function.
- * \param private_data Pointer passed to \a line_handler.
- *
- * If \p line_handler is \p NULL, the function returns the number of complete
- * lines in \p buf. Otherwise, \p line_handler is called for each complete
- * line in \p buf. The first argument to \p line_handler is the current line,
- * and \p private_data is passed as the second argument. The function returns
- * if \p line_handler returns a negative value or no more lines are in the
- * buffer. The rest of the buffer (last chunk containing an incomplete line)
- * is moved to the beginning of the buffer.
- *
- * \return If \p line_handler is not \p NULL, this function returns the number
- * of bytes not handled to \p line_handler on success, or the negative return
- * value of the \p line_handler on errors.
- *
- * \sa for_each_line_ro().
- */
- int for_each_line(char *buf, size_t size, line_handler_t *line_handler,
- void *private_data)
- {
- return for_each_complete_line(LINE_MODE_RW, buf, size, line_handler,
- private_data);
- }
-
- /**
- * Call a custom function for each complete line.
- *
- * \param buf Same meaning as in \p for_each_line().
- * \param size Same meaning as in \p for_each_line().
- * \param line_handler Same meaning as in \p for_each_line().
- * \param private_data Same meaning as in \p for_each_line().
- *
- * This function behaves like \p for_each_line(), but \a buf is left unchanged.
- *
- * \return On success, the function returns the number of complete lines in \p
- * buf, otherwise the (negative) return value of \p line_handler is returned.
- *
- * \sa for_each_line().
- */
- int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler,
- void *private_data)
- {
- return for_each_complete_line(LINE_MODE_RO, buf, size, line_handler,
- private_data);
- }
-
/** Return the hex characters of the lower 4 bits. */
#define hex(a) (hexchar[(a) & 15])
return NULL;
return safe_strdup(src + keylen + 1, len - keylen - 1);
}
+
+static bool utf8_mode(void)
+{
+ static bool initialized, have_utf8;
+
+ if (!initialized) {
+ char *info = nl_langinfo(CODESET);
+ have_utf8 = (info && strcmp(info, "UTF-8") == 0);
+ initialized = true;
+ PARA_INFO_LOG("%susing UTF-8 character encoding\n",
+ have_utf8? "" : "not ");
+ }
+ return have_utf8;
+}
+
+/*
+ * glibc's wcswidth returns -1 if the string contains a tab character, which
+ * makes the function next to useless. The two functions below are taken from
+ * mutt.
+ */
+
+#define IsWPrint(wc) (iswprint(wc) || wc >= 0xa0)
+
+static int mutt_wcwidth(wchar_t wc, size_t pos)
+{
+ int n;
+
+ if (wc == 0x09) /* tab */
+ return (pos | 7) + 1 - pos;
+ n = wcwidth(wc);
+ if (IsWPrint(wc) && n > 0)
+ return n;
+ if (!(wc & ~0x7f))
+ return 2;
+ if (!(wc & ~0xffff))
+ return 6;
+ return 10;
+}
+
+static size_t mutt_wcswidth(const wchar_t *s, size_t n)
+{
+ size_t w = 0;
+
+ while (n--)
+ w += mutt_wcwidth(*s++, w);
+ return w;
+}
+
+/**
+ * Skip a given number of cells at the beginning of a string.
+ *
+ * \param s The input string.
+ * \param cells_to_skip Desired number of cells that should be skipped.
+ * \param bytes_to_skip Result.
+ *
+ * This function computes how many input bytes must be skipped to advance a
+ * string by the given width. If the current character encoding is not UTF-8,
+ * this is simply the given number of cells, i.e. \a cells_to_skip. Otherwise,
+ * \a s is treated as a multibyte string and on successful return, \a s +
+ * bytes_to_skip points to the start of a multibyte string such that the total
+ * width of the multibyte characters that are skipped by advancing \a s that
+ * many bytes equals at least \a cells_to_skip.
+ *
+ * \return Standard.
+ */
+int skip_cells(const char *s, size_t cells_to_skip, size_t *bytes_to_skip)
+{
+ wchar_t wc;
+ mbstate_t ps;
+ size_t n, bytes_parsed, cells_skipped;
+
+ *bytes_to_skip = 0;
+ if (cells_to_skip == 0)
+ return 0;
+ if (!utf8_mode()) {
+ *bytes_to_skip = cells_to_skip;
+ return 0;
+ }
+ bytes_parsed = cells_skipped = 0;
+ memset(&ps, 0, sizeof(ps));
+ n = strlen(s);
+ while (cells_to_skip > cells_skipped) {
+ size_t mbret;
+
+ mbret = mbrtowc(&wc, s + bytes_parsed, n - bytes_parsed, &ps);
+ assert(mbret != 0);
+ if (mbret == (size_t)-1 || mbret == (size_t)-2)
+ return -ERRNO_TO_PARA_ERROR(EILSEQ);
+ bytes_parsed += mbret;
+ cells_skipped += mutt_wcwidth(wc, cells_skipped);
+ }
+ *bytes_to_skip = bytes_parsed;
+ return 1;
+}
+
+/**
+ * Compute the width of an UTF-8 string.
+ *
+ * \param s The string.
+ * \param result The width of \a s is returned here.
+ *
+ * If not in UTF8-mode. this function is just a wrapper for strlen(3).
+ * Otherwise \a s is treated as an UTF-8 string and its display width is
+ * computed. Note that this function may fail if the underlying call to
+ * mbsrtowcs(3) fails, so the caller must check the return value.
+ *
+ * \sa nl_langinfo(3), wcswidth(3).
+ *
+ * \return Standard.
+ */
+__must_check int strwidth(const char *s, size_t *result)
+{
+ const char *src = s;
+ mbstate_t state;
+ static wchar_t *dest;
+ size_t num_wchars;
+
+ /*
+ * Never call any log function here. This may result in an endless loop
+ * as para_gui's para_log() calls this function.
+ */
+
+ if (!utf8_mode()) {
+ *result = strlen(s);
+ return 0;
+ }
+ memset(&state, 0, sizeof(state));
+ *result = 0;
+ num_wchars = mbsrtowcs(NULL, &src, 0, &state);
+ if (num_wchars == (size_t)-1)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ if (num_wchars == 0)
+ return 0;
+ dest = para_malloc(num_wchars * sizeof(*dest));
+ src = s;
+ memset(&state, 0, sizeof(state));
+ num_wchars = mbsrtowcs(dest, &src, num_wchars, &state);
+ assert(num_wchars > 0 && num_wchars != (size_t)-1);
+ *result = mutt_wcswidth(dest, num_wchars);
+ free(dest);
+ return 1;
+}
void *private_data;
};
+ /**
+ * Controls the behavior of for_each_line().
+ *
+ * \sa for_each_line().
+ */
+ enum for_each_line_flags {
+ /** Activate read-only mode. */
+ FELF_READ_ONLY = 1 << 0,
+ /** Don't call line handler for the first input line. */
+ FELF_DISCARD_FIRST = 1 << 1,
+ };
+
+ /** Used for \ref for_each_line(). */
+ typedef int line_handler_t(char *, void *);
+ int for_each_line(unsigned flags, char *buf, size_t size,
+ line_handler_t *line_handler, void *private_data);
+
/**
* Write the contents of a status item to a para_buffer.
*
__must_check __malloc char *para_homedir(void);
__malloc char *para_hostname(void);
__printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...);
- /** Used for for_each_line() and for_each_line_ro(). */
- typedef int line_handler_t(char *, void *);
- int for_each_line(char *buf, size_t size, line_handler_t *line_handler,
- void *private_data);
- int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler,
- void *private_data);
int para_atoi64(const char *str, int64_t *result);
int para_atoi32(const char *str, int32_t *value);
int get_loglevel_by_name(const char *txt);
int compute_word_num(const char *buf, const char *delim, int offset);
char *safe_strdup(const char *src, size_t len);
char *key_value_copy(const char *src, size_t len, const char *key);
+int skip_cells(const char *s, size_t cells_to_skip, size_t *result);
+__must_check int strwidth(const char *s, size_t *result);