]> git.puffer.fish Git - mirror/frr.git/commitdiff
lib: add table generator
authorQuentin Young <qlyoung@cumulusnetworks.com>
Wed, 24 May 2017 02:01:00 +0000 (02:01 +0000)
committerQuentin Young <qlyoung@cumulusnetworks.com>
Fri, 16 Jun 2017 02:02:42 +0000 (02:02 +0000)
Allows for easy preparation of tabular output.

Supports:
 -- Padding
 -- Alignment
 -- Styling

lib/Makefile.am
lib/termtable.c [new file with mode: 0644]
lib/termtable.h [new file with mode: 0644]
tests/.gitignore
tests/Makefile.am
tests/lib/test_ttable.c [new file with mode: 0644]
tests/lib/test_ttable.py [new file with mode: 0644]
tests/lib/test_ttable.refout [new file with mode: 0644]

index a1b78d3c4dae8e985a6bbddc10cb6f882e4e6418..474a932dfe0190fd0cc9ed3e5f58baffaea724d8 100644 (file)
@@ -37,6 +37,7 @@ libfrr_la_SOURCES = \
        module.c \
        hook.c \
        frr_pthread.c \
+       termtable.c \
        # end
 
 BUILT_SOURCES = route_types.h gitversion.h command_parse.h command_lex.h
@@ -81,6 +82,7 @@ pkginclude_HEADERS = \
        sha256.h \
        frr_pthread.h \
        vrf_int.h \
+       termtable.h \
        # end
 
 noinst_HEADERS = \
