summaryrefslogblamecommitdiffstats
path: root/lib/tt.c
blob: cbe4e3b4d05fc0782cbcfe19969695659c05c8d6 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                                                
                  
 
              

                     
               
                     
                     













                                                   









                                                                        


                                  
                                                                          
 
  
                                                                  






















                                                                                
 




                                                                               























































                                                                                       
















                                                                                   































                                                                                  















                                                    
                          




                                                                             

                             


                  
                                   


                       
 






                                                                          








                                 








                                                                              
 





                                                                           










                                                                       












































































                                                                               
                                                             



























                                                                            
 



                                                                      
 
                                    

                                            


                 







                                                        











                                                                             
                                                                         
                           



























                                                                    



                     









                                                                               
                                                                              






                                                                                 








                                                                             
                                                                   
                                                         
 


                             
 
                      
 


                                                                              



                                                                                     
 

                                            
 




                                                              
                 

                                        

         








                                                      
                                                         






















                                                                              




                                                                            



                                                                      
 


                         





                                                                                           
 

                                            
 

                                                               
 




                                                                            

         
                                    
                                                                              









                                                                                    
 
                                                                     

                                                                        
                                                 
                                  
 




















                                                                                      






                                                                          
                                       
                                   






                                                                            
                                                                             


                                                                                                
                                                                     
                                         

                                                       

                                                                 
                                                                               




                                                                         

                                                                               


                                            
 







                                               
 
  





                                                                            
                                                                                          

                                                                   



                                                                                    
         
  


               
                                                 




                                      

                                                                       


                                                   
                                                                    
                      




                                       
                                                   



                                      
                                                  
                                                                       


                                                   
                                                                    
 
                      



                                       


                                                                               


                                                                       

                                 





                                    
                                                




                                            

                                       
                                                 
                                              




                                            
                                                                        



                                          










                                                  
                                                       
                         
                                                         




                                                  

                                                                            

                                                               





                                            



                                                             
                                                                
                                            
                                                                    
                                                                           
                                                                      



                                                                       

                  




















                                                                             

                                             
                                         
                                      




































































                                                                             

                            


                          
 




                                                             


                                           








                                                                             
                                                                 
                               

                          
 

                                                                     
                                                  
 
                                   
                                              
            
                                               

                   

                              



                   













                                                               
                                                              
                                     
                                                            

                                   


                                                               











                                                                   
                                                      




















































                                                                   
/*
 * 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;
	}

	if (!tb->is_term)
		return;

	/* 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->is_term = isatty(STDOUT_FILENO);

		if (tb->is_term && !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