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