]> git.proxmox.com Git - mirror_frr.git/blob - lib/termtable.c
lib: add table generator
[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 "termtable.h"
24 #include "memory.h"
25
26 /* clang-format off */
27 struct ttable_style ttable_styles[] = {
28 { // default ascii
29 .corner = '+',
30 .rownums_on = false,
31 .indent = 1,
32 .border.top = '-',
33 .border.bottom = '-',
34 .border.left = '|',
35 .border.right = '|',
36 .border.top_on = true,
37 .border.bottom_on = true,
38 .border.left_on = true,
39 .border.right_on = true,
40 .cell.lpad = 1,
41 .cell.rpad = 1,
42 .cell.align = LEFT,
43 .cell.border.bottom = '-',
44 .cell.border.bottom_on = true,
45 .cell.border.top = '-',
46 .cell.border.top_on = false,
47 .cell.border.right = '|',
48 .cell.border.right_on = true,
49 .cell.border.left = '|',
50 .cell.border.left_on = false,
51 }, { // blank, suitable for plaintext alignment
52 .corner = ' ',
53 .rownums_on = false,
54 .indent = 1,
55 .border.top = ' ',
56 .border.bottom = ' ',
57 .border.left = ' ',
58 .border.right = ' ',
59 .border.top_on = false,
60 .border.bottom_on = false,
61 .border.left_on = false,
62 .border.right_on = false,
63 .cell.lpad = 0,
64 .cell.rpad = 3,
65 .cell.align = LEFT,
66 .cell.border.bottom = ' ',
67 .cell.border.bottom_on = false,
68 .cell.border.top = ' ',
69 .cell.border.top_on = false,
70 .cell.border.right = ' ',
71 .cell.border.right_on = false,
72 .cell.border.left = ' ',
73 .cell.border.left_on = false,
74 }
75 };
76 /* clang-format on */
77
78 void ttable_del(struct ttable *tt)
79 {
80 for (int i = tt->nrows - 1; i >= 0; i--)
81 ttable_del_row(tt, i);
82
83 XFREE(MTYPE_TMP, tt->table);
84 XFREE(MTYPE_TMP, tt);
85 }
86
87 struct ttable *ttable_new(struct ttable_style *style)
88 {
89 struct ttable *tt;
90
91 tt = XCALLOC(MTYPE_TMP, sizeof(struct ttable));
92 tt->style = *style;
93 tt->nrows = 0;
94 tt->ncols = 0;
95 tt->size = 0;
96 tt->table = NULL;
97
98 return tt;
99 }
100
101 /**
102 * Inserts or appends a new row at the specified index.
103 *
104 * If the index is -1, the row is added to the end of the table. Otherwise the
105 * index must be a valid index into tt->table.
106 *
107 * If the table already has at least one row (and therefore a determinate
108 * number of columns), a format string specifying a number of columns not equal
109 * to tt->ncols will result in a no-op and a return value of NULL.
110 *
111 * @param tt table to insert into
112 * @param i insertion index; inserted row will be (i + 1)'th row
113 * @param format printf format string as in ttable_[add|insert]_row()
114 * @param ap pre-initialized variadic list of arguments for format string
115 *
116 * @return pointer to the first cell of allocated row
117 */
118 static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i,
119 const char *format, va_list ap)
120 {
121 assert(i >= -1 && i < tt->nrows);
122
123 char *res, *orig, *section;
124 const char *f;
125 struct ttable_cell *row;
126 int col = 0;
127 int ncols = 0;
128
129 /* count how many columns we have */
130 f = format;
131 for (; f[ncols]; f[ncols] == '|' ? ncols++ : *f++)
132 ;
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_TMP, tt->table, tt->size);
144 }
145
146 /* CALLOC a block of cells */
147 row = XCALLOC(MTYPE_TMP, 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_TMP, 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_TMP, tt->table[i][j].text);
208
209 XFREE(MTYPE_TMP, 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 char *buf; // print buffer
294 size_t pos; // position in buffer
295 size_t nl_len; // strlen(newline)
296 int cw[tt->ncols]; // calculated column widths
297 int nlines; // total number of newlines / table lines
298 size_t width; // length of one line, with newline
299 int abspad; // calculated whitespace for sprintf
300 char *left; // left part of line
301 size_t lsize; // size of above
302 char *right; // right part of line
303 size_t rsize; // size of above
304 struct ttable_cell *cell, *row; // iteration pointers
305
306 nl_len = strlen(newline);
307
308 /* calculate width of each column */
309 memset(cw, 0x00, sizeof(int) * tt->ncols);
310
311 for (int j = 0; j < tt->ncols; j++)
312 for (int i = 0, cellw = 0; i < tt->nrows; i++) {
313 cell = &tt->table[i][j];
314 cellw = 0;
315 cellw += (int) strlen(cell->text);
316 cellw += cell->style.lpad;
317 cellw += cell->style.rpad;
318 if (j != 0)
319 cellw += cell->style.border.left_on ? 1 : 0;
320 if (j != tt->ncols - 1)
321 cellw += cell->style.border.right_on ? 1 : 0;
322 cw[j] = MAX(cw[j], cellw);
323 }
324
325 /* calculate overall line width, including newline */
326 width = 0;
327 width += tt->style.indent;
328 width += tt->style.border.left_on ? 1 : 0;
329 width += tt->style.border.right_on ? 1 : 0;
330 width += strlen(newline);
331 for (int i = 0; i < tt->ncols; i++)
332 width += cw[i];
333
334 /* calculate number of lines en total */
335 nlines = tt->nrows;
336 nlines += tt->style.border.top_on ? 1 : 0;
337 nlines += tt->style.border.bottom_on ? 1 : 1; // makes life easier
338 for (int i = 0; i < tt->nrows; i++) {
339 /* if leftmost cell has top / bottom border, whole row does */
340 nlines += tt->table[i][0].style.border.top_on ? 1 : 0;
341 nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0;
342 }
343
344 /* initialize left & right */
345 lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0);
346 left = XCALLOC(MTYPE_TMP, lsize);
347 rsize = nl_len + (tt->style.border.right_on ? 1 : 0);
348 right = XCALLOC(MTYPE_TMP, rsize);
349
350 for (size_t i = 0; i < lsize; i++)
351 left[i] = ' ';
352
353 if (tt->style.border.left_on)
354 left[lsize - 1] = tt->style.border.left;
355
356 if (tt->style.border.right_on) {
357 right[0] = tt->style.border.right;
358 memcpy(&right[1], newline, nl_len);
359 } else
360 memcpy(&right[0], newline, nl_len);
361
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_TMP, left);
484 XFREE(MTYPE_TMP, right);
485
486 return buf;
487 }