diff --git a/lib/Makefile.am b/lib/Makefile.am index a1b78d3c4d..474a932dfe 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -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 index 0000000000..ea46072d35 --- /dev/null +++ b/lib/termtable.c @@ -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 +#include + +#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 index 0000000000..6953002e90 --- /dev/null +++ b/lib/termtable.h @@ -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(, , "%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(, "%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 */ diff --git a/tests/.gitignore b/tests/.gitignore index bab3385da2..5279016b92 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -43,3 +43,4 @@ __pycache__ /lib/test_table /lib/test_timer_correctness /lib/test_timer_performance +/lib/test_ttable diff --git a/tests/Makefile.am b/tests/Makefile.am index f48abac47a..da96453a9e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 0000000000..674179b6ab --- /dev/null +++ b/tests/lib/test_ttable.c @@ -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 +#include +#include + +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 index 0000000000..1d93932ad2 --- /dev/null +++ b/tests/lib/test_ttable.py @@ -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 index 0000000000..fb59c0f3e1 --- /dev/null +++ b/tests/lib/test_ttable.refout @@ -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 -*_-* | + |------------------------------------------------------------------------------------------------| +