1/*
2 * Copyright (c) 2000, 2002, 2004 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <config.h>
35
36#include "roken.h"
37#include "rtbl.h"
38
39struct column_entry {
40    char *data;
41};
42
43struct column_data {
44    char *header;
45    char *prefix;
46    int width;
47    unsigned flags;
48    size_t num_rows;
49    struct column_entry *rows;
50    unsigned int column_id;
51    char *suffix;
52};
53
54struct rtbl_data {
55    char *column_prefix;
56    size_t num_columns;
57    struct column_data **columns;
58    unsigned int flags;
59    char *column_separator;
60};
61
62ROKEN_LIB_FUNCTION rtbl_t ROKEN_LIB_CALL
63rtbl_create (void)
64{
65    return calloc (1, sizeof (struct rtbl_data));
66}
67
68ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL
69rtbl_set_flags (rtbl_t table, unsigned int flags)
70{
71    table->flags = flags;
72}
73
74ROKEN_LIB_FUNCTION unsigned int ROKEN_LIB_CALL
75rtbl_get_flags (rtbl_t table)
76{
77    return table->flags;
78}
79
80static struct column_data *
81rtbl_get_column_by_id (rtbl_t table, unsigned int id)
82{
83    size_t i;
84    for(i = 0; i < table->num_columns; i++)
85	if(table->columns[i]->column_id == id)
86	    return table->columns[i];
87    return NULL;
88}
89
90static struct column_data *
91rtbl_get_column (rtbl_t table, const char *column)
92{
93    size_t i;
94    for(i = 0; i < table->num_columns; i++)
95	if(strcmp(table->columns[i]->header, column) == 0)
96	    return table->columns[i];
97    return NULL;
98}
99
100ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL
101rtbl_destroy (rtbl_t table)
102{
103    size_t i, j;
104
105    for (i = 0; i < table->num_columns; i++) {
106	struct column_data *c = table->columns[i];
107
108	for (j = 0; j < c->num_rows; j++)
109	    free (c->rows[j].data);
110	free (c->rows);
111	free (c->header);
112	free (c->prefix);
113	free (c->suffix);
114	free (c);
115    }
116    free (table->column_prefix);
117    free (table->column_separator);
118    free (table->columns);
119    free (table);
120}
121
122ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
123rtbl_add_column_by_id (rtbl_t table, unsigned int id,
124		       const char *header, unsigned int flags)
125{
126    struct column_data *col, **tmp;
127
128    tmp = realloc (table->columns, (table->num_columns + 1) * sizeof (*tmp));
129    if (tmp == NULL)
130	return ENOMEM;
131    table->columns = tmp;
132    col = malloc (sizeof (*col));
133    if (col == NULL)
134	return ENOMEM;
135    col->header = strdup (header);
136    if (col->header == NULL) {
137	free (col);
138	return ENOMEM;
139    }
140    col->prefix = NULL;
141    col->width = 0;
142    col->flags = flags;
143    col->num_rows = 0;
144    col->rows = NULL;
145    col->column_id = id;
146    col->suffix = NULL;
147    table->columns[table->num_columns++] = col;
148    return 0;
149}
150
151ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
152rtbl_add_column (rtbl_t table, const char *header, unsigned int flags)
153{
154    return rtbl_add_column_by_id(table, 0, header, flags);
155}
156
157ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
158rtbl_new_row(rtbl_t table)
159{
160    size_t max_rows = 0;
161    size_t c;
162    for (c = 0; c < table->num_columns; c++)
163	if(table->columns[c]->num_rows > max_rows)
164	    max_rows = table->columns[c]->num_rows;
165    for (c = 0; c < table->num_columns; c++) {
166	struct column_entry *tmp;
167
168	if(table->columns[c]->num_rows == max_rows)
169	    continue;
170	tmp = realloc(table->columns[c]->rows,
171		      max_rows * sizeof(table->columns[c]->rows[0]));
172	if(tmp == NULL)
173	    return ENOMEM;
174	table->columns[c]->rows = tmp;
175	while(table->columns[c]->num_rows < max_rows) {
176	    if((tmp[table->columns[c]->num_rows++].data = strdup("")) == NULL)
177		return ENOMEM;
178	}
179    }
180    return 0;
181}
182
183static void
184column_compute_width (rtbl_t table, struct column_data *column)
185{
186    size_t i;
187
188    if(table->flags & RTBL_HEADER_STYLE_NONE)
189	column->width = 0;
190    else
191	column->width = (int)strlen (column->header);
192    for (i = 0; i < column->num_rows; i++)
193	column->width = max (column->width, (int) strlen (column->rows[i].data));
194}
195
196/* DEPRECATED */
197ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
198rtbl_set_prefix (rtbl_t table, const char *prefix)
199{
200    if (table->column_prefix)
201	free (table->column_prefix);
202    table->column_prefix = strdup (prefix);
203    if (table->column_prefix == NULL)
204	return ENOMEM;
205    return 0;
206}
207
208ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
209rtbl_set_separator (rtbl_t table, const char *separator)
210{
211    if (table->column_separator)
212	free (table->column_separator);
213    table->column_separator = strdup (separator);
214    if (table->column_separator == NULL)
215	return ENOMEM;
216    return 0;
217}
218
219ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
220rtbl_set_column_prefix (rtbl_t table, const char *column,
221			const char *prefix)
222{
223    struct column_data *c = rtbl_get_column (table, column);
224
225    if (c == NULL)
226	return -1;
227    if (c->prefix)
228	free (c->prefix);
229    c->prefix = strdup (prefix);
230    if (c->prefix == NULL)
231	return ENOMEM;
232    return 0;
233}
234
235ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
236rtbl_set_column_affix_by_id(rtbl_t table, unsigned int id,
237			    const char *prefix, const char *suffix)
238{
239    struct column_data *c = rtbl_get_column_by_id (table, id);
240
241    if (c == NULL)
242	return -1;
243    if (c->prefix)
244	free (c->prefix);
245    if(prefix == NULL)
246	c->prefix = NULL;
247    else {
248	c->prefix = strdup (prefix);
249	if (c->prefix == NULL)
250	    return ENOMEM;
251    }
252
253    if (c->suffix)
254	free (c->suffix);
255    if(suffix == NULL)
256	c->suffix = NULL;
257    else {
258	c->suffix = strdup (suffix);
259	if (c->suffix == NULL)
260	    return ENOMEM;
261    }
262    return 0;
263}
264
265
266static const char *
267get_column_prefix (rtbl_t table, struct column_data *c)
268{
269    if (c == NULL)
270	return "";
271    if (c->prefix)
272	return c->prefix;
273    if (table->column_prefix)
274	return table->column_prefix;
275    return "";
276}
277
278static const char *
279get_column_suffix (rtbl_t table, struct column_data *c)
280{
281    if (c && c->suffix)
282	return c->suffix;
283    return "";
284}
285
286static int
287add_column_entry (struct column_data *c, const char *data)
288{
289    struct column_entry row, *tmp;
290
291    row.data = strdup (data);
292    if (row.data == NULL)
293	return ENOMEM;
294    tmp = realloc (c->rows, (c->num_rows + 1) * sizeof (*tmp));
295    if (tmp == NULL) {
296	free (row.data);
297	return ENOMEM;
298    }
299    c->rows = tmp;
300    c->rows[c->num_rows++] = row;
301    return 0;
302}
303
304ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
305rtbl_add_column_entry_by_id (rtbl_t table, unsigned int id, const char *data)
306{
307    struct column_data *c = rtbl_get_column_by_id (table, id);
308
309    if (c == NULL)
310	return -1;
311
312    return add_column_entry(c, data);
313}
314
315ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
316rtbl_add_column_entryv_by_id (rtbl_t table, unsigned int id,
317			      const char *fmt, ...)
318{
319    va_list ap;
320    char *str;
321    int ret;
322
323    va_start(ap, fmt);
324    ret = vasprintf(&str, fmt, ap);
325    va_end(ap);
326    if (ret == -1)
327	return -1;
328    ret = rtbl_add_column_entry_by_id(table, id, str);
329    free(str);
330    return ret;
331}
332
333ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
334rtbl_add_column_entry (rtbl_t table, const char *column, const char *data)
335{
336    struct column_data *c = rtbl_get_column (table, column);
337
338    if (c == NULL)
339	return -1;
340
341    return add_column_entry(c, data);
342}
343
344ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
345rtbl_add_column_entryv (rtbl_t table, const char *column, const char *fmt, ...)
346{
347    va_list ap;
348    char *str;
349    int ret;
350
351    va_start(ap, fmt);
352    ret = vasprintf(&str, fmt, ap);
353    va_end(ap);
354    if (ret == -1)
355	return -1;
356    ret = rtbl_add_column_entry(table, column, str);
357    free(str);
358    return ret;
359}
360
361
362ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
363rtbl_format (rtbl_t table, FILE * f)
364{
365    char *str = rtbl_format_str(table);
366    if (str == NULL)
367	return ENOMEM;
368    fprintf(f, "%s", str);
369    free(str);
370    return 0;
371}
372
373static char *
374rtbl_format_pretty(rtbl_t table)
375{
376    struct rk_strpool *p = NULL;
377    size_t i, j;
378
379    for (i = 0; i < table->num_columns; i++)
380	column_compute_width (table, table->columns[i]);
381    if((table->flags & RTBL_HEADER_STYLE_NONE) == 0) {
382	for (i = 0; i < table->num_columns; i++) {
383	    struct column_data *c = table->columns[i];
384
385	    if(table->column_separator != NULL && i > 0)
386		p = rk_strpoolprintf(p, "%s", table->column_separator);
387	    p = rk_strpoolprintf(p, "%s", get_column_prefix (table, c));
388	    if (c == NULL) {
389		/* do nothing if no column */
390	    } else if(i == table->num_columns - 1 && c->suffix == NULL)
391		/* last column, so no need to pad with spaces */
392		p = rk_strpoolprintf(p, "%-*s", 0, c->header);
393	    else
394		p = rk_strpoolprintf(p, "%-*s", (int)c->width, c->header);
395	    p = rk_strpoolprintf(p, "%s", get_column_suffix (table, c));
396	}
397	p = rk_strpoolprintf(p, "\n");
398    }
399
400    for (j = 0;; j++) {
401	int flag = 0;
402
403	/* are there any more rows left? */
404	for (i = 0; flag == 0 && i < table->num_columns; ++i) {
405	    struct column_data *c = table->columns[i];
406
407	    if (c->num_rows > j) {
408		++flag;
409		break;
410	    }
411	}
412	if (flag == 0)
413	    break;
414
415	for (i = 0; i < table->num_columns; i++) {
416	    int w;
417	    struct column_data *c = table->columns[i];
418
419	    if(table->column_separator != NULL && i > 0)
420		p = rk_strpoolprintf(p, "%s", table->column_separator);
421
422	    w = c->width;
423
424	    if ((c->flags & RTBL_ALIGN_RIGHT) == 0) {
425		if(i == table->num_columns - 1 && c->suffix == NULL)
426		    /* last column, so no need to pad with spaces */
427		    w = 0;
428		else
429		    w = -w;
430	    }
431	    rk_strpoolprintf(p, "%s", get_column_prefix (table, c));
432	    if (c->num_rows <= j)
433		p = rk_strpoolprintf(p, "%*s", w, "");
434	    else
435		p = rk_strpoolprintf(p, "%*s", w, c->rows[j].data);
436	    p = rk_strpoolprintf(p, "%s", get_column_suffix (table, c));
437	}
438	p = rk_strpoolprintf(p, "\n");
439    }
440
441    return rk_strpoolcollect(p);
442}
443
444static char *
445rtbl_format_json(rtbl_t table)
446{
447    struct rk_strpool *p = NULL;
448    size_t i, j;
449    int comma;
450
451    p = rk_strpoolprintf(p, "[");
452    for (j = 0;; j++) {
453	int flag = 0;
454
455	/* are there any more rows left? */
456	for (i = 0; flag == 0 && i < table->num_columns; ++i) {
457	    struct column_data *c = table->columns[i];
458
459	    if (c->num_rows > j) {
460		++flag;
461		break;
462	    }
463	}
464	if (flag == 0)
465	    break;
466
467	p = rk_strpoolprintf(p, "%s{", j > 0 ? "," : "");
468
469	comma = 0;
470	for (i = 0; i < table->num_columns; i++) {
471	    struct column_data *c = table->columns[i];
472
473	    if (c->num_rows > j) {
474		char *header = c->header;
475		while (isspace((int)header[0])) /* trim off prefixed whitespace */
476		    header++;
477		p = rk_strpoolprintf(p, "%s\"%s\" : \"%s\"",
478				     comma ? "," : "", header,
479				     c->rows[j].data);
480		comma = 1;
481	    }
482	}
483	p = rk_strpoolprintf(p, "}");
484    }
485    p = rk_strpoolprintf(p, "]");
486
487    return rk_strpoolcollect(p);
488}
489
490ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL
491rtbl_format_str (rtbl_t table)
492{
493    if (table->flags & RTBL_JSON)
494	return rtbl_format_json(table);
495
496    return rtbl_format_pretty(table);
497}
498
499#ifdef TEST
500int
501main (int argc, char **argv)
502{
503    rtbl_t table;
504
505    table = rtbl_create ();
506    rtbl_add_column_by_id (table, 0, "Issued", 0);
507    rtbl_add_column_by_id (table, 1, "Expires", 0);
508    rtbl_add_column_by_id (table, 2, "Foo", RTBL_ALIGN_RIGHT);
509    rtbl_add_column_by_id (table, 3, "Principal", 0);
510
511    rtbl_add_column_entry_by_id (table, 0, "Jul  7 21:19:29");
512    rtbl_add_column_entry_by_id (table, 1, "Jul  8 07:19:29");
513    rtbl_add_column_entry_by_id (table, 2, "73");
514    rtbl_add_column_entry_by_id (table, 2, "0");
515    rtbl_add_column_entry_by_id (table, 2, "-2000");
516    rtbl_add_column_entry_by_id (table, 3, "krbtgt/NADA.KTH.SE@NADA.KTH.SE");
517
518    rtbl_add_column_entry_by_id (table, 0, "Jul  7 21:19:29");
519    rtbl_add_column_entry_by_id (table, 1, "Jul  8 07:19:29");
520    rtbl_add_column_entry_by_id (table, 3, "afs/pdc.kth.se@NADA.KTH.SE");
521
522    rtbl_add_column_entry_by_id (table, 0, "Jul  7 21:19:29");
523    rtbl_add_column_entry_by_id (table, 1, "Jul  8 07:19:29");
524    rtbl_add_column_entry_by_id (table, 3, "afs@NADA.KTH.SE");
525
526    rtbl_set_separator (table, "  ");
527
528    rtbl_format (table, stdout);
529
530    rtbl_destroy (table);
531
532    printf("\n");
533
534    table = rtbl_create ();
535    rtbl_add_column_by_id (table, 0, "Column A", 0);
536    rtbl_set_column_affix_by_id (table, 0, "<", ">");
537    rtbl_add_column_by_id (table, 1, "Column B", 0);
538    rtbl_set_column_affix_by_id (table, 1, "[", "]");
539    rtbl_add_column_by_id (table, 2, "Column C", 0);
540    rtbl_set_column_affix_by_id (table, 2, "(", ")");
541
542    rtbl_add_column_entry_by_id (table, 0, "1");
543    rtbl_new_row(table);
544    rtbl_add_column_entry_by_id (table, 1, "2");
545    rtbl_new_row(table);
546    rtbl_add_column_entry_by_id (table, 2, "3");
547    rtbl_new_row(table);
548
549    rtbl_set_separator (table, "  ");
550    rtbl_format (table, stdout);
551
552    rtbl_destroy (table);
553
554    return 0;
555}
556
557#endif
558