1000 lines
22 KiB
C
1000 lines
22 KiB
C
/*
|
|
* TT - Table or Tree, features:
|
|
* - column width could be defined as absolute or relative to the terminal width
|
|
* - allows to truncate or wrap data in columns
|
|
* - prints tree if parent->child relation is defined
|
|
* - draws the tree by ASCII or UTF8 lines (depends on terminal setting)
|
|
*
|
|
* Copyright (C) 2010 Karel Zak <kzak@redhat.com>
|
|
*
|
|
* This file may be redistributed under the terms of the
|
|
* GNU Lesser General Public License.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <ctype.h>
|
|
|
|
#include "c.h"
|
|
#include "nls.h"
|
|
#include "widechar.h"
|
|
#include "tt.h"
|
|
#include "mbsalign.h"
|
|
#include "ttyutils.h"
|
|
|
|
struct tt_symbols {
|
|
const char *branch;
|
|
const char *vert;
|
|
const char *right;
|
|
};
|
|
|
|
static const struct tt_symbols ascii_tt_symbols = {
|
|
.branch = "|-",
|
|
.vert = "| ",
|
|
.right = "`-",
|
|
};
|
|
|
|
#ifdef HAVE_WIDECHAR
|
|
#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */
|
|
#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */
|
|
#define UTF_H "\342\224\200" /* U+2500, Horizontal */
|
|
#define UTF_UR "\342\224\224" /* U+2514, Up and right */
|
|
|
|
static const struct tt_symbols utf8_tt_symbols = {
|
|
.branch = UTF_VR UTF_H,
|
|
.vert = UTF_V " ",
|
|
.right = UTF_UR UTF_H,
|
|
};
|
|
#endif /* !HAVE_WIDECHAR */
|
|
|
|
#define is_last_column(_tb, _cl) \
|
|
list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns)
|
|
|
|
/*
|
|
* Counts number of cells in multibyte string. For all control and
|
|
* non-printable chars is the result width enlarged to store \x?? hex
|
|
* sequence. See mbs_safe_encode().
|
|
*/
|
|
static size_t mbs_safe_width(const char *s)
|
|
{
|
|
mbstate_t st;
|
|
const char *p = s;
|
|
size_t width = 0;
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
|
|
while (p && *p) {
|
|
if (iscntrl((unsigned char) *p)) {
|
|
width += 4; /* *p encoded to \x?? */
|
|
p++;
|
|
}
|
|
#ifdef HAVE_WIDECHAR
|
|
else {
|
|
wchar_t wc;
|
|
size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
|
|
|
|
if (len == 0)
|
|
break;
|
|
|
|
if (len == (size_t) -1 || len == (size_t) -2) {
|
|
len = 1;
|
|
width += (isprint((unsigned char) *p) ? 1 : 4);
|
|
|
|
} if (!iswprint(wc))
|
|
width += len * 4; /* hex encode whole sequence */
|
|
else
|
|
width += wcwidth(wc); /* number of cells */
|
|
p += len;
|
|
}
|
|
#else
|
|
else if (!isprint((unsigned char) *p)) {
|
|
width += 4; /* *p encoded to \x?? */
|
|
p++;
|
|
} else {
|
|
width++;
|
|
p++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
/*
|
|
* Returns allocated string where all control and non-printable chars are
|
|
* replaced with \x?? hex sequence.
|
|
*/
|
|
static char *mbs_safe_encode(const char *s, size_t *width)
|
|
{
|
|
mbstate_t st;
|
|
const char *p = s;
|
|
char *res, *r;
|
|
size_t sz = s ? strlen(s) : 0;
|
|
|
|
|
|
if (!sz)
|
|
return NULL;
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
|
|
res = malloc((sz * 4) + 1);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
r = res;
|
|
*width = 0;
|
|
|
|
while (p && *p) {
|
|
if (iscntrl((unsigned char) *p)) {
|
|
sprintf(r, "\\x%02x", (unsigned char) *p);
|
|
r += 4;
|
|
*width += 4;
|
|
p++;
|
|
}
|
|
#ifdef HAVE_WIDECHAR
|
|
else {
|
|
wchar_t wc;
|
|
size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
|
|
|
|
if (len == 0)
|
|
break; /* end of string */
|
|
|
|
if (len == (size_t) -1 || len == (size_t) -2) {
|
|
len = 1;
|
|
/*
|
|
* Not valid multibyte sequence -- maybe it's
|
|
* printable char according to the current locales.
|
|
*/
|
|
if (!isprint((unsigned char) *p)) {
|
|
sprintf(r, "\\x%02x", (unsigned char) *p);
|
|
r += 4;
|
|
*width += 4;
|
|
} else {
|
|
width++;
|
|
*r++ = *p;
|
|
}
|
|
} else if (!iswprint(wc)) {
|
|
size_t i;
|
|
for (i = 0; i < len; i++) {
|
|
sprintf(r, "\\x%02x", (unsigned char) *p);
|
|
r += 4;
|
|
*width += 4;
|
|
}
|
|
} else {
|
|
memcpy(r, p, len);
|
|
r += len;
|
|
*width += wcwidth(wc);
|
|
}
|
|
p += len;
|
|
}
|
|
#else
|
|
else if (!isprint((unsigned char) *p)) {
|
|
sprintf(r, "\\x%02x", (unsigned char) *p);
|
|
p++;
|
|
r += 4;
|
|
*width += 4;
|
|
} else {
|
|
*r++ = *p++;
|
|
*width++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
*r = '\0';
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* @flags: TT_FL_* flags (usually TT_FL_{ASCII,RAW})
|
|
*
|
|
* Returns: newly allocated table
|
|
*/
|
|
struct tt *tt_new_table(int flags)
|
|
{
|
|
struct tt *tb;
|
|
|
|
tb = calloc(1, sizeof(struct tt));
|
|
if (!tb)
|
|
return NULL;
|
|
|
|
tb->flags = flags;
|
|
INIT_LIST_HEAD(&tb->tb_lines);
|
|
INIT_LIST_HEAD(&tb->tb_columns);
|
|
|
|
#if defined(HAVE_WIDECHAR)
|
|
if (!(flags & TT_FL_ASCII) && !strcmp(nl_langinfo(CODESET), "UTF-8"))
|
|
tb->symbols = &utf8_tt_symbols;
|
|
else
|
|
#endif
|
|
tb->symbols = &ascii_tt_symbols;
|
|
|
|
tb->first_run = TRUE;
|
|
return tb;
|
|
}
|
|
|
|
void tt_remove_lines(struct tt *tb)
|
|
{
|
|
if (!tb)
|
|
return;
|
|
|
|
while (!list_empty(&tb->tb_lines)) {
|
|
struct tt_line *ln = list_entry(tb->tb_lines.next,
|
|
struct tt_line, ln_lines);
|
|
list_del(&ln->ln_lines);
|
|
free(ln->data);
|
|
free(ln);
|
|
}
|
|
}
|
|
|
|
void tt_free_table(struct tt *tb)
|
|
{
|
|
if (!tb)
|
|
return;
|
|
|
|
tt_remove_lines(tb);
|
|
|
|
while (!list_empty(&tb->tb_columns)) {
|
|
struct tt_column *cl = list_entry(tb->tb_columns.next,
|
|
struct tt_column, cl_columns);
|
|
list_del(&cl->cl_columns);
|
|
free(cl);
|
|
}
|
|
free(tb);
|
|
}
|
|
|
|
|
|
/*
|
|
* @tb: table
|
|
* @name: column header
|
|
* @whint: column width hint (absolute width: N > 1; relative width: N < 1)
|
|
* @flags: usually TT_FL_{TREE,TRUNCATE}
|
|
*
|
|
* The column width is possible to define by three ways:
|
|
*
|
|
* @whint = 0..1 : relative width, percent of terminal width
|
|
*
|
|
* @whint = 1..N : absolute width, empty colum will be truncated to
|
|
* the column header width
|
|
*
|
|
* @whint = 1..N
|
|
* @flags = TT_FL_STRICTWIDTH
|
|
* : absolute width, empty colum won't be truncated
|
|
*
|
|
* The column is necessary to address (for example for tt_line_set_data()) by
|
|
* sequential number. The first defined column has the colnum = 0. For example:
|
|
*
|
|
* tt_define_column(tab, "FOO", 0.5, 0); // colnum = 0
|
|
* tt_define_column(tab, "BAR", 0.5, 0); // colnum = 1
|
|
* .
|
|
* .
|
|
* tt_line_set_data(line, 0, "foo-data"); // FOO column
|
|
* tt_line_set_data(line, 1, "bar-data"); // BAR column
|
|
*
|
|
* Returns: newly allocated column definition
|
|
*/
|
|
struct tt_column *tt_define_column(struct tt *tb, const char *name,
|
|
double whint, int flags)
|
|
{
|
|
struct tt_column *cl;
|
|
|
|
if (!tb)
|
|
return NULL;
|
|
cl = calloc(1, sizeof(*cl));
|
|
if (!cl)
|
|
return NULL;
|
|
|
|
cl->name = name;
|
|
cl->width_hint = whint;
|
|
cl->flags = flags;
|
|
cl->seqnum = tb->ncols++;
|
|
|
|
if (flags & TT_FL_TREE)
|
|
tb->flags |= TT_FL_TREE;
|
|
|
|
INIT_LIST_HEAD(&cl->cl_columns);
|
|
list_add_tail(&cl->cl_columns, &tb->tb_columns);
|
|
return cl;
|
|
}
|
|
|
|
/*
|
|
* @tb: table
|
|
* @parent: parental line or NULL
|
|
*
|
|
* Returns: newly allocate line
|
|
*/
|
|
struct tt_line *tt_add_line(struct tt *tb, struct tt_line *parent)
|
|
{
|
|
struct tt_line *ln = NULL;
|
|
|
|
if (!tb || !tb->ncols)
|
|
goto err;
|
|
ln = calloc(1, sizeof(*ln));
|
|
if (!ln)
|
|
goto err;
|
|
ln->data = calloc(tb->ncols, sizeof(char *));
|
|
if (!ln->data)
|
|
goto err;
|
|
|
|
ln->table = tb;
|
|
ln->parent = parent;
|
|
INIT_LIST_HEAD(&ln->ln_lines);
|
|
INIT_LIST_HEAD(&ln->ln_children);
|
|
INIT_LIST_HEAD(&ln->ln_branch);
|
|
|
|
list_add_tail(&ln->ln_lines, &tb->tb_lines);
|
|
|
|
if (parent)
|
|
list_add_tail(&ln->ln_children, &parent->ln_branch);
|
|
return ln;
|
|
err:
|
|
free(ln);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* @tb: table
|
|
* @colnum: number of column (0..N)
|
|
*
|
|
* Returns: pointer to column or NULL
|
|
*/
|
|
struct tt_column *tt_get_column(struct tt *tb, size_t colnum)
|
|
{
|
|
struct list_head *p;
|
|
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
if (cl->seqnum == colnum)
|
|
return cl;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* @ln: line
|
|
* @colnum: number of column (0..N)
|
|
* @data: printable data
|
|
*
|
|
* Stores data that will be printed to the table cell.
|
|
*/
|
|
int tt_line_set_data(struct tt_line *ln, int colnum, const char *data)
|
|
{
|
|
struct tt_column *cl;
|
|
|
|
if (!ln)
|
|
return -1;
|
|
cl = tt_get_column(ln->table, colnum);
|
|
if (!cl)
|
|
return -1;
|
|
|
|
if (ln->data[cl->seqnum]) {
|
|
size_t sz = strlen(ln->data[cl->seqnum]);;
|
|
ln->data_sz = ln->data_sz > sz ? ln->data_sz - sz : 0;
|
|
}
|
|
|
|
ln->data[cl->seqnum] = data;
|
|
if (data)
|
|
ln->data_sz += strlen(data);
|
|
return 0;
|
|
}
|
|
|
|
int tt_line_set_userdata(struct tt_line *ln, void *data)
|
|
{
|
|
if (!ln)
|
|
return -1;
|
|
ln->userdata = data;
|
|
return 0;
|
|
}
|
|
|
|
static char *line_get_ascii_art(struct tt_line *ln, char *buf, size_t *bufsz)
|
|
{
|
|
const char *art;
|
|
size_t len;
|
|
|
|
if (!ln->parent)
|
|
return buf;
|
|
|
|
buf = line_get_ascii_art(ln->parent, buf, bufsz);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch))
|
|
art = " ";
|
|
else
|
|
art = ln->table->symbols->vert;
|
|
|
|
len = strlen(art);
|
|
if (*bufsz < len)
|
|
return NULL; /* no space, internal error */
|
|
|
|
memcpy(buf, art, len);
|
|
*bufsz -= len;
|
|
return buf + len;
|
|
}
|
|
|
|
static char *line_get_data(struct tt_line *ln, struct tt_column *cl,
|
|
char *buf, size_t bufsz)
|
|
{
|
|
const char *data = ln->data[cl->seqnum];
|
|
const struct tt_symbols *sym;
|
|
char *p = buf;
|
|
|
|
memset(buf, 0, bufsz);
|
|
|
|
if (!data)
|
|
return NULL;
|
|
if (!(cl->flags & TT_FL_TREE)) {
|
|
strncpy(buf, data, bufsz);
|
|
buf[bufsz - 1] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Tree stuff
|
|
*/
|
|
if (ln->parent) {
|
|
p = line_get_ascii_art(ln->parent, buf, &bufsz);
|
|
if (!p)
|
|
return NULL;
|
|
}
|
|
|
|
sym = ln->table->symbols;
|
|
|
|
if (!ln->parent)
|
|
snprintf(p, bufsz, "%s", data); /* root node */
|
|
else if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch))
|
|
snprintf(p, bufsz, "%s%s", sym->right, data); /* last chaild */
|
|
else
|
|
snprintf(p, bufsz, "%s%s", sym->branch, data); /* any child */
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* This function counts column width.
|
|
*
|
|
* For the TT_FL_NOEXTREMES columns is possible to call this function two
|
|
* times. The first pass counts width and average width. If the column
|
|
* contains too large fields (width greater than 2 * average) then the column
|
|
* is marked as "extreme". In the second pass all extreme fields are ignored
|
|
* and column width is counted from non-extreme fields only.
|
|
*/
|
|
static void count_column_width(struct tt *tb, struct tt_column *cl,
|
|
char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *lp;
|
|
int count = 0;
|
|
size_t sum = 0;
|
|
|
|
cl->width = 0;
|
|
|
|
list_for_each(lp, &tb->tb_lines) {
|
|
struct tt_line *ln = list_entry(lp, struct tt_line, ln_lines);
|
|
char *data = line_get_data(ln, cl, buf, bufsz);
|
|
size_t len = data ? mbs_safe_width(data) : 0;
|
|
|
|
if (len == (size_t) -1) /* ignore broken multibyte strings */
|
|
len = 0;
|
|
|
|
if (len > cl->width_max)
|
|
cl->width_max = len;
|
|
|
|
if (cl->is_extreme && len > cl->width_avg * 2)
|
|
continue;
|
|
else if (cl->flags & TT_FL_NOEXTREMES) {
|
|
sum += len;
|
|
count++;
|
|
}
|
|
if (len > cl->width)
|
|
cl->width = len;
|
|
}
|
|
|
|
if (count && cl->width_avg == 0) {
|
|
cl->width_avg = sum / count;
|
|
|
|
if (cl->width_max > cl->width_avg * 2)
|
|
cl->is_extreme = 1;
|
|
}
|
|
|
|
/* check and set minimal column width */
|
|
if (cl->name)
|
|
cl->width_min = mbs_safe_width(cl->name);
|
|
|
|
/* enlarge to minimal width */
|
|
if (cl->width < cl->width_min && !(cl->flags & TT_FL_STRICTWIDTH))
|
|
cl->width = cl->width_min;
|
|
|
|
/* use relative size for large columns */
|
|
else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint &&
|
|
cl->width_min < (size_t) cl->width_hint)
|
|
|
|
cl->width = (size_t) cl->width_hint;
|
|
}
|
|
|
|
/*
|
|
* This is core of the tt_* voodo...
|
|
*/
|
|
static void recount_widths(struct tt *tb, char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *p;
|
|
size_t width = 0; /* output width */
|
|
int trunc_only;
|
|
int extremes = 0;
|
|
|
|
/* set basic columns width
|
|
*/
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
|
|
count_column_width(tb, cl, buf, bufsz);
|
|
width += cl->width + (is_last_column(tb, cl) ? 0 : 1);
|
|
extremes += cl->is_extreme;
|
|
}
|
|
|
|
/* reduce columns with extreme fields
|
|
*/
|
|
if (width > tb->termwidth && extremes) {
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl = list_entry(p, struct tt_column, cl_columns);
|
|
size_t org_width;
|
|
|
|
if (!cl->is_extreme)
|
|
continue;
|
|
|
|
org_width = cl->width;
|
|
count_column_width(tb, cl, buf, bufsz);
|
|
|
|
if (org_width > cl->width)
|
|
width -= org_width - cl->width;
|
|
else
|
|
extremes--; /* hmm... nothing reduced */
|
|
}
|
|
}
|
|
|
|
if (width < tb->termwidth) {
|
|
/* try to found extreme column which fits into available space
|
|
*/
|
|
if (extremes) {
|
|
/* enlarge the first extreme column */
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
size_t add;
|
|
|
|
if (!cl->is_extreme)
|
|
continue;
|
|
|
|
/* this column is tooo large, ignore?
|
|
if (cl->width_max - cl->width >
|
|
(tb->termwidth - width))
|
|
continue;
|
|
*/
|
|
|
|
add = tb->termwidth - width;
|
|
if (add && cl->width + add > cl->width_max)
|
|
add = cl->width_max - cl->width;
|
|
|
|
cl->width += add;
|
|
width += add;
|
|
|
|
if (width == tb->termwidth)
|
|
break;
|
|
}
|
|
}
|
|
if (width < tb->termwidth) {
|
|
/* enalarge the last column */
|
|
struct tt_column *cl = list_entry(
|
|
tb->tb_columns.prev, struct tt_column, cl_columns);
|
|
|
|
if (!(cl->flags & TT_FL_RIGHT) && tb->termwidth - width > 0) {
|
|
cl->width += tb->termwidth - width;
|
|
width = tb->termwidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* bad, we have to reduce output width, this is done in two steps:
|
|
* 1/ reduce columns with a relative width and with truncate flag
|
|
* 2) reduce columns with a relative width without truncate flag
|
|
*/
|
|
trunc_only = 1;
|
|
while (width > tb->termwidth) {
|
|
size_t org = width;
|
|
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
|
|
if (width <= tb->termwidth)
|
|
break;
|
|
if (cl->width_hint > 1 && !(cl->flags & TT_FL_TRUNC))
|
|
continue; /* never truncate columns with absolute sizes */
|
|
if (cl->flags & TT_FL_TREE)
|
|
continue; /* never truncate the tree */
|
|
if (trunc_only && !(cl->flags & TT_FL_TRUNC))
|
|
continue;
|
|
if (cl->width == cl->width_min)
|
|
continue;
|
|
|
|
/* truncate column with relative sizes */
|
|
if (cl->width_hint < 1 && cl->width > 0 && width > 0 &&
|
|
cl->width > cl->width_hint * tb->termwidth) {
|
|
cl->width--;
|
|
width--;
|
|
}
|
|
/* truncate column with absolute size */
|
|
if (cl->width_hint > 1 && cl->width > 0 && width > 0 &&
|
|
!trunc_only) {
|
|
cl->width--;
|
|
width--;
|
|
}
|
|
|
|
}
|
|
if (org == width) {
|
|
if (trunc_only)
|
|
trunc_only = 0;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
fprintf(stderr, "terminal: %d, output: %d\n", tb->termwidth, width);
|
|
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
|
|
fprintf(stderr, "width: %s=%zd [hint=%d, avg=%zd, max=%zd, extreme=%s]\n",
|
|
cl->name, cl->width,
|
|
cl->width_hint > 1 ? (int) cl->width_hint :
|
|
(int) (cl->width_hint * tb->termwidth),
|
|
cl->width_avg,
|
|
cl->width_max,
|
|
cl->is_extreme ? "yes" : "not");
|
|
}
|
|
*/
|
|
return;
|
|
}
|
|
|
|
void tt_fputs_quoted(const char *data, FILE *out)
|
|
{
|
|
const char *p;
|
|
|
|
fputc('"', out);
|
|
for (p = data; p && *p; p++) {
|
|
if ((unsigned char) *p == 0x22 || /* " */
|
|
(unsigned char) *p == 0x5c || /* \ */
|
|
!isprint((unsigned char) *p) ||
|
|
iscntrl((unsigned char) *p)) {
|
|
|
|
fprintf(out, "\\x%02x", (unsigned char) *p);
|
|
} else
|
|
fputc(*p, out);
|
|
}
|
|
fputc('"', out);
|
|
}
|
|
|
|
void tt_fputs_nonblank(const char *data, FILE *out)
|
|
{
|
|
const char *p;
|
|
|
|
for (p = data; p && *p; p++) {
|
|
if (isblank((unsigned char) *p) ||
|
|
(unsigned char) *p == 0x5c || /* \ */
|
|
!isprint((unsigned char) *p) ||
|
|
iscntrl((unsigned char) *p)) {
|
|
|
|
fprintf(out, "\\x%02x", (unsigned char) *p);
|
|
|
|
} else
|
|
fputc(*p, out);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prints data, data maybe be printed in more formats (raw, NAME=xxx pairs) and
|
|
* control and non-printable chars maybe encoded in \x?? hex encoding.
|
|
*/
|
|
static void print_data(struct tt *tb, struct tt_column *cl, char *data)
|
|
{
|
|
size_t len = 0, i, width;
|
|
char *buf;
|
|
|
|
if (!data)
|
|
data = "";
|
|
|
|
/* raw mode */
|
|
if (tb->flags & TT_FL_RAW) {
|
|
tt_fputs_nonblank(data, stdout);
|
|
if (!is_last_column(tb, cl))
|
|
fputc(' ', stdout);
|
|
return;
|
|
}
|
|
|
|
/* NAME=value mode */
|
|
if (tb->flags & TT_FL_EXPORT) {
|
|
fprintf(stdout, "%s=", cl->name);
|
|
tt_fputs_quoted(data, stdout);
|
|
if (!is_last_column(tb, cl))
|
|
fputc(' ', stdout);
|
|
return;
|
|
}
|
|
|
|
/* note that 'len' and 'width' are number of cells, not bytes */
|
|
buf = mbs_safe_encode(data, &len);
|
|
data = buf;
|
|
if (!data)
|
|
data = "";
|
|
|
|
if (!len || len == (size_t) -1) {
|
|
len = 0;
|
|
data = NULL;
|
|
}
|
|
width = cl->width;
|
|
|
|
if (is_last_column(tb, cl) && len < width)
|
|
width = len;
|
|
|
|
/* truncate data */
|
|
if (len > width && (cl->flags & TT_FL_TRUNC)) {
|
|
if (data)
|
|
len = mbs_truncate(data, &width);
|
|
if (!data || len == (size_t) -1) {
|
|
len = 0;
|
|
data = NULL;
|
|
}
|
|
}
|
|
if (data) {
|
|
if (!(tb->flags & TT_FL_RAW) && (cl->flags & TT_FL_RIGHT)) {
|
|
size_t xw = cl->width;
|
|
fprintf(stdout, "%*s", (int) xw, data);
|
|
if (len < xw)
|
|
len = xw;
|
|
}
|
|
else
|
|
fputs(data, stdout);
|
|
}
|
|
for (i = len; i < width; i++)
|
|
fputc(' ', stdout); /* padding */
|
|
|
|
if (!is_last_column(tb, cl)) {
|
|
if (len > width && !(cl->flags & TT_FL_TRUNC)) {
|
|
fputc('\n', stdout);
|
|
for (i = 0; i <= (size_t) cl->seqnum; i++) {
|
|
struct tt_column *x = tt_get_column(tb, i);
|
|
printf("%*s ", -((int)x->width), " ");
|
|
}
|
|
} else
|
|
fputc(' ', stdout); /* columns separator */
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
static void print_line(struct tt_line *ln, char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *p;
|
|
|
|
/* set width according to the size of data
|
|
*/
|
|
list_for_each(p, &ln->table->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
|
|
print_data(ln->table, cl, line_get_data(ln, cl, buf, bufsz));
|
|
}
|
|
fputc('\n', stdout);
|
|
}
|
|
|
|
static void print_header(struct tt *tb, char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *p;
|
|
|
|
if (!tb->first_run ||
|
|
(tb->flags & TT_FL_NOHEADINGS) ||
|
|
(tb->flags & TT_FL_EXPORT) ||
|
|
list_empty(&tb->tb_lines))
|
|
return;
|
|
|
|
/* set width according to the size of data
|
|
*/
|
|
list_for_each(p, &tb->tb_columns) {
|
|
struct tt_column *cl =
|
|
list_entry(p, struct tt_column, cl_columns);
|
|
|
|
strncpy(buf, cl->name, bufsz);
|
|
buf[bufsz - 1] = '\0';
|
|
print_data(tb, cl, buf);
|
|
}
|
|
fputc('\n', stdout);
|
|
}
|
|
|
|
static void print_table(struct tt *tb, char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *p;
|
|
|
|
print_header(tb, buf, bufsz);
|
|
|
|
list_for_each(p, &tb->tb_lines) {
|
|
struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
|
|
|
|
print_line(ln, buf, bufsz);
|
|
}
|
|
}
|
|
|
|
static void print_tree_line(struct tt_line *ln, char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *p;
|
|
|
|
print_line(ln, buf, bufsz);
|
|
|
|
if (list_empty(&ln->ln_branch))
|
|
return;
|
|
|
|
/* print all children */
|
|
list_for_each(p, &ln->ln_branch) {
|
|
struct tt_line *chld =
|
|
list_entry(p, struct tt_line, ln_children);
|
|
print_tree_line(chld, buf, bufsz);
|
|
}
|
|
}
|
|
|
|
static void print_tree(struct tt *tb, char *buf, size_t bufsz)
|
|
{
|
|
struct list_head *p;
|
|
|
|
print_header(tb, buf, bufsz);
|
|
|
|
list_for_each(p, &tb->tb_lines) {
|
|
struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
|
|
|
|
if (ln->parent)
|
|
continue;
|
|
|
|
print_tree_line(ln, buf, bufsz);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @tb: table
|
|
*
|
|
* Prints the table to stdout
|
|
*/
|
|
int tt_print_table(struct tt *tb)
|
|
{
|
|
char *line;
|
|
size_t line_sz;
|
|
struct list_head *p;
|
|
|
|
if (!tb)
|
|
return -1;
|
|
|
|
if (tb->first_run && !tb->termwidth) {
|
|
tb->termwidth = get_terminal_width();
|
|
if (tb->termwidth <= 0)
|
|
tb->termwidth = 80;
|
|
}
|
|
|
|
line_sz = tb->termwidth;
|
|
|
|
list_for_each(p, &tb->tb_lines) {
|
|
struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
|
|
if (ln->data_sz > line_sz)
|
|
line_sz = ln->data_sz;
|
|
}
|
|
|
|
line_sz++; /* make a space for \0 */
|
|
line = malloc(line_sz);
|
|
if (!line)
|
|
return -1;
|
|
|
|
if (tb->first_run &&
|
|
!((tb->flags & TT_FL_RAW) || (tb->flags & TT_FL_EXPORT)))
|
|
recount_widths(tb, line, line_sz);
|
|
|
|
if (tb->flags & TT_FL_TREE)
|
|
print_tree(tb, line, line_sz);
|
|
else
|
|
print_table(tb, line, line_sz);
|
|
|
|
free(line);
|
|
|
|
tb->first_run = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef TEST_PROGRAM
|
|
#include <errno.h>
|
|
|
|
enum { MYCOL_NAME, MYCOL_FOO, MYCOL_BAR, MYCOL_PATH };
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct tt *tb;
|
|
struct tt_line *ln, *pr, *root;
|
|
int flags = 0, notree = 0, i;
|
|
|
|
if (argc == 2 && !strcmp(argv[1], "--help")) {
|
|
printf("%s [--ascii | --raw | --list]\n",
|
|
program_invocation_short_name);
|
|
return EXIT_SUCCESS;
|
|
} else if (argc == 2 && !strcmp(argv[1], "--ascii")) {
|
|
flags |= TT_FL_ASCII;
|
|
} else if (argc == 2 && !strcmp(argv[1], "--raw")) {
|
|
flags |= TT_FL_RAW;
|
|
notree = 1;
|
|
} else if (argc == 2 && !strcmp(argv[1], "--export")) {
|
|
flags |= TT_FL_EXPORT;
|
|
notree = 1;
|
|
} else if (argc == 2 && !strcmp(argv[1], "--list"))
|
|
notree = 1;
|
|
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
|
|
tb = tt_new_table(flags);
|
|
if (!tb)
|
|
err(EXIT_FAILURE, "table initialization failed");
|
|
|
|
tt_define_column(tb, "NAME", 0.3, notree ? 0 : TT_FL_TREE);
|
|
tt_define_column(tb, "FOO", 0.3, TT_FL_TRUNC);
|
|
tt_define_column(tb, "BAR", 0.3, 0);
|
|
tt_define_column(tb, "PATH", 0.3, 0);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
root = ln = tt_add_line(tb, NULL);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA");
|
|
|
|
pr = ln = tt_add_line(tb, ln);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA.A");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a.a-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A");
|
|
|
|
ln = tt_add_line(tb, pr);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA.A.AAA");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a.a.a-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.A");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/AAA");
|
|
|
|
ln = tt_add_line(tb, root);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA.B");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a.b-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A.B");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/B");
|
|
|
|
ln = tt_add_line(tb, pr);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA.A.BBB");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a.a.b-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.BBB");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/BBB");
|
|
|
|
ln = tt_add_line(tb, pr);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA.A.CCC");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a.a.c-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.CCC");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/CCC");
|
|
|
|
ln = tt_add_line(tb, root);
|
|
tt_line_set_data(ln, MYCOL_NAME, "AAA.C");
|
|
tt_line_set_data(ln, MYCOL_FOO, "a.c-foo-foo");
|
|
tt_line_set_data(ln, MYCOL_BAR, "barBar-A.C");
|
|
tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/C");
|
|
}
|
|
|
|
tt_print_table(tb);
|
|
tt_free_table(tb);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
#endif
|