/* * Copyright(c) 2012-2021 Intel Corporation * SPDX-License-Identifier: BSD-3-Clause-Clear */ #define _GNU_SOURCE #include #include #include #include #include #include #include "vt100codes.h" #include "csvparse.h" #include "statistics_view.h" #include "statistics_view_structs.h" #include "statistics_view_text.h" #include "table.h" #include "intvector.h" #include #include #include #include "safeclib/safe_mem_lib.h" #include "safeclib/safe_str_lib.h" #include #include "cas_lib.h" #define NUMBER_COLOR FG_COLOR_YELLOW #define UNIT_COLOR FG_COLOR_CYAN #define PATH_COLOR FG_COLOR_MAGENTA #define TREE_BRANCH_COLOR FG_COLOR_BLUE /** * table drawing character set */ struct table_draw_characters { int outer_horiz; /**< thin horizontal line */ int outer_right; /**< T facing right border */ int outer_left; /**< T facing left border */ int outer_vert; /**< bold vertical line */ int outer_x; /**< intersection of bold lines */ int outer_lt; /**< Left top corner of a frame */ int outer_lb; /**< Left bottom corner of a frame */ int outer_rt; /**< Right top corner of a frame */ int outer_rb; /**< Right bottom corner of a frame */ int inner_horiz; /**< thin horizontal line */ int inner_right; /**< T facing right border */ int inner_left; /**< T facing right border */ int inner_top; /**< T facing top border */ int inner_bottom; /**< T facing bottom border */ int inner_vert; /**< thin vertical line */ int inner_x; /**< intersection of thin lines */ int tree_node; /**< tree node (but not last) */ int tree_node_last; /**< last tree node */ }; /** * private data of text output formatter */ struct text_out_prv { struct table *t; /**< currently processed table (freed and reallocated frequently */ struct intvector col_w; /**< set of column widths */ struct intvector row_types; /**< set of row types (whenever table rows are headers, sections or regulars */ struct table_draw_characters tc; /**< set of table draw characters */ bool dec_fmt; /**< whenever output shall utilize xterm/DEC VT1xx features */ /** * actual number of columns - may be less than number of * columns in t array, when line breaking is used */ int num_cols; /** size of buffer for reconstructed cell */ int cell_buffer_size; /** buffer for reconstructed cell */ char *cell_buffer; int col_ptr; /**< column pointer for key value printing */ }; int text_construct(struct view_t *this) { struct text_out_prv *prv = calloc(sizeof(struct text_out_prv),1); struct table_draw_characters *tc = &prv->tc; this->ctx.text_prv = prv; if (!prv) { return 1; } prv->t = table_alloc(); if (!prv->t) { return 1; } if (vector_alloc_placement(&prv->col_w)) { table_free(prv->t); return 1; } if (vector_alloc_placement(&prv->row_types)) { table_free(prv->t); vector_free_placement(&prv->col_w); return 1; } const char *term = getenv("TERM"); if (term && (!strncmp(term, "xterm", 5) || !strcmp(term, "screen"))) { prv->dec_fmt = true; } else { prv->dec_fmt = false; } /* use "Lang" to detect if utf8 is about to be used. * additionally use UTF8 frames only for dec_fmt-style terminal (others * typically lack utf8 fonts */ const char *lang = getenv("LANG"); if (prv->dec_fmt && lang && strcasestr(lang, "UTF-8")) { /* * if you want to add more box drawing characters to this mechanism, * please look up for their UNICODE codes i.e. here: * https://en.wikipedia.org/wiki/Box-drawing_character#Unicode * * (don't enter those sequences in code directly as some editors * badly display them - and vi Emacs are OK, current Eclipse is good * too, but others cause problem. Also external tools don't like * them too much) */ tc->outer_horiz = 0x2550; tc->outer_right = 0x2563; /* T facing right border */ tc->outer_left = 0x2560; tc->outer_vert = 0x2551; tc->outer_x = 0x256a; tc->outer_lt = 0x2554; tc->outer_lb = 0x255a; tc->outer_rt = 0x2557; tc->outer_rb = 0x255d; tc->inner_horiz = 0x2500; tc->inner_vert = 0x2502; tc->inner_left = 0x255f; tc->inner_top = 0x2564; tc->inner_bottom = 0x2567; tc->inner_right = 0x2562; tc->inner_x = 0x253c; tc->tree_node = 0x2514; tc->tree_node_last = 0x251c; } else { tc->outer_horiz = '='; tc->outer_right = '+'; /* T facing right border */ tc->outer_left = '+'; tc->outer_vert = '|'; tc->outer_x = '+'; tc->outer_lt = '+'; tc->outer_lb = '+'; tc->outer_rt = '+'; tc->outer_rb = '+'; tc->inner_horiz = '-'; tc->inner_vert = '|'; tc->inner_right = '+'; /* T facing right border */ tc->inner_left = '+'; tc->inner_top = '+'; tc->inner_bottom = '+'; tc->inner_x = '+'; tc->tree_node = '+'; tc->tree_node_last = '+'; } if (!isatty(fileno(this->outfile))) { prv->dec_fmt = false; /* if output is NOT a tty, don't use * dec_fmt features */ } const char *casadm_colors = getenv("CASADM_COLORS"); if (casadm_colors && casadm_colors[0]) { prv->dec_fmt = true; } prv->cell_buffer = 0; prv->cell_buffer_size = 0; return 0; } int text_destruct(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; table_free(prv->t); vector_free_placement(&prv->col_w); vector_free_placement(&prv->row_types); if (prv->cell_buffer) { free(prv->cell_buffer); } free(prv); return 0; } /** * utf8-encoding version of putc */ void putcu8(int c, FILE *out) { if (c < (1 << 7)) /* 7 bit Unicode encoded as plain ascii */ { putc(c, out); return; } if (c < (1 << 11)) /* 11 bit Unicode encoded in 2 UTF-8 bytes */ { putc((c >> 6) | 0xC0, out); putc((c & 0x3F) | 0x80, out); return; } if (c < (1 << 16)) /* 16 bit Unicode encoded in 3 UTF-8 bytes */ { putc(((c >> 12)) | 0xE0, out); putc(((c >> 6) & 0x3F) | 0x80, out); putc((c & 0x3F) | 0x80, out); return; } if (c < (1 << 21))/* 21 bit Unicode encoded in 4 UTF-8 bytes */ { putc(((c >> 18)) | 0xF0, out); putc(((c >> 12) & 0x3F) | 0x80, out); putc(((c >> 6) & 0x3F) | 0x80, out); putc((c & 0x3F) | 0x80, out); return; } } /** * Types of table horizontal rule */ enum hr_type { TOP, AFTER_HEADER, INTERNAL, BOTTOM }; /** * print table horizontal rule. * @param this output formatter object * @param mode style of horizontal rule to be printed, * as per enum hr_type */ static int print_table_hr(struct view_t *this, int mode) { struct text_out_prv *prv = this->ctx.text_prv; struct table_draw_characters *tc = &prv->tc; int i,j; int w = prv->num_cols; for (j = 0 ; j != w ; ++j) { if (0 == j) { if (TOP == mode) { putcu8(tc->outer_lt, this->outfile); } else if (AFTER_HEADER == mode) { putcu8(tc->outer_left, this->outfile); } else if (INTERNAL == mode) { putcu8(tc->inner_left, this->outfile); } else { putcu8(tc->outer_lb, this->outfile); } } else { if (TOP == mode) { putcu8(tc->inner_top, this->outfile); } else if (AFTER_HEADER == mode) { putcu8(tc->outer_x, this->outfile); } else if (INTERNAL == mode) { putcu8(tc->inner_x, this->outfile); } else { putcu8(tc->inner_bottom, this->outfile); } } for (i = 0 ; i != vector_get(&prv->col_w, j) + 2; ++i) { if (INTERNAL == mode) { putcu8(tc->inner_horiz, this->outfile); } else { putcu8(tc->outer_horiz, this->outfile); } } } if (TOP == mode) { putcu8(tc->outer_rt, this->outfile); } else if (AFTER_HEADER == mode) { putcu8(tc->outer_right, this->outfile); } else if (INTERNAL == mode) { putcu8(tc->inner_right, this->outfile); } else { putcu8(tc->outer_rb, this->outfile); } putc('\n', this->outfile); return 0; } /** * configure formatting attribute, if DEC-style formatting is supported by * output terminal. otherwise don't print anything. * @param this formatter object * @param attr formatting attribute to be set (color, bold etc...) * @return 0 when no error happened. */ static int conditional_fmt(struct view_t *this, int attr) { struct text_out_prv *prv = this->ctx.text_prv; if (prv->dec_fmt) { if (fprintf(this->outfile, SET_ATTR, attr)) { return 0; } else { return 1; } } else { return 0; } } /** * @brief return true if cell is a decimal number or %. Signed numbers ae NOT * recognized. */ static bool isnumber(const char *str) { int num_dots = 0; const char *c = str; do { if (isdigit(*c)) { continue; } else if ('.' == *c) { if (num_dots++ || c==str) { return false; /* more than one '.' within string, or '.' as a first characterr */ } } else if ('%' == *c) { if (c[1] || c==str || c[-1]=='.' ) { return false; /* '%' occured and it is not the last character or '%' as a first character */ } } else { return false; /* character outside of set [0-9%.] */ } } while (*(++c)); return true; } static void print_spaces(FILE *outfile, int spaces_no) { int i; for (i = 0; i < spaces_no; i++) { fputc(' ', outfile); } return; } static int calculate_total_width(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; int i; int w = prv->num_cols; int result = 0; for (i = 0 ; i != w ; ++i) { result += vector_get(&prv->col_w, i); } return result; } static int get_window_width() { struct winsize w; if (getenv("CASADM_NO_LINE_BREAK")) { return MAX_STR_LEN; } else if (ioctl(0, TIOCGWINSZ, &w)) { char *cols = getenv("COLUMNS"); int ncols; if (cols && str_to_int(cols, NULL, &ncols)) { return ncols; } else { return 80; /* return default width of 80 if actual width of screen cannot be determined */ } } else { return w.ws_col; } } /** * reconstruct entire cell even if it is splitted into "rows" * due to line breaks; */ static char* get_entire_cell(struct view_t *this, int i, int j) { struct text_out_prv *prv = this->ctx.text_prv; int w = table_get_width(prv->t); int k; int t; int buffer_len = 0; /* calculate buffer length required */ for (k = j % prv->num_cols ; k < w; k += prv->num_cols) { char *sub_cell = (char*)table_get(prv->t, i, k); buffer_len += strnlen(sub_cell, MAX_STR_LEN); } /* make sure, that buffer is allocated */ if (!prv->cell_buffer) { prv->cell_buffer = malloc(1 + buffer_len); if (!prv->cell_buffer) { return 0; } prv->cell_buffer_size = buffer_len; } else if (prv->cell_buffer_size <= buffer_len) { char *tmp = realloc(prv->cell_buffer, 1 + buffer_len); if (tmp) { prv->cell_buffer = tmp; prv->cell_buffer_size = buffer_len; } else { return 0; } } /* reconstruct cell */ t = 0; for (k = j % prv->num_cols ; k < w; k += prv->num_cols) { char *sub_cell = (char*)table_get(prv->t, i, k); int len = strnlen(sub_cell, MAX_STR_LEN); if (len) { memcpy_s(prv->cell_buffer + t, buffer_len - t, sub_cell, len); } t += len; } prv->cell_buffer[buffer_len] = 0; return prv->cell_buffer; } /** * finish printing table (upon last row of table) */ static int finish_table(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; struct table_draw_characters *tc = &prv->tc; int i, j; int w = table_get_width(prv->t); int h = table_get_height(prv->t); int half_space; /* half of space around item (for center-alignment) */ print_table_hr(this, TOP); for (i = 0 ; i!= h ; ++i) { for (j = 0 ; j != w ; ++j) { char *cell_text = (char*)table_get(prv->t, i, j); char *cell = get_entire_cell(this, i, j); int cell_len; int cell_text_len = strnlen(cell_text, MAX_STR_LEN); half_space = 0; if (!cell) { return FAILURE; } cell_len = strnlen(cell, MAX_STR_LEN); /* 0 == j % means first column */ if (0 == j % prv->num_cols) { putcu8(tc->outer_vert, this->outfile); } else { putcu8(tc->inner_vert, this->outfile); } /* digits are right aligned */ if (isnumber(cell)) { print_spaces(this->outfile, vector_get(&prv->col_w, j % prv->num_cols) - cell_text_len); conditional_fmt(this, NUMBER_COLOR); /* highlight first column */ } else if (0 == j % prv->num_cols) { conditional_fmt(this, ATTR_BRIGHT); /* handle table headers specially */ } else if (vector_get(&prv->row_types, i) == TABLE_HEADER) { half_space = (vector_get(&prv->col_w, j) - cell_text_len) / 2; if ('[' == cell[0] && ']' == cell[cell_len-1]) { if (j < prv->num_cols) { cell_text += 1; cell_text_len --; } if (cell_text[cell_text_len - 1] == ']') { cell_text[cell_len - 2] = 0; cell_text_len --; } cell_len -= 2; } print_spaces(this->outfile, half_space); conditional_fmt(this, ATTR_BRIGHT); } else { if (cell[0]=='[' && cell[cell_len-1]==']') { conditional_fmt(this, UNIT_COLOR); if (j < prv->num_cols) { cell_text += 1; cell_text_len --; } if (cell_text[cell_text_len - 1] == ']') { cell_text[cell_len - 2] = 0; cell_text_len --; } } } putc(' ', this->outfile); if (cell_text_len != fwrite(cell_text, 1, cell_text_len, this->outfile)) { abort(); } putc(' ', this->outfile); if (!isnumber(cell)) { print_spaces(this->outfile, vector_get(&prv->col_w, j % prv->num_cols) - cell_text_len - half_space); } conditional_fmt(this, ATTR_RESET); fflush(this->outfile); if (j % prv->num_cols == prv->num_cols - 1 || j == w - 1) { putcu8(tc->outer_vert, this->outfile); putc('\n', this->outfile); /* additionally check if anything worth printing is * in subsequent lines */ int k; bool nothing_more = true; for (k = j + 1; k != w ; ++k) { cell = (char*)table_get(prv->t, i, k); if (cell[0]) { nothing_more = false; } } if (nothing_more) { break; } } } if (vector_get(&prv->row_types, i) == TABLE_HEADER) { print_table_hr(this, AFTER_HEADER); } else if (i + 1 < h && vector_get(&prv->row_types, i+1) == TABLE_SECTION) { print_table_hr(this, INTERNAL); } fflush(this->outfile); } print_table_hr(this, BOTTOM); return 0; } /** * finish printing table (upon last row of table) */ static int finish_tree(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; struct table_draw_characters *tc = &prv->tc; int i, j; int w = table_get_width(prv->t); int h = table_get_height(prv->t); for (i = 0 ; i!= h ; ++i) { for (j = 0 ; j != w ; ++j) { char *mother_cell = (char*)table_get(prv->t, i, j % prv->num_cols); char *cell = (char*)table_get(prv->t, i, j); int cell_len = strnlen(cell, MAX_STR_LEN); int out_len = cell_len; /* digits are right aligned */ if (0 == j && (vector_get(&prv->row_types, i) == TREE_LEAF)) { if (h - 1 == i || (i < h - 1 && vector_get(&prv->row_types, i + 1) == TREE_BRANCH)) { putcu8(tc->tree_node, this->outfile); } else { putcu8(tc->tree_node_last, this->outfile); } cell_len++; } /* apply bright colors for all rows except leaves */ if (0 == j || (vector_get(&prv->row_types, i) != TREE_LEAF)) { conditional_fmt(this, ATTR_BRIGHT); } if (3 == j) { if (!strncmp(cell, "Active", MAX_STR_LEN) || (!strncmp(cell, "Running", MAX_STR_LEN)) || (!strncmp(cell, "Stopping", MAX_STR_LEN))) { conditional_fmt(this, FG_COLOR_GREEN); } if (!strncmp(cell, "Inactive", MAX_STR_LEN) || !strncmp(cell, "Detached", MAX_STR_LEN)) { conditional_fmt(this, FG_COLOR_RED); conditional_fmt(this, ATTR_BRIGHT); } if (!strncmp(cell, "Incomplete", MAX_STR_LEN)) { conditional_fmt(this, FG_COLOR_YELLOW); conditional_fmt(this, ATTR_BRIGHT); } } if (isnumber(cell)) { conditional_fmt(this, NUMBER_COLOR); } if ('/' == mother_cell[0]) { if (vector_get(&prv->row_types, i) == TREE_BRANCH) { conditional_fmt(this, TREE_BRANCH_COLOR); } else { conditional_fmt(this, PATH_COLOR); } } if (out_len != fwrite(cell, 1, out_len, this->outfile)) { abort(); } /* for column that is NOT last - fill spaces between * columns accordingly */ if (j % prv->num_cols != prv->num_cols - 1) { print_spaces(this->outfile, vector_get(&prv->col_w, j % prv->num_cols) - cell_len + 3); } conditional_fmt(this, ATTR_RESET); fflush(this->outfile); /* for last column or last entry in a row */ if (j % prv->num_cols == prv->num_cols - 1 || j == w - 1) { putc('\n', this->outfile); /* additionally check if anything worth printing is * in subsequent lines */ int k; bool nothing_more = true; for (k = j + 1; k != w ; ++k) { cell = (char*)table_get(prv->t, i, k); if (cell[0]) { nothing_more = false; } } if (nothing_more) { break; } } /* for last entry in last column (in case of line breaks) */ if (j % prv->num_cols == prv->num_cols - 1 && j != w - 1) { if (i == h - 1) { putc(' ', this->outfile); } else { putcu8(tc->inner_vert, this->outfile); } } } fflush(this->outfile); } return 0; } static int print_word_break_lines(struct view_t *this, char *word, int word_len, int screen_width) { struct text_out_prv *prv = this->ctx.text_prv; if (prv->col_ptr + word_len > screen_width) { putc('\n', this->outfile); prv->col_ptr = 1 + vector_get(&prv->col_w, 0); print_spaces(this->outfile, prv->col_ptr); } prv->col_ptr += word_len; return word_len != fwrite(word, 1, word_len, this->outfile); } static void print_spaces_state(struct view_t *this, int spaces_no, int screen_width) { struct text_out_prv *prv = this->ctx.text_prv; int i; if (prv->col_ptr + spaces_no > screen_width) { putc('\n', this->outfile); prv->col_ptr = 1 + vector_get(&prv->col_w, 0); print_spaces(this->outfile, prv->col_ptr); } else { prv->col_ptr += spaces_no; for (i = 0; i < spaces_no; i++) { fputc(' ', this->outfile); } } return; } static int print_cell_break_lines(struct view_t *this, char *cell, int cell_len, int screen_width) { struct text_out_prv *prv = this->ctx.text_prv; if (prv->col_ptr + cell_len > screen_width) { int off = 0; int word_off = 0; do { if (' ' == cell[word_off + off] || !cell[word_off + off]) { if (off) { print_spaces_state(this, 1, screen_width); } print_word_break_lines(this, cell + off, word_off, screen_width); off += word_off + 1; word_off = 0; } else { word_off ++; } } while (off + word_off <= cell_len); return 0; } else { prv->col_ptr += cell_len; return cell_len != fwrite(cell, 1, cell_len, this->outfile); } } /** * finish KV pairs... */ static int finish_kvs(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; int i,j; int screen_width = get_window_width(); int w = prv->num_cols; int h = table_get_height(prv->t); for (i = 0 ; i!= h ; ++i) { prv->col_ptr = 0; for (j = 0 ; j != w ; ++j) { char *cell = table_get(prv->t, i, j); int cell_len = strnlen(cell, MAX_STR_LEN); if (j && !table_get(prv->t, i, j)[0]) { continue; /* don't bother with empty strings */ } if (j == 0) { conditional_fmt(this, ATTR_BRIGHT); } else if (j==1) { print_spaces_state(this, 1, screen_width); } else if (cell[0] == '[') { print_spaces_state(this, 1, screen_width); conditional_fmt(this, UNIT_COLOR); } else { print_cell_break_lines(this, " / ", 3, screen_width); } if (isdigit(cell[0]) && (isdigit(cell[cell_len-1]) || '%'==cell[cell_len-1])) { conditional_fmt(this, NUMBER_COLOR); } else if ('/'==cell[0]) { conditional_fmt(this, PATH_COLOR); } if (print_cell_break_lines(this, cell, cell_len, screen_width)) { abort(); } if (j == 0) { print_spaces_state(this, vector_get(&prv->col_w, 0) - cell_len, screen_width); } conditional_fmt(this, ATTR_RESET); fflush(this->outfile); } putc('\n', this->outfile); fflush(this->outfile); } return 0; } static void set_column_widths(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; int i, j; int w = table_get_width(prv->t); int h = table_get_height(prv->t); vector_resize(&prv->col_w, w); vector_zero(&prv->col_w); for (i = 0 ; i!= h ; ++i) { for (j = 0 ; j != w ; ++j) { const char *cell = table_get(prv->t, i, j); int cell_len = strnlen(cell, MAX_STR_LEN); if (cell[0]=='[' && cell[cell_len-1]==']') { cell_len -= 2; } vector_set(&prv->col_w, j % prv->num_cols, maxi(vector_get(&prv->col_w, j % prv->num_cols), cell_len)); } } } /* if sum of column widths is less than width of a screen, * attempt to shrink some * @param tbl_margin space between left edge and start of cell 0 text * @param cell_margin space between cells; */ static int adjust_column_widths(struct view_t *this, int cell_margin, int tbl_margin) { struct text_out_prv *prv = this->ctx.text_prv; int i, j; int w = prv->num_cols; int h = table_get_height(prv->t); int screen_width = get_window_width(); int table_width; int margins_width = (w - 1) * cell_margin + tbl_margin * 2; int avg_width; int above_avg_cols = 0; int excess_width; if (screen_width < 0) { return 0; } table_width = calculate_total_width(this); if (table_width + margins_width <= screen_width) { return 0; } /* perform magic to adjust table to a screen */ avg_width = table_width / w; excess_width = table_width + margins_width - screen_width; for (i = 0 ; i != w ; ++i) { if (vector_get(&prv->col_w, i) > avg_width) { above_avg_cols ++; } } for (i = 0 ; i != w ; ++i) { int this_width = vector_get(&prv->col_w, i); /* * This condition is exactly the same as in above loop, where * above_avg_cols is incremented. So there is no risk of division by 0. */ if (this_width > avg_width) { int reduce_by = excess_width / above_avg_cols; vector_set(&prv->col_w, i, this_width - reduce_by); above_avg_cols --; excess_width -= reduce_by; } } /* proper widths set */ /* now proceed with line breaking */ for (i = 0 ; i!= h ; ++i) { for (j = 0 ; j != w ; ++j) { char *field = (char*)table_get(prv->t, i, j); int k; int col_w = vector_get(&prv->col_w, j); int strlen_f = strnlen(field, MAX_STR_LEN); int last_breakpoint = 0; int num_breakpoints = 0; int breakpoint = 0; /* offset at which line is broken */ for (k = 0; k != strlen_f ; ++k) { if (field[k] == '/' || field[k] == ' ' || field[k] == '-') { breakpoint = k; } if (k - last_breakpoint >= col_w && breakpoint > last_breakpoint) { if (k < strlen_f && breakpoint && table_set(prv->t, i, j + w * (1 + num_breakpoints), field + breakpoint)) { return 1; } field[breakpoint] = 0; if (last_breakpoint) { ((char*)table_get(prv->t, i, j + w * num_breakpoints)) [breakpoint - last_breakpoint] = 0; } last_breakpoint = breakpoint; num_breakpoints ++; } } } } table_set_width(prv->t, ((table_get_width(prv->t) + w - 1) / w ) * w); set_column_widths(this); return 0; } static int finish_structured_data(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; int w = table_get_width(prv->t); prv->num_cols = w; set_column_widths(this); if (vector_get(&prv->row_types, 0) == KV_PAIR) { finish_kvs(this); } else if (vector_get(&prv->row_types, 0) == TABLE_HEADER) { adjust_column_widths(this, 3, 4); finish_table(this); } else if (vector_get(&prv->row_types, 0) == TREE_HEADER) { adjust_column_widths(this, 3, 0); finish_tree(this); } table_reset(prv->t); vector_resize(&prv->row_types, 0); return 0; } /** * handle single line of text in intermediate format. (already split&parsed). * params as per interface. */ int text_process_row(struct view_t *this, int type, int num_fields, char *fields[]) { int i; struct text_out_prv *prv = this->ctx.text_prv; int table_h = table_get_height(prv->t); switch (type) { case FREEFORM: if (table_h) { finish_structured_data(this); } conditional_fmt(this, ATTR_BRIGHT); for (i = 0; i!= num_fields; ++i) { fprintf(this->outfile, "%s", fields[i]); fflush(this->outfile); } conditional_fmt(this, ATTR_RESET); putc('\n', this->outfile); break; case DATA_SET: case RECORD: if (table_h) { finish_structured_data(this); if (table_h) { putc('\n', this->outfile); } table_h = 0; } break; default: if (table_h && (TABLE_HEADER == type || (vector_get(&prv->row_types, 0) == KV_PAIR && type != KV_PAIR))) { finish_structured_data(this); table_h = 0; putc('\n', this->outfile); } for (i = 0; i!= num_fields; ++i) { if (table_set(prv->t, table_h, i, fields[i])) { return 1; } } vector_push_back(&prv->row_types, type); break; } return 0; } /** * @handles closing file. */ int text_end_input(struct view_t *this) { struct text_out_prv *prv = this->ctx.text_prv; int table_h = table_get_height(prv->t); if (table_h) { finish_structured_data(this); } return 0; }