]> git.proxmox.com Git - mirror_frr.git/blob - lib/termtable.c
doc: Add `show ipv6 rpf X:X::X:X` command to docs
[mirror_frr.git] / lib / termtable.c
1 /*
2 * ASCII table generator.
3 * Copyright (C) 2017 Cumulus Networks
4 * Quentin Young
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; see the file COPYING; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 #include <zebra.h>
21 #include <stdio.h>
22
23 #include "printfrr.h"
24 #include "memory.h"
25 #include "termtable.h"
26
27 DEFINE_MTYPE_STATIC(LIB, TTABLE, "ASCII table");
28
29 /* clang-format off */
30 const struct ttable_style ttable_styles[] = {
31 { // default ascii
32 .corner = '+',
33 .rownums_on = false,
34 .indent = 1,
35 .border = {
36 .top = '-',
37 .bottom = '-',
38 .left = '|',
39 .right = '|',
40 .top_on = true,
41 .bottom_on = true,
42 .left_on = true,
43 .right_on = true,
44 },
45 .cell = {
46 .lpad = 1,
47 .rpad = 1,
48 .align = LEFT,
49 .border = {
50 .bottom = '-',
51 .bottom_on = true,
52 .top = '-',
53 .top_on = false,
54 .right = '|',
55 .right_on = true,
56 .left = '|',
57 .left_on = false,
58 },
59 },
60 }, { // blank, suitable for plaintext alignment
61 .corner = ' ',
62 .rownums_on = false,
63 .indent = 1,
64 .border = {
65 .top = ' ',
66 .bottom = ' ',
67 .left = ' ',
68 .right = ' ',
69 .top_on = false,
70 .bottom_on = false,
71 .left_on = false,
72 .right_on = false,
73 },
74 .cell = {
75 .lpad = 0,
76 .rpad = 3,
77 .align = LEFT,
78 .border = {
79 .bottom = ' ',
80 .bottom_on = false,
81 .top = ' ',
82 .top_on = false,
83 .right = ' ',
84 .right_on = false,
85 .left = ' ',
86 .left_on = false,
87 },
88 }
89 }
90 };
91 /* clang-format on */
92
93 void ttable_del(struct ttable *tt)
94 {
95 for (int i = tt->nrows - 1; i >= 0; i--)
96 ttable_del_row(tt, i);
97
98 XFREE(MTYPE_TTABLE, tt->table);
99 XFREE(MTYPE_TTABLE, tt);
100 }
101
102 struct ttable *ttable_new(const struct ttable_style *style)
103 {
104 struct ttable *tt;
105
106 tt = XCALLOC(MTYPE_TTABLE, sizeof(struct ttable));
107 tt->style = *style;
108 tt->nrows = 0;
109 tt->ncols = 0;
110 tt->size = 0;
111 tt->table = NULL;
112
113 return tt;
114 }
115
116 /**
117 * Inserts or appends a new row at the specified index.
118 *
119 * If the index is -1, the row is added to the end of the table. Otherwise the
120 * index must be a valid index into tt->table.
121 *
122 * If the table already has at least one row (and therefore a determinate
123 * number of columns), a format string specifying a number of columns not equal
124 * to tt->ncols will result in a no-op and a return value of NULL.
125 *
126 * @param tt table to insert into
127 * @param i insertion index; inserted row will be (i + 1)'th row
128 * @param format printf format string as in ttable_[add|insert]_row()
129 * @param ap pre-initialized variadic list of arguments for format string
130 *
131 * @return pointer to the first cell of allocated row
132 */
133 PRINTFRR(3, 0)
134 static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i,
135 const char *format, va_list ap)
136 {
137 assert(i >= -1 && i < tt->nrows);
138
139 char shortbuf[256];
140 char *res, *orig, *section;
141 struct ttable_cell *row;
142 int col = 0;
143 int ncols = 0;
144
145 /* count how many columns we have */
146 for (int j = 0; format[j]; j++)
147 ncols += !!(format[j] == '|');
148 ncols++;
149
150 if (tt->ncols == 0)
151 tt->ncols = ncols;
152 else if (ncols != tt->ncols)
153 return NULL;
154
155 /* reallocate chunk if necessary */
156 while (tt->size < (tt->nrows + 1) * sizeof(struct ttable_cell *)) {
157 tt->size = MAX(2 * tt->size, 2 * sizeof(struct ttable_cell *));
158 tt->table = XREALLOC(MTYPE_TTABLE, tt->table, tt->size);
159 }
160
161 /* CALLOC a block of cells */
162 row = XCALLOC(MTYPE_TTABLE, tt->ncols * sizeof(struct ttable_cell));
163
164 res = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap);
165 orig = res;
166
167 while (res && col < tt->ncols) {
168 section = strsep(&res, "|");
169 row[col].text = XSTRDUP(MTYPE_TTABLE, section);
170 row[col].style = tt->style.cell;
171 col++;
172 }
173
174 if (orig != shortbuf)
175 XFREE(MTYPE_TMP, orig);
176
177 /* insert row */
178 if (i == -1 || i == tt->nrows)
179 tt->table[tt->nrows] = row;
180 else {
181 memmove(&tt->table[i + 1], &tt->table[i],
182 (tt->nrows - i) * sizeof(struct ttable_cell *));
183 tt->table[i] = row;
184 }
185
186 tt->nrows++;
187
188 return row;
189 }
190
191 struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i,
192 const char *format, ...)
193 {
194 struct ttable_cell *ret;
195 va_list ap;
196
197 va_start(ap, format);
198 ret = ttable_insert_row_va(tt, i, format, ap);
199 va_end(ap);
200
201 return ret;
202 }
203
204 struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...)
205 {
206 struct ttable_cell *ret;
207 va_list ap;
208
209 va_start(ap, format);
210 ret = ttable_insert_row_va(tt, -1, format, ap);
211 va_end(ap);
212
213 return ret;
214 }
215
216 void ttable_del_row(struct ttable *tt, unsigned int i)
217 {
218 assert((int)i < tt->nrows);
219
220 for (int j = 0; j < tt->ncols; j++)
221 XFREE(MTYPE_TTABLE, tt->table[i][j].text);
222
223 XFREE(MTYPE_TTABLE, tt->table[i]);
224
225 memmove(&tt->table[i], &tt->table[i + 1],
226 (tt->nrows - i - 1) * sizeof(struct ttable_cell *));
227
228 tt->nrows--;
229
230 if (tt->nrows == 0)
231 tt->ncols = 0;
232 }
233
234 void ttable_align(struct ttable *tt, unsigned int row, unsigned int col,
235 unsigned int nrow, unsigned int ncol, enum ttable_align align)
236 {
237 assert((int)row < tt->nrows);
238 assert((int)col < tt->ncols);
239 assert((int)row + (int)nrow <= tt->nrows);
240 assert((int)col + (int)ncol <= tt->ncols);
241
242 for (unsigned int i = row; i < row + nrow; i++)
243 for (unsigned int j = col; j < col + ncol; j++)
244 tt->table[i][j].style.align = align;
245 }
246
247 static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align,
248 short pad)
249 {
250 if (align == LEFT)
251 cell->style.lpad = pad;
252 else
253 cell->style.rpad = pad;
254 }
255
256 void ttable_pad(struct ttable *tt, unsigned int row, unsigned int col,
257 unsigned int nrow, unsigned int ncol, enum ttable_align align,
258 short pad)
259 {
260 assert((int)row < tt->nrows);
261 assert((int)col < tt->ncols);
262 assert((int)row + (int)nrow <= tt->nrows);
263 assert((int)col + (int)ncol <= tt->ncols);
264
265 for (unsigned int i = row; i < row + nrow; i++)
266 for (unsigned int j = col; j < col + ncol; j++)
267 ttable_cell_pad(&tt->table[i][j], align, pad);
268 }
269
270 void ttable_restyle(struct ttable *tt)
271 {
272 for (int i = 0; i < tt->nrows; i++)
273 for (int j = 0; j < tt->ncols; j++)
274 tt->table[i][j].style = tt->style.cell;
275 }
276
277 void ttable_colseps(struct ttable *tt, unsigned int col,
278 enum ttable_align align, bool on, char sep)
279 {
280 for (int i = 0; i < tt->nrows; i++) {
281 if (align == RIGHT) {
282 tt->table[i][col].style.border.right_on = on;
283 tt->table[i][col].style.border.right = sep;
284 } else {
285 tt->table[i][col].style.border.left_on = on;
286 tt->table[i][col].style.border.left = sep;
287 }
288 }
289 }
290
291 void ttable_rowseps(struct ttable *tt, unsigned int row,
292 enum ttable_align align, bool on, char sep)
293 {
294 for (int i = 0; i < tt->ncols; i++) {
295 if (align == TOP) {
296 tt->table[row][i].style.border.top_on = on;
297 tt->table[row][i].style.border.top = sep;
298 } else {
299 tt->table[row][i].style.border.bottom_on = on;
300 tt->table[row][i].style.border.bottom = sep;
301 }
302 }
303 }
304
305 char *ttable_dump(struct ttable *tt, const char *newline)
306 {
307 /* clang-format off */
308 char *buf; // print buffer
309 size_t pos; // position in buffer
310 size_t nl_len; // strlen(newline)
311 int cw[tt->ncols]; // calculated column widths
312 int nlines; // total number of newlines / table lines
313 size_t width; // length of one line, with newline
314 int abspad; // calculated whitespace for sprintf
315 char *left; // left part of line
316 size_t lsize; // size of above
317 char *right; // right part of line
318 size_t rsize; // size of above
319 struct ttable_cell *cell, *row; // iteration pointers
320 /* clang-format on */
321
322 nl_len = strlen(newline);
323
324 /* calculate width of each column */
325 memset(cw, 0x00, sizeof(int) * tt->ncols);
326
327 for (int j = 0; j < tt->ncols; j++)
328 for (int i = 0, cellw = 0; i < tt->nrows; i++) {
329 cell = &tt->table[i][j];
330 cellw = 0;
331 cellw += (int)strlen(cell->text);
332 cellw += cell->style.lpad;
333 cellw += cell->style.rpad;
334 if (j != 0)
335 cellw += cell->style.border.left_on ? 1 : 0;
336 if (j != tt->ncols - 1)
337 cellw += cell->style.border.right_on ? 1 : 0;
338 cw[j] = MAX(cw[j], cellw);
339 }
340
341 /* calculate overall line width, including newline */
342 width = 0;
343 width += tt->style.indent;
344 width += tt->style.border.left_on ? 1 : 0;
345 width += tt->style.border.right_on ? 1 : 0;
346 width += strlen(newline);
347 for (int i = 0; i < tt->ncols; i++)
348 width += cw[i];
349
350 /* calculate number of lines en total */
351 nlines = tt->nrows;
352 nlines += tt->style.border.top_on ? 1 : 0;
353 nlines += 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier
354 for (int i = 0; i < tt->nrows; i++) {
355 /* if leftmost cell has top / bottom border, whole row does */
356 nlines += tt->table[i][0].style.border.top_on ? 1 : 0;
357 nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0;
358 }
359
360 /* initialize left & right */
361 lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0);
362 left = XCALLOC(MTYPE_TTABLE, lsize);
363 rsize = nl_len + (tt->style.border.right_on ? 1 : 0);
364 right = XCALLOC(MTYPE_TTABLE, rsize);
365
366 memset(left, ' ', lsize);
367
368 if (tt->style.border.left_on)
369 left[lsize - 1] = tt->style.border.left;
370
371 if (tt->style.border.right_on) {
372 right[0] = tt->style.border.right;
373 memcpy(&right[1], newline, nl_len);
374 } else
375 memcpy(&right[0], newline, nl_len);
376
377 /* allocate print buffer */
378 buf = XCALLOC(MTYPE_TMP, width * (nlines + 1) + 1);
379 pos = 0;
380
381 if (tt->style.border.top_on) {
382 memcpy(&buf[pos], left, lsize);
383 pos += lsize;
384
385 for (size_t i = 0; i < width - lsize - rsize; i++)
386 buf[pos++] = tt->style.border.top;
387
388 memcpy(&buf[pos], right, rsize);
389 pos += rsize;
390 }
391
392 for (int i = 0; i < tt->nrows; i++) {
393 row = tt->table[i];
394
395 /* if top border and not first row, print top row border */
396 if (row[0].style.border.top_on && i != 0) {
397 memcpy(&buf[pos], left, lsize);
398 pos += lsize;
399
400 for (size_t l = 0; l < width - lsize - rsize; l++)
401 buf[pos++] = row[0].style.border.top;
402
403 pos -= width - lsize - rsize;
404 for (int k = 0; k < tt->ncols; k++) {
405 if (k != 0 && row[k].style.border.left_on)
406 buf[pos] = tt->style.corner;
407 pos += cw[k];
408 if (row[k].style.border.right_on
409 && k != tt->ncols - 1)
410 buf[pos - 1] = tt->style.corner;
411 }
412
413 memcpy(&buf[pos], right, rsize);
414 pos += rsize;
415 }
416
417 memcpy(&buf[pos], left, lsize);
418 pos += lsize;
419
420 for (int j = 0; j < tt->ncols; j++) {
421 /* if left border && not first col print left border */
422 if (row[j].style.border.left_on && j != 0)
423 buf[pos++] = row[j].style.border.left;
424
425 /* print left padding */
426 for (int k = 0; k < row[j].style.lpad; k++)
427 buf[pos++] = ' ';
428
429 /* calculate padding for sprintf */
430 abspad = cw[j];
431 abspad -= row[j].style.rpad;
432 abspad -= row[j].style.lpad;
433 if (j != 0)
434 abspad -= row[j].style.border.left_on ? 1 : 0;
435 if (j != tt->ncols - 1)
436 abspad -= row[j].style.border.right_on ? 1 : 0;
437
438 /* print text */
439 if (row[j].style.align == LEFT)
440 pos += sprintf(&buf[pos], "%-*s", abspad,
441 row[j].text);
442 else
443 pos += sprintf(&buf[pos], "%*s", abspad,
444 row[j].text);
445
446 /* print right padding */
447 for (int k = 0; k < row[j].style.rpad; k++)
448 buf[pos++] = ' ';
449
450 /* if right border && not last col print right border */
451 if (row[j].style.border.right_on && j != tt->ncols - 1)
452 buf[pos++] = row[j].style.border.right;
453 }
454
455 memcpy(&buf[pos], right, rsize);
456 pos += rsize;
457
458 /* if bottom border and not last row, print bottom border */
459 if (row[0].style.border.bottom_on && i != tt->nrows - 1) {
460 memcpy(&buf[pos], left, lsize);
461 pos += lsize;
462
463 for (size_t l = 0; l < width - lsize - rsize; l++)
464 buf[pos++] = row[0].style.border.bottom;
465
466 pos -= width - lsize - rsize;
467 for (int k = 0; k < tt->ncols; k++) {
468 if (k != 0 && row[k].style.border.left_on)
469 buf[pos] = tt->style.corner;
470 pos += cw[k];
471 if (row[k].style.border.right_on
472 && k != tt->ncols - 1)
473 buf[pos - 1] = tt->style.corner;
474 }
475
476 memcpy(&buf[pos], right, rsize);
477 pos += rsize;
478 }
479
480 assert(!buf[pos]); /* pos == & of first \0 in buf */
481 }
482
483 if (tt->style.border.bottom_on) {
484 memcpy(&buf[pos], left, lsize);
485 pos += lsize;
486
487 for (size_t l = 0; l < width - lsize - rsize; l++)
488 buf[pos++] = tt->style.border.bottom;
489
490 memcpy(&buf[pos], right, rsize);
491 pos += rsize;
492 }
493
494 buf[pos] = '\0';
495
496 XFREE(MTYPE_TTABLE, left);
497 XFREE(MTYPE_TTABLE, right);
498
499 return buf;
500 }