diff options
Diffstat (limited to 'lib/termtable.c')
-rw-r--r-- | lib/termtable.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/lib/termtable.c b/lib/termtable.c new file mode 100644 index 0000000..9b36d5e --- /dev/null +++ b/lib/termtable.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASCII table generator. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#include <zebra.h> +#include <stdio.h> + +#include "lib/json.h" +#include "printfrr.h" +#include "memory.h" +#include "termtable.h" + +DEFINE_MTYPE_STATIC(LIB, TTABLE, "ASCII table"); + +/* clang-format off */ +const struct ttable_style ttable_styles[] = { + { // default ascii + .corner = '+', + .rownums_on = false, + .indent = 1, + .border = { + .top = '-', + .bottom = '-', + .left = '|', + .right = '|', + .top_on = true, + .bottom_on = true, + .left_on = true, + .right_on = true, + }, + .cell = { + .lpad = 1, + .rpad = 1, + .align = LEFT, + .border = { + .bottom = '-', + .bottom_on = true, + .top = '-', + .top_on = false, + .right = '|', + .right_on = true, + .left = '|', + .left_on = false, + }, + }, + }, { // blank, suitable for plaintext alignment + .corner = ' ', + .rownums_on = false, + .indent = 1, + .border = { + .top = ' ', + .bottom = ' ', + .left = ' ', + .right = ' ', + .top_on = false, + .bottom_on = false, + .left_on = false, + .right_on = false, + }, + .cell = { + .lpad = 0, + .rpad = 3, + .align = LEFT, + .border = { + .bottom = ' ', + .bottom_on = false, + .top = ' ', + .top_on = false, + .right = ' ', + .right_on = false, + .left = ' ', + .left_on = false, + }, + } + } +}; +/* clang-format on */ + +void ttable_del(struct ttable *tt) +{ + for (int i = tt->nrows - 1; i >= 0; i--) + ttable_del_row(tt, i); + + XFREE(MTYPE_TTABLE, tt->table); + XFREE(MTYPE_TTABLE, tt); +} + +struct ttable *ttable_new(const struct ttable_style *style) +{ + struct ttable *tt; + + tt = XCALLOC(MTYPE_TTABLE, sizeof(struct ttable)); + tt->style = *style; + tt->nrows = 0; + tt->ncols = 0; + tt->size = 0; + tt->table = NULL; + + return tt; +} + +/** + * Inserts or appends a new row at the specified index. + * + * If the index is -1, the row is added to the end of the table. Otherwise the + * index must be a valid index into tt->table. + * + * If the table already has at least one row (and therefore a determinate + * number of columns), a format string specifying a number of columns not equal + * to tt->ncols will result in a no-op and a return value of NULL. + * + * @param tt table to insert into + * @param i insertion index; inserted row will be (i + 1)'th row + * @param format printf format string as in ttable_[add|insert]_row() + * @param ap pre-initialized variadic list of arguments for format string + * + * @return pointer to the first cell of allocated row + */ +PRINTFRR(3, 0) +static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i, + const char *format, va_list ap) +{ + assert(i >= -1 && i < tt->nrows); + + char shortbuf[256]; + char *res, *orig, *section; + struct ttable_cell *row; + int col = 0; + int ncols = 0; + + /* count how many columns we have */ + for (int j = 0; format[j]; j++) + ncols += !!(format[j] == '|'); + ncols++; + + if (tt->ncols == 0) + tt->ncols = ncols; + else if (ncols != tt->ncols) + return NULL; + + /* reallocate chunk if necessary */ + while (tt->size < (tt->nrows + 1) * sizeof(struct ttable_cell *)) { + tt->size = MAX(2 * tt->size, 2 * sizeof(struct ttable_cell *)); + tt->table = XREALLOC(MTYPE_TTABLE, tt->table, tt->size); + } + + /* CALLOC a block of cells */ + row = XCALLOC(MTYPE_TTABLE, tt->ncols * sizeof(struct ttable_cell)); + + res = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); + orig = res; + + while (res && col < tt->ncols) { + section = strsep(&res, "|"); + row[col].text = XSTRDUP(MTYPE_TTABLE, section); + row[col].style = tt->style.cell; + col++; + } + + if (orig != shortbuf) + XFREE(MTYPE_TMP, orig); + + /* insert row */ + if (i == -1 || i == tt->nrows) + tt->table[tt->nrows] = row; + else { + memmove(&tt->table[i + 1], &tt->table[i], + (tt->nrows - i) * sizeof(struct ttable_cell *)); + tt->table[i] = row; + } + + tt->nrows++; + + return row; +} + +struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i, + const char *format, ...) +{ + struct ttable_cell *ret; + va_list ap; + + va_start(ap, format); + ret = ttable_insert_row_va(tt, i, format, ap); + va_end(ap); + + return ret; +} + +struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...) +{ + struct ttable_cell *ret; + va_list ap; + + va_start(ap, format); + ret = ttable_insert_row_va(tt, -1, format, ap); + va_end(ap); + + return ret; +} + +void ttable_del_row(struct ttable *tt, unsigned int i) +{ + assert((int)i < tt->nrows); + + for (int j = 0; j < tt->ncols; j++) + XFREE(MTYPE_TTABLE, tt->table[i][j].text); + + XFREE(MTYPE_TTABLE, tt->table[i]); + + memmove(&tt->table[i], &tt->table[i + 1], + (tt->nrows - i - 1) * sizeof(struct ttable_cell *)); + + tt->nrows--; + + if (tt->nrows == 0) + tt->ncols = 0; +} + +void ttable_align(struct ttable *tt, unsigned int row, unsigned int col, + unsigned int nrow, unsigned int ncol, enum ttable_align align) +{ + assert((int)row < tt->nrows); + assert((int)col < tt->ncols); + assert((int)row + (int)nrow <= tt->nrows); + assert((int)col + (int)ncol <= tt->ncols); + + for (unsigned int i = row; i < row + nrow; i++) + for (unsigned int j = col; j < col + ncol; j++) + tt->table[i][j].style.align = align; +} + +static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align, + short pad) +{ + if (align == LEFT) + cell->style.lpad = pad; + else + cell->style.rpad = pad; +} + +void ttable_pad(struct ttable *tt, unsigned int row, unsigned int col, + unsigned int nrow, unsigned int ncol, enum ttable_align align, + short pad) +{ + assert((int)row < tt->nrows); + assert((int)col < tt->ncols); + assert((int)row + (int)nrow <= tt->nrows); + assert((int)col + (int)ncol <= tt->ncols); + + for (unsigned int i = row; i < row + nrow; i++) + for (unsigned int j = col; j < col + ncol; j++) + ttable_cell_pad(&tt->table[i][j], align, pad); +} + +void ttable_restyle(struct ttable *tt) +{ + for (int i = 0; i < tt->nrows; i++) + for (int j = 0; j < tt->ncols; j++) + tt->table[i][j].style = tt->style.cell; +} + +void ttable_colseps(struct ttable *tt, unsigned int col, + enum ttable_align align, bool on, char sep) +{ + for (int i = 0; i < tt->nrows; i++) { + if (align == RIGHT) { + tt->table[i][col].style.border.right_on = on; + tt->table[i][col].style.border.right = sep; + } else { + tt->table[i][col].style.border.left_on = on; + tt->table[i][col].style.border.left = sep; + } + } +} + +void ttable_rowseps(struct ttable *tt, unsigned int row, + enum ttable_align align, bool on, char sep) +{ + for (int i = 0; i < tt->ncols; i++) { + if (align == TOP) { + tt->table[row][i].style.border.top_on = on; + tt->table[row][i].style.border.top = sep; + } else { + tt->table[row][i].style.border.bottom_on = on; + tt->table[row][i].style.border.bottom = sep; + } + } +} + +char *ttable_dump(struct ttable *tt, const char *newline) +{ + /* clang-format off */ + char *buf; // print buffer + size_t pos; // position in buffer + size_t nl_len; // strlen(newline) + int cw[tt->ncols]; // calculated column widths + int nlines; // total number of newlines / table lines + size_t width; // length of one line, with newline + int abspad; // calculated whitespace for sprintf + char *left; // left part of line + size_t lsize; // size of above + char *right; // right part of line + size_t rsize; // size of above + struct ttable_cell *cell, *row; // iteration pointers + /* clang-format on */ + + nl_len = strlen(newline); + + /* calculate width of each column */ + memset(cw, 0x00, sizeof(int) * tt->ncols); + + for (int j = 0; j < tt->ncols; j++) + for (int i = 0, cellw = 0; i < tt->nrows; i++) { + cell = &tt->table[i][j]; + cellw = 0; + cellw += (int)strlen(cell->text); + cellw += cell->style.lpad; + cellw += cell->style.rpad; + if (j != 0) + cellw += cell->style.border.left_on ? 1 : 0; + if (j != tt->ncols - 1) + cellw += cell->style.border.right_on ? 1 : 0; + cw[j] = MAX(cw[j], cellw); + } + + /* calculate overall line width, including newline */ + width = 0; + width += tt->style.indent; + width += tt->style.border.left_on ? 1 : 0; + width += tt->style.border.right_on ? 1 : 0; + width += strlen(newline); + for (int i = 0; i < tt->ncols; i++) + width += cw[i]; + + /* calculate number of lines en total */ + nlines = tt->nrows; + nlines += tt->style.border.top_on ? 1 : 0; + nlines += 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier + for (int i = 0; i < tt->nrows; i++) { + /* if leftmost cell has top / bottom border, whole row does */ + nlines += tt->table[i][0].style.border.top_on ? 1 : 0; + nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0; + } + + /* initialize left & right */ + lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0); + left = XCALLOC(MTYPE_TTABLE, lsize); + rsize = nl_len + (tt->style.border.right_on ? 1 : 0); + right = XCALLOC(MTYPE_TTABLE, rsize); + + memset(left, ' ', lsize); + + if (tt->style.border.left_on) + left[lsize - 1] = tt->style.border.left; + + if (tt->style.border.right_on) { + right[0] = tt->style.border.right; + memcpy(&right[1], newline, nl_len); + } else + memcpy(&right[0], newline, nl_len); + + /* allocate print buffer */ + buf = XCALLOC(MTYPE_TMP, width * (nlines + 1) + 1); + pos = 0; + + if (tt->style.border.top_on) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t i = 0; i < width - lsize - rsize; i++) + buf[pos++] = tt->style.border.top; + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + for (int i = 0; i < tt->nrows; i++) { + row = tt->table[i]; + + /* if top border and not first row, print top row border */ + if (row[0].style.border.top_on && i != 0) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t l = 0; l < width - lsize - rsize; l++) + buf[pos++] = row[0].style.border.top; + + pos -= width - lsize - rsize; + for (int k = 0; k < tt->ncols; k++) { + if (k != 0 && row[k].style.border.left_on) + buf[pos] = tt->style.corner; + pos += cw[k]; + if (row[k].style.border.right_on + && k != tt->ncols - 1) + buf[pos - 1] = tt->style.corner; + } + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (int j = 0; j < tt->ncols; j++) { + /* if left border && not first col print left border */ + if (row[j].style.border.left_on && j != 0) + buf[pos++] = row[j].style.border.left; + + /* print left padding */ + for (int k = 0; k < row[j].style.lpad; k++) + buf[pos++] = ' '; + + /* calculate padding for sprintf */ + abspad = cw[j]; + abspad -= row[j].style.rpad; + abspad -= row[j].style.lpad; + if (j != 0) + abspad -= row[j].style.border.left_on ? 1 : 0; + if (j != tt->ncols - 1) + abspad -= row[j].style.border.right_on ? 1 : 0; + + /* print text */ + if (row[j].style.align == LEFT) + pos += sprintf(&buf[pos], "%-*s", abspad, + row[j].text); + else + pos += sprintf(&buf[pos], "%*s", abspad, + row[j].text); + + /* print right padding */ + for (int k = 0; k < row[j].style.rpad; k++) + buf[pos++] = ' '; + + /* if right border && not last col print right border */ + if (row[j].style.border.right_on && j != tt->ncols - 1) + buf[pos++] = row[j].style.border.right; + } + + memcpy(&buf[pos], right, rsize); + pos += rsize; + + /* if bottom border and not last row, print bottom border */ + if (row[0].style.border.bottom_on && i != tt->nrows - 1) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t l = 0; l < width - lsize - rsize; l++) + buf[pos++] = row[0].style.border.bottom; + + pos -= width - lsize - rsize; + for (int k = 0; k < tt->ncols; k++) { + if (k != 0 && row[k].style.border.left_on) + buf[pos] = tt->style.corner; + pos += cw[k]; + if (row[k].style.border.right_on + && k != tt->ncols - 1) + buf[pos - 1] = tt->style.corner; + } + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + assert(!buf[pos]); /* pos == & of first \0 in buf */ + } + + if (tt->style.border.bottom_on) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t l = 0; l < width - lsize - rsize; l++) + buf[pos++] = tt->style.border.bottom; + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + buf[pos] = '\0'; + + XFREE(MTYPE_TTABLE, left); + XFREE(MTYPE_TTABLE, right); + + return buf; +} + +/* Crude conversion from ttable to json array. + * Assume that the first row has column headings. + * + * Formats are: + * d int32 + * f double + * l int64 + * s string (default) + */ +json_object *ttable_json(struct ttable *tt, const char *const formats) +{ + struct ttable_cell *row; /* iteration pointers */ + json_object *json = NULL; + + json = json_object_new_array(); + + for (int i = 1; i < tt->nrows; i++) { + json_object *jobj; + json_object *val; + + row = tt->table[i]; + jobj = json_object_new_object(); + json_object_array_add(json, jobj); + for (int j = 0; j < tt->ncols; j++) { + switch (formats[j]) { + case 'd': + case 'l': + val = json_object_new_int64(atol(row[j].text)); + break; + case 'f': + val = json_object_new_double(atof(row[j].text)); + break; + default: + val = json_object_new_string(row[j].text); + } + json_object_object_add(jobj, tt->table[0][j].text, val); + } + } + + return json; +} |