2 * ASCII table generator.
3 * Copyright (C) 2017 Cumulus Networks
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)
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
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
25 #include "termtable.h"
27 DEFINE_MTYPE_STATIC(LIB
, TTABLE
, "ASCII table");
29 /* clang-format off */
30 const struct ttable_style ttable_styles
[] = {
60 }, { // blank, suitable for plaintext alignment
93 void ttable_del(struct ttable
*tt
)
95 for (int i
= tt
->nrows
- 1; i
>= 0; i
--)
96 ttable_del_row(tt
, i
);
98 XFREE(MTYPE_TTABLE
, tt
->table
);
99 XFREE(MTYPE_TTABLE
, tt
);
102 struct ttable
*ttable_new(const struct ttable_style
*style
)
106 tt
= XCALLOC(MTYPE_TTABLE
, sizeof(struct ttable
));
117 * Inserts or appends a new row at the specified index.
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.
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.
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
131 * @return pointer to the first cell of allocated row
134 static struct ttable_cell
*ttable_insert_row_va(struct ttable
*tt
, int i
,
135 const char *format
, va_list ap
)
137 assert(i
>= -1 && i
< tt
->nrows
);
140 char *res
, *orig
, *section
;
141 struct ttable_cell
*row
;
145 /* count how many columns we have */
146 for (int j
= 0; format
[j
]; j
++)
147 ncols
+= !!(format
[j
] == '|');
152 else if (ncols
!= tt
->ncols
)
155 /* reallocate chunk if necessary */
156 while (tt
->size
< (tt
->nrows
+ 1) * sizeof(struct ttable_cell
*)) {
157 tt
->size
= MAX(2 * tt
->size
, 2 * sizeof(struct ttable_cell
*));
158 tt
->table
= XREALLOC(MTYPE_TTABLE
, tt
->table
, tt
->size
);
161 /* CALLOC a block of cells */
162 row
= XCALLOC(MTYPE_TTABLE
, tt
->ncols
* sizeof(struct ttable_cell
));
164 res
= vasnprintfrr(MTYPE_TMP
, shortbuf
, sizeof(shortbuf
), format
, ap
);
167 while (res
&& col
< tt
->ncols
) {
168 section
= strsep(&res
, "|");
169 row
[col
].text
= XSTRDUP(MTYPE_TTABLE
, section
);
170 row
[col
].style
= tt
->style
.cell
;
174 if (orig
!= shortbuf
)
175 XFREE(MTYPE_TMP
, orig
);
178 if (i
== -1 || i
== tt
->nrows
)
179 tt
->table
[tt
->nrows
] = row
;
181 memmove(&tt
->table
[i
+ 1], &tt
->table
[i
],
182 (tt
->nrows
- i
) * sizeof(struct ttable_cell
*));
191 struct ttable_cell
*ttable_insert_row(struct ttable
*tt
, unsigned int i
,
192 const char *format
, ...)
194 struct ttable_cell
*ret
;
197 va_start(ap
, format
);
198 ret
= ttable_insert_row_va(tt
, i
, format
, ap
);
204 struct ttable_cell
*ttable_add_row(struct ttable
*tt
, const char *format
, ...)
206 struct ttable_cell
*ret
;
209 va_start(ap
, format
);
210 ret
= ttable_insert_row_va(tt
, -1, format
, ap
);
216 void ttable_del_row(struct ttable
*tt
, unsigned int i
)
218 assert((int)i
< tt
->nrows
);
220 for (int j
= 0; j
< tt
->ncols
; j
++)
221 XFREE(MTYPE_TTABLE
, tt
->table
[i
][j
].text
);
223 XFREE(MTYPE_TTABLE
, tt
->table
[i
]);
225 memmove(&tt
->table
[i
], &tt
->table
[i
+ 1],
226 (tt
->nrows
- i
- 1) * sizeof(struct ttable_cell
*));
234 void ttable_align(struct ttable
*tt
, unsigned int row
, unsigned int col
,
235 unsigned int nrow
, unsigned int ncol
, enum ttable_align align
)
237 assert((int)row
< tt
->nrows
);
238 assert((int)col
< tt
->ncols
);
239 assert((int)row
+ (int)nrow
<= tt
->nrows
);
240 assert((int)col
+ (int)ncol
<= tt
->ncols
);
242 for (unsigned int i
= row
; i
< row
+ nrow
; i
++)
243 for (unsigned int j
= col
; j
< col
+ ncol
; j
++)
244 tt
->table
[i
][j
].style
.align
= align
;
247 static void ttable_cell_pad(struct ttable_cell
*cell
, enum ttable_align align
,
251 cell
->style
.lpad
= pad
;
253 cell
->style
.rpad
= pad
;
256 void ttable_pad(struct ttable
*tt
, unsigned int row
, unsigned int col
,
257 unsigned int nrow
, unsigned int ncol
, enum ttable_align align
,
260 assert((int)row
< tt
->nrows
);
261 assert((int)col
< tt
->ncols
);
262 assert((int)row
+ (int)nrow
<= tt
->nrows
);
263 assert((int)col
+ (int)ncol
<= tt
->ncols
);
265 for (unsigned int i
= row
; i
< row
+ nrow
; i
++)
266 for (unsigned int j
= col
; j
< col
+ ncol
; j
++)
267 ttable_cell_pad(&tt
->table
[i
][j
], align
, pad
);
270 void ttable_restyle(struct ttable
*tt
)
272 for (int i
= 0; i
< tt
->nrows
; i
++)
273 for (int j
= 0; j
< tt
->ncols
; j
++)
274 tt
->table
[i
][j
].style
= tt
->style
.cell
;
277 void ttable_colseps(struct ttable
*tt
, unsigned int col
,
278 enum ttable_align align
, bool on
, char sep
)
280 for (int i
= 0; i
< tt
->nrows
; i
++) {
281 if (align
== RIGHT
) {
282 tt
->table
[i
][col
].style
.border
.right_on
= on
;
283 tt
->table
[i
][col
].style
.border
.right
= sep
;
285 tt
->table
[i
][col
].style
.border
.left_on
= on
;
286 tt
->table
[i
][col
].style
.border
.left
= sep
;
291 void ttable_rowseps(struct ttable
*tt
, unsigned int row
,
292 enum ttable_align align
, bool on
, char sep
)
294 for (int i
= 0; i
< tt
->ncols
; i
++) {
296 tt
->table
[row
][i
].style
.border
.top_on
= on
;
297 tt
->table
[row
][i
].style
.border
.top
= sep
;
299 tt
->table
[row
][i
].style
.border
.bottom_on
= on
;
300 tt
->table
[row
][i
].style
.border
.bottom
= sep
;
305 char *ttable_dump(struct ttable
*tt
, const char *newline
)
307 /* clang-format off */
308 char *buf
; // print buffer
309 size_t pos
; // position in buffer
310 size_t nl_len
; // strlen(newline)
311 int cw
[tt
->ncols
]; // calculated column widths
312 int nlines
; // total number of newlines / table lines
313 size_t width
; // length of one line, with newline
314 int abspad
; // calculated whitespace for sprintf
315 char *left
; // left part of line
316 size_t lsize
; // size of above
317 char *right
; // right part of line
318 size_t rsize
; // size of above
319 struct ttable_cell
*cell
, *row
; // iteration pointers
320 /* clang-format on */
322 nl_len
= strlen(newline
);
324 /* calculate width of each column */
325 memset(cw
, 0x00, sizeof(int) * tt
->ncols
);
327 for (int j
= 0; j
< tt
->ncols
; j
++)
328 for (int i
= 0, cellw
= 0; i
< tt
->nrows
; i
++) {
329 cell
= &tt
->table
[i
][j
];
331 cellw
+= (int)strlen(cell
->text
);
332 cellw
+= cell
->style
.lpad
;
333 cellw
+= cell
->style
.rpad
;
335 cellw
+= cell
->style
.border
.left_on
? 1 : 0;
336 if (j
!= tt
->ncols
- 1)
337 cellw
+= cell
->style
.border
.right_on
? 1 : 0;
338 cw
[j
] = MAX(cw
[j
], cellw
);
341 /* calculate overall line width, including newline */
343 width
+= tt
->style
.indent
;
344 width
+= tt
->style
.border
.left_on
? 1 : 0;
345 width
+= tt
->style
.border
.right_on
? 1 : 0;
346 width
+= strlen(newline
);
347 for (int i
= 0; i
< tt
->ncols
; i
++)
350 /* calculate number of lines en total */
352 nlines
+= tt
->style
.border
.top_on
? 1 : 0;
353 nlines
+= 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier
354 for (int i
= 0; i
< tt
->nrows
; i
++) {
355 /* if leftmost cell has top / bottom border, whole row does */
356 nlines
+= tt
->table
[i
][0].style
.border
.top_on
? 1 : 0;
357 nlines
+= tt
->table
[i
][0].style
.border
.bottom_on
? 1 : 0;
360 /* initialize left & right */
361 lsize
= tt
->style
.indent
+ (tt
->style
.border
.left_on
? 1 : 0);
362 left
= XCALLOC(MTYPE_TTABLE
, lsize
);
363 rsize
= nl_len
+ (tt
->style
.border
.right_on
? 1 : 0);
364 right
= XCALLOC(MTYPE_TTABLE
, rsize
);
366 memset(left
, ' ', lsize
);
368 if (tt
->style
.border
.left_on
)
369 left
[lsize
- 1] = tt
->style
.border
.left
;
371 if (tt
->style
.border
.right_on
) {
372 right
[0] = tt
->style
.border
.right
;
373 memcpy(&right
[1], newline
, nl_len
);
375 memcpy(&right
[0], newline
, nl_len
);
377 /* allocate print buffer */
378 buf
= XCALLOC(MTYPE_TMP
, width
* (nlines
+ 1) + 1);
381 if (tt
->style
.border
.top_on
) {
382 memcpy(&buf
[pos
], left
, lsize
);
385 for (size_t i
= 0; i
< width
- lsize
- rsize
; i
++)
386 buf
[pos
++] = tt
->style
.border
.top
;
388 memcpy(&buf
[pos
], right
, rsize
);
392 for (int i
= 0; i
< tt
->nrows
; i
++) {
395 /* if top border and not first row, print top row border */
396 if (row
[0].style
.border
.top_on
&& i
!= 0) {
397 memcpy(&buf
[pos
], left
, lsize
);
400 for (size_t l
= 0; l
< width
- lsize
- rsize
; l
++)
401 buf
[pos
++] = row
[0].style
.border
.top
;
403 pos
-= width
- lsize
- rsize
;
404 for (int k
= 0; k
< tt
->ncols
; k
++) {
405 if (k
!= 0 && row
[k
].style
.border
.left_on
)
406 buf
[pos
] = tt
->style
.corner
;
408 if (row
[k
].style
.border
.right_on
409 && k
!= tt
->ncols
- 1)
410 buf
[pos
- 1] = tt
->style
.corner
;
413 memcpy(&buf
[pos
], right
, rsize
);
417 memcpy(&buf
[pos
], left
, lsize
);
420 for (int j
= 0; j
< tt
->ncols
; j
++) {
421 /* if left border && not first col print left border */
422 if (row
[j
].style
.border
.left_on
&& j
!= 0)
423 buf
[pos
++] = row
[j
].style
.border
.left
;
425 /* print left padding */
426 for (int k
= 0; k
< row
[j
].style
.lpad
; k
++)
429 /* calculate padding for sprintf */
431 abspad
-= row
[j
].style
.rpad
;
432 abspad
-= row
[j
].style
.lpad
;
434 abspad
-= row
[j
].style
.border
.left_on
? 1 : 0;
435 if (j
!= tt
->ncols
- 1)
436 abspad
-= row
[j
].style
.border
.right_on
? 1 : 0;
439 if (row
[j
].style
.align
== LEFT
)
440 pos
+= sprintf(&buf
[pos
], "%-*s", abspad
,
443 pos
+= sprintf(&buf
[pos
], "%*s", abspad
,
446 /* print right padding */
447 for (int k
= 0; k
< row
[j
].style
.rpad
; k
++)
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
;
455 memcpy(&buf
[pos
], right
, rsize
);
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
);
463 for (size_t l
= 0; l
< width
- lsize
- rsize
; l
++)
464 buf
[pos
++] = row
[0].style
.border
.bottom
;
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
;
471 if (row
[k
].style
.border
.right_on
472 && k
!= tt
->ncols
- 1)
473 buf
[pos
- 1] = tt
->style
.corner
;
476 memcpy(&buf
[pos
], right
, rsize
);
480 assert(!buf
[pos
]); /* pos == & of first \0 in buf */
483 if (tt
->style
.border
.bottom_on
) {
484 memcpy(&buf
[pos
], left
, lsize
);
487 for (size_t l
= 0; l
< width
- lsize
- rsize
; l
++)
488 buf
[pos
++] = tt
->style
.border
.bottom
;
490 memcpy(&buf
[pos
], right
, rsize
);
496 XFREE(MTYPE_TTABLE
, left
);
497 XFREE(MTYPE_TTABLE
, right
);