diff --git a/lib/termtable.c b/lib/termtable.c
new file mode 100644 (file)
index 0000000..ea46072
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * ASCII table generator.
+ * Copyright (C) 2017  Cumulus Networks
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+#include <stdio.h>
+
+#include "termtable.h"
+#include "memory.h"
+
+/* clang-format off */
+struct ttable_style ttable_styles[] = {
+       {       // default ascii
+               .corner = '+',
+               .rownums_on = false,
+               .indent = 1,
+               .border.top = '-',
+               .border.bottom = '-',
+               .border.left = '|',
+               .border.right = '|',
+               .border.top_on = true,
+               .border.bottom_on = true,
+               .border.left_on = true,
+               .border.right_on = true,
+               .cell.lpad = 1,
+               .cell.rpad = 1,
+               .cell.align = LEFT,
+               .cell.border.bottom = '-',
+               .cell.border.bottom_on = true,
+               .cell.border.top = '-',
+               .cell.border.top_on = false,
+               .cell.border.right = '|',
+               .cell.border.right_on = true,
+               .cell.border.left = '|',
+               .cell.border.left_on = false,
+       }, {    // blank, suitable for plaintext alignment
+               .corner = ' ',
+               .rownums_on = false,
+               .indent = 1,
+               .border.top = ' ',
+               .border.bottom = ' ',
+               .border.left = ' ',
+               .border.right = ' ',
+               .border.top_on = false,
+               .border.bottom_on = false,
+               .border.left_on = false,
+               .border.right_on = false,
+               .cell.lpad = 0,
+               .cell.rpad = 3,
+               .cell.align = LEFT,
+               .cell.border.bottom = ' ',
+               .cell.border.bottom_on = false,
+               .cell.border.top = ' ',
+               .cell.border.top_on = false,
+               .cell.border.right = ' ',
+               .cell.border.right_on = false,
+               .cell.border.left = ' ',
+               .cell.border.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_TMP, tt->table);
+       XFREE(MTYPE_TMP, tt);
+}
+
+struct ttable *ttable_new(struct ttable_style *style)
+{
+       struct ttable *tt;
+
+       tt = XCALLOC(MTYPE_TMP, 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
+ */
+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 *res, *orig, *section;
+       const char *f;
+       struct ttable_cell *row;
+       int col = 0;
+       int ncols = 0;
+
+       /* count how many columns we have */
+       f = format;
+       for (; f[ncols]; f[ncols] == '|' ? ncols++ : *f++)
+               ;
+       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_TMP, tt->table, tt->size);
+       }
+
+       /* CALLOC a block of cells */
+       row = XCALLOC(MTYPE_TMP, tt->ncols * sizeof(struct ttable_cell));
+
+       res = NULL;
+       vasprintf(&res, format, ap);
+
+       orig = res;
+
+       while (res) {
+               section = strsep(&res, "|");
+               row[col].text = XSTRDUP(MTYPE_TMP, section);
+               row[col].style = tt->style.cell;
+               col++;
+       }
+
+       free(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_TMP, tt->table[i][j].text);
+
+       XFREE(MTYPE_TMP, 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)
+{
+       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
+
+       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 += 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_TMP, lsize);
+       rsize = nl_len + (tt->style.border.right_on ? 1 : 0);
+       right = XCALLOC(MTYPE_TMP, rsize);
+
+       for (size_t i = 0; i < lsize; i++)
+               left[i] = ' ';
+
+       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 i = 0; i < width - lsize - rsize; i++)
+                               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 i = 0; i < row[j].style.lpad; i++)
+                               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 */
+                       const char *fmt;
+                       if (row[j].style.align == LEFT)
+                               fmt = "%-*s";
+                       else
+                               fmt = "%*s";
+
+                       pos += sprintf(&buf[pos], fmt, abspad, row[j].text);
+
+                       /* print right padding */
+                       for (int i = 0; i < row[j].style.rpad; i++)
+                               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 i = 0; i < width - lsize - rsize; i++)
+                               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 i = 0; i < width - lsize - rsize; i++)
+                       buf[pos++] = tt->style.border.bottom;
+
+               memcpy(&buf[pos], right, rsize);
+               pos += rsize;
+       }
+
+       buf[pos] = '\0';
+
+       XFREE(MTYPE_TMP, left);
+       XFREE(MTYPE_TMP, right);
+
+       return buf;
+}
diff --git a/lib/termtable.h b/lib/termtable.h
new file mode 100644 (file)
index 0000000..6953002
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * ASCII table generator.
+ * Copyright (C) 2017  Cumulus Networks
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef _TERMTABLE_H_
+#define _TERMTABLE_H_
+
+enum ttable_align {
+       LEFT,
+       RIGHT,
+       TOP,
+       BOTTOM,
+};
+
+struct ttable_border {
+       char top;
+       char bottom;
+       char left;
+       char right;
+
+       bool top_on;
+       bool bottom_on;
+       bool left_on;
+       bool right_on;
+};
+
+/* cell style and cell */
+struct ttable_cellstyle {
+       short lpad;
+       short rpad;
+       enum ttable_align align;
+       struct ttable_border border;
+};
+
+struct ttable_cell {
+       char *text;
+       struct ttable_cellstyle style;
+};
+
+/* table style and table */
+struct ttable_style {
+       char corner;     /* intersection */
+       int indent;      /* left table indent */
+       bool rownums_on; /* show row numbers; unimplemented */
+
+       struct ttable_border border;
+       struct ttable_cellstyle cell;
+};
+
+struct ttable {
+       int nrows;                  /* number of rows */
+       int ncols;                  /* number of cols */
+       struct ttable_cell **table; /* table, row x col */
+       size_t size;                /* size */
+       struct ttable_style style;  /* style */
+};
+
+/* some predefined styles */
+#define TTSTYLE_ASCII 0
+#define TTSTYLE_BLANK 1
+
+extern struct ttable_style ttable_styles[2];
+
+/**
+ * Creates a new table with the default style, which looks like this:
+ *
+ * +----------+----------+
+ * | column 1 | column 2 |
+ * +----------+----------+
+ * | data...  | data!!   |
+ * +----------+----------+
+ * | datums   | 12345    |
+ * +----------+----------+
+ *
+ * @return the created table
+ */
+struct ttable *ttable_new(struct ttable_style *tts);
+
+/**
+ * Deletes a table and releases all associated resources.
+ *
+ * @param tt the table to destroy
+ */
+void ttable_del(struct ttable *tt);
+
+/**
+ * Deletes an individual cell.
+ *
+ * @param cell the cell to destroy
+ */
+void ttable_cell_del(struct ttable_cell *cell);
+
+/**
+ * Inserts a new row at the given index.
+ *
+ * The row contents are determined by a format string. The format string has
+ * the same form as a regular printf format string, except that columns are
+ * delimited by '|'. For example, to make the first column of the table above,
+ * the call is:
+ *
+ *   ttable_insert_row(<tt>, <n>, "%s|%s", "column 1", "column 2");
+ *
+ * All features of printf format strings are permissible here.
+ *
+ * Caveats:
+ *  - At present you cannot insert '|' into a cell's contents.
+ *  - If there are N columns, '|' must appear n-1 times or the row will not be
+ *    created
+ *
+ * @param tt table to insert row into
+ * @param row the row number (begins at 0)
+ * @param format column-separated format string
+ * @param ... arguments to format string
+ *
+ * @return pointer to the first cell in the created row, or NULL if not enough
+ * columns were specified
+ */
+struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int row,
+                                     const char *format, ...);
+/**
+ * Inserts a new row at the end of the table.
+ *
+ * The row contents are determined by a format string. The format string has
+ * the same form as a regular printf format string, except that columns are
+ * delimited by '|'. For example, to make the first column of the table above,
+ * the call is:
+ *
+ *   ttable_add_row(<tt>, "%s|%s", "column 1", "column 2");
+ *
+ * All features of printf format strings are permissible here.
+ *
+ * Caveats:
+ *  - At present you cannot insert '|' into a cell's contents.
+ *  - If there are N columns, '|' must appear n-1 times or the row will not be
+ *    created
+ *
+ * @param tt table to insert row into
+ * @param format column-separated format string
+ * @param ... arguments to format string
+ *
+ * @return pointer to the first cell in the created row, or NULL if not enough
+ * columns were specified
+ */
+struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...);
+
+/**
+ * Removes a row from the table.
+ *
+ * @param tt table to delete row from
+ * @param row the row number (begins at 0)
+ */
+void ttable_del_row(struct ttable *tt, unsigned int row);
+
+/**
+ * Sets alignment for a range of cells.
+ *
+ * Available alignments are LEFT and RIGHT. Cell contents will be aligned
+ * accordingly, while respecting padding (if any). Suppose a cell has:
+ *
+ * lpad = 1
+ * rpad = 1
+ * align = RIGHT
+ * text = 'datums'
+ *
+ * The cell would look like:
+ *
+ *  +-------------------+
+ *  |            datums |
+ *  +-------------------+
+ *
+ * On the other hand:
+ *
+ * lpad = 1
+ * rpad = 10
+ * align = RIGHT
+ * text = 'datums'
+ *
+ *  +-------------------+
+ *  |   datums          |
+ *  +-------------------+
+ *
+ * The default alignment is LEFT.
+ *
+ * @param tt the table to set alignment on
+ * @param srow starting row index
+ * @param scol starting column index
+ * @param nrow # rows to align
+ * @param ncol # cols to align
+ * @param align the alignment to set
+ */
+void ttable_align(struct ttable *tt, unsigned int srow, unsigned int scol,
+                 unsigned int erow, unsigned int ecol,
+                 enum ttable_align align);
+
+/**
+ * Sets padding for a range of cells.
+ *
+ * Available padding options are LEFT and RIGHT (the alignment enum is reused).
+ * Both options may be set. Padding is treated as though it is stuck to the
+ * walls of the cell. Suppose a cell has:
+ *
+ * lpad = 4
+ * rpad = 2
+ * align = RIGHT
+ * text = 'datums'
+ *
+ * The cell padding, marked by '.', would look like:
+ *
+ *  +--------------+
+ *  |    .datums.  |
+ *  +--------------+
+ *
+ * If the column is wider than the cell, the cell contents are aligned in an
+ * additional padded field according to the cell alignment.
+ *
+ *  +--------------------+
+ *  | Data!!!11!~~~~~:-) |
+ *  +--------------------+
+ *  |    .      datums.  |
+ *  +--------------------+
+ *
+ * @param tt the table to set padding on
+ * @param srow starting row index
+ * @param scol starting column index
+ * @param nrow # rows to pad
+ * @param ncol # cols to pad
+ * @param align LEFT or RIGHT
+ * @param pad # spaces to pad with
+ */
+void ttable_pad(struct ttable *tt, unsigned int srow, unsigned int scol,
+               unsigned int nrow, unsigned int ncol, enum ttable_align align,
+               short pad);
+
+/**
+ * Restyle all cells according to table.cell.style.
+ *
+ * @param tt table to restyle
+ */
+void ttable_restyle(struct ttable *tt);
+
+/**
+ * Turn left/right column separators on or off for specified column.
+ *
+ * @param tt table
+ * @param col column index
+ * @param align left or right separators
+ * @param on true/false for on/off
+ * @param sep character to use
+ */
+void ttable_colseps(struct ttable *tt, unsigned int col,
+                   enum ttable_align align, bool on, char sep);
+
+/**
+ * Turn bottom row separators on or off for specified row.
+ *
+ * @param tt table
+ * @param row row index
+ * @param align left or right separators
+ * @param on true/false for on/off
+ * @param sep character to use
+ */
+void ttable_rowseps(struct ttable *tt, unsigned int row,
+                   enum ttable_align align, bool on, char sep);
+
+/**
+ * Dumps a table to a heap-allocated string.
+ *
+ * Caller must free this string after use with
+ *
+ *   XFREE (MTYPE_TMP, str);
+ *
+ * @param tt the table to dump
+ * @param newline the desired newline sequence to use, null terminated.
+ * @return table in text form
+ */
+char *ttable_dump(struct ttable *tt, const char *newline);
+
+#endif /* _TERMTABLE_H */
index bab3385da27b4b5d9826e17c95a6e760f66127f5..5279016b92126e46f7fd2264e54a8ad31aa15b4a 100644 (file)
@@ -43,3 +43,4 @@ __pycache__
 /lib/test_table
 /lib/test_timer_correctness
 /lib/test_timer_performance
