| /* |
| * table.c - functions handling the data at the table level |
| * |
| * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> |
| * |
| * This file may be redistributed under the terms of the |
| * GNU Lesser General Public License. |
| */ |
| |
| /** |
| * SECTION: table_print |
| * @title: Table print |
| * @short_description: output functions |
| * |
| * Table output API. |
| */ |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <ctype.h> |
| |
| #include "nls.h" |
| #include "mbsalign.h" |
| #include "widechar.h" |
| #include "ttyutils.h" |
| #include "carefulputc.h" |
| #include "smartcolsP.h" |
| |
| /* This is private struct to work with output data */ |
| struct libscols_buffer { |
| char *begin; /* begin of the buffer */ |
| char *cur; /* current end of the buffer */ |
| char *encdata; /* encoded buffer mbs_safe_encode() */ |
| |
| size_t bufsz; /* size of the buffer */ |
| size_t art_idx; /* begin of the tree ascii art or zero */ |
| }; |
| |
| static struct libscols_buffer *new_buffer(size_t sz) |
| { |
| struct libscols_buffer *buf = malloc(sz + sizeof(struct libscols_buffer)); |
| |
| if (!buf) |
| return NULL; |
| |
| buf->cur = buf->begin = ((char *) buf) + sizeof(struct libscols_buffer); |
| buf->encdata = NULL; |
| buf->bufsz = sz; |
| |
| DBG(BUFF, ul_debugobj(buf, "alloc (size=%zu)", sz)); |
| return buf; |
| } |
| |
| static void free_buffer(struct libscols_buffer *buf) |
| { |
| if (!buf) |
| return; |
| DBG(BUFF, ul_debugobj(buf, "dealloc")); |
| free(buf->encdata); |
| free(buf); |
| } |
| |
| static int buffer_reset_data(struct libscols_buffer *buf) |
| { |
| if (!buf) |
| return -EINVAL; |
| |
| /*DBG(BUFF, ul_debugobj(buf, "reset data"));*/ |
| buf->begin[0] = '\0'; |
| buf->cur = buf->begin; |
| buf->art_idx = 0; |
| return 0; |
| } |
| |
| static int buffer_append_data(struct libscols_buffer *buf, const char *str) |
| { |
| size_t maxsz, sz; |
| |
| if (!buf) |
| return -EINVAL; |
| if (!str || !*str) |
| return 0; |
| |
| sz = strlen(str); |
| maxsz = buf->bufsz - (buf->cur - buf->begin); |
| |
| if (maxsz <= sz) |
| return -EINVAL; |
| |
| memcpy(buf->cur, str, sz + 1); |
| buf->cur += sz; |
| return 0; |
| } |
| |
| static int buffer_set_data(struct libscols_buffer *buf, const char *str) |
| { |
| int rc = buffer_reset_data(buf); |
| return rc ? rc : buffer_append_data(buf, str); |
| } |
| |
| /* save the current buffer position to art_idx */ |
| static void buffer_set_art_index(struct libscols_buffer *buf) |
| { |
| if (buf) { |
| buf->art_idx = buf->cur - buf->begin; |
| /*DBG(BUFF, ul_debugobj(buf, "art index: %zu", buf->art_idx));*/ |
| } |
| } |
| |
| static char *buffer_get_data(struct libscols_buffer *buf) |
| { |
| return buf ? buf->begin : NULL; |
| } |
| |
| /* encode data by mbs_safe_encode() to avoid control and non-printable chars */ |
| static char *buffer_get_safe_data(struct libscols_buffer *buf, size_t *cells) |
| { |
| char *data = buffer_get_data(buf); |
| char *res = NULL; |
| |
| if (!data) |
| goto nothing; |
| |
| if (!buf->encdata) { |
| buf->encdata = malloc(mbs_safe_encode_size(buf->bufsz) + 1); |
| if (!buf->encdata) |
| goto nothing; |
| } |
| |
| res = mbs_safe_encode_to_buffer(data, cells, buf->encdata); |
| if (!res || !*cells || *cells == (size_t) -1) |
| goto nothing; |
| return res; |
| nothing: |
| *cells = 0; |
| return NULL; |
| } |
| |
| /* returns size in bytes of the ascii art (according to art_idx) in safe encoding */ |
| static size_t buffer_get_safe_art_size(struct libscols_buffer *buf) |
| { |
| char *data = buffer_get_data(buf); |
| size_t bytes = 0; |
| |
| if (!data || !buf->art_idx) |
| return 0; |
| |
| mbs_safe_nwidth(data, buf->art_idx, &bytes); |
| return bytes; |
| } |
| |
| /* returns pointer to the end of used data */ |
| static int line_ascii_art_to_buffer(struct libscols_table *tb, |
| struct libscols_line *ln, |
| struct libscols_buffer *buf) |
| { |
| const char *art; |
| int rc; |
| |
| assert(ln); |
| assert(buf); |
| |
| if (!ln->parent) |
| return 0; |
| |
| rc = line_ascii_art_to_buffer(tb, ln->parent, buf); |
| if (rc) |
| return rc; |
| |
| if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) |
| art = " "; |
| else |
| art = tb->symbols->vert; |
| |
| return buffer_append_data(buf, art); |
| } |
| |
| #define is_last_column(_tb, _cl) \ |
| list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) |
| |
| #define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") |
| #define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") |
| |
| /* print padding or asci-art instead of data of @cl */ |
| static void print_empty_cell(struct libscols_table *tb, |
| struct libscols_column *cl, |
| struct libscols_line *ln, /* optional */ |
| size_t bufsz) |
| { |
| size_t len_pad = 0; /* in screen cells as opposed to bytes */ |
| |
| /* generate tree asci-art rather than padding */ |
| if (ln && scols_column_is_tree(cl)) { |
| if (!ln->parent) { |
| /* only print symbols->vert if followed by something */ |
| if (!list_empty(&ln->ln_branch)) { |
| fputs(tb->symbols->vert, tb->out); |
| len_pad = mbs_safe_width(tb->symbols->vert); |
| } |
| } else { |
| /* use the same draw function as though we were intending to draw an L-shape */ |
| struct libscols_buffer *art = new_buffer(bufsz); |
| char *data; |
| |
| if (art) { |
| /* whatever the rc, len_pad will be sensible */ |
| line_ascii_art_to_buffer(tb, ln, art); |
| data = buffer_get_safe_data(art, &len_pad); |
| if (data && len_pad) |
| fputs(data, tb->out); |
| free_buffer(art); |
| } |
| } |
| } |
| /* fill rest of cell with space */ |
| for(; len_pad <= cl->width; ++len_pad) |
| fputc(' ', tb->out); |
| } |
| |
| /* Fill the start of a line with padding (or with tree ascii-art). |
| * |
| * This is necessary after a long non-truncated column, as this requires the |
| * next column to be printed on the next line. For example (see 'DDD'): |
| * |
| * aaa bbb ccc ddd eee |
| * AAA BBB CCCCCCC |
| * DDD EEE |
| * ^^^^^^^^^^^^ |
| * new line padding |
| */ |
| static void print_newline_padding(struct libscols_table *tb, |
| struct libscols_column *cl, |
| struct libscols_line *ln, /* optional */ |
| size_t bufsz, |
| size_t len) |
| { |
| size_t i; |
| |
| assert(tb); |
| assert(cl); |
| |
| fputs(linesep(tb), tb->out); /* line break */ |
| |
| /* fill cells after line break */ |
| for (i = 0; i <= (size_t) cl->seqnum; i++) |
| print_empty_cell(tb, scols_table_get_column(tb, i), ln, bufsz); |
| } |
| |
| static int print_data(struct libscols_table *tb, |
| struct libscols_column *cl, |
| struct libscols_line *ln, /* optional */ |
| struct libscols_cell *ce, /* optional */ |
| struct libscols_buffer *buf) |
| { |
| size_t len = 0, i, width, bytes; |
| const char *color = NULL; |
| char *data; |
| |
| assert(tb); |
| assert(cl); |
| |
| DBG(TAB, ul_debugobj(tb, |
| " -> data, column=%p, line=%p, cell=%p, buff=%p", |
| cl, ln, ce, buf)); |
| |
| data = buffer_get_data(buf); |
| if (!data) |
| data = ""; |
| |
| /* raw mode */ |
| if (scols_table_is_raw(tb)) { |
| fputs_nonblank(data, tb->out); |
| if (!is_last_column(tb, cl)) |
| fputs(colsep(tb), tb->out); |
| return 0; |
| } |
| |
| /* NAME=value mode */ |
| if (scols_table_is_export(tb)) { |
| fprintf(tb->out, "%s=", scols_cell_get_data(&cl->header)); |
| fputs_quoted(data, tb->out); |
| if (!is_last_column(tb, cl)) |
| fputs(colsep(tb), tb->out); |
| return 0; |
| } |
| |
| if (tb->colors_wanted) { |
| if (ce && !color) |
| color = ce->color; |
| if (ln && !color) |
| color = ln->color; |
| if (!color) |
| color = cl->color; |
| } |
| |
| /* encode, note that 'len' and 'width' are number of cells, not bytes */ |
| data = buffer_get_safe_data(buf, &len); |
| if (!data) |
| data = ""; |
| width = cl->width; |
| bytes = strlen(data); |
| |
| if (is_last_column(tb, cl) && len < width && !scols_table_is_maxout(tb)) |
| width = len; |
| |
| /* truncate data */ |
| if (len > width && scols_column_is_trunc(cl)) { |
| len = width; |
| bytes = mbs_truncate(data, &len); /* updates 'len' */ |
| |
| if (bytes == (size_t) -1) { |
| bytes = len = 0; |
| data = NULL; |
| } |
| } |
| |
| if (data) { |
| if (scols_column_is_right(cl)) { |
| size_t xw = cl->width; |
| if (color) |
| fputs(color, tb->out); |
| fprintf(tb->out, "%*s", (int) xw, data); |
| if (color) |
| fputs(UL_COLOR_RESET, tb->out); |
| if (len < xw) |
| len = xw; |
| } else if (color) { |
| char *p = data; |
| size_t art = buffer_get_safe_art_size(buf); |
| |
| /* we don't want to colorize tree ascii art */ |
| if (scols_column_is_tree(cl) && art && art < bytes) { |
| fwrite(p, 1, art, tb->out); |
| p += art; |
| } |
| |
| fputs(color, tb->out); |
| fputs(p, tb->out); |
| fputs(UL_COLOR_RESET, tb->out); |
| } else |
| fputs(data, tb->out); |
| } |
| for (i = len; i < width; i++) |
| fputc(' ', tb->out); /* padding */ |
| |
| if (is_last_column(tb, cl)) |
| return 0; |
| |
| if (len > width && !scols_column_is_trunc(cl)) |
| print_newline_padding(tb, cl, ln, buf->bufsz, len); /* next column starts on next line */ |
| else |
| fputs(colsep(tb), tb->out); /* columns separator */ |
| |
| return 0; |
| } |
| |
| static int cell_to_buffer(struct libscols_table *tb, |
| struct libscols_line *ln, |
| struct libscols_column *cl, |
| struct libscols_buffer *buf) |
| { |
| const char *data; |
| struct libscols_cell *ce; |
| int rc = 0; |
| |
| assert(tb); |
| assert(ln); |
| assert(cl); |
| assert(buf); |
| assert(cl->seqnum <= tb->ncols); |
| |
| buffer_reset_data(buf); |
| |
| ce = scols_line_get_cell(ln, cl->seqnum); |
| data = ce ? scols_cell_get_data(ce) : NULL; |
| if (!data) |
| return 0; |
| |
| if (!scols_column_is_tree(cl)) |
| return buffer_set_data(buf, data); |
| |
| /* |
| * Tree stuff |
| */ |
| if (ln->parent) { |
| rc = line_ascii_art_to_buffer(tb, ln->parent, buf); |
| |
| if (!rc && list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) |
| rc = buffer_append_data(buf, tb->symbols->right); |
| else if (!rc) |
| rc = buffer_append_data(buf, tb->symbols->branch); |
| if (!rc) |
| buffer_set_art_index(buf); |
| } |
| |
| if (!rc) |
| rc = buffer_append_data(buf, data); |
| return rc; |
| } |
| |
| /* |
| * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and |
| * control and non-printable characters can be encoded in the \x?? encoding. |
| */ |
| static int print_line(struct libscols_table *tb, |
| struct libscols_line *ln, |
| struct libscols_buffer *buf) |
| { |
| int rc = 0; |
| struct libscols_column *cl; |
| struct libscols_iter itr; |
| |
| assert(ln); |
| |
| DBG(TAB, ul_debugobj(tb, "printing line, line=%p, buff=%p", ln, buf)); |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { |
| rc = cell_to_buffer(tb, ln, cl, buf); |
| if (!rc) |
| rc = print_data(tb, cl, ln, |
| scols_line_get_cell(ln, cl->seqnum), |
| buf); |
| } |
| |
| if (rc == 0) |
| fputs(linesep(tb), tb->out); |
| return 0; |
| } |
| |
| static int print_header(struct libscols_table *tb, struct libscols_buffer *buf) |
| { |
| int rc = 0; |
| struct libscols_column *cl; |
| struct libscols_iter itr; |
| |
| assert(tb); |
| |
| if (scols_table_is_noheadings(tb) || |
| scols_table_is_export(tb) || |
| list_empty(&tb->tb_lines)) |
| return 0; |
| |
| DBG(TAB, ul_debugobj(tb, "printing header")); |
| |
| /* set the width according to the size of the data */ |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { |
| rc = buffer_set_data(buf, scols_cell_get_data(&cl->header)); |
| if (!rc) |
| rc = print_data(tb, cl, NULL, &cl->header, buf); |
| } |
| |
| if (rc == 0) |
| fputs(linesep(tb), tb->out); |
| return rc; |
| } |
| |
| static int print_table(struct libscols_table *tb, struct libscols_buffer *buf) |
| { |
| int rc; |
| struct libscols_line *ln; |
| struct libscols_iter itr; |
| |
| assert(tb); |
| |
| rc = print_header(tb, buf); |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) |
| rc = print_line(tb, ln, buf); |
| |
| return rc; |
| } |
| |
| static int print_tree_line(struct libscols_table *tb, |
| struct libscols_line *ln, |
| struct libscols_buffer *buf) |
| { |
| int rc; |
| struct list_head *p; |
| |
| rc = print_line(tb, ln, buf); |
| if (rc) |
| return rc; |
| if (list_empty(&ln->ln_branch)) |
| return 0; |
| |
| /* print all children */ |
| list_for_each(p, &ln->ln_branch) { |
| struct libscols_line *chld = |
| list_entry(p, struct libscols_line, ln_children); |
| rc = print_tree_line(tb, chld, buf); |
| if (rc) |
| break; |
| } |
| |
| return rc; |
| } |
| |
| static int print_tree(struct libscols_table *tb, struct libscols_buffer *buf) |
| { |
| int rc; |
| struct libscols_line *ln; |
| struct libscols_iter itr; |
| |
| assert(tb); |
| |
| DBG(TAB, ul_debugobj(tb, "printing tree")); |
| |
| rc = print_header(tb, buf); |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) { |
| if (ln->parent) |
| continue; |
| rc = print_tree_line(tb, ln, buf); |
| } |
| |
| return rc; |
| } |
| |
| static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) |
| { |
| DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " |
| "hint=%d, avg=%zu, max=%zu, min=%zu, " |
| "extreme=%s", |
| |
| cl->header.data, cl->seqnum, cl->width, |
| cl->width_hint > 1 ? (int) cl->width_hint : |
| (int) (cl->width_hint * tb->termwidth), |
| cl->width_avg, |
| cl->width_max, |
| cl->width_min, |
| cl->is_extreme ? "yes" : "not")); |
| } |
| |
| static void dbg_columns(struct libscols_table *tb) |
| { |
| struct libscols_iter itr; |
| struct libscols_column *cl; |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_column(tb, &itr, &cl) == 0) |
| dbg_column(tb, cl); |
| } |
| |
| /* |
| * This function counts column width. |
| * |
| * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function |
| * two times. The first pass counts the width and average width. If the column |
| * contains fields that are too large (a width greater than 2 * average) then |
| * the column is marked as "extreme". In the second pass all extreme fields |
| * are ignored and the column width is counted from non-extreme fields only. |
| */ |
| static int count_column_width(struct libscols_table *tb, |
| struct libscols_column *cl, |
| struct libscols_buffer *buf) |
| { |
| struct libscols_line *ln; |
| struct libscols_iter itr; |
| int count = 0, rc = 0; |
| size_t sum = 0; |
| |
| assert(tb); |
| assert(cl); |
| |
| cl->width = 0; |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_line(tb, &itr, &ln) == 0) { |
| size_t len; |
| char *data; |
| |
| rc = cell_to_buffer(tb, ln, cl, buf); |
| if (rc) |
| return rc; |
| |
| data = buffer_get_data(buf); |
| len = data ? mbs_safe_width(data) : 0; |
| |
| if (len == (size_t) -1) /* ignore broken multibyte strings */ |
| len = 0; |
| if (len > cl->width_max) |
| cl->width_max = len; |
| |
| if (cl->is_extreme && len > cl->width_avg * 2) |
| continue; |
| else if (scols_column_is_noextremes(cl)) { |
| sum += len; |
| count++; |
| } |
| if (len > cl->width) |
| cl->width = len; |
| } |
| |
| if (count && cl->width_avg == 0) { |
| cl->width_avg = sum / count; |
| |
| if (cl->width_max > cl->width_avg * 2) |
| cl->is_extreme = 1; |
| } |
| |
| /* check and set minimal column width */ |
| if (scols_cell_get_data(&cl->header)) |
| cl->width_min = mbs_safe_width(scols_cell_get_data(&cl->header)); |
| |
| /* enlarge to minimal width */ |
| if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) |
| cl->width = cl->width_min; |
| |
| /* use relative size for large columns */ |
| else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint |
| && cl->width_min < (size_t) cl->width_hint) |
| |
| cl->width = (size_t) cl->width_hint; |
| |
| ON_DBG(COL, dbg_column(tb, cl)); |
| return rc; |
| } |
| |
| |
| /* |
| * This is core of the scols_* voodo... |
| */ |
| static int recount_widths(struct libscols_table *tb, struct libscols_buffer *buf) |
| { |
| struct libscols_column *cl; |
| struct libscols_iter itr; |
| size_t width = 0; /* output width */ |
| int trunc_only, rc = 0; |
| int extremes = 0; |
| |
| |
| DBG(TAB, ul_debugobj(tb, "recounting widths (termwidth=%zu)", tb->termwidth)); |
| |
| /* set basic columns width |
| */ |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_column(tb, &itr, &cl) == 0) { |
| rc = count_column_width(tb, cl, buf); |
| if (rc) |
| return rc; |
| |
| width += cl->width + (is_last_column(tb, cl) ? 0 : 1); |
| extremes += cl->is_extreme; |
| } |
| |
| if (!tb->is_term) |
| return 0; |
| |
| /* reduce columns with extreme fields */ |
| if (width > tb->termwidth && extremes) { |
| DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)")); |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_column(tb, &itr, &cl) == 0) { |
| size_t org_width; |
| |
| if (!cl->is_extreme) |
| continue; |
| |
| org_width = cl->width; |
| rc = count_column_width(tb, cl, buf); |
| if (rc) |
| return rc; |
| |
| if (org_width > cl->width) |
| width -= org_width - cl->width; |
| else |
| extremes--; /* hmm... nothing reduced */ |
| } |
| } |
| |
| if (width < tb->termwidth) { |
| if (extremes) { |
| DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); |
| |
| /* enlarge the first extreme column */ |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_column(tb, &itr, &cl) == 0) { |
| size_t add; |
| |
| if (!cl->is_extreme) |
| continue; |
| |
| /* this column is tooo large, ignore? |
| if (cl->width_max - cl->width > |
| (tb->termwidth - width)) |
| continue; |
| */ |
| |
| add = tb->termwidth - width; |
| if (add && cl->width + add > cl->width_max) |
| add = cl->width_max - cl->width; |
| |
| cl->width += add; |
| width += add; |
| |
| if (width == tb->termwidth) |
| break; |
| } |
| } |
| |
| if (width < tb->termwidth && scols_table_is_maxout(tb)) { |
| DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); |
| |
| /* try enlarging all columns */ |
| while (width < tb->termwidth) { |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_column(tb, &itr, &cl) == 0) { |
| cl->width++; |
| width++; |
| if (width == tb->termwidth) |
| break; |
| } |
| } |
| } else if (width < tb->termwidth) { |
| /* enlarge the last column */ |
| struct libscols_column *col = list_entry( |
| tb->tb_columns.prev, struct libscols_column, cl_columns); |
| |
| DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); |
| |
| if (!scols_column_is_right(col) && tb->termwidth - width > 0) { |
| col->width += tb->termwidth - width; |
| width = tb->termwidth; |
| } |
| } |
| } |
| |
| /* bad, we have to reduce output width, this is done in two steps: |
| * 1) reduce columns with a relative width and with truncate flag |
| * 2) reduce columns with a relative width without truncate flag |
| */ |
| trunc_only = 1; |
| while (width > tb->termwidth) { |
| size_t org = width; |
| |
| DBG(TAB, ul_debugobj(tb, " reduce width (current=%zu, " |
| "wanted=%zu, mode=%s)", |
| width, tb->termwidth, |
| trunc_only ? "trunc-only" : "all-relative")); |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_column(tb, &itr, &cl) == 0) { |
| if (width <= tb->termwidth) |
| break; |
| if (cl->width_hint > 1 && !scols_column_is_trunc(cl)) |
| continue; /* never truncate columns with absolute sizes */ |
| if (scols_column_is_tree(cl)) |
| continue; /* never truncate the tree */ |
| if (trunc_only && !scols_column_is_trunc(cl)) |
| continue; |
| if (cl->width == cl->width_min) |
| continue; |
| |
| /* truncate column with relative sizes */ |
| if (cl->width_hint < 1 && cl->width > 0 && width > 0 && |
| cl->width > cl->width_hint * tb->termwidth) { |
| cl->width--; |
| width--; |
| } |
| /* truncate column with absolute size */ |
| if (cl->width_hint > 1 && cl->width > 0 && width > 0 && |
| !trunc_only) { |
| cl->width--; |
| width--; |
| } |
| |
| } |
| if (org == width) { |
| if (trunc_only) |
| trunc_only = 0; |
| else |
| break; |
| } |
| } |
| |
| DBG(TAB, ul_debugobj(tb, " result: %zu", width)); |
| ON_DBG(TAB, dbg_columns(tb)); |
| |
| return rc; |
| } |
| |
| static size_t strlen_line(struct libscols_line *ln) |
| { |
| size_t i, sz = 0; |
| |
| assert(ln); |
| |
| for (i = 0; i < ln->ncells; i++) { |
| struct libscols_cell *ce = scols_line_get_cell(ln, i); |
| const char *data = ce ? scols_cell_get_data(ce) : NULL; |
| |
| sz += data ? strlen(data) : 0; |
| } |
| |
| return sz; |
| } |
| |
| |
| |
| /** |
| * scols_print_table: |
| * @tb: table |
| * |
| * Prints the table to the output stream. |
| * |
| * Returns: 0, a negative value in case of an error. |
| */ |
| int scols_print_table(struct libscols_table *tb) |
| { |
| int rc = 0; |
| size_t bufsz; |
| struct libscols_line *ln; |
| struct libscols_iter itr; |
| struct libscols_buffer *buf; |
| |
| if (!tb) |
| return -EINVAL; |
| |
| DBG(TAB, ul_debugobj(tb, "printing")); |
| if (!tb->symbols) |
| scols_table_set_symbols(tb, NULL); /* use default */ |
| |
| tb->is_term = isatty(STDOUT_FILENO) ? 1 : 0; |
| tb->termwidth = tb->is_term ? get_terminal_width() : 0; |
| if (tb->termwidth <= 0) |
| tb->termwidth = 80; |
| tb->termwidth -= tb->termreduce; |
| |
| bufsz = tb->termwidth; |
| |
| scols_reset_iter(&itr, SCOLS_ITER_FORWARD); |
| while (scols_table_next_line(tb, &itr, &ln) == 0) { |
| size_t sz = strlen_line(ln); |
| if (sz > bufsz) |
| bufsz = sz; |
| } |
| |
| buf = new_buffer(bufsz + 1); /* data + space for \0 */ |
| if (!buf) |
| return -ENOMEM; |
| |
| if (!(scols_table_is_raw(tb) || scols_table_is_export(tb))) { |
| rc = recount_widths(tb, buf); |
| if (rc != 0) |
| goto done; |
| } |
| |
| if (scols_table_is_tree(tb)) |
| rc = print_tree(tb, buf); |
| else |
| rc = print_table(tb, buf); |
| |
| done: |
| free_buffer(buf); |
| return rc; |
| } |
| |
| /** |
| * scols_print_table_to_string: |
| * @tb: table |
| * @data: pointer to the beginning of a memory area to print to |
| * |
| * Prints the table to @data. |
| * |
| * Returns: 0, a negative value in case of an error. |
| */ |
| int scols_print_table_to_string(struct libscols_table *tb, char **data) |
| { |
| #ifdef HAVE_OPEN_MEMSTREAM |
| FILE *stream; |
| size_t sz; |
| int rc; |
| |
| if (!tb) |
| return -EINVAL; |
| |
| DBG(TAB, ul_debugobj(tb, "printing to string")); |
| |
| /* create a stream for output */ |
| stream = open_memstream(data, &sz); |
| if (!stream) |
| return -ENOMEM; |
| |
| scols_table_set_stream(tb, stream); |
| rc = scols_print_table(tb); |
| fclose(stream); |
| |
| return rc; |
| #else |
| return -ENOSYS; |
| #endif |
| } |
| |