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