]> git.proxmox.com Git - mirror_frr.git/blob - lib/termtable.c
Merge pull request #8296 from chiragshah6/mdev
[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 static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i,
134 const char *format, va_list ap)
135 {
136 assert(i >= -1 && i < tt->nrows);
137
138 char shortbuf[256];
139 char *res, *orig, *section;
140 struct ttable_cell *row;
141 int col = 0;
142 int ncols = 0;
143
144 /* count how many columns we have */
145 for (int j = 0; format[j]; j++)
146 ncols += !!(format[j] == '|');
147 ncols++;
148
149 if (tt->ncols == 0)
150 tt->ncols = ncols;
151 else if (ncols != tt->ncols)
152 return NULL;
153
154 /* reallocate chunk if necessary */
155 while (tt->size < (tt->nrows + 1) * sizeof(struct ttable_cell *)) {
156 tt->size = MAX(2 * tt->size, 2 * sizeof(struct ttable_cell *));
157 tt->table = XREALLOC(MTYPE_TTABLE, tt->table, tt->size);
158 }
159
160 /* CALLOC a block of cells */
161 row = XCALLOC(MTYPE_TTABLE, tt->ncols * sizeof(struct ttable_cell));
162
163 res = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap);
164 orig = res;
165
166 while (res && col < tt->ncols) {
167 section = strsep(&res, "|");
168 row[col].text = XSTRDUP(MTYPE_TTABLE, section);
169 row[col].style = tt->style.cell;
170 col++;
171 }
172
173 if (orig != shortbuf)
174 XFREE(MTYPE_TMP, orig);
175
176 /* insert row */
177 if (i == -1 || i == tt->nrows)
178 tt->table[tt->nrows] = row;
179 else {
180 memmove(&tt->table[i + 1], &tt->table[i],
181 (tt->nrows - i) * sizeof(struct ttable_cell *));
182 tt->table[i] = row;
183 }
184
185 tt->nrows++;
186
187 return row;
188 }
189
190 struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i,
191 const char *format, ...)
192 {
193 struct ttable_cell *ret;
194 va_list ap;
195
196 va_start(ap, format);
197 ret = ttable_insert_row_va(tt, i, format, ap);
198 va_end(ap);
199
200 return ret;
201 }
202
203 struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...)
204 {
205 struct ttable_cell *ret;
206 va_list ap;
207
208 va_start(ap, format);
209 ret = ttable_insert_row_va(tt, -1, format, ap);
210 va_end(ap);
211
212 return ret;
213 }
214
215 void ttable_del_row(struct ttable *tt, unsigned int i)
216 {
217 assert((int)i < tt->nrows);
218
219 for (int j = 0; j < tt->ncols; j++)
220 XFREE(MTYPE_TTABLE, tt->table[i][j].text);
221
222 XFREE(MTYPE_TTABLE, tt->table[i]);
223
224 memmove(&tt->table[i], &tt->table[i + 1],
225 (tt->nrows - i - 1) * sizeof(struct ttable_cell *));
226
227 tt->nrows--;
228
229 if (tt->nrows == 0)
230 tt->ncols = 0;
231 }
232
233 void ttable_align(struct ttable *tt, unsigned int row, unsigned int col,
234 unsigned int nrow, unsigned int ncol, enum ttable_align align)
235 {
236 assert((int)row < tt->nrows);
237 assert((int)col < tt->ncols);
238 assert((int)row + (int)nrow <= tt->nrows);
239 assert((int)col + (int)ncol <= tt->ncols);
240
241 for (unsigned int i = row; i < row + nrow; i++)
242 for (unsigned int j = col; j < col + ncol; j++)
243 tt->table[i][j].style.align = align;
244 }
245
246 static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align,
247 short pad)
248 {
249 if (align == LEFT)
250 cell->style.lpad = pad;
251 else
252 cell->style.rpad = pad;
253 }
254
255 void ttable_pad(struct ttable *tt, unsigned int row, unsigned int col,
256 unsigned int nrow, unsigned int ncol, enum ttable_align align,
257 short pad)
258 {
259 assert((int)row < tt->nrows);
260 assert((int)col < tt->ncols);
261 assert((int)row + (int)nrow <= tt->nrows);
262 assert((int)col + (int)ncol <= tt->ncols);
263
264 for (unsigned int i = row; i < row + nrow; i++)
265 for (unsigned int j = col; j < col + ncol; j++)
266 ttable_cell_pad(&tt->table[i][j], align, pad);
267 }
268
269 void ttable_restyle(struct ttable *tt)
270 {
271 for (int i = 0; i < tt->nrows; i++)
272 for (int j = 0; j < tt->ncols; j++)
273 tt->table[i][j].style = tt->style.cell;
274 }
275
276 void ttable_colseps(struct ttable *tt, unsigned int col,
277 enum ttable_align align, bool on, char sep)
278 {
279 for (int i = 0; i < tt->nrows; i++) {
280 if (align == RIGHT) {
281 tt->table[i][col].style.border.right_on = on;
282 tt->table[i][col].style.border.right = sep;
283 } else {
284 tt->table[i][col].style.border.left_on = on;
285 tt->table[i][col].style.border.left = sep;
286 }
287 }
288 }
289
290 void ttable_rowseps(struct ttable *tt, unsigned int row,
291 enum ttable_align align, bool on, char sep)
292 {
293 for (int i = 0; i < tt->ncols; i++) {
294 if (align == TOP) {
295 tt->table[row][i].style.border.top_on = on;
296 tt->table[row][i].style.border.top = sep;
297 } else {
298 tt->table[row][i].style.border.bottom_on = on;
299 tt->table[row][i].style.border.bottom = sep;
300 }
301 }
302 }
303
304 char *ttable_dump(struct ttable *tt, const char *newline)
305 {
306 /* clang-format off */
307 char *buf; // print buffer
308 size_t pos; // position in buffer
309 size_t nl_len; // strlen(newline)
310 int cw[tt->ncols]; // calculated column widths
311 int nlines; // total number of newlines / table lines
312 size_t width; // length of one line, with newline
313 int abspad; // calculated whitespace for sprintf
314 char *left; // left part of line
315 size_t lsize; // size of above
316 char *right; // right part of line
317 size_t rsize; // size of above
318 struct ttable_cell *cell, *row; // iteration pointers
319 /* clang-format on */
320
321 nl_len = strlen(newline);
322
323 /* calculate width of each column */
324 memset(cw, 0x00, sizeof(int) * tt->ncols);
325
326 for (int j = 0; j < tt->ncols; j++)
327 for (int i = 0, cellw = 0; i < tt->nrows; i++) {
328 cell = &tt->table[i][j];
329 cellw = 0;
330 cellw += (int)strlen(cell->text);
331 cellw += cell->style.lpad;
332 cellw += cell->style.rpad;
333 if (j != 0)
334 cellw += cell->style.border.left_on ? 1 : 0;
335 if (j != tt->ncols - 1)
336 cellw += cell->style.border.right_on ? 1 : 0;
337 cw[j] = MAX(cw[j], cellw);
338 }
339
340 /* calculate overall line width, including newline */
341 width = 0;
342 width += tt->style.indent;
343 width += tt->style.border.left_on ? 1 : 0;
344 width += tt->style.border.right_on ? 1 : 0;
345 width += strlen(newline);
346 for (int i = 0; i < tt->ncols; i++)
347 width += cw[i];
348
349 /* calculate number of lines en total */
350 nlines = tt->nrows;
351 nlines += tt->style.border.top_on ? 1 : 0;
352 nlines += 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier
353 for (int i = 0; i < tt->nrows; i++) {
354 /* if leftmost cell has top / bottom border, whole row does */
355 nlines += tt->table[i][0].style.border.top_on ? 1 : 0;
356 nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0;
357 }
358
359 /* initialize left & right */
360 lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0);
361 left = XCALLOC(MTYPE_TTABLE, lsize);
362 rsize = nl_len + (tt->style.border.right_on ? 1 : 0);
363 right = XCALLOC(MTYPE_TTABLE, rsize);
364
365 memset(left, ' ', lsize);
366
367 if (tt->style.border.left_on)
368 left[lsize - 1] = tt->style.border.left;
369
370 if (tt->style.border.right_on) {
371 right[0] = tt->style.border.right;
372 memcpy(&right[1], newline, nl_len);
373 } else
374 memcpy(&right[0], newline, nl_len);
375
376 /* allocate print buffer */
377 buf = XCALLOC(MTYPE_TMP, width * (nlines + 1) + 1);
378 pos = 0;
379
380 if (tt->style.border.top_on) {
381 memcpy(&buf[pos], left, lsize);
382 pos += lsize;
383
384 for (size_t i = 0; i < width - lsize - rsize; i++)
385 buf[pos++] = tt->style.border.top;
386
387 memcpy(&buf[pos], right, rsize);
388 pos += rsize;
389 }
390
391 for (int i = 0; i < tt->nrows; i++) {
392 row = tt->table[i];
393
394 /* if top border and not first row, print top row border */
395 if (row[0].style.border.top_on && i != 0) {
396 memcpy(&buf[pos], left, lsize);
397 pos += lsize;
398
399 for (size_t l = 0; l < width - lsize - rsize; l++)
400 buf[pos++] = row[0].style.border.top;
401
402 pos -= width - lsize - rsize;
403 for (int k = 0; k < tt->ncols; k++) {
404 if (k != 0 && row[k].style.border.left_on)
405 buf[pos] = tt->style.corner;
406 pos += cw[k];
407 if (row[k].style.border.right_on
408 && k != tt->ncols - 1)
409 buf[pos - 1] = tt->style.corner;
410 }
411
412 memcpy(&buf[pos], right, rsize);
413 pos += rsize;
414 }
415
416 memcpy(&buf[pos], left, lsize);
417 pos += lsize;
418
419 for (int j = 0; j < tt->ncols; j++) {
420 /* if left border && not first col print left border */
421 if (row[j].style.border.left_on && j != 0)
422 buf[pos++] = row[j].style.border.left;
423
424 /* print left padding */
425 for (int k = 0; k < row[j].style.lpad; k++)
426 buf[pos++] = ' ';
427
428 /* calculate padding for sprintf */
429 abspad = cw[j];
430 abspad -= row[j].style.rpad;
431 abspad -= row[j].style.lpad;
432 if (j != 0)
433 abspad -= row[j].style.border.left_on ? 1 : 0;
434 if (j != tt->ncols - 1)
435 abspad -= row[j].style.border.right_on ? 1 : 0;
436
437 /* print text */
438 const char *fmt;
439 if (row[j].style.align == LEFT)
440 fmt = "%-*s";
441 else
442 fmt = "%*s";
443
444 pos += sprintf(&buf[pos], fmt, abspad, 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 }