]> mj.ucw.cz Git - libucw.git/blob - ucw/table.c
tableprinter: code cleanup
[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 #include <ucw/strtonum.h>
13
14 #include <stdlib.h>
15 #include <stdio.h>
16
17 /* Forward declarations */
18
19 static void table_update_ll(struct table *tbl);
20
21 /*** Management of tables ***/
22
23 static struct table *table_make_instance(struct table_template *tbl_template)
24 {
25   struct table *new_inst = xmalloc_zero(sizeof(struct table));
26
27   new_inst->column_count = tbl_template->column_count;
28   new_inst->pool = mp_new(4096);
29   if(tbl_template->column_order) {
30     new_inst->column_order = mp_alloc_zero(new_inst->pool, sizeof(struct table_col_info) * tbl_template->cols_to_output);
31     memcpy(new_inst->column_order, tbl_template->column_order, sizeof(struct table_col_info) * tbl_template->cols_to_output);
32     for(uint i = 0; i < new_inst->cols_to_output; i++) {
33       new_inst->column_order[i].cell_content = NULL;
34       new_inst->column_order[i].col_def = NULL;
35       new_inst->column_order[i].output_type = tbl_template->column_order[i].output_type;
36     }
37
38     new_inst->cols_to_output = tbl_template->cols_to_output;
39   }
40
41   new_inst->columns = tbl_template->columns; // FIXME: copy also columns, if there will be two instances of table, then there will be clash between the linked lists!
42
43   new_inst->col_delimiter = tbl_template->col_delimiter;
44   new_inst->print_header = 1;
45   new_inst->out = 0;
46   new_inst->last_printed_col = -1;
47   new_inst->row_printing_started = 0;
48   new_inst->col_out = -1;
49   new_inst->formatter = tbl_template->formatter;
50   new_inst->data = NULL;
51   return new_inst;
52 }
53
54 struct table *table_init(struct table_template *tbl_template)
55 {
56   int col_count = 0; // count the number of columns in the struct table
57
58   struct table *tbl = table_make_instance(tbl_template);
59
60   for(;;) {
61     if(tbl->columns[col_count].name == NULL &&
62        tbl->columns[col_count].fmt == NULL &&
63        tbl->columns[col_count].width == 0 &&
64        tbl->columns[col_count].type == COL_TYPE_LAST)
65       break;
66     ASSERT(tbl->columns[col_count].name != NULL);
67     ASSERT(tbl->columns[col_count].type == COL_TYPE_ANY || tbl_template->columns[col_count].fmt != NULL);
68     ASSERT(tbl->columns[col_count].width != 0);
69
70     col_count++;
71   }
72
73   tbl->column_count = col_count;
74
75   if(!tbl->formatter) {
76     tbl->formatter = &table_fmt_human_readable;
77   }
78
79   tbl->print_header = 1; // by default, print header
80   return tbl;
81 }
82
83 void table_cleanup(struct table *tbl)
84 {
85   mp_delete(tbl->pool);
86   memset(tbl, 0, sizeof(struct table));
87   xfree(tbl);
88 }
89
90 // TODO: test default column order
91 static void table_make_default_column_order(struct table *tbl)
92 {
93   int *col_order_int = mp_alloc_zero(tbl->pool, sizeof(int) * tbl->column_count); // FIXME: use stack instead of memory pool
94   for(int i = 0; i < tbl->column_count; i++) {
95     col_order_int[i] = i;
96   }
97   table_set_col_order(tbl, col_order_int, tbl->column_count);
98 }
99
100 void table_start(struct table *tbl, struct fastbuf *out)
101 {
102   tbl->last_printed_col = -1;
103   tbl->row_printing_started = 0;
104   tbl->out = out;
105
106   ASSERT_MSG(tbl->out, "Output fastbuf not specified.");
107
108   if(tbl->column_order == NULL) table_make_default_column_order(tbl);
109   else {
110     // update linked lists
111     table_update_ll(tbl);
112   }
113   if(tbl->formatter->table_start != NULL) tbl->formatter->table_start(tbl);
114
115   mp_save(tbl->pool, &tbl->pool_state);
116
117   ASSERT_MSG(tbl->col_delimiter, "In-between column delimiter not specified.");
118 }
119
120 void table_end(struct table *tbl)
121 {
122   tbl->last_printed_col = -1;
123   tbl->row_printing_started = 0;
124
125   mp_restore(tbl->pool, &tbl->pool_state);
126
127   if(tbl->formatter->table_end) tbl->formatter->table_end(tbl);
128 }
129
130 /*** Configuration ***/
131
132 void table_set_formatter(struct table *tbl, struct table_formatter *fmt)
133 {
134   tbl->formatter = fmt;
135 }
136
137 int table_get_col_idx(struct table *tbl, const char *col_name)
138 {
139   for(int i = 0; i < tbl->column_count; i++) {
140     if(strcmp(tbl->columns[i].name, col_name) == 0) return i;
141   }
142   return -1;
143 }
144
145 const char * table_get_col_list(struct table *tbl)
146 {
147   if(tbl->column_count == 0) return "";
148
149   char *tmp = mp_strdup(tbl->pool, tbl->columns[0].name);
150
151   for(int i = 1; i < tbl->column_count; i++) {
152     tmp = mp_printf_append(tbl->pool, tmp, ", %s", tbl->columns[i].name);
153   }
154
155   return tmp;
156 }
157
158 static void table_update_ll(struct table *tbl)
159 {
160   int cols_to_output = tbl->cols_to_output;
161
162   for(int i = 0; i < tbl->column_count; i++) {
163     tbl->columns[i].first_column = -1;
164     tbl->columns[i].last_column = -1;
165   }
166
167   for(int i = 0; i < cols_to_output; i++) {
168     int idx = tbl->column_order[i].idx;
169     tbl->column_order[i].col_def = tbl->columns + idx;
170   }
171
172   for(int i = 0; i < cols_to_output; i++) {
173     int last = tbl->column_order[i].col_def->last_column;
174     if(last != -1) {
175       tbl->column_order[i].col_def->last_column = i;
176       tbl->column_order[last].next_column = i;
177     } else {
178       tbl->column_order[i].col_def->last_column = i;
179       tbl->column_order[i].col_def->first_column = i;
180     }
181     tbl->column_order[i].next_column = -1;
182   }
183 }
184
185 void table_set_col_order(struct table *tbl, int *col_order, int cols_to_output)
186 {
187   for(int i = 0; i < cols_to_output; i++) {
188     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 - 1);
189   }
190
191   tbl->cols_to_output = cols_to_output;
192   tbl->column_order = mp_alloc_zero(tbl->pool, sizeof(struct table_col_info) * cols_to_output);
193   for(int i = 0; i < cols_to_output; i++) {
194     int col_idx = col_order[i];
195     tbl->column_order[i].idx = col_idx;
196     tbl->column_order[i].col_def = tbl->columns + col_idx;
197     tbl->column_order[i].cell_content = NULL;
198     tbl->column_order[i].output_type = CELL_OUT_UNINITIALIZED;
199   }
200   table_update_ll(tbl);
201 }
202
203 bool table_col_is_printed(struct table *tbl, uint col_idx)
204 {
205   if(tbl->columns[col_idx].first_column == -1) return 0;
206
207   return 1;
208 }
209
210 static char * table_parse_col_arg(char *col_def)
211 {
212   // FIXME: should be switched to str_sepsplit
213   char * left_br = strchr(col_def, '[');
214   if(left_br == NULL) return NULL;
215   *left_br = 0;
216   left_br++;
217   char *right_br = strchr(left_br, ']');
218   *right_br = 0;
219   return left_br;
220 }
221
222 /**
223  * Setting options for basic table types (as defined in table.h)
224  **/
225 bool table_set_col_opt_default(struct table *tbl, int col_idx, const char *col_arg, char **err)
226 {
227   struct table_column *col_def = tbl->column_order[col_idx].col_def;
228
229   if(col_def->type == COL_TYPE_DOUBLE) {
230     uint precision = 0;
231     const char *tmp_err = str_to_uint(&precision, col_arg, NULL, 0);
232     if(tmp_err) {
233       *err = mp_printf(tbl->pool, "An error occured while parsing precision: %s", tmp_err);
234       return false;
235     }
236     tbl->column_order[col_idx].output_type = precision;
237     return true;
238   }
239
240   *err = mp_printf(tbl->pool, "Invalid column format option: '%s' for column %d.", col_arg, col_idx);
241   return false;
242 }
243
244 /**
245  * TODO: This function deliberately leaks memory. When it is called multiple times,
246  * previous column orders still remain allocated in the table's memory pool.
247  **/
248 const char * table_set_col_order_by_name(struct table *tbl, const char *col_order_str)
249 {
250   if(col_order_str[0] == '*') {
251     int *col_order_int = alloca(sizeof(int) * tbl->column_count);
252     for(int i = 0; i < tbl->column_count; i++) {
253       col_order_int[i] = i;
254     }
255     table_set_col_order(tbl, col_order_int, tbl->column_count);
256
257     return NULL;
258   }
259
260   if(!col_order_str[0]) {
261     tbl->column_order = mp_alloc(tbl->pool, 0);
262     tbl->cols_to_output = 0;
263     return NULL;
264   }
265
266   char *tmp_col_order = stk_strdup(col_order_str);
267
268   int col_count = 1;
269   for(int i = 0; col_order_str[i] != 0; i++) {
270     if(col_order_str[i] == ',') {
271       col_count++;
272     }
273   }
274
275   tbl->cols_to_output = col_count;
276   tbl->column_order = mp_alloc_zero(tbl->pool, sizeof(struct table_col_info) * col_count);
277
278   int curr_col_idx = 0;
279   char *name_start = tmp_col_order;
280   while(name_start) {
281     char *next = strchr(name_start, ',');
282     if(next) {
283       *next++ = 0;
284     }
285
286     char *arg = table_parse_col_arg(name_start); // this sets 0 on the '['
287     int col_idx = table_get_col_idx(tbl, name_start);
288
289     if(col_idx == -1) {
290       return mp_printf(tbl->pool, "Unknown table column '%s', possible column names are: %s.", name_start, table_get_col_list(tbl));
291     }
292     tbl->column_order[curr_col_idx].col_def = tbl->columns + col_idx;
293     tbl->column_order[curr_col_idx].idx = col_idx;
294     tbl->column_order[curr_col_idx].cell_content = NULL;
295     tbl->column_order[curr_col_idx].output_type = CELL_OUT_UNINITIALIZED;
296     if(tbl->columns[col_idx].type_def && tbl->columns[col_idx].type_def->set_col_instance_option) {
297       char *err = NULL;
298       tbl->columns[col_idx].type_def->set_col_instance_option(tbl, curr_col_idx, arg, &err);
299       if(err) return mp_printf(tbl->pool, "Error occured while setting column option: %s.", err);
300     }
301
302     name_start = next;
303     curr_col_idx++;
304   }
305
306   table_update_ll(tbl);
307
308   return NULL;
309 }
310
311 /*** Table cells ***/
312
313 static void table_set_all_inst_content(struct table *tbl, int col_templ, char *col_content, int override)
314 {
315   TBL_COL_ITER_START(tbl, col_templ, curr_col_ptr, curr_col) {
316     if(override == 0 && curr_col_ptr->output_type != CELL_OUT_UNINITIALIZED ) {
317       die("Error while setting content of all cells of a single type column, cell format should not be overriden.");
318     }
319     curr_col_ptr->cell_content = col_content;
320   } TBL_COL_ITER_END
321 }
322
323 void table_col_printf(struct table *tbl, int col, const char *fmt, ...)
324 {
325   ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);
326   tbl->last_printed_col = col;
327   tbl->row_printing_started = 1;
328   va_list args;
329   va_start(args, fmt);
330   char *cell_content = mp_vprintf(tbl->pool, fmt, args);
331   table_set_all_inst_content(tbl, col, cell_content, 1);
332   va_end(args);
333 }
334
335 static const char *table_col_default_fmts[] = {
336   [COL_TYPE_STR] = "%s",
337   [COL_TYPE_INT] = "%d",
338   [COL_TYPE_S64] = "%lld",
339   [COL_TYPE_INTMAX] = "%jd",
340   [COL_TYPE_UINT] = "%u",
341   [COL_TYPE_U64] = "%llu",
342   [COL_TYPE_UINTMAX] = "%ju",
343   [COL_TYPE_BOOL] = "%d",
344   [COL_TYPE_DOUBLE] = "%.2lf",
345   [COL_TYPE_ANY] = NULL,
346   [COL_TYPE_LAST] = NULL
347 };
348
349 #define TABLE_COL(_name_, _type_, _typeconst_) void table_col_##_name_(struct table *tbl, int col, _type_ val)\
350   {\
351     const char *fmt = tbl->columns[col].fmt;\
352     if(tbl->columns[col].type == COL_TYPE_ANY) {\
353        fmt = table_col_default_fmts[_typeconst_];\
354     }\
355     table_col_##_name_##_fmt(tbl, col, fmt, val);\
356   }
357
358 #define TABLE_COL_STR(_name_, _type_, _typeconst_) void table_col_##_name_##_name(struct table *tbl, const char *col_name, _type_ val)\
359   {\
360     int col = table_get_col_idx(tbl, col_name);\
361     table_col_##_name_(tbl, col, val);\
362   }
363
364 #define TABLE_COL_FMT(_name_, _type_, _typeconst_, _override) void table_col_##_name_##_fmt(struct table *tbl, int col, const char *fmt, _type_ val) \
365   {\
366      ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);\
367      ASSERT(tbl->columns[col].type == COL_TYPE_ANY || _typeconst_ == tbl->columns[col].type);\
368      ASSERT(fmt != NULL);\
369      tbl->last_printed_col = col;\
370      tbl->row_printing_started = 1;\
371      char *cell_content = mp_printf(tbl->pool, fmt, val);\
372      table_set_all_inst_content(tbl, col, cell_content, _override);\
373   }
374
375 #define TABLE_COL_BODIES(_name_, _type_, _typeconst_, _override) TABLE_COL(_name_, _type_, _typeconst_); \
376   TABLE_COL_STR(_name_, _type_, _typeconst_);\
377   TABLE_COL_FMT(_name_, _type_, _typeconst_, _override);
378
379 TABLE_COL_BODIES(int, int, COL_TYPE_INT, 0)
380 TABLE_COL_BODIES(uint, uint, COL_TYPE_UINT, 0)
381 TABLE_COL_BODIES(str, const char *, COL_TYPE_STR, 1)
382 TABLE_COL_BODIES(intmax, intmax_t, COL_TYPE_INTMAX, 0)
383 TABLE_COL_BODIES(uintmax, uintmax_t, COL_TYPE_UINTMAX, 0)
384 TABLE_COL_BODIES(s64, s64, COL_TYPE_S64, 0)
385 TABLE_COL_BODIES(u64, u64, COL_TYPE_U64, 0)
386
387 // column type double is a special case
388 TABLE_COL(double, double, COL_TYPE_DOUBLE);
389 TABLE_COL_STR(double, double, COL_TYPE_DOUBLE);
390 #undef TABLE_COL
391 #undef TABLE_COL_FMT
392 #undef TABLE_COL_STR
393 #undef TABLE_COL_BODIES
394
395 void table_col_double_fmt(struct table *tbl, int col, const char *fmt, double val)
396 {
397   ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);
398   ASSERT(tbl->columns[col].type == COL_TYPE_ANY || COL_TYPE_DOUBLE == tbl->columns[col].type);
399   ASSERT(fmt != NULL);
400   tbl->last_printed_col = col;
401   tbl->row_printing_started = 1;
402   char *cell_content = mp_printf(tbl->pool, fmt, val);
403   int curr_col = tbl->columns[col].first_column;
404   while(curr_col != -1) {
405     if(tbl->column_order[curr_col].output_type < 0) tbl->column_order[curr_col].cell_content = cell_content;
406     else {
407       char *cell_content_tmp = mp_printf(tbl->pool, "%.*lf", tbl->column_order[curr_col].output_type, val);
408       tbl->column_order[curr_col].cell_content = cell_content_tmp;
409     }
410     curr_col = tbl->column_order[curr_col].next_column;
411   }
412 }
413
414 void table_col_bool(struct table *tbl, int col, uint val)
415 {
416   table_col_bool_fmt(tbl, col, tbl->columns[col].fmt, val);
417 }
418
419 void table_col_bool_name(struct table *tbl, const char *col_name, uint val)
420 {
421   int col = table_get_col_idx(tbl, col_name);
422   table_col_bool(tbl, col, val);
423 }
424
425 void table_col_bool_fmt(struct table *tbl, int col, const char *fmt, uint val)
426 {
427   ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exist.", col);
428   ASSERT(COL_TYPE_BOOL == tbl->columns[col].type);
429
430   tbl->last_printed_col = col;
431   tbl->row_printing_started = 1;
432
433   int curr_col = tbl->columns[col].first_column;
434   while(curr_col != -1) {
435     switch(tbl->column_order[curr_col].output_type) {
436     case CELL_OUT_HUMAN_READABLE:
437     case CELL_OUT_UNINITIALIZED:
438       tbl->column_order[curr_col].cell_content = mp_printf(tbl->pool, fmt, val ? "true" : "false");
439       break;
440     case CELL_OUT_MACHINE_READABLE:
441       // FIXME: this is just an example of printing in different formats
442       tbl->column_order[curr_col].cell_content = mp_printf(tbl->pool, fmt, val ? "1" : "0");
443       break;
444     default:
445       die("Unsupported output type.");
446     }
447     curr_col = tbl->column_order[curr_col].next_column;
448   }
449 }
450
451 void table_reset_row(struct table *tbl)
452 {
453   for(uint i = 0; i < tbl->cols_to_output; i++) {
454     tbl->column_order[i].cell_content = NULL;
455   }
456   mp_restore(tbl->pool, &tbl->pool_state);
457   tbl->last_printed_col = -1;
458   tbl->row_printing_started = 0;
459 }
460
461 void table_end_row(struct table *tbl)
462 {
463   ASSERT(tbl->formatter->row_output);
464   if(tbl->row_printing_started == 0) return;
465   tbl->formatter->row_output(tbl);
466   table_reset_row(tbl);
467 }
468
469 /* Construction of a cell using a fastbuf */
470
471 struct fastbuf *table_col_fbstart(struct table *tbl, int col)
472 {
473   fbpool_init(&tbl->fb_col_out);
474   fbpool_start(&tbl->fb_col_out, tbl->pool, 1);
475   tbl->col_out = col;
476   return &tbl->fb_col_out.fb;
477 }
478
479 void table_col_fbend(struct table *tbl)
480 {
481   char *cell_content = fbpool_end(&tbl->fb_col_out);
482   table_set_all_inst_content(tbl, tbl->col_out, cell_content, 1);
483   tbl->col_out = -1;
484 }
485
486 /*** Option parsing ***/
487
488 const char *table_set_option_value(struct table *tbl, const char *key, const char *value)
489 {
490   // Options with no value
491   if(value == NULL || (value != NULL && strlen(value) == 0)) {
492     if(strcmp(key, "noheader") == 0) {
493       tbl->print_header = 0;
494       return NULL;
495     }
496   }
497
498   // Options with a value
499   if(value) {
500     if(strcmp(key, "header") == 0) {
501       if(value[1] != 0)
502         return mp_printf(tbl->pool, "Invalid header parameter: '%s' has invalid value: '%s'.", key, value);
503       uint tmp = value[0] - '0';
504       if(tmp > 1)
505         return mp_printf(tbl->pool, "Invalid header parameter: '%s' has invalid value: '%s'.", key, value);
506       tbl->print_header = tmp;
507       return NULL;
508     } else if(strcmp(key, "cols") == 0) {
509       return table_set_col_order_by_name(tbl, value);
510     } else if(strcmp(key, "fmt") == 0) {
511       if(strcmp(value, "human") == 0) table_set_formatter(tbl, &table_fmt_human_readable);
512       else if(strcmp(value, "machine") == 0) table_set_formatter(tbl, &table_fmt_machine_readable);
513       else if(strcmp(value, "blockline") == 0) table_set_formatter(tbl, &table_fmt_blockline);
514       else {
515         return "Invalid argument to output-type option.";
516       }
517       return NULL;
518     } else if(strcmp(key, "col-delim") == 0) {
519       char * d = mp_printf(tbl->pool, "%s", value);
520       tbl->col_delimiter = d;
521       return NULL;
522     }
523   }
524
525   // Formatter options
526   if(tbl->formatter && tbl->formatter->process_option) {
527     const char *err = NULL;
528     if(tbl->formatter->process_option(tbl, key, value, &err)) {
529       return err;
530     }
531   }
532
533   // Unrecognized option
534   return mp_printf(tbl->pool, "Invalid option: '%s%s%s'.", key, (value ? ":" : ""), (value ? : ""));
535 }
536
537 const char *table_set_option(struct table *tbl, const char *opt)
538 {
539   char *key = stk_strdup(opt);
540   char *value = strchr(key, ':');
541   if(value) {
542     *value++ = 0;
543   }
544   return table_set_option_value(tbl, key, value);
545 }
546
547 const char *table_set_gary_options(struct table *tbl, char **gary_table_opts)
548 {
549   for (uint i = 0; i < GARY_SIZE(gary_table_opts); i++) {
550     const char *rv = table_set_option(tbl, gary_table_opts[i]);
551     if(rv != NULL) {
552       return rv;
553     }
554   }
555   return NULL;
556 }
557
558 /*** Default formatter for human-readable output ***/
559
560 static void table_row_human_readable(struct table *tbl)
561 {
562   for(uint i = 0; i < tbl->cols_to_output; i++) {
563     struct table_column *col_def = tbl->column_order[i].col_def;
564     if(i) {
565       bputs(tbl->out, tbl->col_delimiter);
566     }
567     int col_width = col_def->width & CELL_WIDTH_MASK;
568     if(col_def->width & CELL_ALIGN_LEFT) col_width = -1 * col_width;
569     bprintf(tbl->out, "%*s", col_width, tbl->column_order[i].cell_content);
570   }
571   bputc(tbl->out, '\n');
572 }
573
574 static void table_write_header(struct table *tbl)
575 {
576   for(uint i = 0; i < tbl->cols_to_output; i++) {
577     struct table_column *col_def = tbl->column_order[i].col_def;
578     if(i) {
579       bputs(tbl->out, tbl->col_delimiter);
580     }
581     int col_width = col_def->width & CELL_WIDTH_MASK;
582     if(col_def->width & CELL_ALIGN_LEFT) col_width = -1 * col_width;
583     bprintf(tbl->out, "%*s", col_width, col_def->name);
584   }
585   bputc(tbl->out, '\n');
586 }
587
588 static void table_start_human_readable(struct table *tbl)
589 {
590   if(tbl->col_delimiter == NULL) {
591     tbl->col_delimiter = " ";
592   }
593
594   if(tbl->print_header != 0) {
595     table_write_header(tbl);
596   }
597 }
598
599 struct table_formatter table_fmt_human_readable = {
600   .row_output = table_row_human_readable,
601   .table_start = table_start_human_readable,
602 };
603
604 /*** Default formatter for machine-readable output ***/
605
606 static void table_row_machine_readable(struct table *tbl)
607 {
608   for(uint i = 0; i < tbl->cols_to_output; i++) {
609     if(i) {
610       bputs(tbl->out, tbl->col_delimiter);
611     }
612     bputs(tbl->out, tbl->column_order[i].cell_content);
613   }
614   bputc(tbl->out, '\n');
615 }
616
617 static void table_start_machine_readable(struct table *tbl)
618 {
619   if(tbl->col_delimiter == NULL) {
620     tbl->col_delimiter = "\t";
621   }
622
623   if(tbl->print_header != 0 && tbl->cols_to_output > 0) {
624     bputs(tbl->out, tbl->column_order[0].col_def->name);
625     for(uint i = 1; i < tbl->cols_to_output; i++) {
626       bputs(tbl->out, tbl->col_delimiter);
627       bputs(tbl->out, tbl->column_order[i].col_def->name);
628     }
629     bputc(tbl->out, '\n');
630   }
631 }
632
633 struct table_formatter table_fmt_machine_readable = {
634   .row_output = table_row_machine_readable,
635   .table_start = table_start_machine_readable,
636 };
637
638
639 /*** Blockline formatter ***/
640
641 static void table_row_blockline_output(struct table *tbl)
642 {
643   for(uint i = 0; i < tbl->cols_to_output; i++) {
644     struct table_column *col_def = tbl->column_order[i].col_def;
645     bprintf(tbl->out, "%s: %s\n", col_def->name, tbl->column_order[i].cell_content);
646   }
647   bputc(tbl->out, '\n');
648 }
649
650 static void table_start_blockline(struct table *tbl)
651 {
652   if(tbl->col_delimiter == NULL) {
653     tbl->col_delimiter = "\n";
654   }
655 }
656
657 struct table_formatter table_fmt_blockline = {
658   .row_output = table_row_blockline_output,
659   .table_start = table_start_blockline
660 };
661
662
663
664 /*** Tests ***/
665
666 #ifdef TEST
667
668 #include <stdio.h>
669
670 enum test_table_cols {
671   test_col0_str, test_col1_int, test_col2_uint, test_col3_bool, test_col4_double
672 };
673
674 static struct table_col_info test_column_order[] = { TBL_COL(test_col3_bool), TBL_COL(test_col4_double), TBL_COL(test_col2_uint), TBL_COL(test_col1_int), TBL_COL(test_col0_str) };
675
676 static struct table_template test_tbl = {
677   TBL_COLUMNS {
678     [test_col0_str] = TBL_COL_STR("col0_str", 20),
679     [test_col1_int] = TBL_COL_INT("col1_int", 8),
680     [test_col2_uint] = TBL_COL_UINT("col2_uint", 9),
681     [test_col3_bool] = TBL_COL_BOOL("col3_bool", 9),
682     [test_col4_double] = TBL_COL_DOUBLE("col4_double", 11, 2),
683     TBL_COL_END
684   },
685   TBL_COL_ORDER(test_column_order),
686   TBL_OUTPUT_HUMAN_READABLE,
687   TBL_COL_DELIMITER("\t"),
688 };
689
690 /**
691  * tests: table_set_nt, table_set_uint, table_set_bool, table_set_double, table_set_printf
692  **/
693 static void do_print1(struct table *test_tbl)
694 {
695   table_col_str(test_tbl, test_col0_str, "sdsdf");
696   table_col_int(test_tbl, test_col1_int, -10);
697   table_col_int(test_tbl, test_col1_int, 10000);
698   table_col_uint(test_tbl, test_col2_uint, 10);
699   table_col_printf(test_tbl, test_col2_uint, "XXX-%u", 22222);
700   table_col_bool(test_tbl, test_col3_bool, 1);
701   table_col_double(test_tbl, test_col4_double, 1.5);
702   table_col_printf(test_tbl, test_col4_double, "AAA");
703   table_end_row(test_tbl);
704
705   table_col_str(test_tbl, test_col0_str, "test");
706   table_col_int(test_tbl, test_col1_int, -100);
707   table_col_uint(test_tbl, test_col2_uint, 100);
708   table_col_bool(test_tbl, test_col3_bool, 0);
709   table_col_printf(test_tbl, test_col4_double, "%.2lf", 1.5);
710   table_end_row(test_tbl);
711 }
712
713 static void test_simple1(struct fastbuf *out)
714 {
715   struct table *tbl = table_init(&test_tbl);
716
717   // print table with header
718   table_set_col_order_by_name(tbl, "col3_bool");
719   table_start(tbl, out);
720   do_print1(tbl);
721   table_end(tbl);
722
723   // print the same table as in the previous case without header
724   table_set_col_order_by_name(tbl, "col0_str,col2_uint,col1_int,col3_bool");
725   table_start(tbl, out);
726   do_print1(tbl);
727   table_end(tbl);
728
729   // this also tests whether there is need to call table_set_col_order_by_name after table_end was called
730   tbl->print_header = 0;
731   table_start(tbl, out);
732   do_print1(tbl);
733   table_end(tbl);
734   tbl->print_header = 1;
735
736   table_set_col_order_by_name(tbl, "col3_bool");
737   table_start(tbl, out);
738   do_print1(tbl);
739   table_end(tbl);
740
741   table_set_col_order_by_name(tbl, "col3_bool,col0_str");
742   table_start(tbl, out);
743   do_print1(tbl);
744   table_end(tbl);
745
746   table_set_col_order_by_name(tbl, "col0_str,col3_bool,col2_uint");
747   table_start(tbl, out);
748   do_print1(tbl);
749   table_end(tbl);
750
751   table_set_col_order_by_name(tbl, "col0_str,col3_bool,col2_uint,col0_str,col3_bool,col2_uint,col0_str,col3_bool,col2_uint");
752   table_start(tbl, out);
753   do_print1(tbl);
754   table_end(tbl);
755
756   table_set_col_order_by_name(tbl, "col0_str,col1_int,col2_uint,col3_bool,col4_double");
757   table_start(tbl, out);
758   do_print1(tbl);
759   table_end(tbl);
760
761   table_cleanup(tbl);
762 }
763
764 enum test_any_table_cols {
765   test_any_col0_int, test_any_col1_any
766 };
767
768 static struct table_col_info test_any_column_order[] = { TBL_COL(test_any_col0_int), TBL_COL(test_any_col1_any) };
769
770 static struct table_template test_any_tbl = {
771   TBL_COLUMNS {
772     [test_any_col0_int] = TBL_COL_INT("col0_int", 8),
773     [test_any_col1_any] = TBL_COL_ANY("col1_any", 9),
774     TBL_COL_END
775   },
776   TBL_COL_ORDER(test_any_column_order),
777   TBL_OUTPUT_HUMAN_READABLE,
778   TBL_COL_DELIMITER("\t"),
779 };
780
781 static void test_any_type(struct fastbuf *out)
782 {
783   struct table *tbl = table_init(&test_any_tbl);
784
785   table_start(tbl, out);
786
787   table_col_int(tbl, test_any_col0_int, -10);
788   table_col_int(tbl, test_any_col1_any, 10000);
789   table_end_row(tbl);
790
791   table_col_int(tbl, test_any_col0_int, -10);
792   table_col_double(tbl, test_any_col1_any, 1.4);
793   table_end_row(tbl);
794
795   table_col_printf(tbl, test_any_col0_int, "%d", 10);
796   table_col_double(tbl, test_any_col1_any, 1.4);
797   table_end_row(tbl);
798
799   table_end(tbl);
800   table_cleanup(tbl);
801 }
802
803 int main(int argc UNUSED, char **argv UNUSED)
804 {
805   struct fastbuf *out;
806   out = bfdopen_shared(1, 4096);
807
808   test_simple1(out);
809
810   test_any_type(out);
811
812   bclose(out);
813   return 0;
814 }
815
816 #endif