+/lib/test_ttable
index f48abac47a935bf7c9e6d0a05a608178204b6c79..da96453a9e3ae9b68669e4ab207346db3717a0fc 100644 (file)
@@ -44,6 +44,7 @@ check_PROGRAMS = \
        lib/test_table \
        lib/test_timer_correctness \
        lib/test_timer_performance \
+       lib/test_ttable \
        lib/cli/test_cli \
        lib/cli/test_commands \
        $(TESTS_BGPD)
@@ -84,6 +85,7 @@ lib_test_timer_correctness_SOURCES = lib/test_timer_correctness.c \
                                      helpers/c/prng.c
 lib_test_timer_performance_SOURCES = lib/test_timer_performance.c \
                                      helpers/c/prng.c
+lib_test_ttable_SOURCES = lib/test_ttable.c
 lib_cli_test_cli_SOURCES = lib/cli/test_cli.c lib/cli/common_cli.c
 lib_cli_test_commands_SOURCES = lib/cli/test_commands_defun.c \
                                 lib/cli/test_commands.c \
@@ -112,6 +114,7 @@ lib_test_stream_LDADD = $(ALL_TESTS_LDADD)
 lib_test_table_LDADD = $(ALL_TESTS_LDADD) -lm
 lib_test_timer_correctness_LDADD = $(ALL_TESTS_LDADD)
 lib_test_timer_performance_LDADD = $(ALL_TESTS_LDADD)
