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