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