]> mj.ucw.cz Git - libucw.git/blob - ucw/table.c
006e44f9f057ecf66225c7e804c731d757368dd3
[libucw.git] / ucw / table.c
1 /*
2  *      UCW Library -- Table printer
3  *
4  *      (c) 2014 Robert Kessl <robert.kessl@economia.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/string.h>
9 #include <ucw/stkstring.h>
10 #include <ucw/gary.h>
11 #include <ucw/table.h>
12
13 #include <stdlib.h>
14
15 /*** Management of tables ***/
16
17 void table_init(struct table *tbl, struct fastbuf *out)
18 {
19   tbl->out = out;
20
21   int col_count = 0; // count the number of columns in the struct table
22
23   for(;;) {
24     if(tbl->columns[col_count].name == NULL &&
25        tbl->columns[col_count].fmt == NULL &&
26        tbl->columns[col_count].width == 0 &&
27        tbl->columns[col_count].type == COL_TYPE_LAST)
28       break;
29     ASSERT(tbl->columns[col_count].name != NULL);
30     ASSERT(tbl->columns[col_count].type == COL_TYPE_ANY || tbl->columns[col_count].fmt != NULL);
31     ASSERT(tbl->columns[col_count].width != 0);
32     ASSERT(tbl->columns[col_count].type < COL_TYPE_LAST);
33     col_count++;
34   }
35   tbl->pool = mp_new(4096);
36
37   tbl->column_count = col_count;
38
39   if(!tbl->formatter) {
40     tbl->formatter = &table_fmt_human_readable;
41   }
42
43   tbl->print_header = 1; // by default, print header
44 }
45
46 void table_cleanup(struct table *tbl)
47 {
48   mp_delete(tbl->pool);
49   memset(tbl, 0, sizeof(struct table));
50 }
51
52 // TODO: test default column order
53 static void table_make_default_column_order(struct table *tbl)
54 {
55   int *col_order_int = mp_alloc_zero(tbl->pool, sizeof(int) * tbl->column_count);
56   for(int i = 0; i < tbl->column_count; i++) {
57     col_order_int[i] = i;
58   }
59   table_col_order(tbl, col_order_int, tbl->column_count);
60 }
61
62 void table_start(struct table *tbl)
63 {
64   tbl->last_printed_col = -1;
65   tbl->row_printing_started = 0;
66
67   // FIXME: Memory leak
68   tbl->col_str_ptrs = mp_alloc_zero(tbl->pool, sizeof(char *) * tbl->column_count);
69
70   if(tbl->column_order == NULL) table_make_default_column_order(tbl);
71
72   if(tbl->formatter->table_start != NULL) tbl->formatter->table_start(tbl);
73   if(tbl->cols_to_output == 0) {
74     // FIXME: Why?
75     die("Table should output at least one column.");
76   }
77
78   mp_save(tbl->pool, &tbl->pool_state);
79
80   ASSERT_MSG(tbl->col_delimiter, "In-between column delimiter not specified.");
81   ASSERT_MSG(tbl->append_delimiter, "Append delimiter not specified.");
82 }
83
84 void table_end(struct table *tbl)
85 {
86   tbl->last_printed_col = -1;
87   tbl->row_printing_started = 0;
88
89   mp_restore(tbl->pool, &tbl->pool_state);
90
91   if(tbl->formatter->table_end) tbl->formatter->table_end(tbl);
92 }
93
94 /*** Configuration ***/
95
96 void table_set_formatter(struct table *tbl, struct table_formatter *fmt)
97 {
98   tbl->formatter = fmt;
99 }
100
101 int table_get_col_idx(struct table *tbl, const char *col_name)
102 {
103   for(int i = 0; i < tbl->column_count; i++) {
104     if(strcmp(tbl->columns[i].name, col_name) == 0) return i;
105   }
106   return -1;
107 }
108
109 const char * table_get_col_list(struct table *tbl)
110 {
111   if(tbl->column_count == 0) return NULL;
112
113   // FIXME: This does not work! The start of the string may be reallocated later.
114   char *tmp = mp_printf(tbl->pool, "%s", tbl->columns[0].name);
115
116   for(int i = 1; i < tbl->column_count; i++) {
117     mp_printf_append(tbl->pool, tmp, ",%s", tbl->columns[i].name);
118   }
119
120   return tmp;
121 }
122
123 // FIXME: Shouldn't this be table_SET_col_order() ?
124 void table_col_order(struct table *tbl, int *col_order, int cols_to_output)
125 {
126   for(int i = 0; i < cols_to_output; i++) {
127     ASSERT_MSG(col_order[i] >= 0 && col_order[i] < tbl->column_count, "Column %d does not exist (column number should be between 0 and %d)", col_order[i], tbl->column_count);
128   }
129
130   tbl->column_order = col_order;
131   tbl->cols_to_output = cols_to_output;
132 }
133
134 /**
135  * TODO: ERROR! this function deliberately causes memory leak.  the
136  * problem is that when table_col_order_by_name is called multiple-times,
137  * the mp_save adds all the resulting column orders on the memory pool.
138  * The memory leak is small, but it is present.
139  **/
140 int table_col_order_by_name(struct table *tbl, const char *col_order_str)
141 {
142   int col_order_len = strlen(col_order_str);
143
144   char *tmp_col_order = stk_strdup(col_order_str);
145
146   int col_count = 1;
147   for(int i = 0; i < col_order_len; i++) {
148     if(col_order_str[i] == ',') {
149       col_count++;
150     }
151   }
152
153   struct mempool_state mp_tmp_state;
154   mp_save(tbl->pool, &mp_tmp_state);
155
156   int *col_order_int = mp_alloc_zero(tbl->pool, sizeof(int) * col_count);
157   int curr_col_order_int = 0;
158   const char *name_start = tmp_col_order;
159   while(name_start) {
160     char *next = strchr(name_start, ',');
161     if(next) {
162       *next++ = 0;
163     }
164
165     int idx = table_get_col_idx(tbl, name_start);
166     col_order_int[curr_col_order_int] = idx;
167     if(idx == -1) {
168       //ASSERT_MSG(idx != -1, "Table column with name '%s' does not exist.", name_start);
169       mp_restore(tbl->pool, &mp_tmp_state);
170       return -1;
171     }
172     curr_col_order_int++;
173
174     name_start = next;
175   }
176
177   tbl->column_order = col_order_int;
178   tbl->cols_to_output = curr_col_order_int;
179   return 0;
180 }
181
182 /*** Table cells ***/
183
184 void table_set_printf(struct table *tbl, int col, const char *fmt, ...)
185 {
186   ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);
187   tbl->last_printed_col = col;
188   tbl->row_printing_started = 1;
189   va_list args;
190   va_start(args, fmt);
191   tbl->col_str_ptrs[col] = mp_vprintf(tbl->pool, fmt, args);
192   va_end(args);
193 }
194
195 static const char *table_set_col_default_fmts[] = {
196   [COL_TYPE_STR] = "%s",
197   [COL_TYPE_INT] = "%d",
198   [COL_TYPE_INTMAX] = "%jd",
199   [COL_TYPE_UINT] = "%u",
200   [COL_TYPE_UINTMAX] = "%ju",
201   [COL_TYPE_BOOL] = "%d",
202   [COL_TYPE_DOUBLE] = "%.2lf",
203   [COL_TYPE_ANY] = NULL,
204   [COL_TYPE_LAST] = NULL
205 };
206
207 #define TABLE_SET_COL(_name_, _type_, _typeconst_) void table_set_##_name_(struct table *tbl, int col, _type_ val) \
208   {\
209     const char *fmt = tbl->columns[col].fmt;\
210     if(tbl->columns[col].type == COL_TYPE_ANY) {\
211        fmt = table_set_col_default_fmts[_typeconst_];\
212     }\
213     table_set_##_name_##_fmt(tbl, col, fmt, val);\
214   }
215
216 #define TABLE_SET_COL_STR(_name_, _type_, _typeconst_) void table_set_##_name_##_name(struct table *tbl, const char *col_name, _type_ val) \
217   {\
218     int col = table_get_col_idx(tbl, col_name);\
219     table_set_##_name_(tbl, col, val);\
220   }
221
222 #define TABLE_SET_COL_FMT(_name_, _type_, _typeconst_) void table_set_##_name_##_fmt(struct table *tbl, int col, const char *fmt, _type_ val)\
223   {\
224      ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);\
225      ASSERT(tbl->columns[col].type == COL_TYPE_ANY || _typeconst_ == tbl->columns[col].type);\
226      ASSERT(fmt != NULL);\
227      tbl->last_printed_col = col;\
228      tbl->row_printing_started = 1;\
229      tbl->col_str_ptrs[col] = mp_printf(tbl->pool, fmt, val);\
230   }
231
232 #define TABLE_SET(_name_, _type_, _typeconst_) TABLE_SET_COL(_name_, _type_, _typeconst_);\
233   TABLE_SET_COL_STR(_name_, _type_, _typeconst_);\
234   TABLE_SET_COL_FMT(_name_, _type_, _typeconst_);
235
236 TABLE_SET(int, int, COL_TYPE_INT)
237 TABLE_SET(uint, uint, COL_TYPE_UINT)
238 TABLE_SET(double, double, COL_TYPE_DOUBLE)
239 TABLE_SET(str, const char *, COL_TYPE_STR)
240 TABLE_SET(intmax, intmax_t, COL_TYPE_INTMAX)
241 TABLE_SET(uintmax, uintmax_t, COL_TYPE_UINTMAX)
242 #undef TABLE_SET_COL_FMT
243 #undef TABLE_SET_COL_STR
244 #undef TABLE_SET_COL
245 #undef TABLE_SET
246
247 void table_set_bool(struct table *tbl, int col, uint val)
248 {
249   table_set_bool_fmt(tbl, col, tbl->columns[col].fmt, val);
250 }
251
252 void table_set_bool_name(struct table *tbl, const char *col_name, uint val)
253 {
254   int col = table_get_col_idx(tbl, col_name);
255   table_set_bool(tbl, col, val);
256 }
257
258 void table_set_bool_fmt(struct table *tbl, int col, const char *fmt, uint val)
259 {
260   ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);
261   ASSERT(COL_TYPE_BOOL == tbl->columns[col].type);
262
263   tbl->last_printed_col = col;
264   tbl->row_printing_started = 1;
265   tbl->col_str_ptrs[col] = mp_printf(tbl->pool, fmt, val ? "true" : "false");
266 }
267
268 #define TABLE_APPEND(_name_, _type_, _typeconst_) void table_append_##_name_(struct table *tbl, _type_ val) \
269   {\
270      ASSERT(tbl->last_printed_col != -1 || tbl->row_printing_started != 0);\
271      ASSERT(_typeconst_ == tbl->columns[tbl->last_printed_col].type);\
272      int col = tbl->last_printed_col;\
273      mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], "%s", tbl->append_delimiter);\
274      tbl->col_str_ptrs[col] = mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], tbl->columns[col].fmt, val);\
275   }
276
277 TABLE_APPEND(int, int, COL_TYPE_INT)
278 TABLE_APPEND(uint, uint, COL_TYPE_UINT)
279 TABLE_APPEND(double, double, COL_TYPE_DOUBLE)
280 TABLE_APPEND(str, const char *, COL_TYPE_STR)
281 TABLE_APPEND(intmax, intmax_t, COL_TYPE_INTMAX)
282 TABLE_APPEND(uintmax, uintmax_t, COL_TYPE_UINTMAX)
283 #undef TABLE_APPEND
284
285 void table_append_bool(struct table *tbl, int val)
286 {
287   ASSERT(tbl->last_printed_col != -1 || tbl->row_printing_started != 0);
288   ASSERT(COL_TYPE_BOOL == tbl->columns[tbl->last_printed_col].type);
289
290   int col = tbl->last_printed_col;
291
292   mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], "%s", tbl->append_delimiter);
293
294   tbl->col_str_ptrs[col] = mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], tbl->columns[col].fmt, val ? "true" : "false");
295 }
296
297 void table_append_printf(struct table *tbl, const char *fmt, ...)
298 {
299   ASSERT(tbl->last_printed_col != -1 || tbl->row_printing_started != 0);
300   int col = tbl->last_printed_col;
301
302   va_list args;
303   va_start(args, fmt);
304
305   mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], "%s", tbl->append_delimiter);
306   tbl->col_str_ptrs[col] = mp_vprintf_append(tbl->pool, tbl->col_str_ptrs[col], fmt, args);
307
308   va_end(args);
309 }
310
311 void table_end_row(struct table *tbl)
312 {
313   ASSERT(tbl->formatter->row_output);
314   tbl->formatter->row_output(tbl);
315   memset(tbl->col_str_ptrs, 0, sizeof(char *) * tbl->column_count);
316   mp_restore(tbl->pool, &tbl->pool_state);
317   tbl->last_printed_col = -1;
318   tbl->row_printing_started = 0;
319 }
320
321 /* Construction of a cell using a fastbuf */
322
323 struct fastbuf *table_col_fbstart(struct table *tbl, int col)
324 {
325   fbpool_init(&tbl->fb_col_out);
326   fbpool_start(&tbl->fb_col_out, tbl->pool, 1);
327   tbl->col_out = col;
328   return &tbl->fb_col_out.fb;
329 }
330
331 void table_col_fbend(struct table *tbl)
332 {
333   tbl->col_str_ptrs[tbl->col_out] = fbpool_end(&tbl->fb_col_out);
334   tbl->col_out = -1;
335 }
336
337 /*** Option parsing ***/
338
339 const char *table_set_option_value(struct table *tbl, const char *key, const char *value)
340 {
341   // Options with no value
342   if(value == NULL || (value != NULL && strlen(value) == 0)) {
343     if(strcmp(key, "noheader") == 0) {
344       tbl->print_header = 0;
345       return NULL;
346     }
347   }
348
349   // Options with a value
350   if(value) {
351     if(strcmp(key, "header") == 0) {
352       // FIXME: Check syntax of value.
353       //tbl->print_header = strtol(value, NULL, 10); //atoi(value);
354       //if(errno != 0) tbl->print_header
355       if(value[1] != 0)
356         return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s' has invalid value: '%s'.", key, value);
357       uint tmp = value[0] - '0';
358       if(tmp > 1)
359         return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s' has invalid value: '%s'.", key, value);
360       tbl->print_header = tmp;
361       return NULL;
362     } else if(strcmp(key, "cols") == 0) {
363       // FIXME: We should not exit/abort on errors caused from command line.
364       if(table_col_order_by_name(tbl, value) != 0) {
365         const char *tmp = table_get_col_list(tbl);
366         return mp_printf(tbl->pool, "Invalid tableprinter column list: possible column names are %s.", tmp);
367       }
368       return NULL;
369     } else if(strcmp(key, "fmt") == 0) {
370       if(strcmp(value, "human") == 0) table_set_formatter(tbl, &table_fmt_human_readable);
371       else if(strcmp(value, "machine") == 0) table_set_formatter(tbl, &table_fmt_machine_readable);
372       else {
373         return "Tableprinter: invalid argument to output-type option.";
374       }
375       return NULL;
376     } else if(strcmp(key, "col-delim") == 0) {
377       char * d = mp_printf(tbl->pool, "%s", value);
378       tbl->col_delimiter = d;
379       return NULL;
380     }
381   }
382
383   // Formatter options
384   if(tbl->formatter && tbl->formatter->process_option) {
385     const char *err = NULL;
386     if (tbl->formatter->process_option(tbl, key, value, &err)) {
387       return err;
388     }
389   }
390
391   // Unrecognized option
392   return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s%s%s'.", key, (value ? ":" : ""), (value ? : ""));
393 }
394
395 const char *table_set_option(struct table *tbl, const char *opt)
396 {
397   char *key = stk_strdup(opt);
398   char *value = strchr(key, ':');
399   if(value) {
400     *value++ = 0;
401   }
402   return table_set_option_value(tbl, key, value);
403 }
404
405 const char *table_set_gary_options(struct table *tbl, char **gary_table_opts)
406 {
407   for (uint i = 0; i < GARY_SIZE(gary_table_opts); i++) {
408     const char *rv = table_set_option(tbl, gary_table_opts[i]);
409     if(rv != NULL) {
410       return rv;
411     }
412   }
413   return NULL;
414 }
415
416 /*** Default formatter for human-readable output ***/
417
418 static void table_row_human_readable(struct table *tbl)
419 {
420   uint col = tbl->column_order[0];
421   int col_width = tbl->columns[col].width;
422   bprintf(tbl->out, "%*s", col_width, tbl->col_str_ptrs[col]);
423   for(uint i = 1; i < tbl->cols_to_output; i++) {
424     col = tbl->column_order[i];
425     col_width = tbl->columns[col].width;
426     bputs(tbl->out, tbl->col_delimiter);
427     bprintf(tbl->out, "%*s", col_width, tbl->col_str_ptrs[col]);
428   }
429
430   bputc(tbl->out, '\n');
431 }
432
433 static void table_write_header(struct table *tbl)
434 {
435   uint col_idx = tbl->column_order[0];
436   bprintf(tbl->out, "%*s", tbl->columns[col_idx].width, tbl->columns[col_idx].name);
437
438   for(uint i = 1; i < tbl->cols_to_output; i++) {
439     col_idx = tbl->column_order[i];
440     bputs(tbl->out, tbl->col_delimiter);
441     bprintf(tbl->out, "%*s", tbl->columns[col_idx].width, tbl->columns[col_idx].name);
442   }
443
444   bputc(tbl->out, '\n');
445 }
446
447 static void table_start_human_readable(struct table *tbl)
448 {
449   if(tbl->col_delimiter == NULL) {
450     tbl->col_delimiter = " ";
451   }
452
453   if(tbl->append_delimiter == NULL) {
454     tbl->append_delimiter = ",";
455   }
456
457   if(tbl->print_header != 0) {
458     table_write_header(tbl);
459   }
460 }
461
462 struct table_formatter table_fmt_human_readable = {
463   .row_output = table_row_human_readable,
464   .table_start = table_start_human_readable,
465 };
466
467 /*** Default formatter for machine-readable output ***/
468
469 static void table_row_machine_readable(struct table *tbl)
470 {
471   uint col = tbl->column_order[0];
472   bputs(tbl->out, tbl->col_str_ptrs[col]);
473   for(uint i = 1; i < tbl->cols_to_output; i++) {
474     col = tbl->column_order[i];
475     bputs(tbl->out, tbl->col_delimiter);
476     bputs(tbl->out, tbl->col_str_ptrs[col]);
477   }
478
479   bputc(tbl->out, '\n');
480 }
481
482 static void table_start_machine_readable(struct table *tbl)
483 {
484   if(tbl->col_delimiter == NULL) {
485     tbl->col_delimiter = ";";
486   }
487
488   if(tbl->append_delimiter == NULL) {
489     tbl->append_delimiter = ",";
490   }
491
492   if(tbl->print_header != 0) {
493     uint col_idx = tbl->column_order[0];
494     bputs(tbl->out, tbl->columns[col_idx].name);
495     for(uint i = 1; i < tbl->cols_to_output; i++) {
496       col_idx = tbl->column_order[i];
497       bputs(tbl->out, tbl->col_delimiter);
498       bputs(tbl->out, tbl->columns[col_idx].name);
499     }
500     bputc(tbl->out, '\n');
501   }
502 }
503
504 struct table_formatter table_fmt_machine_readable = {
505   .row_output = table_row_machine_readable,
506   .table_start = table_start_machine_readable,
507 };
508
509 /*** Tests ***/
510
511 #ifdef TEST
512
513 #include <stdio.h>
514
515 enum test_table_cols {
516   test_col0_str, test_col1_int, test_col2_uint, test_col3_bool, test_col4_double
517 };
518
519 static uint test_column_order[] = {test_col3_bool, test_col4_double, test_col2_uint,test_col1_int, test_col0_str};
520
521 static struct table test_tbl = {
522   TBL_COLUMNS {
523     TBL_COL_STR(test, col0_str, 20),
524     TBL_COL_INT(test, col1_int, 8),
525     TBL_COL_UINT(test, col2_uint, 9),
526     TBL_COL_BOOL(test, col3_bool, 9),
527     TBL_COL_DOUBLE(test, col4_double, 11, 2),
528     TBL_COL_END
529   },
530   TBL_COL_ORDER(test_column_order),
531   TBL_OUTPUT_HUMAN_READABLE,
532   TBL_COL_DELIMITER("\t"),
533   TBL_APPEND_DELIMITER(",")
534 };
535
536 /**
537  * tests: table_set_nt, table_set_uint, table_set_bool, table_set_double, table_set_printf
538  **/
539 static void do_print1(struct table *test_tbl)
540 {
541   table_set_str(test_tbl, test_col0_str, "sdsdf");
542   table_append_str(test_tbl, "aaaaa");
543   table_set_int(test_tbl, test_col1_int, -10);
544   table_set_int(test_tbl, test_col1_int, 10000);
545   table_set_uint(test_tbl, test_col2_uint, 10);
546   table_set_printf(test_tbl, test_col2_uint, "XXX-%u", 22222);
547   table_set_bool(test_tbl, test_col3_bool, 1);
548   table_set_double(test_tbl, test_col4_double, 1.5);
549   table_set_printf(test_tbl, test_col4_double, "AAA");
550   table_end_row(test_tbl);
551
552   table_set_str(test_tbl, test_col0_str, "test");
553   table_append_str(test_tbl, "bbbbb");
554   table_set_int(test_tbl, test_col1_int, -100);
555   table_set_uint(test_tbl, test_col2_uint, 100);
556   table_set_bool(test_tbl, test_col3_bool, 0);
557   table_set_printf(test_tbl, test_col4_double, "%.2lf", 1.5);
558   table_end_row(test_tbl);
559 }
560
561 static void test_simple1(struct fastbuf *out)
562 {
563   table_init(&test_tbl, out);
564   // print table with header
565   table_col_order_by_name(&test_tbl, "col3_bool");
566   table_start(&test_tbl);
567   do_print1(&test_tbl);
568   table_end(&test_tbl);
569
570   // print the same table as in the previous case without header
571   table_col_order_by_name(&test_tbl, "col0_str,col2_uint,col1_int,col3_bool");
572   table_start(&test_tbl);
573   do_print1(&test_tbl);
574   table_end(&test_tbl);
575
576   // this also tests whether there is need to call table_col_order_by_name after table_end was called
577   test_tbl.print_header = 0;
578   table_start(&test_tbl);
579   do_print1(&test_tbl);
580   table_end(&test_tbl);
581   test_tbl.print_header = 1;
582
583   table_col_order_by_name(&test_tbl, "col3_bool");
584   table_start(&test_tbl);
585   do_print1(&test_tbl);
586   table_end(&test_tbl);
587
588   table_col_order_by_name(&test_tbl, "col3_bool,col0_str");
589   table_start(&test_tbl);
590   do_print1(&test_tbl);
591   table_end(&test_tbl);
592
593   table_col_order_by_name(&test_tbl, "col0_str,col3_bool,col2_uint");
594   table_start(&test_tbl);
595   do_print1(&test_tbl);
596   table_end(&test_tbl);
597
598   table_col_order_by_name(&test_tbl, "col0_str,col3_bool,col2_uint,col0_str,col3_bool,col2_uint,col0_str,col3_bool,col2_uint");
599   table_start(&test_tbl);
600   do_print1(&test_tbl);
601   table_end(&test_tbl);
602
603   table_col_order_by_name(&test_tbl, "col0_str,col1_int,col2_uint,col3_bool,col4_double");
604   table_start(&test_tbl);
605   do_print1(&test_tbl);
606   table_end(&test_tbl);
607
608   table_cleanup(&test_tbl);
609 }
610
611 enum test_any_table_cols {
612   test_any_col0_int, test_any_col1_any
613 };
614
615 static uint test_any_column_order[] = { test_any_col0_int, test_any_col1_any };
616
617 static struct table test_any_tbl = {
618   TBL_COLUMNS {
619     TBL_COL_INT(test_any, col0_int, 8),
620     TBL_COL_ANY(test_any, col1_any, 9),
621     TBL_COL_END
622   },
623   TBL_COL_ORDER(test_any_column_order),
624   TBL_OUTPUT_HUMAN_READABLE,
625   TBL_COL_DELIMITER("\t"),
626   TBL_APPEND_DELIMITER(",")
627 };
628
629 static void test_any_type(struct fastbuf *out)
630 {
631   table_init(&test_any_tbl, out);
632   table_start(&test_any_tbl);
633
634   table_set_int(&test_any_tbl, test_any_col0_int, -10);
635   table_set_int(&test_any_tbl, test_any_col1_any, 10000);
636   table_end_row(&test_any_tbl);
637
638   table_set_int(&test_any_tbl, test_any_col0_int, -10);
639   table_set_double(&test_any_tbl, test_any_col1_any, 1.4);
640   table_end_row(&test_any_tbl);
641
642   table_set_printf(&test_any_tbl, test_any_col0_int, "%d", 10);
643   table_append_printf(&test_any_tbl, "%d", 20);
644   table_append_printf(&test_any_tbl, "%d", 30);
645   table_set_double(&test_any_tbl, test_any_col1_any, 1.4);
646   table_append_printf(&test_any_tbl, "%.2lf", 1.5);
647   table_append_printf(&test_any_tbl, "%.2lf", 1.6);
648   table_end_row(&test_any_tbl);
649
650   table_end(&test_any_tbl);
651   table_cleanup(&test_any_tbl);
652 }
653
654 int main(int argc UNUSED, char **argv UNUSED)
655 {
656   struct fastbuf *out;
657   out = bfdopen_shared(1, 4096);
658
659   test_simple1(out);
660
661   test_any_type(out);
662
663   bclose(out);
664   return 0;
665 }
666
667 #endif