Coverage Report

Created: 2025-08-28 06:29

/src/frr/lib/termtable.c
Line
Count
Source (jump to first uncovered line)
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
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
void ttable_del(struct ttable *tt)
82
0
{
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
struct ttable *ttable_new(const struct ttable_style *style)
91
0
{
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
0
}
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
static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i,
123
            const char *format, va_list ap)
124
0
{
125
0
  assert(i >= -1 && i < tt->nrows);
126
127
0
  char shortbuf[256];
128
0
  char *res, *orig, *section;
129
0
  struct ttable_cell *row;
130
0
  int col = 0;
131
0
  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
0
    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
0
  }
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
0
  }
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
0
  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
0
  }
173
174
0
  tt->nrows++;
175
176
0
  return row;
177
0
}
178
179
struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i,
180
              const char *format, ...)
181
0
{
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
0
}
191
192
struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...)
193
0
{
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
0
}
203
204
void ttable_del_row(struct ttable *tt, unsigned int i)
205
0
{
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
void ttable_align(struct ttable *tt, unsigned int row, unsigned int col,
223
      unsigned int nrow, unsigned int ncol, enum ttable_align align)
224
0
{
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
static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align,
236
          short pad)
237
0
{
238
0
  if (align == LEFT)
239
0
    cell->style.lpad = pad;
240
0
  else
241
0
    cell->style.rpad = pad;
242
0
}
243
244
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
0
{
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
void ttable_restyle(struct ttable *tt)
259
0
{
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
void ttable_colseps(struct ttable *tt, unsigned int col,
266
        enum ttable_align align, bool on, char sep)
267
0
{
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
0
    } else {
273
0
      tt->table[i][col].style.border.left_on = on;
274
0
      tt->table[i][col].style.border.left = sep;
275
0
    }
276
0
  }
277
0
}
278
279
void ttable_rowseps(struct ttable *tt, unsigned int row,
280
        enum ttable_align align, bool on, char sep)
281
0
{
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
0
    } else {
287
0
      tt->table[row][i].style.border.bottom_on = on;
288
0
      tt->table[row][i].style.border.bottom = sep;
289
0
    }
290
0
  }
291
0
}
292
293
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
0
    }
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
0
  }
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
0
  } 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
0
  }
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
0
      }
400
401
0
      memcpy(&buf[pos], right, rsize);
402
0
      pos += rsize;
403
0
    }
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
0
                 row[j].text);
430
0
      else
431
0
        pos += sprintf(&buf[pos], "%*s", abspad,
432
0
                 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
0
    }
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
0
      }
463
464
0
      memcpy(&buf[pos], right, rsize);
465
0
      pos += rsize;
466
0
    }
467
468
0
    assert(!buf[pos]); /* pos == & of first \0 in buf */
469
0
  }
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
0
  }
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
0
}
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
json_object *ttable_json(struct ttable *tt, const char *const formats)
500
0
{
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
0
      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
0
      }
525
0
      json_object_object_add(jobj, tt->table[0][j].text, val);
526
0
    }
527
0
  }
528
529
0
  return json;
530
0
}