/* print.c - functions to print table * * Copyright (C) 2010-2014 Karel Zak * Copyright (C) 2016 Igor Gnatenko * * 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 #include #include #include #include #include "mbsalign.h" #include "carefulputc.h" #include "smartcolsP.h" /* Fallback for symbols * * Note that by default library define all the symbols, but in case user does * not define all symbols or if we extended the symbols struct then we need * fallback to be more robust and backwardly compatible. */ #define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ") #define branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-") #define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ") #define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-") #define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|") #define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-") #define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->") #define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->") #define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->") #define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-") #define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-") #define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \ ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " ")) #define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used) static int is_next_columns_empty( struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln) { struct libscols_iter itr; if (!tb || !cl) return 0; if (is_last_column(cl)) return 1; if (!ln) return 0; scols_reset_iter(&itr, SCOLS_ITER_FORWARD); scols_table_set_columns_iter(tb, &itr, cl); /* skip current column */ scols_table_next_column(tb, &itr, &cl); while (scols_table_next_column(tb, &itr, &cl) == 0) { struct libscols_cell *ce; const char *data = NULL; if (scols_column_is_hidden(cl)) continue; if (scols_column_is_tree(cl)) return 0; ce = scols_line_get_cell(ln, cl->seqnum); if (ce) data = scols_cell_get_data(ce); if (data && *data) return 0; } return 1; } /* returns pointer to the end of used data */ static int tree_ascii_art_to_buffer(struct libscols_table *tb, struct libscols_line *ln, struct ul_buffer *buf) { const char *art; int rc; assert(ln); assert(buf); if (!ln->parent) return 0; rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); if (rc) return rc; if (is_last_child(ln)) art = " "; else art = vertical_symbol(tb); return ul_buffer_append_string(buf, art); } static int grpset_is_empty( struct libscols_table *tb, size_t idx, size_t *rest) { size_t i; for (i = idx; i < tb->grpset_size; i++) { if (tb->grpset[i] == NULL) { if (rest) (*rest)++; } else return 0; } return 1; } static int groups_ascii_art_to_buffer( struct libscols_table *tb, struct libscols_line *ln, struct ul_buffer *buf, int empty) { int filled = 0; size_t i, rest = 0; const char *filler = cellpadding_symbol(tb); if (!has_groups(tb)) return 0; DBG(LINE, ul_debugobj(ln, "printing groups chart")); if (tb->is_dummy_print) return 0; /* allocate grpset[] only */ for (i = 0; i < tb->grpset_size; i += SCOLS_GRPSET_CHUNKSIZ) { struct libscols_group *gr = tb->grpset[i]; if (!gr) { ul_buffer_append_ntimes(buf, SCOLS_GRPSET_CHUNKSIZ, cellpadding_symbol(tb)); continue; } /* * Empty cells (multi-line entries, etc.), print vertical symbols only * to show that the group continues. */ if (empty) { switch (gr->state) { case SCOLS_GSTATE_FIRST_MEMBER: case SCOLS_GSTATE_MIDDLE_MEMBER: case SCOLS_GSTATE_CONT_MEMBERS: ul_buffer_append_string(buf, grp_vertical_symbol(tb)); ul_buffer_append_ntimes(buf, 2, filler); break; case SCOLS_GSTATE_LAST_MEMBER: case SCOLS_GSTATE_MIDDLE_CHILD: case SCOLS_GSTATE_CONT_CHILDREN: ul_buffer_append_string(buf, filler); ul_buffer_append_string(buf, grp_vertical_symbol(tb)); ul_buffer_append_string(buf, filler); break; case SCOLS_GSTATE_LAST_CHILD: ul_buffer_append_ntimes(buf, 3, filler); break; } continue; } /* * Regular cell */ switch (gr->state) { case SCOLS_GSTATE_FIRST_MEMBER: ul_buffer_append_string(buf, grp_m_first_symbol(tb)); break; case SCOLS_GSTATE_MIDDLE_MEMBER: ul_buffer_append_string(buf, grp_m_middle_symbol(tb)); break; case SCOLS_GSTATE_LAST_MEMBER: ul_buffer_append_string(buf, grp_m_last_symbol(tb)); break; case SCOLS_GSTATE_CONT_MEMBERS: ul_buffer_append_string(buf, grp_vertical_symbol(tb)); ul_buffer_append_ntimes(buf, 2, filler); break; case SCOLS_GSTATE_MIDDLE_CHILD: ul_buffer_append_string(buf, filler); ul_buffer_append_string(buf, grp_c_middle_symbol(tb)); if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) { ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); filled = 1; } filler = grp_horizontal_symbol(tb); break; case SCOLS_GSTATE_LAST_CHILD: ul_buffer_append_string(buf, cellpadding_symbol(tb)); ul_buffer_append_string(buf, grp_c_last_symbol(tb)); if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) { ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); filled = 1; } filler = grp_horizontal_symbol(tb); break; case SCOLS_GSTATE_CONT_CHILDREN: ul_buffer_append_string(buf, filler); ul_buffer_append_string(buf, grp_vertical_symbol(tb)); ul_buffer_append_string(buf, filler); break; } if (filled) break; } if (!filled) ul_buffer_append_string(buf, filler); return 0; } static int has_pending_data(struct libscols_table *tb) { struct libscols_column *cl; struct libscols_iter itr; scols_reset_iter(&itr, SCOLS_ITER_FORWARD); while (scols_table_next_column(tb, &itr, &cl) == 0) { if (scols_column_is_hidden(cl)) continue; if (cl->pending_data) return 1; } return 0; } static void fputs_color_reset(struct libscols_table *tb) { if (tb->cur_color) { fputs(UL_COLOR_RESET, tb->out); tb->cur_color = NULL; } } static void fputs_color(struct libscols_table *tb, const char *color) { if (tb->cur_color) fputs_color_reset(tb); tb->cur_color = color; if (color) fputs(color, tb->out); } static const char *get_cell_color(struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, struct libscols_cell *ce) { const char *color = NULL; if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) return NULL; if (ce) color = ce->color; if (!color && (!ln || !ln->color) && cl) color = cl->color; return color; } /* switch from line color to cell/column color */ static void fputs_color_cell_open(struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, struct libscols_cell *ce) { const char *color = get_cell_color(tb, cl, ln, ce); if (color) fputs_color(tb, color); } /* switch from cell/column color to line color or reset */ static void fputs_color_cell_close(struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, struct libscols_cell *ce) { const char *color = get_cell_color(tb, cl, ln, ce); if (color) fputs_color(tb, ln ? ln->color : NULL); } /* switch to line color */ static void fputs_color_line_open(struct libscols_table *tb, struct libscols_line *ln) { if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) return; fputs_color(tb, ln ? ln->color : NULL); } /* switch off all colors */ static void fputs_color_line_close(struct libscols_table *tb) { if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) return; fputs_color_reset(tb); } /* print padding or ASCII-art instead of data of @cl */ static void print_empty_cell(struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, /* optional */ struct libscols_cell *ce, size_t bufsz) { size_t len_pad = 0; /* in screen cells as opposed to bytes */ DBG(COL, ul_debugobj(cl, " printing empty cell")); fputs_color_cell_open(tb, cl, ln, ce); /* generate tree/group ASCII-art rather than padding */ if (ln && scols_column_is_tree(cl)) { struct ul_buffer art = UL_INIT_BUFFER; char *data; if (ul_buffer_alloc_data(&art, bufsz) != 0) goto done; if (cl->is_groups) groups_ascii_art_to_buffer(tb, ln, &art, 1); tree_ascii_art_to_buffer(tb, ln, &art); if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) ul_buffer_append_string(&art, vertical_symbol(tb)); if (scols_table_is_noencoding(tb)) data = ul_buffer_get_data(&art, NULL, &len_pad); else data = ul_buffer_get_safe_data(&art, NULL, &len_pad, NULL); if (data && len_pad) fputs(data, tb->out); ul_buffer_free_data(&art); } done: /* minout -- don't fill */ if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { fputs_color_cell_close(tb, cl, ln, ce); return; } /* default -- fill except last column */ if (!scols_table_is_maxout(tb) && is_last_column(cl)) { fputs_color_cell_close(tb, cl, ln, ce); return; } /* fill rest of cell with space */ for(; len_pad < cl->width; ++len_pad) fputs(cellpadding_symbol(tb), tb->out); fputs_color_cell_close(tb, cl, ln, ce); if (!is_last_column(cl)) fputs(colsep(tb), 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 */ struct libscols_cell *ce, size_t bufsz) { size_t i; assert(tb); assert(cl); DBG(LINE, ul_debugobj(ln, "printing newline padding")); fputs(linesep(tb), tb->out); /* line break */ tb->termlines_used++; fputs_color_line_open(tb, ln); /* 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, ce, bufsz); fputs_color_line_close(tb); } /* * Pending data * * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is * printed as usually and output is truncated to match column width. * * The rest of the long text is printed on next extra line(s). The extra lines * don't exist in the table (not represented by libscols_line). The data for * the extra lines are stored in libscols_column->pending_data_buf and the * function print_line() adds extra lines until the buffer is not empty in all * columns. */ /* set data that will be printed by extra lines */ static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz) { char *p = NULL; if (data && *data) { DBG(COL, ul_debugobj(cl, "setting pending data")); assert(sz); p = strdup(data); if (!p) return -ENOMEM; } free(cl->pending_data_buf); cl->pending_data_buf = p; cl->pending_data_sz = sz; cl->pending_data = cl->pending_data_buf; return 0; } /* the next extra line has been printed, move pending data cursor */ static int step_pending_data(struct libscols_column *cl, size_t bytes) { DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes)); if (bytes >= cl->pending_data_sz) return set_pending_data(cl, NULL, 0); cl->pending_data += bytes; cl->pending_data_sz -= bytes; return 0; } /* print next pending data for the column @cl */ static int print_pending_data( struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, /* optional */ struct libscols_cell *ce) { size_t width = cl->width, bytes; size_t len = width, i; char *data; char *nextchunk = NULL; if (!cl->pending_data) return 0; if (!width) return -EINVAL; DBG(COL, ul_debugobj(cl, "printing pending data")); data = strdup(cl->pending_data); if (!data) goto err; if (scols_column_is_customwrap(cl) && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { bytes = nextchunk - data; len = scols_table_is_noencoding(tb) ? mbs_nwidth(data, bytes) : mbs_safe_nwidth(data, bytes, NULL); } else bytes = mbs_truncate(data, &len); if (bytes == (size_t) -1) goto err; if (bytes) step_pending_data(cl, bytes); fputs_color_cell_open(tb, cl, ln, ce); fputs(data, tb->out); free(data); /* minout -- don't fill */ if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { fputs_color_cell_close(tb, cl, ln, ce); return 0; } /* default -- fill except last column */ if (!scols_table_is_maxout(tb) && is_last_column(cl)) { fputs_color_cell_close(tb, cl, ln, ce); return 0; } /* fill rest of cell with space */ for(i = len; i < width; i++) fputs(cellpadding_symbol(tb), tb->out); fputs_color_cell_close(tb, cl, ln, ce); if (!is_last_column(cl)) fputs(colsep(tb), tb->out); return 0; err: free(data); return -errno; } static void print_json_data(struct libscols_table *tb, struct libscols_column *cl, const char *name, char *data) { switch (cl->json_type) { case SCOLS_JSON_STRING: /* name: "aaa" */ ul_jsonwrt_value_s(&tb->json, name, data); break; case SCOLS_JSON_NUMBER: /* name: 123 */ ul_jsonwrt_value_raw(&tb->json, name, data); break; case SCOLS_JSON_BOOLEAN: case SCOLS_JSON_BOOLEAN_OPTIONAL: /* name: true|false|null */ if (cl->json_type == SCOLS_JSON_BOOLEAN_OPTIONAL && (!*data || !strcmp(data, "-"))) { ul_jsonwrt_value_null(&tb->json, name); } else { ul_jsonwrt_value_boolean(&tb->json, name, !*data ? 0 : *data == '0' ? 0 : *data == 'N' || *data == 'n' ? 0 : 1); } break; case SCOLS_JSON_ARRAY_STRING: case SCOLS_JSON_ARRAY_NUMBER: /* name: [ "aaa", "bbb", "ccc" ] */ ul_jsonwrt_array_open(&tb->json, name); if (!scols_column_is_customwrap(cl)) ul_jsonwrt_value_s(&tb->json, NULL, data); else do { char *next = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data); if (cl->json_type == SCOLS_JSON_ARRAY_STRING) ul_jsonwrt_value_s(&tb->json, NULL, data); else ul_jsonwrt_value_raw(&tb->json, NULL, data); data = next; } while (data); ul_jsonwrt_array_close(&tb->json); break; } } static int print_data(struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, /* optional */ struct libscols_cell *ce, /* optional */ struct ul_buffer *buf) { size_t len = 0, i, width, bytes; char *data, *nextchunk; const char *name = NULL; int is_last; assert(tb); assert(cl); data = ul_buffer_get_data(buf, NULL, NULL); if (!data) data = ""; if (tb->format != SCOLS_FMT_HUMAN) { name = scols_table_is_shellvar(tb) ? scols_column_get_name_as_shellvar(cl) : scols_column_get_name(cl); } is_last = is_last_column(cl); if (is_last && scols_table_is_json(tb) && scols_table_is_tree(tb) && has_children(ln)) /* "children": [] is the real last value */ is_last = 0; switch (tb->format) { case SCOLS_FMT_RAW: fputs_nonblank(data, tb->out); if (!is_last) fputs(colsep(tb), tb->out); return 0; case SCOLS_FMT_EXPORT: fputs(name, tb->out); fputc('=', tb->out); fputs_quoted(data, tb->out); if (!is_last) fputs(colsep(tb), tb->out); return 0; case SCOLS_FMT_JSON: print_json_data(tb, cl, name, data); return 0; case SCOLS_FMT_HUMAN: break; /* continue below */ } /* Encode. Note that 'len' and 'width' are number of cells, not bytes. */ if (scols_table_is_noencoding(tb)) data = ul_buffer_get_data(buf, &bytes, &len); else data = ul_buffer_get_safe_data(buf, &bytes, &len, scols_column_get_safechars(cl)); if (!data) data = ""; width = cl->width; /* custom multi-line cell based */ if (*data && scols_column_is_customwrap(cl) && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { set_pending_data(cl, nextchunk, bytes - (nextchunk - data)); bytes = nextchunk - data; len = scols_table_is_noencoding(tb) ? mbs_nwidth(data, bytes) : mbs_safe_nwidth(data, bytes, NULL); } if (is_last && len < width && !scols_table_is_maxout(tb) && !scols_column_is_right(cl)) width = len; /* truncate data */ if (len > width && scols_column_is_trunc(cl)) { len = width; bytes = mbs_truncate(data, &len); /* updates 'len' */ } /* standard multi-line cell */ if (len > width && scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)) { set_pending_data(cl, data, bytes); len = width; bytes = mbs_truncate(data, &len); if (bytes != (size_t) -1 && bytes > 0) step_pending_data(cl, bytes); } if (bytes == (size_t) -1) { bytes = len = 0; data = NULL; } fputs_color_cell_open(tb, cl, ln, ce); if (data && *data) { if (scols_column_is_right(cl)) { for (i = len; i < width; i++) fputs(cellpadding_symbol(tb), tb->out); len = width; } fputs(data, tb->out); } /* minout -- don't fill */ if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { fputs_color_cell_close(tb, cl, ln, ce); return 0; } /* default -- fill except last column */ if (!scols_table_is_maxout(tb) && is_last) { fputs_color_cell_close(tb, cl, ln, ce); return 0; } /* fill rest of cell with space */ for(i = len; i < width; i++) fputs(cellpadding_symbol(tb), tb->out); fputs_color_cell_close(tb, cl, ln, ce); if (len > width && !scols_column_is_trunc(cl)) { DBG(COL, ul_debugobj(cl, "*** data len=%zu > column width=%zu", len, width)); print_newline_padding(tb, cl, ln, ce, ul_buffer_get_bufsiz(buf)); /* next column starts on next line */ } else if (!is_last) fputs(colsep(tb), tb->out); /* columns separator */ return 0; } int __cell_to_buffer(struct libscols_table *tb, struct libscols_line *ln, struct libscols_column *cl, struct ul_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); ul_buffer_reset_data(buf); ce = scols_line_get_cell(ln, cl->seqnum); data = ce ? scols_cell_get_data(ce) : NULL; if (!scols_column_is_tree(cl)) return data ? ul_buffer_append_string(buf, data) : 0; /* * Group stuff */ if (!scols_table_is_json(tb) && cl->is_groups) rc = groups_ascii_art_to_buffer(tb, ln, buf, 0); /* * Tree stuff */ if (!rc && ln->parent && !scols_table_is_json(tb)) { rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); if (!rc && is_last_child(ln)) rc = ul_buffer_append_string(buf, right_symbol(tb)); else if (!rc) rc = ul_buffer_append_string(buf, branch_symbol(tb)); } if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb)) ul_buffer_save_pointer(buf, SCOLS_BUFPTR_TREEEND); if (!rc && data) rc = ul_buffer_append_string(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 ul_buffer *buf) { int rc = 0, pending = 0; struct libscols_column *cl; struct libscols_iter itr; assert(ln); DBG(LINE, ul_debugobj(ln, " printing line")); fputs_color_line_open(tb, ln); /* regular line */ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { if (scols_column_is_hidden(cl)) continue; rc = __cell_to_buffer(tb, ln, cl, buf); if (rc == 0) rc = print_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum), buf); if (rc == 0 && cl->pending_data) pending = 1; } fputs_color_line_close(tb); /* extra lines of the multi-line cells */ while (rc == 0 && pending) { DBG(LINE, ul_debugobj(ln, "printing pending data")); pending = 0; fputs(linesep(tb), tb->out); fputs_color_line_open(tb, ln); tb->termlines_used++; scols_reset_iter(&itr, SCOLS_ITER_FORWARD); while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { if (scols_column_is_hidden(cl)) continue; if (cl->pending_data) { rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum)); if (rc == 0 && cl->pending_data) pending = 1; } else print_empty_cell(tb, cl, ln, NULL, ul_buffer_get_bufsiz(buf)); } fputs_color_line_close(tb); } return 0; } int __scols_print_title(struct libscols_table *tb) { int rc; mbs_align_t align; size_t width, len = 0, bufsz, titlesz; char *title = NULL, *buf = NULL; assert(tb); if (!tb->title.data) return 0; DBG(TAB, ul_debugobj(tb, "printing title")); /* encode data */ if (tb->no_encode) { len = bufsz = strlen(tb->title.data) + 1; buf = strdup(tb->title.data); if (!buf) { rc = -ENOMEM; goto done; } } else { bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1; if (bufsz == 1) { DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore")); return 0; } buf = malloc(bufsz); if (!buf) { rc = -ENOMEM; goto done; } if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) || !len || len == (size_t) -1) { rc = -EINVAL; goto done; } } /* truncate and align */ width = tb->is_term ? tb->termwidth : 80; titlesz = width + bufsz; title = malloc(titlesz); if (!title) { rc = -EINVAL; goto done; } switch (scols_cell_get_alignment(&tb->title)) { case SCOLS_CELL_FL_RIGHT: align = MBS_ALIGN_RIGHT; break; case SCOLS_CELL_FL_CENTER: align = MBS_ALIGN_CENTER; break; case SCOLS_CELL_FL_LEFT: default: align = MBS_ALIGN_LEFT; /* * Don't print extra blank chars after the title if on left * (that's same as we use for the last column in the table). */ if (len < width && !scols_table_is_maxout(tb) && isblank(*titlepadding_symbol(tb))) width = len; break; } /* copy from buf to title and align to width with title_padding */ rc = mbsalign_with_padding(buf, title, titlesz, &width, align, 0, (int) *titlepadding_symbol(tb)); if (rc == -1) { rc = -EINVAL; goto done; } if (tb->colors_wanted) fputs_color(tb, tb->title.color); fputs(title, tb->out); if (tb->colors_wanted) fputs_color_reset(tb); fputc('\n', tb->out); rc = 0; done: free(buf); free(title); DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc)); return rc; } int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf) { int rc = 0; struct libscols_column *cl; struct libscols_iter itr; assert(tb); if ((tb->header_printed == 1 && tb->header_repeat == 0) || scols_table_is_noheadings(tb) || scols_table_is_export(tb) || scols_table_is_json(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) { if (scols_column_is_hidden(cl)) continue; ul_buffer_reset_data(buf); if (cl->is_groups && scols_table_is_tree(tb) && scols_column_is_tree(cl)) { size_t i; for (i = 0; i < tb->grpset_size + 1; i++) { rc = ul_buffer_append_data(buf, " ", 1); if (rc) break; } } if (!rc) rc = ul_buffer_append_string(buf, scols_table_is_shellvar(tb) ? scols_column_get_name_as_shellvar(cl) : scols_column_get_name(cl)); if (!rc) rc = print_data(tb, cl, NULL, &cl->header, buf); } if (rc == 0) { fputs(linesep(tb), tb->out); tb->termlines_used++; } tb->header_printed = 1; tb->header_next = tb->termlines_used + tb->termheight; if (tb->header_repeat) DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]", tb->header_next, tb->termlines_used, rc)); return rc; } int __scols_print_range(struct libscols_table *tb, struct ul_buffer *buf, struct libscols_iter *itr, struct libscols_line *end) { int rc = 0; struct libscols_line *ln; assert(tb); DBG(TAB, ul_debugobj(tb, "printing range")); while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { int last = scols_iter_is_last(itr); if (scols_table_is_json(tb)) ul_jsonwrt_object_open(&tb->json, NULL); rc = print_line(tb, ln, buf); if (scols_table_is_json(tb)) ul_jsonwrt_object_close(&tb->json); else if (last == 0 && tb->no_linesep == 0) { fputs(linesep(tb), tb->out); tb->termlines_used++; } if (end && ln == end) break; if (!last && want_repeat_header(tb)) __scols_print_header(tb, buf); } return rc; } int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf) { struct libscols_iter itr; scols_reset_iter(&itr, SCOLS_ITER_FORWARD); return __scols_print_range(tb, buf, &itr, NULL); } /* scols_walk_tree() callback to print tree line */ static int print_tree_line(struct libscols_table *tb, struct libscols_line *ln, struct libscols_column *cl __attribute__((__unused__)), void *data) { struct ul_buffer *buf = (struct ul_buffer *) data; int rc; DBG(LINE, ul_debugobj(ln, " printing tree line")); if (scols_table_is_json(tb)) ul_jsonwrt_object_open(&tb->json, NULL); rc = print_line(tb, ln, buf); if (rc) return rc; if (has_children(ln)) { if (scols_table_is_json(tb)) ul_jsonwrt_array_open(&tb->json, "children"); else { /* between parent and child is separator */ fputs(linesep(tb), tb->out); tb->termlines_used++; } } else { int last; /* terminate all open last children for JSON */ if (scols_table_is_json(tb)) { do { last = (is_child(ln) && is_last_child(ln)) || (is_tree_root(ln) && is_last_tree_root(tb, ln)); ul_jsonwrt_object_close(&tb->json); if (last && is_child(ln)) ul_jsonwrt_array_close(&tb->json); ln = ln->parent; } while(ln && last); } else if (tb->no_linesep == 0) { int last_in_tree = scols_walk_is_last(tb, ln); if (last_in_tree == 0) { /* standard output */ fputs(linesep(tb), tb->out); tb->termlines_used++; } } } return 0; } int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf) { assert(tb); DBG(TAB, ul_debugobj(tb, "----printing-tree-----")); return scols_walk_tree(tb, NULL, print_tree_line, (void *) buf); } 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; } void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf) { if (!tb) return; ul_buffer_free_data(buf); if (tb->priv_symbols) { scols_table_set_symbols(tb, NULL); tb->priv_symbols = 0; } } int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf) { size_t bufsz, extra_bufsz = 0; struct libscols_line *ln; struct libscols_iter itr; int rc; DBG(TAB, ul_debugobj(tb, "initialize printing")); if (!tb->symbols) { rc = scols_table_set_default_symbols(tb); if (rc) goto err; tb->priv_symbols = 1; } else tb->priv_symbols = 0; if (tb->format == SCOLS_FMT_HUMAN) tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 : tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 : isatty(STDOUT_FILENO); if (tb->is_term) { size_t width = (size_t) scols_table_get_termwidth(tb); if (tb->termreduce > 0 && tb->termreduce < width) { width -= tb->termreduce; scols_table_set_termwidth(tb, width); } bufsz = width; } else bufsz = BUFSIZ; if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) tb->header_repeat = 0; /* * Estimate extra space necessary for tree, JSON or another output * decoration. */ if (scols_table_is_tree(tb)) extra_bufsz += tb->nlines * strlen(vertical_symbol(tb)); switch (tb->format) { case SCOLS_FMT_RAW: extra_bufsz += tb->ncols; /* separator between columns */ break; case SCOLS_FMT_JSON: ul_jsonwrt_init(&tb->json, tb->out, 0); extra_bufsz += tb->nlines * 3; /* indentation */ /* fallthrough */ case SCOLS_FMT_EXPORT: { struct libscols_column *cl; scols_reset_iter(&itr, SCOLS_ITER_FORWARD); while (scols_table_next_column(tb, &itr, &cl) == 0) { if (scols_column_is_hidden(cl)) continue; if (scols_column_get_name(cl)) extra_bufsz += strlen(scols_column_get_name(cl)); /* data */ extra_bufsz += 2; /* separators */ } break; } case SCOLS_FMT_HUMAN: break; } /* * Enlarge buffer if necessary, the buffer should be large enough to * store line data and tree ascii art (or another decoration). */ scols_reset_iter(&itr, SCOLS_ITER_FORWARD); while (scols_table_next_line(tb, &itr, &ln) == 0) { size_t sz; sz = strlen_line(ln) + extra_bufsz; if (sz > bufsz) bufsz = sz; } /* pre-allocate space for data */ rc = ul_buffer_alloc_data(buf, bufsz + 1); /* data + space for \0 */ if (rc) goto err; /* * Make sure groups members are in the same orders as the tree */ if (has_groups(tb) && scols_table_is_tree(tb)) scols_groups_fix_members_order(tb); if (tb->format == SCOLS_FMT_HUMAN) { rc = __scols_calculate(tb, buf); if (rc != 0) goto err; } return 0; err: __scols_cleanup_printing(tb, buf); return rc; }