summaryrefslogtreecommitdiffstats
path: root/lib/termtable.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/termtable.c530
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;
+}