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
23 #include "termtable.h"
26 /* clang-format off */
27 struct ttable_style ttable_styles
[] = {
36 .border
.top_on
= true,
37 .border
.bottom_on
= true,
38 .border
.left_on
= true,
39 .border
.right_on
= true,
43 .cell
.border
.bottom
= '-',
44 .cell
.border
.bottom_on
= true,
45 .cell
.border
.top
= '-',
46 .cell
.border
.top_on
= false,
47 .cell
.border
.right
= '|',
48 .cell
.border
.right_on
= true,
49 .cell
.border
.left
= '|',
50 .cell
.border
.left_on
= false,
51 }, { // blank, suitable for plaintext alignment
59 .border
.top_on
= false,
60 .border
.bottom_on
= false,
61 .border
.left_on
= false,
62 .border
.right_on
= false,
66 .cell
.border
.bottom
= ' ',
67 .cell
.border
.bottom_on
= false,
68 .cell
.border
.top
= ' ',
69 .cell
.border
.top_on
= false,
70 .cell
.border
.right
= ' ',
71 .cell
.border
.right_on
= false,
72 .cell
.border
.left
= ' ',
73 .cell
.border
.left_on
= false,
78 void ttable_del(struct ttable
*tt
)
80 for (int i
= tt
->nrows
- 1; i
>= 0; i
--)
81 ttable_del_row(tt
, i
);
83 XFREE(MTYPE_TMP
, tt
->table
);
87 struct ttable
*ttable_new(struct ttable_style
*style
)
91 tt
= XCALLOC(MTYPE_TMP
, sizeof(struct ttable
));
102 * Inserts or appends a new row at the specified index.
104 * If the index is -1, the row is added to the end of the table. Otherwise the
105 * index must be a valid index into tt->table.
107 * If the table already has at least one row (and therefore a determinate
108 * number of columns), a format string specifying a number of columns not equal
109 * to tt->ncols will result in a no-op and a return value of NULL.
111 * @param tt table to insert into
112 * @param i insertion index; inserted row will be (i + 1)'th row
113 * @param format printf format string as in ttable_[add|insert]_row()
114 * @param ap pre-initialized variadic list of arguments for format string
116 * @return pointer to the first cell of allocated row
118 static struct ttable_cell
*ttable_insert_row_va(struct ttable
*tt
, int i
,
119 const char *format
, va_list ap
)
121 assert(i
>= -1 && i
< tt
->nrows
);
123 char *res
, *orig
, *section
;
125 struct ttable_cell
*row
;
129 /* count how many columns we have */
131 for (; f
[ncols
]; f
[ncols
] == '|' ? ncols
++ : *f
++)
137 else if (ncols
!= tt
->ncols
)
140 /* reallocate chunk if necessary */
141 while (tt
->size
< (tt
->nrows
+ 1) * sizeof(struct ttable_cell
*)) {
142 tt
->size
= MAX(2 * tt
->size
, 2 * sizeof(struct ttable_cell
*));
143 tt
->table
= XREALLOC(MTYPE_TMP
, tt
->table
, tt
->size
);
146 /* CALLOC a block of cells */
147 row
= XCALLOC(MTYPE_TMP
, tt
->ncols
* sizeof(struct ttable_cell
));
150 vasprintf(&res
, format
, ap
);
155 section
= strsep(&res
, "|");
156 row
[col
].text
= XSTRDUP(MTYPE_TMP
, section
);
157 row
[col
].style
= tt
->style
.cell
;
164 if (i
== -1 || i
== tt
->nrows
)
165 tt
->table
[tt
->nrows
] = row
;
167 memmove(&tt
->table
[i
+ 1], &tt
->table
[i
],
168 (tt
->nrows
- i
) * sizeof(struct ttable_cell
*));
177 struct ttable_cell
*ttable_insert_row(struct ttable
*tt
, unsigned int i
,
178 const char *format
, ...)
180 struct ttable_cell
*ret
;
183 va_start(ap
, format
);
184 ret
= ttable_insert_row_va(tt
, i
, format
, ap
);
190 struct ttable_cell
*ttable_add_row(struct ttable
*tt
, const char *format
, ...)
192 struct ttable_cell
*ret
;
195 va_start(ap
, format
);
196 ret
= ttable_insert_row_va(tt
, -1, format
, ap
);
202 void ttable_del_row(struct ttable
*tt
, unsigned int i
)
204 assert((int) i
< tt
->nrows
);
206 for (int j
= 0; j
< tt
->ncols
; j
++)
207 XFREE(MTYPE_TMP
, tt
->table
[i
][j
].text
);
209 XFREE(MTYPE_TMP
, tt
->table
[i
]);
211 memmove(&tt
->table
[i
], &tt
->table
[i
+ 1],
212 (tt
->nrows
- i
- 1) * sizeof(struct ttable_cell
*));
220 void ttable_align(struct ttable
*tt
, unsigned int row
, unsigned int col
,
221 unsigned int nrow
, unsigned int ncol
, enum ttable_align align
)
223 assert((int) row
< tt
->nrows
);
224 assert((int) col
< tt
->ncols
);
225 assert((int) row
+ (int) nrow
<= tt
->nrows
);
226 assert((int) col
+ (int) ncol
<= tt
->ncols
);
228 for (unsigned int i
= row
; i
< row
+ nrow
; i
++)
229 for (unsigned int j
= col
; j
< col
+ ncol
; j
++)
230 tt
->table
[i
][j
].style
.align
= align
;
233 static void ttable_cell_pad(struct ttable_cell
*cell
, enum ttable_align align
,
237 cell
->style
.lpad
= pad
;
239 cell
->style
.rpad
= pad
;
242 void ttable_pad(struct ttable
*tt
, unsigned int row
, unsigned int col
,
243 unsigned int nrow
, unsigned int ncol
, enum ttable_align align
,
246 assert((int) row
< tt
->nrows
);
247 assert((int) col
< tt
->ncols
);
248 assert((int) row
+ (int) nrow
<= tt
->nrows
);
249 assert((int) col
+ (int) ncol
<= tt
->ncols
);
251 for (unsigned int i
= row
; i
< row
+ nrow
; i
++)
252 for (unsigned int j
= col
; j
< col
+ ncol
; j
++)
253 ttable_cell_pad(&tt
->table
[i
][j
], align
, pad
);
256 void ttable_restyle(struct ttable
*tt
)
258 for (int i
= 0; i
< tt
->nrows
; i
++)
259 for (int j
= 0; j
< tt
->ncols
; j
++)
260 tt
->table
[i
][j
].style
= tt
->style
.cell
;
263 void ttable_colseps(struct ttable
*tt
, unsigned int col
,
264 enum ttable_align align
, bool on
, char sep
)
266 for (int i
= 0; i
< tt
->nrows
; i
++) {
267 if (align
== RIGHT
) {
268 tt
->table
[i
][col
].style
.border
.right_on
= on
;
269 tt
->table
[i
][col
].style
.border
.right
= sep
;
271 tt
->table
[i
][col
].style
.border
.left_on
= on
;
272 tt
->table
[i
][col
].style
.border
.left
= sep
;
277 void ttable_rowseps(struct ttable
*tt
, unsigned int row
,
278 enum ttable_align align
, bool on
, char sep
)
280 for (int i
= 0; i
< tt
->ncols
; i
++) {
282 tt
->table
[row
][i
].style
.border
.top_on
= on
;
283 tt
->table
[row
][i
].style
.border
.top
= sep
;
285 tt
->table
[row
][i
].style
.border
.bottom_on
= on
;
286 tt
->table
[row
][i
].style
.border
.bottom
= sep
;
291 char *ttable_dump(struct ttable
*tt
, const char *newline
)
293 char *buf
; // print buffer
294 size_t pos
; // position in buffer
295 size_t nl_len
; // strlen(newline)
296 int cw
[tt
->ncols
]; // calculated column widths
297 int nlines
; // total number of newlines / table lines
298 size_t width
; // length of one line, with newline
299 int abspad
; // calculated whitespace for sprintf
300 char *left
; // left part of line
301 size_t lsize
; // size of above
302 char *right
; // right part of line
303 size_t rsize
; // size of above
304 struct ttable_cell
*cell
, *row
; // iteration pointers
306 nl_len
= strlen(newline
);
308 /* calculate width of each column */
309 memset(cw
, 0x00, sizeof(int) * tt
->ncols
);
311 for (int j
= 0; j
< tt
->ncols
; j
++)
312 for (int i
= 0, cellw
= 0; i
< tt
->nrows
; i
++) {
313 cell
= &tt
->table
[i
][j
];
315 cellw
+= (int) strlen(cell
->text
);
316 cellw
+= cell
->style
.lpad
;
317 cellw
+= cell
->style
.rpad
;
319 cellw
+= cell
->style
.border
.left_on
? 1 : 0;
320 if (j
!= tt
->ncols
- 1)
321 cellw
+= cell
->style
.border
.right_on
? 1 : 0;
322 cw
[j
] = MAX(cw
[j
], cellw
);
325 /* calculate overall line width, including newline */
327 width
+= tt
->style
.indent
;
328 width
+= tt
->style
.border
.left_on
? 1 : 0;
329 width
+= tt
->style
.border
.right_on
? 1 : 0;
330 width
+= strlen(newline
);
331 for (int i
= 0; i
< tt
->ncols
; i
++)
334 /* calculate number of lines en total */
336 nlines
+= tt
->style
.border
.top_on
? 1 : 0;
337 nlines
+= tt
->style
.border
.bottom_on
? 1 : 1; // makes life easier
338 for (int i
= 0; i
< tt
->nrows
; i
++) {
339 /* if leftmost cell has top / bottom border, whole row does */
340 nlines
+= tt
->table
[i
][0].style
.border
.top_on
? 1 : 0;
341 nlines
+= tt
->table
[i
][0].style
.border
.bottom_on
? 1 : 0;
344 /* initialize left & right */
345 lsize
= tt
->style
.indent
+ (tt
->style
.border
.left_on
? 1 : 0);
346 left
= XCALLOC(MTYPE_TMP
, lsize
);
347 rsize
= nl_len
+ (tt
->style
.border
.right_on
? 1 : 0);
348 right
= XCALLOC(MTYPE_TMP
, rsize
);
350 for (size_t i
= 0; i
< lsize
; i
++)
353 if (tt
->style
.border
.left_on
)
354 left
[lsize
- 1] = tt
->style
.border
.left
;
356 if (tt
->style
.border
.right_on
) {
357 right
[0] = tt
->style
.border
.right
;
358 memcpy(&right
[1], newline
, nl_len
);
360 memcpy(&right
[0], newline
, nl_len
);
363 /* allocate print buffer */
364 buf
= XCALLOC(MTYPE_TMP
, width
* (nlines
+ 1) + 1);
367 if (tt
->style
.border
.top_on
) {
368 memcpy(&buf
[pos
], left
, lsize
);
371 for (size_t i
= 0; i
< width
- lsize
- rsize
; i
++)
372 buf
[pos
++] = tt
->style
.border
.top
;
374 memcpy(&buf
[pos
], right
, rsize
);
378 for (int i
= 0; i
< tt
->nrows
; i
++) {
381 /* if top border and not first row, print top row border */
382 if (row
[0].style
.border
.top_on
&& i
!= 0) {
383 memcpy(&buf
[pos
], left
, lsize
);
386 for (size_t i
= 0; i
< width
- lsize
- rsize
; i
++)
387 buf
[pos
++] = row
[0].style
.border
.top
;
389 pos
-= width
- lsize
- rsize
;
390 for (int k
= 0; k
< tt
->ncols
; k
++) {
391 if (k
!= 0 && row
[k
].style
.border
.left_on
)
392 buf
[pos
] = tt
->style
.corner
;
394 if (row
[k
].style
.border
.right_on
395 && k
!= tt
->ncols
- 1)
396 buf
[pos
- 1] = tt
->style
.corner
;
399 memcpy(&buf
[pos
], right
, rsize
);
403 memcpy(&buf
[pos
], left
, lsize
);
406 for (int j
= 0; j
< tt
->ncols
; j
++) {
407 /* if left border && not first col print left border */
408 if (row
[j
].style
.border
.left_on
&& j
!= 0)
409 buf
[pos
++] = row
[j
].style
.border
.left
;
411 /* print left padding */
412 for (int i
= 0; i
< row
[j
].style
.lpad
; i
++)
415 /* calculate padding for sprintf */
417 abspad
-= row
[j
].style
.rpad
;
418 abspad
-= row
[j
].style
.lpad
;
420 abspad
-= row
[j
].style
.border
.left_on
? 1 : 0;
421 if (j
!= tt
->ncols
- 1)
422 abspad
-= row
[j
].style
.border
.right_on
? 1 : 0;
426 if (row
[j
].style
.align
== LEFT
)
431 pos
+= sprintf(&buf
[pos
], fmt
, abspad
, row
[j
].text
);
433 /* print right padding */
434 for (int i
= 0; i
< row
[j
].style
.rpad
; i
++)
437 /* if right border && not last col print right border */
438 if (row
[j
].style
.border
.right_on
&& j
!= tt
->ncols
- 1)
439 buf
[pos
++] = row
[j
].style
.border
.right
;
442 memcpy(&buf
[pos
], right
, rsize
);
445 /* if bottom border and not last row, print bottom border */
446 if (row
[0].style
.border
.bottom_on
&& i
!= tt
->nrows
- 1) {
447 memcpy(&buf
[pos
], left
, lsize
);
450 for (size_t i
= 0; i
< width
- lsize
- rsize
; i
++)
451 buf
[pos
++] = row
[0].style
.border
.bottom
;
453 pos
-= width
- lsize
- rsize
;
454 for (int k
= 0; k
< tt
->ncols
; k
++) {
455 if (k
!= 0 && row
[k
].style
.border
.left_on
)
456 buf
[pos
] = tt
->style
.corner
;
458 if (row
[k
].style
.border
.right_on
459 && k
!= tt
->ncols
- 1)
460 buf
[pos
- 1] = tt
->style
.corner
;
463 memcpy(&buf
[pos
], right
, rsize
);
467 assert(!buf
[pos
]); /* pos == & of first \0 in buf */
470 if (tt
->style
.border
.bottom_on
) {
471 memcpy(&buf
[pos
], left
, lsize
);
474 for (size_t i
= 0; i
< width
- lsize
- rsize
; i
++)
475 buf
[pos
++] = tt
->style
.border
.bottom
;
477 memcpy(&buf
[pos
], right
, rsize
);
483 XFREE(MTYPE_TMP
, left
);
484 XFREE(MTYPE_TMP
, right
);