+lib_test_ttable_LDADD = $(ALL_TESTS_LDADD)
 lib_cli_test_cli_LDADD = $(ALL_TESTS_LDADD)
 lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD)
 bgpd_test_aspath_LDADD = $(BGP_TEST_LDADD)
@@ -140,7 +143,8 @@ EXTRA_DIST = \
     lib/test_stream.py \
     lib/test_stream.refout \
     lib/test_table.py \
-    lib/test_timer_correctness.py
+    lib/test_timer_correctness.py \
+    lib/test_ttable.py
 
 .PHONY: tests.xml
 tests.xml: $(check_PROGRAMS)
diff --git a/tests/lib/test_ttable.c b/tests/lib/test_ttable.c
new file mode 100644 (file)
index 0000000..674179b
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * ASCII table generator.
+ * Copyright (C) 2017  Cumulus Networks
+ * Quentin Young
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+#include <termtable.h>
+#include <memory.h>
+
+int main(int argc, char **argv)
+{
+       char *table;
+
+       struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_ASCII]);
+
+       /* test printf compatibility and dimension counters */
+       ttable_add_row(tt, "%s|%s|%s", "Column 1", "Column 2", "Column 3");
+       assert(tt->ncols == 3);
+       assert(tt->nrows == 1);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* add new row with 1 column, assert that it is not added */
+       assert(ttable_add_row(tt, "%s", "Garbage") == NULL);
+       assert(tt->ncols == 3);
+       assert(tt->nrows == 1);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* add new row, assert that it is added */
+       assert(ttable_add_row(tt, "%s|%s|%s", "a", "b", "c"));
+       assert(tt->ncols == 3);
+       assert(tt->nrows == 2);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* add empty row, assert that it is added */
+       assert(ttable_add_row(tt, "||"));
+       assert(tt->ncols == 3);
+       assert(tt->nrows == 3);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* delete 1st row, assert that it is removed */
+       ttable_del_row(tt, 0);
+       assert(tt->ncols == 3);
+       assert(tt->nrows == 2);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* delete last row, assert that it is removed */
+       ttable_del_row(tt, 0);
+       assert(tt->ncols == 3);
+       assert(tt->nrows == 1);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* delete the remaining row, check dumping an empty table */
+       ttable_del_row(tt, 0);
+       assert(tt->ncols == 0);
+       assert(tt->nrows == 0);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* add new row */
+       ttable_add_row(tt, "%s|%s||%s|%9d", "slick", "black", "triple", 1337);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 1);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* add bigger row */
+       ttable_add_row(tt, "%s|%s||%s|%s",
+                      "nebula dusk session streets twilight "
+                      "pioneer beats yeah",
+                      "prarie dog", "cornmeal", ":O -*_-*");
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 2);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* insert new row at beginning */
+       ttable_insert_row(tt, 0, "%s|%s||%d|%lf", "converting", "vegetarians",
+                         2, 2015.0);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 3);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* insert new row at end */
+       ttable_insert_row(tt, tt->nrows - 1, "%s|%s||%d|%ld", "converting",
+                         "vegetarians", 1, 2003L);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 4);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* insert new row at middle */
+       ttable_insert_row(tt, 1, "%s|%s||%s|%ld", "she", "pioneer", "aki", 1l);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 5);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* set alignment */
+       ttable_align(tt, 0, 1, 2, 2, LEFT);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 5);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       ttable_align(tt, 0, 1, 5, 1, RIGHT);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 5);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* set padding */
+       ttable_pad(tt, 0, 1, 1, 1, RIGHT, 2);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 5);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       ttable_pad(tt, 0, 0, 5, 4, LEFT, 2);
+       assert(tt->ncols == 5);
+       assert(tt->nrows == 5);
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* restyle */
+       tt->style.cell.border.bottom_on = false;
+       tt->style.cell.border.top_on = false;
+       tt->style.cell.border.right_on = false;
+       tt->style.cell.border.left_on = false;
+       ttable_restyle(tt);
+
+       /* top & double bottom border for top row */
+       ttable_rowseps(tt, 0, BOTTOM, true, '-');
+       ttable_rowseps(tt, 1, TOP, true, '-');
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* column separators for leftmost column */
+       ttable_colseps(tt, 0, RIGHT, true, '|');
+       table = ttable_dump(tt, "\n");
+       fprintf(stdout, "%s\n", table);
+       XFREE(MTYPE_TMP, table);
+
+       /* delete table */
+       ttable_del(tt);
+}
diff --git a/tests/lib/test_ttable.py b/tests/lib/test_ttable.py
new file mode 100644 (file)
index 0000000..1d93932
--- /dev/null
@@ -0,0 +1,4 @@
+import frrtest
+
+class TestTTable(frrtest.TestRefOut):
+    program = './test_ttable'
diff --git a/tests/lib/test_ttable.refout b/tests/lib/test_ttable.refout
new file mode 100644 (file)
index 0000000..fb59c0f
--- /dev/null
@@ -0,0 +1,143 @@
+ |--------------------------------|
+ | Column 1 | Column 2 | Column 3 |
+ |--------------------------------|
+
+ |--------------------------------|
+ | Column 1 | Column 2 | Column 3 |
+ |--------------------------------|
+
+ |--------------------------------|
+ | Column 1 | Column 2 | Column 3 |
+ |----------+----------+----------|
+ | a        | b        | c        |
+ |--------------------------------|
+
+ |--------------------------------|
+ | Column 1 | Column 2 | Column 3 |
+ |----------+----------+----------|
+ | a        | b        | c        |
+ |----------+----------+----------|
+ |          |          |          |
+ |--------------------------------|
+
+ |-----------|
+ | a | b | c |
+ |---+---+---|
+ |   |   |   |
+ |-----------|
+
+ |--------|
+ |  |  |  |
+ |--------|
+
+ ||
+ ||
+
+ |---------------------------------------|
+ | slick | black |  | triple |      1337 |
+ |---------------------------------------|
+
+ |------------------------------------------------------------------------------------------------|
+ | slick                                                   | black      |  | triple   |      1337 |
+ |---------------------------------------------------------+------------+--+----------+-----------|
+ | nebula dusk session streets twilight pioneer beats yeah | prarie dog |  | cornmeal | :O -*_-*  |
+ |------------------------------------------------------------------------------------------------|
+
+ |---------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians |  | 2        | 2015.000000 |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | slick                                                   | black       |  | triple   |      1337   |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | nebula dusk session streets twilight pioneer beats yeah | prarie dog  |  | cornmeal | :O -*_-*    |
+ |---------------------------------------------------------------------------------------------------|
+
+ |---------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians |  | 2        | 2015.000000 |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | slick                                                   | black       |  | triple   |      1337   |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | converting                                              | vegetarians |  | 1        | 2003        |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | nebula dusk session streets twilight pioneer beats yeah | prarie dog  |  | cornmeal | :O -*_-*    |
+ |---------------------------------------------------------------------------------------------------|
+
+ |---------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians |  | 2        | 2015.000000 |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | she                                                     | pioneer     |  | aki      | 1           |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | slick                                                   | black       |  | triple   |      1337   |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | converting                                              | vegetarians |  | 1        | 2003        |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | nebula dusk session streets twilight pioneer beats yeah | prarie dog  |  | cornmeal | :O -*_-*    |
+ |---------------------------------------------------------------------------------------------------|
+
+ |---------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians |  | 2        | 2015.000000 |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | she                                                     | pioneer     |  | aki      | 1           |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | slick                                                   | black       |  | triple   |      1337   |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | converting                                              | vegetarians |  | 1        | 2003        |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | nebula dusk session streets twilight pioneer beats yeah | prarie dog  |  | cornmeal | :O -*_-*    |
+ |---------------------------------------------------------------------------------------------------|
+
+ |---------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians |  | 2        | 2015.000000 |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | she                                                     |     pioneer |  | aki      | 1           |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | slick                                                   |       black |  | triple   |      1337   |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | converting                                              | vegetarians |  | 1        | 2003        |
+ |---------------------------------------------------------+-------------+--+----------+-------------|
+ | nebula dusk session streets twilight pioneer beats yeah |  prarie dog |  | cornmeal | :O -*_-*    |
+ |---------------------------------------------------------------------------------------------------|
+
+ |----------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians  |  | 2        | 2015.000000 |
+ |---------------------------------------------------------+--------------+--+----------+-------------|
+ | she                                                     |      pioneer |  | aki      | 1           |
+ |---------------------------------------------------------+--------------+--+----------+-------------|
+ | slick                                                   |        black |  | triple   |      1337   |
+ |---------------------------------------------------------+--------------+--+----------+-------------|
+ | converting                                              |  vegetarians |  | 1        | 2003        |
+ |---------------------------------------------------------+--------------+--+----------+-------------|
+ | nebula dusk session streets twilight pioneer beats yeah |   prarie dog |  | cornmeal | :O -*_-*    |
+ |----------------------------------------------------------------------------------------------------|
+
+ |--------------------------------------------------------------------------------------------------------|
+ |  converting                                              |  vegetarians  |   |  2        | 2015.000000 |
+ |----------------------------------------------------------+---------------+---+-----------+-------------|
+ |  she                                                     |       pioneer |   |  aki      | 1           |
+ |----------------------------------------------------------+---------------+---+-----------+-------------|
+ |  slick                                                   |         black |   |  triple   |      1337   |
+ |----------------------------------------------------------+---------------+---+-----------+-------------|
+ |  converting                                              |   vegetarians |   |  1        | 2003        |
+ |----------------------------------------------------------+---------------+---+-----------+-------------|
+ |  nebula dusk session streets twilight pioneer beats yeah |    prarie dog |   |  cornmeal | :O -*_-*    |
+ |--------------------------------------------------------------------------------------------------------|
+
+ |-----------------------------------------------------------------------------------------------|
+ | converting                                               vegetarians    2         2015.000000 |
+ |-----------------------------------------------------------------------------------------------|
+ |-----------------------------------------------------------------------------------------------|
+ | she                                                      pioneer        aki       1           |
+ | slick                                                    black          triple         1337   |
+ | converting                                               vegetarians    1         2003        |
+ | nebula dusk session streets twilight pioneer beats yeah  prarie dog     cornmeal  :O -*_-*    |
+ |-----------------------------------------------------------------------------------------------|
+
+ |------------------------------------------------------------------------------------------------|
+ | converting                                              | vegetarians    2         2015.000000 |
+ |---------------------------------------------------------+--------------------------------------|
+ |---------------------------------------------------------+--------------------------------------|
+ | she                                                     | pioneer        aki       1           |
+ | slick                                                   | black          triple         1337   |
+ | converting                                              | vegetarians    1         2003        |
+ | nebula dusk session streets twilight pioneer beats yeah | prarie dog     cornmeal  :O -*_-*    |
+ |------------------------------------------------------------------------------------------------|
+