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