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