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