]>
Commit | Line | Data |
---|---|---|
c15b21e4 KZ |
1 | /* |
2 | * TT - Table or Tree, features: | |
3 | * - column width could be defined as absolute or relative to the terminal width | |
4 | * - allows to truncate or wrap data in columns | |
5 | * - prints tree if parent->child relation is defined | |
6 | * - draws the tree by ASCII or UTF8 lines (depends on terminal setting) | |
7 | * | |
8 | * Copyright (C) 2010 Karel Zak <kzak@redhat.com> | |
9 | * | |
10 | * This file may be redistributed under the terms of the | |
11 | * GNU Lesser General Public License. | |
12 | */ | |
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <unistd.h> | |
16 | #include <string.h> | |
17 | #include <termios.h> | |
c15b21e4 KZ |
18 | #ifdef HAVE_SYS_IOCTL_H |
19 | #include <sys/ioctl.h> | |
20 | #endif | |
21 | ||
22 | #include "nls.h" | |
23 | #include "widechar.h" | |
24 | #include "c.h" | |
25 | #include "tt.h" | |
26 | ||
27 | struct tt_symbols { | |
28 | const char *branch; | |
29 | const char *vert; | |
30 | const char *right; | |
31 | }; | |
32 | ||
33 | static const struct tt_symbols ascii_tt_symbols = { | |
34 | .branch = "|-", | |
35 | .vert = "| ", | |
36 | .right = "`-", | |
37 | }; | |
38 | ||
39 | #ifdef HAVE_WIDECHAR | |
40 | #define mbs_width(_s) mbstowcs(NULL, _s, 0) | |
41 | ||
42 | #define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ | |
43 | #define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ | |
44 | #define UTF_H "\342\224\200" /* U+2500, Horizontal */ | |
45 | #define UTF_UR "\342\224\224" /* U+2514, Up and right */ | |
46 | ||
47 | static const struct tt_symbols utf8_tt_symbols = { | |
48 | .branch = UTF_VR UTF_H, | |
49 | .vert = UTF_V " ", | |
50 | .right = UTF_UR UTF_H, | |
51 | }; | |
52 | ||
53 | #else /* !HAVE_WIDECHAR */ | |
54 | # define mbs_width strlen(_s) | |
55 | #endif /* !HAVE_WIDECHAR */ | |
56 | ||
57 | #define is_last_column(_tb, _cl) \ | |
58 | list_last_entry(&(_cl)->cl_columns, &(_tb)->tb_columns) | |
59 | ||
60 | /* TODO: move to lib/mbalign.c */ | |
61 | #ifdef HAVE_WIDECHAR | |
62 | static size_t wc_truncate (wchar_t *wc, size_t width) | |
63 | { | |
64 | size_t cells = 0; | |
65 | int next_cells = 0; | |
66 | ||
67 | while (*wc) | |
68 | { | |
69 | next_cells = wcwidth (*wc); | |
70 | if (next_cells == -1) /* non printable */ | |
71 | { | |
72 | *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */ | |
73 | next_cells = 1; | |
74 | } | |
75 | if (cells + next_cells > width) | |
76 | break; | |
77 | cells += next_cells; | |
78 | wc++; | |
79 | } | |
80 | *wc = L'\0'; | |
81 | return cells; | |
82 | } | |
83 | #endif | |
84 | ||
85 | ||
86 | /* TODO: move to lib/mbalign.c */ | |
87 | static size_t mbs_truncate(char *str, size_t width) | |
88 | { | |
89 | size_t bytes = strlen(str) + 1; | |
90 | #ifdef HAVE_WIDECHAR | |
91 | size_t sz = mbs_width(str); | |
92 | wchar_t *wcs = NULL; | |
93 | int rc = -1; | |
94 | ||
95 | if (sz <= width) | |
96 | return sz; /* truncate is unnecessary */ | |
97 | ||
98 | if (sz == (size_t) -1) | |
99 | goto done; | |
100 | ||
101 | wcs = malloc(sz * sizeof(wchar_t)); | |
102 | if (!wcs) | |
103 | goto done; | |
104 | ||
105 | if (!mbstowcs(wcs, str, sz)) | |
106 | goto done; | |
107 | rc = wc_truncate(wcs, width); | |
108 | wcstombs(str, wcs, bytes); | |
109 | done: | |
110 | free(wcs); | |
111 | return rc; | |
112 | #else | |
113 | if (width < bytes) { | |
114 | str[width] = '\0'; | |
115 | return width; | |
116 | } | |
117 | return bytes; /* truncate is unnecessary */ | |
118 | #endif | |
119 | } | |
120 | ||
121 | /* | |
122 | * @flags: TT_FL_* flags (usually TT_FL_{ASCII,RAW}) | |
123 | * | |
124 | * Returns: newly allocated table | |
125 | */ | |
126 | struct tt *tt_new_table(int flags) | |
127 | { | |
128 | struct tt *tb; | |
129 | ||
130 | tb = calloc(1, sizeof(struct tt)); | |
131 | if (!tb) | |
132 | return NULL; | |
133 | ||
134 | tb->flags = flags; | |
135 | INIT_LIST_HEAD(&tb->tb_lines); | |
136 | INIT_LIST_HEAD(&tb->tb_columns); | |
137 | ||
f7a29259 | 138 | #if defined(HAVE_WIDECHAR) |
c15b21e4 KZ |
139 | if (!(flags & TT_FL_ASCII) && !strcmp(nl_langinfo(CODESET), "UTF-8")) |
140 | tb->symbols = &utf8_tt_symbols; | |
141 | else | |
142 | #endif | |
143 | tb->symbols = &ascii_tt_symbols; | |
144 | return tb; | |
145 | } | |
146 | ||
147 | void tt_free_table(struct tt *tb) | |
148 | { | |
149 | if (!tb) | |
150 | return; | |
151 | while (!list_empty(&tb->tb_lines)) { | |
152 | struct tt_line *ln = list_entry(tb->tb_lines.next, | |
153 | struct tt_line, ln_lines); | |
154 | list_del(&ln->ln_lines); | |
155 | free(ln->data); | |
156 | free(ln); | |
157 | } | |
158 | while (!list_empty(&tb->tb_columns)) { | |
159 | struct tt_column *cl = list_entry(tb->tb_columns.next, | |
160 | struct tt_column, cl_columns); | |
161 | list_del(&cl->cl_columns); | |
162 | free(cl); | |
163 | } | |
164 | free(tb); | |
165 | } | |
166 | ||
167 | /* | |
168 | * @tb: table | |
169 | * @name: column header | |
170 | * @whint: column width hint (absolute width: N > 1; relative width: N < 1) | |
171 | * @flags: usually TT_FL_{TREE,TRUNCATE} | |
172 | * | |
173 | * The column is necessary to address (for example for tt_line_set_data()) by | |
174 | * sequential number. The first defined column has the colnum = 0. For example: | |
175 | * | |
176 | * tt_define_column(tab, "FOO", 0.5, 0); // colnum = 0 | |
177 | * tt_define_column(tab, "BAR", 0.5, 0); // colnum = 1 | |
178 | * . | |
179 | * . | |
180 | * tt_line_set_data(line, 0, "foo-data"); // FOO column | |
181 | * tt_line_set_data(line, 1, "bar-data"); // BAR column | |
182 | * | |
183 | * Returns: newly allocated column definition | |
184 | */ | |
185 | struct tt_column *tt_define_column(struct tt *tb, const char *name, | |
186 | double whint, int flags) | |
187 | { | |
188 | struct tt_column *cl; | |
189 | ||
190 | if (!tb) | |
191 | return NULL; | |
192 | cl = calloc(1, sizeof(*cl)); | |
193 | if (!cl) | |
194 | return NULL; | |
195 | ||
196 | cl->name = name; | |
197 | cl->width_hint = whint; | |
198 | cl->flags = flags; | |
199 | cl->seqnum = tb->ncols++; | |
200 | ||
201 | if (flags & TT_FL_TREE) | |
202 | tb->flags |= TT_FL_TREE; | |
203 | ||
204 | INIT_LIST_HEAD(&cl->cl_columns); | |
205 | list_add_tail(&cl->cl_columns, &tb->tb_columns); | |
206 | return cl; | |
207 | } | |
208 | ||
209 | /* | |
210 | * @tb: table | |
211 | * @parent: parental line or NULL | |
212 | * | |
213 | * Returns: newly allocate line | |
214 | */ | |
215 | struct tt_line *tt_add_line(struct tt *tb, struct tt_line *parent) | |
216 | { | |
217 | struct tt_line *ln = NULL; | |
218 | ||
219 | if (!tb || !tb->ncols) | |
220 | goto err; | |
221 | ln = calloc(1, sizeof(*ln)); | |
222 | if (!ln) | |
223 | goto err; | |
224 | ln->data = calloc(tb->ncols, sizeof(char *)); | |
225 | if (!ln->data) | |
226 | goto err; | |
227 | ||
228 | ln->table = tb; | |
229 | ln->parent = parent; | |
230 | INIT_LIST_HEAD(&ln->ln_lines); | |
231 | INIT_LIST_HEAD(&ln->ln_children); | |
232 | INIT_LIST_HEAD(&ln->ln_branch); | |
233 | ||
234 | list_add_tail(&ln->ln_lines, &tb->tb_lines); | |
235 | ||
236 | if (parent) | |
237 | list_add_tail(&ln->ln_children, &parent->ln_branch); | |
238 | return ln; | |
239 | err: | |
240 | free(ln); | |
241 | return NULL; | |
242 | } | |
243 | ||
244 | /* | |
245 | * @tb: table | |
246 | * @colnum: number of column (0..N) | |
247 | * | |
248 | * Returns: pointer to column or NULL | |
249 | */ | |
250 | struct tt_column *tt_get_column(struct tt *tb, int colnum) | |
251 | { | |
252 | struct list_head *p; | |
253 | ||
254 | list_for_each(p, &tb->tb_columns) { | |
255 | struct tt_column *cl = | |
256 | list_entry(p, struct tt_column, cl_columns); | |
257 | if (cl->seqnum == colnum) | |
258 | return cl; | |
259 | } | |
260 | return NULL; | |
261 | } | |
262 | ||
263 | /* | |
264 | * @ln: line | |
265 | * @colnum: number of column (0..N) | |
266 | * @data: printable data | |
267 | * | |
268 | * Stores data that will be printed to the table cell. | |
269 | */ | |
270 | int tt_line_set_data(struct tt_line *ln, int colnum, const char *data) | |
271 | { | |
272 | struct tt_column *cl; | |
273 | ||
274 | if (!ln) | |
275 | return -1; | |
276 | cl = tt_get_column(ln->table, colnum); | |
277 | if (!cl) | |
278 | return -1; | |
1f51db36 KZ |
279 | |
280 | if (ln->data[cl->seqnum]) | |
281 | ln->data_sz -= strlen(ln->data[cl->seqnum]); | |
282 | ||
c15b21e4 | 283 | ln->data[cl->seqnum] = data; |
1f51db36 KZ |
284 | if (data) |
285 | ln->data_sz += strlen(data); | |
c15b21e4 KZ |
286 | return 0; |
287 | } | |
288 | ||
289 | static int get_terminal_width(void) | |
290 | { | |
291 | #ifdef TIOCGSIZE | |
292 | struct ttysize t_win; | |
293 | #endif | |
294 | #ifdef TIOCGWINSZ | |
295 | struct winsize w_win; | |
296 | #endif | |
297 | const char *cp; | |
298 | ||
299 | #ifdef TIOCGSIZE | |
300 | if (ioctl (0, TIOCGSIZE, &t_win) == 0) | |
301 | return t_win.ts_cols; | |
302 | #endif | |
303 | #ifdef TIOCGWINSZ | |
304 | if (ioctl (0, TIOCGWINSZ, &w_win) == 0) | |
305 | return w_win.ws_col; | |
306 | #endif | |
307 | cp = getenv("COLUMNS"); | |
308 | if (cp) | |
309 | return strtol(cp, NULL, 10); | |
310 | return 0; | |
311 | } | |
312 | ||
049caefd KZ |
313 | int tt_line_set_userdata(struct tt_line *ln, void *data) |
314 | { | |
315 | if (!ln) | |
316 | return -1; | |
317 | ln->userdata = data; | |
318 | return 0; | |
319 | } | |
320 | ||
c15b21e4 KZ |
321 | static char *line_get_ascii_art(struct tt_line *ln, char *buf, size_t *bufsz) |
322 | { | |
323 | const char *art; | |
324 | size_t len; | |
325 | ||
326 | if (!ln->parent) | |
327 | return buf; | |
328 | ||
329 | buf = line_get_ascii_art(ln->parent, buf, bufsz); | |
330 | if (!buf) | |
331 | return NULL; | |
332 | ||
333 | if (list_last_entry(&ln->ln_children, &ln->parent->ln_branch)) | |
f872ed1f | 334 | art = " "; |
c15b21e4 KZ |
335 | else |
336 | art = ln->table->symbols->vert; | |
337 | ||
338 | len = strlen(art); | |
339 | if (*bufsz < len) | |
340 | return NULL; /* no space, internal error */ | |
341 | ||
342 | memcpy(buf, art, len); | |
343 | *bufsz -= len; | |
344 | return buf + len; | |
345 | } | |
346 | ||
347 | static char *line_get_data(struct tt_line *ln, struct tt_column *cl, | |
348 | char *buf, size_t bufsz) | |
349 | { | |
350 | const char *data = ln->data[cl->seqnum]; | |
351 | const struct tt_symbols *sym; | |
352 | char *p = buf; | |
353 | ||
354 | memset(buf, 0, bufsz); | |
355 | ||
356 | if (!data) | |
357 | return NULL; | |
358 | if (!(cl->flags & TT_FL_TREE)) { | |
359 | strncpy(buf, data, bufsz); | |
360 | buf[bufsz - 1] = '\0'; | |
361 | return buf; | |
362 | } | |
363 | if (ln->parent) { | |
364 | p = line_get_ascii_art(ln->parent, buf, &bufsz); | |
365 | if (!p) | |
366 | return NULL; | |
367 | } | |
368 | ||
369 | sym = ln->table->symbols; | |
370 | ||
371 | if (!ln->parent) | |
372 | snprintf(p, bufsz, "%s", data); /* root node */ | |
373 | else if (list_last_entry(&ln->ln_children, &ln->parent->ln_branch)) | |
374 | snprintf(p, bufsz, "%s%s", sym->right, data); /* last chaild */ | |
375 | else | |
376 | snprintf(p, bufsz, "%s%s", sym->branch, data); /* any child */ | |
377 | ||
378 | return buf; | |
379 | } | |
380 | ||
381 | static void recount_widths(struct tt *tb, char *buf, size_t bufsz) | |
382 | { | |
383 | struct list_head *p; | |
384 | int width = 0, trunc_only; | |
385 | ||
386 | /* set width according to the size of data | |
387 | */ | |
388 | list_for_each(p, &tb->tb_columns) { | |
389 | struct tt_column *cl = | |
390 | list_entry(p, struct tt_column, cl_columns); | |
391 | struct list_head *lp; | |
392 | ||
393 | list_for_each(lp, &tb->tb_lines) { | |
394 | struct tt_line *ln = | |
395 | list_entry(lp, struct tt_line, ln_lines); | |
396 | ||
397 | char *data = line_get_data(ln, cl, buf, bufsz); | |
398 | size_t len = data ? mbs_width(data) : 0; | |
399 | ||
400 | if (cl->width < len) | |
401 | cl->width = len; | |
402 | } | |
403 | } | |
404 | ||
405 | /* set minimal width (= size of column header) | |
406 | */ | |
407 | list_for_each(p, &tb->tb_columns) { | |
408 | struct tt_column *cl = | |
409 | list_entry(p, struct tt_column, cl_columns); | |
410 | ||
3e451589 KZ |
411 | if (cl->name) |
412 | cl->width_min = mbs_width(cl->name); | |
c15b21e4 | 413 | |
ac3d410c KZ |
414 | if (cl->width < cl->width_min) |
415 | cl->width = cl->width_min; | |
3e451589 | 416 | |
ac3d410c | 417 | else if (cl->width_hint >= 1 && |
3e451589 | 418 | cl->width < (int) cl->width_hint && |
ac3d410c | 419 | cl->width_min < (int) cl->width_hint) |
3e451589 | 420 | |
c15b21e4 KZ |
421 | cl->width = (int) cl->width_hint; |
422 | ||
423 | width += cl->width + (is_last_column(tb, cl) ? 0 : 1); | |
424 | } | |
425 | ||
426 | if (width == tb->termwidth) | |
427 | goto leave; | |
428 | if (width < tb->termwidth) { | |
429 | /* cool, use the extra space for the last column */ | |
430 | struct tt_column *cl = list_entry( | |
431 | tb->tb_columns.prev, struct tt_column, cl_columns); | |
432 | ||
3e451589 KZ |
433 | if (!(cl->flags & TT_FL_RIGHT)) |
434 | cl->width += tb->termwidth - width; | |
c15b21e4 KZ |
435 | goto leave; |
436 | } | |
437 | ||
438 | /* bad, we have to reduce output width, this is done in two steps: | |
439 | * 1/ reduce columns with a relative width and with truncate flag | |
440 | * 2) reduce columns with a relative width without truncate flag | |
441 | */ | |
442 | trunc_only = 1; | |
443 | while(width > tb->termwidth) { | |
444 | int org = width; | |
445 | ||
446 | list_for_each(p, &tb->tb_columns) { | |
447 | struct tt_column *cl = | |
448 | list_entry(p, struct tt_column, cl_columns); | |
449 | ||
450 | if (width <= tb->termwidth) | |
451 | break; | |
452 | if (cl->width_hint > 1) | |
453 | continue; /* never truncate columns with absolute sizes */ | |
454 | if (cl->flags & TT_FL_TREE) | |
455 | continue; /* never truncate the tree */ | |
3e451589 | 456 | if (trunc_only && !(cl->flags & TT_FL_TRUNC)) |
c15b21e4 | 457 | continue; |
ac3d410c KZ |
458 | if (cl->width == cl->width_min) |
459 | continue; | |
c15b21e4 KZ |
460 | if (cl->width > cl->width_hint * tb->termwidth) { |
461 | cl->width--; | |
462 | width--; | |
463 | } | |
464 | } | |
465 | if (org == width) { | |
466 | if (trunc_only) | |
467 | trunc_only = 0; | |
468 | else | |
469 | break; | |
470 | } | |
471 | } | |
472 | leave: | |
e7baa971 | 473 | /* |
c15b21e4 KZ |
474 | fprintf(stderr, "terminal: %d, output: %d\n", tb->termwidth, width); |
475 | ||
476 | list_for_each(p, &tb->tb_columns) { | |
477 | struct tt_column *cl = | |
478 | list_entry(p, struct tt_column, cl_columns); | |
479 | ||
3e451589 | 480 | fprintf(stderr, "width: %s=%d [hint=%d]\n", |
c15b21e4 KZ |
481 | cl->name, cl->width, |
482 | cl->width_hint > 1 ? (int) cl->width_hint : | |
483 | (int) (cl->width_hint * tb->termwidth)); | |
484 | } | |
e7baa971 | 485 | */ |
c15b21e4 KZ |
486 | return; |
487 | } | |
488 | ||
489 | /* note that this function modifies @data | |
490 | */ | |
491 | static void print_data(struct tt *tb, struct tt_column *cl, char *data) | |
492 | { | |
493 | size_t len, i; | |
494 | int width; | |
495 | ||
496 | if (!data) | |
497 | data = ""; | |
498 | ||
499 | /* raw mode */ | |
500 | if (tb->flags & TT_FL_RAW) { | |
501 | fputs(data, stdout); | |
502 | if (!is_last_column(tb, cl)) | |
503 | fputc(' ', stdout); | |
504 | return; | |
505 | } | |
506 | ||
507 | /* note that 'len' and 'width' are number of cells, not bytes */ | |
508 | len = mbs_width(data); | |
509 | ||
510 | if (!len || len == (size_t) -1) { | |
511 | len = 0; | |
512 | data = NULL; | |
513 | } | |
514 | width = cl->width; | |
515 | ||
516 | if (is_last_column(tb, cl) && len < width) | |
517 | width = len; | |
518 | ||
519 | /* truncate data */ | |
3e451589 | 520 | if (len > width && (cl->flags & TT_FL_TRUNC)) { |
c15b21e4 KZ |
521 | len = mbs_truncate(data, width); |
522 | if (!data || len == (size_t) -1) { | |
523 | len = 0; | |
524 | data = NULL; | |
525 | } | |
526 | } | |
3e451589 KZ |
527 | if (data) { |
528 | if (!(tb->flags & TT_FL_RAW) && (cl->flags & TT_FL_RIGHT)) { | |
529 | int xw = cl->width; | |
530 | fprintf(stdout, "%*s", xw, data); | |
531 | if (len < xw) | |
532 | len = xw; | |
533 | } | |
534 | else | |
535 | fputs(data, stdout); | |
536 | } | |
c15b21e4 KZ |
537 | for (i = len; i < width; i++) |
538 | fputc(' ', stdout); /* padding */ | |
539 | ||
540 | if (!is_last_column(tb, cl)) { | |
3e451589 | 541 | if (len > width && !(cl->flags & TT_FL_TRUNC)) { |
c15b21e4 KZ |
542 | fputc('\n', stdout); |
543 | for (i = 0; i <= cl->seqnum; i++) { | |
544 | struct tt_column *x = tt_get_column(tb, i); | |
545 | printf("%*s ", -x->width, " "); | |
546 | } | |
547 | } else | |
548 | fputc(' ', stdout); /* columns separator */ | |
549 | } | |
550 | } | |
551 | ||
552 | static void print_line(struct tt_line *ln, char *buf, size_t bufsz) | |
553 | { | |
554 | struct list_head *p; | |
555 | ||
556 | /* set width according to the size of data | |
557 | */ | |
558 | list_for_each(p, &ln->table->tb_columns) { | |
559 | struct tt_column *cl = | |
560 | list_entry(p, struct tt_column, cl_columns); | |
561 | ||
562 | print_data(ln->table, cl, line_get_data(ln, cl, buf, bufsz)); | |
563 | } | |
564 | fputc('\n', stdout); | |
565 | } | |
566 | ||
567 | static void print_header(struct tt *tb, char *buf, size_t bufsz) | |
568 | { | |
569 | struct list_head *p; | |
570 | ||
46e9ff0a | 571 | if ((tb->flags & TT_FL_NOHEADINGS) || list_empty(&tb->tb_lines)) |
c15b21e4 KZ |
572 | return; |
573 | ||
574 | /* set width according to the size of data | |
575 | */ | |
576 | list_for_each(p, &tb->tb_columns) { | |
577 | struct tt_column *cl = | |
578 | list_entry(p, struct tt_column, cl_columns); | |
579 | ||
580 | strncpy(buf, cl->name, bufsz); | |
581 | buf[bufsz - 1] = '\0'; | |
582 | print_data(tb, cl, buf); | |
583 | } | |
584 | fputc('\n', stdout); | |
585 | } | |
586 | ||
587 | static void print_table(struct tt *tb, char *buf, size_t bufsz) | |
588 | { | |
589 | struct list_head *p; | |
590 | ||
591 | print_header(tb, buf, bufsz); | |
592 | ||
593 | list_for_each(p, &tb->tb_lines) { | |
594 | struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); | |
595 | ||
596 | print_line(ln, buf, bufsz); | |
597 | } | |
598 | } | |
599 | ||
600 | static void print_tree_line(struct tt_line *ln, char *buf, size_t bufsz) | |
601 | { | |
602 | struct list_head *p; | |
603 | ||
604 | print_line(ln, buf, bufsz); | |
605 | ||
606 | if (list_empty(&ln->ln_branch)) | |
607 | return; | |
608 | ||
609 | /* print all children */ | |
610 | list_for_each(p, &ln->ln_branch) { | |
611 | struct tt_line *chld = | |
612 | list_entry(p, struct tt_line, ln_children); | |
613 | print_tree_line(chld, buf, bufsz); | |
614 | } | |
615 | } | |
616 | ||
617 | static void print_tree(struct tt *tb, char *buf, size_t bufsz) | |
618 | { | |
619 | struct list_head *p; | |
620 | ||
621 | print_header(tb, buf, bufsz); | |
622 | ||
623 | list_for_each(p, &tb->tb_lines) { | |
624 | struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); | |
625 | ||
626 | if (ln->parent) | |
627 | continue; | |
628 | ||
629 | print_tree_line(ln, buf, bufsz); | |
630 | } | |
631 | } | |
632 | ||
633 | /* | |
634 | * @tb: table | |
635 | * | |
636 | * Prints the table to stdout | |
637 | */ | |
638 | int tt_print_table(struct tt *tb) | |
639 | { | |
640 | char *line; | |
1f51db36 KZ |
641 | size_t line_sz; |
642 | struct list_head *p; | |
c15b21e4 KZ |
643 | |
644 | if (!tb) | |
645 | return -1; | |
646 | if (!tb->termwidth) { | |
647 | tb->termwidth = get_terminal_width(); | |
648 | if (tb->termwidth <= 0) | |
649 | tb->termwidth = 80; | |
650 | } | |
1f51db36 KZ |
651 | |
652 | line_sz = tb->termwidth; | |
653 | ||
654 | list_for_each(p, &tb->tb_lines) { | |
655 | struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); | |
656 | if (ln->data_sz > line_sz) | |
657 | line_sz = ln->data_sz; | |
658 | } | |
659 | ||
660 | line = malloc(line_sz); | |
c15b21e4 KZ |
661 | if (!line) |
662 | return -1; | |
663 | if (!(tb->flags & TT_FL_RAW)) | |
1f51db36 | 664 | recount_widths(tb, line, line_sz); |
c15b21e4 | 665 | if (tb->flags & TT_FL_TREE) |
1f51db36 | 666 | print_tree(tb, line, line_sz); |
c15b21e4 | 667 | else |
1f51db36 | 668 | print_table(tb, line, line_sz); |
c15b21e4 KZ |
669 | |
670 | free(line); | |
671 | return 0; | |
672 | } | |
673 | ||
3e451589 KZ |
674 | int tt_parse_columns_list(const char *list, int cols[], int *ncols, |
675 | int (name2id)(const char *, size_t)) | |
676 | { | |
677 | const char *begin = NULL, *p; | |
678 | ||
679 | if (!list || !*list || !cols || !ncols || !name2id) | |
680 | return -1; | |
681 | ||
682 | *ncols = 0; | |
683 | ||
684 | for (p = list; p && *p; p++) { | |
685 | const char *end = NULL; | |
686 | int id; | |
687 | ||
688 | if (!begin) | |
689 | begin = p; /* begin of the column name */ | |
690 | if (*p == ',') | |
691 | end = p; /* terminate the name */ | |
692 | if (*(p + 1) == '\0') | |
693 | end = p + 1; /* end of string */ | |
694 | if (!begin || !end) | |
695 | continue; | |
696 | if (end <= begin) | |
697 | return -1; | |
698 | ||
699 | id = name2id(begin, end - begin); | |
700 | if (id == -1) | |
701 | return -1; | |
702 | cols[ *ncols ] = id; | |
703 | (*ncols)++; | |
704 | begin = NULL; | |
705 | if (end && !*end) | |
706 | break; | |
707 | } | |
708 | return 0; | |
709 | } | |
710 | ||
c15b21e4 | 711 | #ifdef TEST_PROGRAM |
c15b21e4 KZ |
712 | #include <errno.h> |
713 | ||
714 | enum { MYCOL_NAME, MYCOL_FOO, MYCOL_BAR, MYCOL_PATH }; | |
715 | ||
716 | int main(int argc, char *argv[]) | |
717 | { | |
718 | struct tt *tb; | |
719 | struct tt_line *ln, *pr, *root; | |
720 | int flags = 0, notree = 0, i; | |
721 | ||
722 | if (argc == 2 && !strcmp(argv[1], "--help")) { | |
723 | printf("%s [--ascii | --raw | --list]\n", | |
724 | program_invocation_short_name); | |
725 | return EXIT_SUCCESS; | |
726 | } else if (argc == 2 && !strcmp(argv[1], "--ascii")) | |
727 | flags |= TT_FL_ASCII; | |
728 | else if (argc == 2 && !strcmp(argv[1], "--raw")) { | |
729 | flags |= TT_FL_RAW; | |
730 | notree = 1; | |
731 | } else if (argc == 2 && !strcmp(argv[1], "--list")) | |
732 | notree = 1; | |
733 | ||
734 | setlocale(LC_ALL, ""); | |
735 | bindtextdomain(PACKAGE, LOCALEDIR); | |
736 | textdomain(PACKAGE); | |
737 | ||
738 | tb = tt_new_table(flags); | |
739 | if (!tb) | |
740 | err(EXIT_FAILURE, "table initialization failed"); | |
741 | ||
742 | tt_define_column(tb, "NAME", 0.3, notree ? 0 : TT_FL_TREE); | |
3e451589 | 743 | tt_define_column(tb, "FOO", 0.3, TT_FL_TRUNC); |
c15b21e4 KZ |
744 | tt_define_column(tb, "BAR", 0.3, 0); |
745 | tt_define_column(tb, "PATH", 0.3, 0); | |
746 | ||
747 | for (i = 0; i < 2; i++) { | |
748 | root = ln = tt_add_line(tb, NULL); | |
749 | tt_line_set_data(ln, MYCOL_NAME, "AAA"); | |
750 | tt_line_set_data(ln, MYCOL_FOO, "a-foo-foo"); | |
751 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A"); | |
752 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA"); | |
753 | ||
754 | pr = ln = tt_add_line(tb, ln); | |
755 | tt_line_set_data(ln, MYCOL_NAME, "AAA.A"); | |
756 | tt_line_set_data(ln, MYCOL_FOO, "a.a-foo-foo"); | |
757 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A"); | |
758 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A"); | |
759 | ||
760 | ln = tt_add_line(tb, pr); | |
761 | tt_line_set_data(ln, MYCOL_NAME, "AAA.A.AAA"); | |
762 | tt_line_set_data(ln, MYCOL_FOO, "a.a.a-foo-foo"); | |
763 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.A"); | |
764 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/AAA"); | |
765 | ||
766 | ln = tt_add_line(tb, root); | |
767 | tt_line_set_data(ln, MYCOL_NAME, "AAA.B"); | |
768 | tt_line_set_data(ln, MYCOL_FOO, "a.b-foo-foo"); | |
769 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A.B"); | |
770 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/B"); | |
771 | ||
772 | ln = tt_add_line(tb, pr); | |
773 | tt_line_set_data(ln, MYCOL_NAME, "AAA.A.BBB"); | |
774 | tt_line_set_data(ln, MYCOL_FOO, "a.a.b-foo-foo"); | |
775 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.BBB"); | |
776 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/BBB"); | |
777 | ||
778 | ln = tt_add_line(tb, pr); | |
779 | tt_line_set_data(ln, MYCOL_NAME, "AAA.A.CCC"); | |
780 | tt_line_set_data(ln, MYCOL_FOO, "a.a.c-foo-foo"); | |
781 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.CCC"); | |
782 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/CCC"); | |
783 | ||
784 | ln = tt_add_line(tb, root); | |
785 | tt_line_set_data(ln, MYCOL_NAME, "AAA.C"); | |
786 | tt_line_set_data(ln, MYCOL_FOO, "a.c-foo-foo"); | |
787 | tt_line_set_data(ln, MYCOL_BAR, "barBar-A.C"); | |
788 | tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/C"); | |
789 | } | |
790 | ||
791 | tt_print_table(tb); | |
792 | tt_free_table(tb); | |
793 | ||
794 | return EXIT_SUCCESS; | |
795 | } | |
796 | #endif |