resource trans res-fd res-mem res-subpool res-mempool res-eltpool \
daemon daemon-ctrl \
signames \
- opt opt-help opt-conf
+ opt opt-help opt-conf \
+ table
LIBUCW_MAIN_INCLUDES= \
lib.h log.h tbf.h threads.h time.h \
daemon.h \
signames.h \
sighandler.h \
- opt.h
+ opt.h \
+ table.h
ifdef CONFIG_UCW_THREADS
# Some modules require threading
$(o)/ucw/ipaccess-test: $(o)/ucw/ipaccess-test.o $(LIBUCW)
$(o)/ucw/trie-test: $(o)/ucw/trie-test.o $(LIBUCW)
$(o)/ucw/opt-test: $(o)/ucw/opt-test.o $(LIBUCW)
+$(o)/ucw/table-test: $(o)/ucw/table-test.o $(LIBUCW)
TESTS+=$(addprefix $(o)/ucw/,regex.test unicode.test hash-test.test mempool.test stkstring.test \
slists.test bbuf.test kmp-test.test getopt.test ff-unicode.test eltpool.test \
fb-file.test fb-socket.test fb-grow.test fb-pool.test fb-atomic.test fb-limfd.test fb-temp.test \
fb-mem.test fb-buffer.test fb-mmap.test fb-multi.test fb-null.test \
redblack-test.test url.test strtonum-test.test \
- gary.test time.test crc.test signames.test md5.test bitops.test opt.test)
+ gary.test time.test crc.test signames.test md5.test bitops.test opt.test \
+ table.test table-test.test)
$(o)/ucw/varint.test: $(o)/ucw/varint-t
$(o)/ucw/regex.test: $(o)/ucw/regex-t
$(o)/ucw/signames.test: $(o)/ucw/signames-t
$(o)/ucw/md5.test: $(o)/ucw/md5-t
$(o)/ucw/opt.test: $(o)/ucw/opt-test
+$(o)/ucw/table.test: $(o)/ucw/table-t
+$(o)/ucw/table-test.test: $(o)/ucw/table-test
ifdef CONFIG_UCW_THREADS
TESTS+=$(addprefix $(o)/ucw/,asio.test)
--- /dev/null
+/*
+ * Unit tests of table printer
+ *
+ * (c) 2014 Robert Kessl <robert.kessl@economia.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/table.h>
+#include <ucw/opt.h>
+#include <stdio.h>
+
+enum test_table_cols {
+ test_col0_str, test_col1_int, test_col2_uint, test_col3_bool, test_col4_double
+};
+
+static uint test_column_order[] = { test_col3_bool, test_col4_double, test_col2_uint,test_col1_int, test_col0_str };
+
+static struct table test_tbl = {
+ TBL_COLUMNS {
+ TBL_COL_STR(test, col0_str, 20),
+ TBL_COL_INT(test, col1_int, 8),
+ TBL_COL_UINT(test, col2_uint, 9),
+ TBL_COL_BOOL(test, col3_bool, 9),
+ TBL_COL_DOUBLE(test, col4_double, 11, 2),
+ TBL_COL_END
+ },
+ TBL_COL_ORDER(test_column_order),
+ TBL_OUTPUT_HUMAN_READABLE,
+ TBL_COL_DELIMITER("\t"),
+};
+
+enum test_default_order_cols {
+ test_default_order_col0_int, test_default_order_col1_int, test_default_order_col2_int
+};
+
+static struct table test_default_order_tbl = {
+ TBL_COLUMNS {
+ TBL_COL_INT(test_default_order, col0_int, 8),
+ TBL_COL_INT(test_default_order, col1_int, 9),
+ TBL_COL_INT(test_default_order, col2_int, 9),
+ TBL_COL_END
+ },
+ TBL_OUTPUT_HUMAN_READABLE,
+ TBL_COL_DELIMITER("\t"),
+};
+
+static void do_default_order_test(struct fastbuf *out)
+{
+ table_init(&test_default_order_tbl, out);
+ table_start(&test_default_order_tbl);
+
+ table_set_int(&test_default_order_tbl, test_default_order_col0_int, 0);
+ table_set_int(&test_default_order_tbl, test_default_order_col1_int, 1);
+ table_set_int(&test_default_order_tbl, test_default_order_col2_int, 2);
+ table_end_row(&test_default_order_tbl);
+
+ table_set_int(&test_default_order_tbl, test_default_order_col0_int, 10);
+ table_set_int(&test_default_order_tbl, test_default_order_col1_int, 11);
+ table_set_int(&test_default_order_tbl, test_default_order_col2_int, 12);
+ table_end_row(&test_default_order_tbl);
+
+ table_end(&test_default_order_tbl);
+ table_cleanup(&test_default_order_tbl);
+}
+
+/**
+ * tests: table_set_nt, table_set_uint, table_set_bool, table_set_double, table_set_printf
+ **/
+static void do_print1(struct table *test_tbl)
+{
+ table_set_str(test_tbl, test_col0_str, "sdsdf");
+ table_append_str(test_tbl, "aaaaa");
+ table_set_int(test_tbl, test_col1_int, -10);
+ table_set_int(test_tbl, test_col1_int, 10000);
+ table_set_uint(test_tbl, test_col2_uint, 10);
+ table_set_printf(test_tbl, test_col2_uint, "XXX-%u", 22222);
+ table_set_bool(test_tbl, test_col3_bool, 1);
+ table_set_double(test_tbl, test_col4_double, 1.5);
+ table_end_row(test_tbl);
+
+ table_set_str(test_tbl, test_col0_str, "test");
+ table_append_str(test_tbl, "bbbbb");
+ table_set_int(test_tbl, test_col1_int, -100);
+ table_set_uint(test_tbl, test_col2_uint, 100);
+ table_set_bool(test_tbl, test_col3_bool, 0);
+ table_set_double(test_tbl, test_col4_double, 1.5);
+ table_end_row(test_tbl);
+}
+
+static char **cli_table_opts;
+static int test_default_column_order;
+static int test_invalid_option;
+static int test_invalid_order;
+
+static struct opt_section table_printer_opts = {
+ OPT_ITEMS {
+ OPT_HELP("Options:"),
+ OPT_STRING_MULTIPLE('T', "table", cli_table_opts, OPT_REQUIRED_VALUE, "\tSets options for the table."),
+ OPT_BOOL('d', 0, test_default_column_order, 0, "\tRun the test that uses the default column order."),
+ OPT_BOOL('i', 0, test_invalid_option, 0, "\tTest the output for invalid option."),
+ OPT_BOOL('n', 0, test_invalid_order, 0, "\tTest the output for invalid names of columns for the 'cols' option."),
+ OPT_END
+ }
+};
+
+static void process_command_line_opts(char *argv[], struct table *tbl)
+{
+ GARY_INIT(cli_table_opts, 0);
+
+ opt_parse(&table_printer_opts, argv+1);
+
+ for(uint i = 0; i < GARY_SIZE(cli_table_opts); i++) {
+ const char *rv = table_set_option(tbl, cli_table_opts[i]);
+ ASSERT_MSG(rv == NULL, "Tableprinter option parser returned error: '%s'.", rv);
+ }
+
+ GARY_FREE(cli_table_opts);
+}
+
+static int user_defined_option(struct table *tbl UNUSED, const char *key, const char *value)
+{
+ if(value == NULL && strcmp(key, "novaluekey") == 0) {
+ printf("setting key: %s; value: (null)\n", key);
+ return 0;
+ }
+ if(value != NULL && strcmp(value, "value") == 0 &&
+ key != NULL && strcmp(key, "valuekey") == 0) {
+ printf("setting key: %s; value: %s\n", key, value);
+ return 0;
+ }
+ return 1;
+}
+
+static void test_option_parser(struct table *tbl)
+{
+ tbl->callbacks->process_option = user_defined_option;
+ const char *rv = table_set_option(tbl, "invalid:option");
+ if(rv) printf("Tableprinter option parser returned error: \"%s\".\n", rv);
+
+ rv = table_set_option(tbl, "invalid");
+ if(rv) printf("Tableprinter option parser returned error: \"%s\".\n", rv);
+
+ rv = table_set_option(tbl, "novaluekey");
+ if(rv) printf("Tableprinter option parser returned error: \"%s\".\n", rv);
+
+ rv = table_set_option(tbl, "valuekey:value");
+ if(rv) printf("Tableprinter option parser returned error: \"%s\".\n", rv);
+}
+
+int main(int argc UNUSED, char **argv)
+{
+ struct fastbuf *out;
+ out = bfdopen_shared(1, 4096);
+
+ table_init(&test_tbl, out);
+
+ process_command_line_opts(argv, &test_tbl);
+
+ if(test_invalid_order == 1) {
+ const char *rv = table_set_option(&test_tbl, "cols:test_col0_str,test_col1_int,xxx");
+ if(rv) printf("Tableprinter option parser returned: '%s'.\n", rv);
+ return 0;
+ } else if(test_default_column_order == 1) {
+ do_default_order_test(out);
+ bclose(out);
+ return 0;
+ } else if(test_invalid_option == 1) {
+ test_option_parser(&test_tbl);
+ bclose(out);
+ return 0;
+ }
+
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+ table_cleanup(&test_tbl);
+
+ bclose(out);
+
+ return 0;
+}
--- /dev/null
+Run: ../obj/ucw/table-test -T cols:col3_bool
+Out <<EOF
+col3_bool
+ true
+ false
+EOF
+
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str
+Out <<EOF
+col3_bool col0_str
+ true sdsdf,aaaaa
+ false test,bbbbb
+EOF
+
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str
+Out <<EOF
+col3_bool col0_str
+ true sdsdf,aaaaa
+ false test,bbbbb
+EOF
+
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str,col4_double
+Out <<EOF
+col3_bool col0_str col4_double
+ true sdsdf,aaaaa 1.50
+ false test,bbbbb 1.50
+EOF
+
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str,col4_double -T 'col-delim:;' -T fmt:machine
+Out <<EOF
+col3_bool;col0_str;col4_double
+true;sdsdf,aaaaa;1.50
+false;test,bbbbb;1.50
+EOF
+
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str,col4_double -T 'col-delim:;' -T fmt:machine -T header:1
+Out <<EOF
+col3_bool;col0_str;col4_double
+true;sdsdf,aaaaa;1.50
+false;test,bbbbb;1.50
+EOF
+
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str,col4_double -T 'col-delim:;' -T fmt:machine -T header:0
+Out <<EOF
+true;sdsdf,aaaaa;1.50
+false;test,bbbbb;1.50
+EOF
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str,col4_double -T 'col-delim:;' -T fmt:machine -T noheader
+Out <<EOF
+true;sdsdf,aaaaa;1.50
+false;test,bbbbb;1.50
+EOF
+
+Run: ../obj/ucw/table-test -T cols:col3_bool,col0_str,col4_double -T 'col-delim:AHOJ' -T fmt:machine -T noheader
+Out <<EOF
+trueAHOJsdsdf,aaaaaAHOJ1.50
+falseAHOJtest,bbbbbAHOJ1.50
+EOF
+
+
+Run: ../obj/ucw/table-test -n
+Out <<EOF
+Tableprinter option parser returned: 'Invalid tableprinter column list: possible column names are col0_str,col1_int,col2_uint,col3_bool,col4_double.'.
+EOF
+
+
+Run: ../obj/ucw/table-test -d
+Out <<EOF
+col0_int col1_int col2_int
+ 0 1 2
+ 10 11 12
+EOF
+
+
+Run: ../obj/ucw/table-test -i
+Out <<EOF
+Tableprinter option parser returned error: "Tableprinter: invalid option: 'invalid:option'.".
+Tableprinter option parser returned error: "Tableprinter: invalid option: 'invalid'.".
+setting key: novaluekey; value: (null)
+setting key: valuekey; value: value
+EOF
--- /dev/null
+/*
+ * UCW Library -- Table printer
+ *
+ * (c) 2014 Robert Kessl <robert.kessl@economia.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/string.h>
+#include <ucw/stkstring.h>
+#include <ucw/gary.h>
+#include <ucw/table.h>
+
+#include <stdlib.h>
+
+/**
+ * Forward defintions of table callbacks. Should these routines be static/private in the
+ * table.c ? What about the return value? Is it better to have void instead of int?
+ **/
+static int table_oneline_human_readable(struct table *tbl);
+static int table_start_human_readable(struct table *tbl);
+static int table_end_human_readable(struct table *tbl);
+
+static int table_oneline_machine_readable(struct table *tbl);
+static int table_start_machine_readable(struct table *tbl);
+static int table_end_machine_readable(struct table *tbl);
+
+struct table_output_callbacks table_fmt_human_readable = {
+ .row_output_func = table_oneline_human_readable,
+ .table_start_callback = table_start_human_readable,
+ .table_end_callback = table_end_human_readable,
+};
+
+struct table_output_callbacks table_fmt_machine_readable = {
+ .row_output_func = table_oneline_machine_readable,
+ .table_start_callback = table_start_machine_readable,
+ .table_end_callback = table_end_machine_readable,
+};
+
+void table_init(struct table *tbl, struct fastbuf *out)
+{
+ tbl->out = out;
+
+ int col_count = 0; // count the number of columns in the struct table
+
+ for(;;) {
+ if(tbl->columns[col_count].name == NULL &&
+ tbl->columns[col_count].fmt == NULL &&
+ tbl->columns[col_count].width == 0 &&
+ tbl->columns[col_count].type == COL_TYPE_LAST)
+ break;
+ ASSERT(tbl->columns[col_count].name != NULL);
+ ASSERT(tbl->columns[col_count].type == COL_TYPE_ANY || tbl->columns[col_count].fmt != NULL);
+ ASSERT(tbl->columns[col_count].width != 0);
+ ASSERT(tbl->columns[col_count].type < COL_TYPE_LAST);
+ col_count++;
+ }
+ tbl->pool = mp_new(4096);
+
+ tbl->column_count = col_count;
+
+ if(tbl->callbacks->row_output_func == NULL && tbl->callbacks->table_start_callback == NULL && tbl->callbacks->table_end_callback == NULL) {
+ tbl->callbacks = &table_fmt_human_readable;
+ }
+
+ tbl->print_header = 1; // by default, print header
+}
+
+void table_cleanup(struct table *tbl)
+{
+ mp_delete(tbl->pool);
+ memset(tbl, 0, sizeof(struct table));
+}
+
+// TODO: test default column order
+static void table_make_default_column_order(struct table *tbl)
+{
+ int *col_order_int = mp_alloc_zero(tbl->pool, sizeof(int) * tbl->column_count);
+ for(int i = 0; i < tbl->column_count; i++) {
+ col_order_int[i] = i;
+ }
+ table_col_order(tbl, col_order_int, tbl->column_count);
+}
+
+void table_start(struct table *tbl)
+{
+ tbl->last_printed_col = -1;
+ tbl->row_printing_started = 0;
+
+ tbl->col_str_ptrs = mp_alloc_zero(tbl->pool, sizeof(char *) * tbl->column_count);
+
+ if(tbl->column_order == NULL) table_make_default_column_order(tbl);
+
+ if(tbl->callbacks->table_start_callback != NULL) tbl->callbacks->table_start_callback(tbl);
+ if(tbl->cols_to_output == 0) {
+ die("Table should output at least one column.");
+ }
+
+ mp_save(tbl->pool, &tbl->pool_state);
+
+ ASSERT_MSG(tbl->col_delimiter, "In-between column delimiter not specified.");
+ ASSERT_MSG(tbl->append_delimiter, "Append delimiter not specified.");
+}
+
+void table_end(struct table *tbl)
+{
+ tbl->last_printed_col = -1;
+ tbl->row_printing_started = 0;
+ tbl->print_header = 1;
+
+ mp_restore(tbl->pool, &tbl->pool_state);
+
+ if(tbl->callbacks->table_end_callback) tbl->callbacks->table_end_callback(tbl);
+}
+
+static void table_write_header(struct table *tbl)
+{
+ uint col_idx = tbl->column_order[0];
+ bprintf(tbl->out, "%*s", tbl->columns[col_idx].width, tbl->columns[col_idx].name);
+
+ for(uint i = 1; i < tbl->cols_to_output; i++) {
+ col_idx = tbl->column_order[i];
+ bputs(tbl->out, tbl->col_delimiter);
+ bprintf(tbl->out, "%*s", tbl->columns[col_idx].width, tbl->columns[col_idx].name);
+ }
+
+ bputc(tbl->out, '\n');
+}
+
+int table_get_col_idx(struct table *tbl, const char *col_name)
+{
+ for(int i = 0; i < tbl->column_count; i++) {
+ if(strcmp(tbl->columns[i].name, col_name) == 0) return i;
+ }
+ return -1;
+}
+
+const char * table_get_col_list(struct table *tbl)
+{
+ if(tbl->column_count == 0) return NULL;
+
+ char *tmp = mp_printf(tbl->pool, "%s", tbl->columns[0].name);
+
+ for(int i = 1; i < tbl->column_count; i++) {
+ mp_printf_append(tbl->pool, tmp, ",%s", tbl->columns[i].name);
+ }
+
+ return tmp;
+}
+
+void table_col_order(struct table *tbl, int *col_order, int cols_to_output)
+{
+ for(int i = 0; i < cols_to_output; i++) {
+ ASSERT_MSG(col_order[i] >= 0 && col_order[i] < tbl->column_count, "Column %d does not exists (column number should be between 0 and %d)", col_order[i], tbl->column_count);
+ }
+
+ tbl->column_order = col_order;
+ tbl->cols_to_output = cols_to_output;
+}
+
+/**
+ * TODO: ERROR! this function deliberately causes memory leak. the
+ * problem is that when table_col_order_by_name is called multiple-times,
+ * the mp_save adds all the resulting column orders on the memory pool.
+ * The memory leak is small, but it is present.
+ **/
+int table_col_order_by_name(struct table *tbl, const char *col_order_str)
+{
+ int col_order_len = strlen(col_order_str);
+
+ char *tmp_col_order = stk_strdup(col_order_str);
+
+ int col_count = 1;
+ for(int i = 0; i < col_order_len; i++) {
+ if(col_order_str[i] == ',') {
+ col_count++;
+ }
+ }
+
+ struct mempool_state mp_tmp_state;
+ mp_save(tbl->pool, &mp_tmp_state);
+
+ int *col_order_int = mp_alloc_zero(tbl->pool, sizeof(int) * col_count);
+ int curr_col_order_int = 0;
+ const char *name_start = tmp_col_order;
+ while(name_start) {
+ char *next = strchr(name_start, ',');
+ if(next) {
+ *next++ = 0;
+ }
+
+ int idx = table_get_col_idx(tbl, name_start);
+ col_order_int[curr_col_order_int] = idx;
+ if(idx == -1) {
+ //ASSERT_MSG(idx != -1, "Table column with name '%s' does not exists.", name_start);
+ mp_restore(tbl->pool, &mp_tmp_state);
+ return -1;
+ }
+ curr_col_order_int++;
+
+ name_start = next;
+ }
+
+ tbl->column_order = col_order_int;
+ tbl->cols_to_output = curr_col_order_int;
+ return 0;
+}
+
+void table_set_printf(struct table *tbl, int col, const char *fmt, ...)
+{
+ ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exists.", col);
+ tbl->last_printed_col = col;
+ tbl->row_printing_started = 1;
+ va_list args;
+ va_start(args, fmt);
+ tbl->col_str_ptrs[col] = mp_vprintf(tbl->pool, fmt, args);
+ va_end(args);
+}
+
+static const char *table_set_col_default_fmts[] = {
+ [COL_TYPE_STR] = "%s",
+ [COL_TYPE_INT] = "%d",
+ [COL_TYPE_INTMAX] = "%jd",
+ [COL_TYPE_UINT] = "%u",
+ [COL_TYPE_UINTMAX] = "%ju",
+ [COL_TYPE_BOOL] = "%d",
+ [COL_TYPE_DOUBLE] = "%.2lf",
+ [COL_TYPE_ANY] = NULL,
+ [COL_TYPE_LAST] = NULL
+};
+
+#define TABLE_SET_COL(_name_, _type_, _typeconst_) void table_set_##_name_(struct table *tbl, int col, _type_ val) \
+ {\
+ const char *fmt = tbl->columns[col].fmt;\
+ if(tbl->columns[col].type == COL_TYPE_ANY) {\
+ fmt = table_set_col_default_fmts[_typeconst_];\
+ }\
+ table_set_##_name_##_fmt(tbl, col, fmt, val);\
+ }
+
+#define TABLE_SET_COL_STR(_name_, _type_, _typeconst_) void table_set_##_name_##_name(struct table *tbl, const char *col_name, _type_ val) \
+ {\
+ int col = table_get_col_idx(tbl, col_name);\
+ table_set_##_name_(tbl, col, val);\
+ }
+
+#define TABLE_SET_COL_FMT(_name_, _type_, _typeconst_) void table_set_##_name_##_fmt(struct table *tbl, int col, const char *fmt, _type_ val)\
+ {\
+ ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exists.", col);\
+ ASSERT(tbl->columns[col].type == COL_TYPE_ANY || _typeconst_ == tbl->columns[col].type);\
+ ASSERT(fmt != NULL);\
+ tbl->last_printed_col = col;\
+ tbl->row_printing_started = 1;\
+ tbl->col_str_ptrs[col] = mp_printf(tbl->pool, fmt, val);\
+ }
+
+#define TABLE_SET(_name_, _type_, _typeconst_) TABLE_SET_COL(_name_, _type_, _typeconst_);\
+ TABLE_SET_COL_STR(_name_, _type_, _typeconst_);\
+ TABLE_SET_COL_FMT(_name_, _type_, _typeconst_);
+
+TABLE_SET(int, int, COL_TYPE_INT)
+TABLE_SET(uint, uint, COL_TYPE_UINT)
+TABLE_SET(double, double, COL_TYPE_DOUBLE)
+TABLE_SET(str, const char *, COL_TYPE_STR)
+TABLE_SET(intmax, intmax_t, COL_TYPE_INTMAX)
+TABLE_SET(uintmax, uintmax_t, COL_TYPE_UINTMAX)
+#undef TABLE_SET_COL_FMT
+#undef TABLE_SET_COL_STR
+#undef TABLE_SET_COL
+#undef TABLE_SET
+
+void table_set_bool(struct table *tbl, int col, uint val)
+{
+ table_set_bool_fmt(tbl, col, tbl->columns[col].fmt, val);
+}
+
+void table_set_bool_name(struct table *tbl, const char *col_name, uint val)
+{
+ int col = table_get_col_idx(tbl, col_name);
+ table_set_bool(tbl, col, val);
+}
+
+void table_set_bool_fmt(struct table *tbl, int col, const char *fmt, uint val)
+{
+ ASSERT_MSG(col < tbl->column_count && col >= 0, "Table column %d does not exists.", col);
+ ASSERT(COL_TYPE_BOOL == tbl->columns[col].type);
+
+ tbl->last_printed_col = col;
+ tbl->row_printing_started = 1;
+ tbl->col_str_ptrs[col] = mp_printf(tbl->pool, fmt, val ? "true" : "false");
+}
+
+#define TABLE_APPEND(_name_, _type_, _typeconst_) void table_append_##_name_(struct table *tbl, _type_ val) \
+ {\
+ ASSERT(tbl->last_printed_col != -1 || tbl->row_printing_started != 0);\
+ ASSERT(_typeconst_ == tbl->columns[tbl->last_printed_col].type);\
+ int col = tbl->last_printed_col;\
+ mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], "%s", tbl->append_delimiter);\
+ tbl->col_str_ptrs[col] = mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], tbl->columns[col].fmt, val);\
+ }
+
+TABLE_APPEND(int, int, COL_TYPE_INT)
+TABLE_APPEND(uint, uint, COL_TYPE_UINT)
+TABLE_APPEND(double, double, COL_TYPE_DOUBLE)
+TABLE_APPEND(str, const char *, COL_TYPE_STR)
+TABLE_APPEND(intmax, intmax_t, COL_TYPE_INTMAX)
+TABLE_APPEND(uintmax, uintmax_t, COL_TYPE_UINTMAX)
+#undef TABLE_APPEND
+
+void table_append_bool(struct table *tbl, int val)
+{
+ ASSERT(tbl->last_printed_col != -1 || tbl->row_printing_started != 0);
+ ASSERT(COL_TYPE_BOOL == tbl->columns[tbl->last_printed_col].type);
+
+ int col = tbl->last_printed_col;
+
+ mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], "%s", tbl->append_delimiter);
+
+ tbl->col_str_ptrs[col] = mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], tbl->columns[col].fmt, val ? "true" : "false");
+}
+
+void table_append_printf(struct table *tbl, const char *fmt, ...)
+{
+ ASSERT(tbl->last_printed_col != -1 || tbl->row_printing_started != 0);
+ int col = tbl->last_printed_col;
+
+ va_list args;
+ va_start(args, fmt);
+
+ mp_printf_append(tbl->pool, tbl->col_str_ptrs[col], "%s", tbl->append_delimiter);
+ tbl->col_str_ptrs[col] = mp_vprintf_append(tbl->pool, tbl->col_str_ptrs[col], fmt, args);
+
+ va_end(args);
+}
+
+void table_end_row(struct table *tbl)
+{
+ if(tbl->callbacks->row_output_func) {
+ tbl->callbacks->row_output_func(tbl);
+ } else {
+ die("Tableprinter: invalid parameter, struct table does not have filled row_output_func");
+ }
+ memset(tbl->col_str_ptrs, 0, sizeof(char *) * tbl->column_count);
+ mp_restore(tbl->pool, &tbl->pool_state);
+ tbl->last_printed_col = -1;
+ tbl->row_printing_started = 0;
+}
+
+/*
+ * construction of string in mempool using fastbuf
+ *
+ */
+struct fastbuf *table_col_fbstart(struct table *tbl, int col)
+{
+ fbpool_init(&tbl->fb_col_out);
+ fbpool_start(&tbl->fb_col_out, tbl->pool, 1);
+ tbl->col_out = col;
+ return &tbl->fb_col_out.fb;
+}
+
+void table_col_fbend(struct table *tbl)
+{
+ tbl->col_str_ptrs[tbl->col_out] = fbpool_end(&tbl->fb_col_out);
+ tbl->col_out = -1;
+}
+
+void table_set_output_callbacks(struct table *tbl, struct table_output_callbacks *callbacks)
+{
+ tbl->callbacks = callbacks;
+}
+
+// Row output routines
+static int table_oneline_human_readable(struct table *tbl)
+{
+ uint col = tbl->column_order[0];
+ int col_width = tbl->columns[col].width;
+ bprintf(tbl->out, "%*s", col_width, tbl->col_str_ptrs[col]);
+ for(uint i = 1; i < tbl->cols_to_output; i++) {
+ col = tbl->column_order[i];
+ col_width = tbl->columns[col].width;
+ bputs(tbl->out, tbl->col_delimiter);
+ bprintf(tbl->out, "%*s", col_width, tbl->col_str_ptrs[col]);
+ }
+
+ bputc(tbl->out, '\n');
+ return 0;
+}
+
+static int table_oneline_machine_readable(struct table *tbl)
+{
+ uint col = tbl->column_order[0];
+ bputs(tbl->out, tbl->col_str_ptrs[col]);
+ for(uint i = 1; i < tbl->cols_to_output; i++) {
+ col = tbl->column_order[i];
+ bputs(tbl->out, tbl->col_delimiter);
+ bputs(tbl->out, tbl->col_str_ptrs[col]);
+ }
+
+ bputc(tbl->out, '\n');
+ return 0;
+}
+
+static int table_start_human_readable(struct table *tbl)
+{
+ if(tbl->col_delimiter == NULL) {
+ tbl->col_delimiter = " ";
+ }
+
+ if(tbl->append_delimiter == NULL) {
+ tbl->append_delimiter = ",";
+ }
+
+ if(tbl->print_header != 0) {
+ tbl->print_header = 0;
+ table_write_header(tbl);
+ }
+ return 0;
+}
+
+static int table_end_human_readable(struct table *tbl UNUSED)
+{
+ return 0;
+}
+
+static int table_start_machine_readable(struct table *tbl)
+{
+ if(tbl->col_delimiter == NULL) {
+ tbl->col_delimiter = ";";
+ }
+
+ if(tbl->append_delimiter == NULL) {
+ tbl->append_delimiter = ",";
+ }
+
+ if(tbl->print_header != 0) {
+ tbl->print_header = 0;
+
+ uint col_idx = tbl->column_order[0];
+ bputs(tbl->out, tbl->columns[col_idx].name);
+ for(uint i = 1; i < tbl->cols_to_output; i++) {
+ col_idx = tbl->column_order[i];
+ bputs(tbl->out, tbl->col_delimiter);
+ bputs(tbl->out, tbl->columns[col_idx].name);
+ }
+ bputc(tbl->out, '\n');
+ }
+ return 0;
+}
+
+static int table_end_machine_readable(struct table *tbl UNUSED)
+{
+ return 0;
+}
+
+static int get_colon(char *str)
+{
+ int l = strlen(str);
+ for(int i = 0; i < l; i++) {
+ if(str[i] == ':') return i;
+ }
+ return -1;
+}
+
+static const char *table_set_option2(struct table *tbl, const char *key, const char *value)
+{
+ if(value == NULL || (value != NULL && strlen(value) == 0)) {
+ if(strcmp(key, "noheader") == 0) {
+ tbl->print_header = 0;
+ return NULL;
+ } else {
+ int rv = 1;
+ if(tbl->callbacks && tbl->callbacks->process_option) rv = tbl->callbacks->process_option(tbl, key, value);
+ if(rv) {
+ return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s'.", key);
+ }
+ }
+ } else {
+ if(strcmp(key, "header") == 0) {
+ // FIXME: Check syntax of value.
+ //tbl->print_header = strtol(value, NULL, 10); //atoi(value);
+ //if(errno != 0) tbl->print_header
+ if(value[1] != 0)
+ return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s' has invalid value: '%s'.", key, value);
+ uint tmp = value[0] - '0';
+ if(tmp > 1)
+ return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s' has invalid value: '%s'.", key, value);
+ tbl->print_header = tmp;
+ return NULL;
+ } else if(strcmp(key, "cols") == 0) {
+ // FIXME: We should not exit/abort on errors caused from command line.
+ if(table_col_order_by_name(tbl, value) != 0) {
+ const char *tmp = table_get_col_list(tbl);
+ return mp_printf(tbl->pool, "Invalid tableprinter column list: possible column names are %s.", tmp);
+ }
+ return NULL;
+ } else if(strcmp(key, "fmt") == 0) {
+ if(strcmp(value, "human") == 0) table_set_output_callbacks(tbl, &table_fmt_human_readable);
+ else if(strcmp(value, "machine") == 0) table_set_output_callbacks(tbl, &table_fmt_machine_readable);
+ else {
+ return "Tableprinter: invalid argument to output-type option.";
+ }
+ return NULL;
+ } else if(strcmp(key, "col-delim") == 0) {
+ char * d = mp_printf(tbl->pool, "%s", value);
+ tbl->col_delimiter = d;
+ return NULL;
+ } else {
+ int rv = 1;
+ if(tbl->callbacks && tbl->callbacks->process_option) rv = tbl->callbacks->process_option(tbl, key, value);
+ if(rv) {
+ return mp_printf(tbl->pool, "Tableprinter: invalid option: '%s:%s'.", key, value);
+ }
+ }
+ }
+ return NULL;
+}
+
+const char *table_set_option(struct table *tbl, const char *opt)
+{
+ char *opt_dup = stk_strdup(opt);
+ int colidx = get_colon(opt_dup);
+ if(colidx > 0) opt_dup[colidx] = 0;
+ char *key = opt_dup;
+ char *value = NULL;
+ if(colidx > 0) value = opt_dup + colidx + 1;
+ return table_set_option2(tbl, key, value);
+}
+
+const char *table_set_gary_options(struct table *tbl, char **gary_table_opts)
+{
+ for (uint i = 0; i < GARY_SIZE(gary_table_opts); i++) {
+ const char *rv = table_set_option(tbl, gary_table_opts[i]);
+ if (rv != NULL) {
+ return rv;
+ }
+ }
+ return NULL;
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+
+enum test_table_cols {
+ test_col0_str, test_col1_int, test_col2_uint, test_col3_bool, test_col4_double
+};
+
+static uint test_column_order[] = {test_col3_bool, test_col4_double, test_col2_uint,test_col1_int, test_col0_str};
+
+static struct table test_tbl = {
+ TBL_COLUMNS {
+ TBL_COL_STR(test, col0_str, 20),
+ TBL_COL_INT(test, col1_int, 8),
+ TBL_COL_UINT(test, col2_uint, 9),
+ TBL_COL_BOOL(test, col3_bool, 9),
+ TBL_COL_DOUBLE(test, col4_double, 11, 2),
+ TBL_COL_END
+ },
+ TBL_COL_ORDER(test_column_order),
+ TBL_OUTPUT_HUMAN_READABLE,
+ TBL_COL_DELIMITER("\t"),
+ TBL_APPEND_DELIMITER(",")
+};
+
+/**
+ * tests: table_set_nt, table_set_uint, table_set_bool, table_set_double, table_set_printf
+ **/
+static void do_print1(struct table *test_tbl)
+{
+ table_set_str(test_tbl, test_col0_str, "sdsdf");
+ table_append_str(test_tbl, "aaaaa");
+ table_set_int(test_tbl, test_col1_int, -10);
+ table_set_int(test_tbl, test_col1_int, 10000);
+ table_set_uint(test_tbl, test_col2_uint, 10);
+ table_set_printf(test_tbl, test_col2_uint, "XXX-%u", 22222);
+ table_set_bool(test_tbl, test_col3_bool, 1);
+ table_set_double(test_tbl, test_col4_double, 1.5);
+ table_set_printf(test_tbl, test_col4_double, "AAA");
+ table_end_row(test_tbl);
+
+ table_set_str(test_tbl, test_col0_str, "test");
+ table_append_str(test_tbl, "bbbbb");
+ table_set_int(test_tbl, test_col1_int, -100);
+ table_set_uint(test_tbl, test_col2_uint, 100);
+ table_set_bool(test_tbl, test_col3_bool, 0);
+ table_set_printf(test_tbl, test_col4_double, "%.2lf", 1.5);
+ table_end_row(test_tbl);
+}
+
+static void test_simple1(struct fastbuf *out)
+{
+ table_init(&test_tbl, out);
+ // print table with header
+ table_col_order_by_name(&test_tbl, "col3_bool");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ // print the same table as in the previous case without header
+ table_col_order_by_name(&test_tbl, "col0_str,col2_uint,col1_int,col3_bool");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ // this also tests whether there is need to call table_col_order_by_name after table_end was called
+ test_tbl.print_header = 0;
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ table_col_order_by_name(&test_tbl, "col3_bool");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ table_col_order_by_name(&test_tbl, "col3_bool,col0_str");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ table_col_order_by_name(&test_tbl, "col0_str,col3_bool,col2_uint");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ 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");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ table_col_order_by_name(&test_tbl, "col0_str,col1_int,col2_uint,col3_bool,col4_double");
+ table_start(&test_tbl);
+ do_print1(&test_tbl);
+ table_end(&test_tbl);
+
+ table_cleanup(&test_tbl);
+}
+
+enum test_any_table_cols {
+ test_any_col0_int, test_any_col1_any
+};
+
+static uint test_any_column_order[] = { test_any_col0_int, test_any_col1_any };
+
+static struct table test_any_tbl = {
+ TBL_COLUMNS {
+ TBL_COL_INT(test_any, col0_int, 8),
+ TBL_COL_ANY(test_any, col1_any, 9),
+ TBL_COL_END
+ },
+ TBL_COL_ORDER(test_any_column_order),
+ TBL_OUTPUT_HUMAN_READABLE,
+ TBL_COL_DELIMITER("\t"),
+ TBL_APPEND_DELIMITER(",")
+};
+
+static void test_any_type(struct fastbuf *out)
+{
+ table_init(&test_any_tbl, out);
+ table_start(&test_any_tbl);
+
+ table_set_int(&test_any_tbl, test_any_col0_int, -10);
+ table_set_int(&test_any_tbl, test_any_col1_any, 10000);
+ table_end_row(&test_any_tbl);
+
+ table_set_int(&test_any_tbl, test_any_col0_int, -10);
+ table_set_double(&test_any_tbl, test_any_col1_any, 1.4);
+ table_end_row(&test_any_tbl);
+
+ table_set_printf(&test_any_tbl, test_any_col0_int, "%d", 10);
+ table_append_printf(&test_any_tbl, "%d", 20);
+ table_append_printf(&test_any_tbl, "%d", 30);
+ table_set_double(&test_any_tbl, test_any_col1_any, 1.4);
+ table_append_printf(&test_any_tbl, "%.2lf", 1.5);
+ table_append_printf(&test_any_tbl, "%.2lf", 1.6);
+ table_end_row(&test_any_tbl);
+
+ table_end(&test_any_tbl);
+ table_cleanup(&test_any_tbl);
+}
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ struct fastbuf *out;
+ out = bfdopen_shared(1, 4096);
+
+ test_simple1(out);
+
+ test_any_type(out);
+
+ bclose(out);
+ return 0;
+}
+
+#endif
--- /dev/null
+/*
+ * UCW Library -- Table printer
+ *
+ * (c) 2014 Robert Kessl <robert.kessl@economia.cz>
+ */
+
+#ifndef _TABLEPRINTER_H
+#define _TABLEPRINTER_H
+
+#include <ucw/fastbuf.h>
+#include <ucw/mempool.h>
+
+enum column_type {
+ COL_TYPE_STR, COL_TYPE_INT, COL_TYPE_INTMAX, COL_TYPE_UINT, COL_TYPE_UINTMAX, COL_TYPE_BOOL, COL_TYPE_DOUBLE, COL_TYPE_ANY, COL_TYPE_LAST
+};
+
+#define TBL_COL_STR(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%s", .type = COL_TYPE_STR }
+#define TBL_COL_INT(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%d", .type = COL_TYPE_INT }
+#define TBL_COL_UINT(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%u", .type = COL_TYPE_UINT }
+#define TBL_COL_INTMAX(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%jd", .type = COL_TYPE_INTMAX }
+#define TBL_COL_UINTMAX(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%ju", .type = COL_TYPE_UINTMAX }
+#define TBL_COL_HEXUINT(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "0x%x", .type = COL_TYPE_UINT }
+#define TBL_COL_DOUBLE(_enum_prefix, _name, _width, _prec) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%." #_prec "lf", .type = COL_TYPE_DOUBLE }
+#define TBL_COL_BOOL(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = "%s", .type = COL_TYPE_BOOL }
+#define TBL_COL_ANY(_enum_prefix, _name, _width) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = 0, .type = COL_TYPE_ANY }
+
+#define TBL_COL_STR_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_STR }
+#define TBL_COL_INT_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_INT }
+#define TBL_COL_UINT_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_UINT }
+#define TBL_COL_INTMAX_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_INTMAX }
+#define TBL_COL_UINTMAX_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_UINTMAX }
+#define TBL_COL_HEXUINT_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_UINT }
+#define TBL_COL_BOOL_FMT(_enum_prefix, _name, _width, _fmt) [_enum_prefix##_##_name] = { .name = #_name, .width = _width, .fmt = _fmt, .type = COL_TYPE_BOOL }
+
+#define TBL_COL_END { .name = 0, .width = 0, .fmt = 0, .type = COL_TYPE_LAST }
+
+#define TBL_COLUMNS .columns = (struct table_column [])
+#define TBL_COL_ORDER(order) .column_order = (int *) order, .cols_to_output = ARRAY_SIZE(order)
+#define TBL_COL_DELIMITER(_delimiter_) .col_delimiter = _delimiter_
+#define TBL_APPEND_DELIMITER(_delimiter_) .append_delimiter = _delimiter_
+
+#define TBL_OUTPUT_HUMAN_READABLE .callbacks = &table_fmt_human_readable
+#define TBL_OUTPUT_MACHINE_READABLE .callbacks = &table_fmt_machine_readable
+
+/***
+ * [[ Usage ]]
+ * The table works as follows:
+ * The table can be used after table_init is called. Then at the beginning of each printing, the
+ * table_start function must be called. After printing, the table_end must be called. The
+ * table_start MUST be paired with table_end. Inbetween table_start/table_end the user can set the
+ * cells of one row and one row is finished and printed using table_end_of_row. The pairs
+ * table_start/table_end can be used multiple-times for one table. The table is deallocated using
+ * table_cleanup. After table_cleanup is called it is not possible to further use the struct table.
+ * The struct table must be reinitialized.
+ *
+ * Default behaviour of the table_set_col_* is replacement of already set data. To append, the user
+ * must use table_append_*
+ *
+ * To summarize:
+ * 1) @table_init is called;
+ * 2) @table_start is called following by table_set_xxx functions and @table_end.
+ * table_start/table_end forms 1-level parenthesis structure. Some of the table
+ * settings can be changed only between table_init and @table_start or after table_end
+ * is called (but before next table_start.
+ * 3) the table is deallocated using @table_cleanup. After the cleanup
+ * is done, the struct table is unusable and must be initialized.
+ *
+ *
+ * An example of the procedure is following sequence of calls:
+ * table_init
+ *
+ * table_start
+ * table_end
+ * table_start
+ * table_end
+ *
+ * table_cleanup
+ *
+ * The tableprinter supports user-specified callback for each row and table-print (i.e., a callback
+ * that is called in table_end).
+ *
+ * The table is initialized by defining a table struct using the following macros:
+ * o TBL_START_COLUMNS indicates start of definition of columns
+ * o TBL_COL_XXX macros specify the column types with some default formatting the column is specified using a column
+ * name (which should be C identifier) and a prefix. the column name is the a string with the column
+ * name. The prefix is used for discriminating between columns from different tables. The column index
+ * should be taken from an enum. The enum identifier is prefix concatenated with the column name identifier.
+ * o TBL_COL_XXX_F macros specify column types with user supplied formatting
+ * o TBL_COL_END indicates end of column definitions
+ * o TBL_COL_ORDER specify the column order
+ * o TBL_COL_DELIMITER specify the in-between cell delimiter
+ *
+ * The table cells have strict type control, with the exception of type TBL_COL_ANY. In the case of
+ * TBL_COL_ANY, the type is not tested and an arbitrary value can be printed into the cell.
+ * It is also possible to print string to an arbitrary cell.
+ *
+ * Features:
+ * * user supplied callback functions can be used for modifying the output format.
+ *
+ * Non-tested features:
+ * * computing statistics of columns via the table_start_callback/table_end_callback.
+ * TODO: is it better to have callback for each cell with the original value supplied by the caller of the table_set_* functions?
+ * TODO:
+ * * unsupported: (dynamic) alignment of cells which is computed in table_end
+ *
+ * TODO: table_set_col_fmt: this functin takes the format string and the value. But I'm not able to
+ * test whether the format string and the type match !!!
+ *
+ * TODO: Return value of the parser should be a string allocated on the mempool of the table. But:
+ * is the return value really necessary? The error should be show to the user on the terminal
+ * (std. out).
+ * TODO: all macros prefix TBL_ should be changed to TABLE_ ?
+ * TODO: how to print column which is aligned to the left flag for alignment: 1) left; 2) right;
+ * 3) decimal point alignment; 4) arbitrary separator alignment
+ ***/
+
+struct table;
+
+/** Specification of a single table column */
+struct table_column {
+ const char *name; // [*] name of the column displayed in the header
+ int width; // [*] width of the column (in characters). Negative number indicates alignment to left. SHOULD BE RATHER INDICATED BY A FLAG?
+ const char *fmt; // [*] default format of each cell in the column
+ enum column_type type; // type of the cells in the column.
+};
+
+struct table_output_callbacks {
+ int (*row_output_func)(struct table *tbl); // [*] function that outputs one row
+ int (*table_start_callback)(struct table *tbl); // [*] table_start callback
+ int (*table_end_callback)(struct table *tbl); // [*] table_end callback
+ // FIXME: Int -> void?
+ int (*process_option)(struct table *tbl, const char *key, const char *value);
+ // FIXME: Shouldn't it be possible to return also a custom error string? For example in an optionally writeable `const char **' argument.
+};
+
+/** The definition of a table. Contains column definitions plus internal data. */
+struct table {
+ struct table_column *columns; // [*] columns definition
+ int column_count; // [*] number of columns of the table
+ struct mempool *pool; // memory pool used for storing all the data. At the beggining are the data needed for printing
+ // the whole table (delimited by table_start, table_end)
+ struct mempool_state pool_state; // state of the pool AFTER the table is initialized. The state is used for
+ // deallocation of the strings used for printing single table row
+
+ char **col_str_ptrs; // used to store the position of the row in the memory pool
+
+ uint *column_order; // [*] order of the columns in the print-out of the table.
+ uint cols_to_output; // [*] number of columns that are printed.
+ const char *col_delimiter; // [*] delimiter that is placed between the columns
+ const char *append_delimiter; // [*] character used for delimiting the values in a single cell
+ uint print_header; // [*] 0 indicates that the header should not be printed
+
+ struct fastbuf *out; // fastbuffer that is used for outputing the table rows
+ int last_printed_col; // index of the last column which was set. -1 indicates start of row. Used for example for
+ // appending to the last column.
+ int row_printing_started; // this can be considered as a duplicity of last_printed_col (it is -1 if the row printing
+ // did not start), but it is probably better to store the flag separately
+
+ struct fbpool fb_col_out; // used for printing using the fast buffers(into the mempool), @see table_col_fbstart()
+ int col_out; // index of the column that is currently printed using fb_col_out
+
+ struct table_output_callbacks *callbacks;
+ void *data; // [*] user-managed data. the data can be used for example in row_output_func, table_start_callback, table_end_callback
+};
+
+
+/**
+ * table_init serves for initialization of the table. The @tbl parameter should have set the columns member of
+ * the table structure. The @out parameter is supplied by the caller and can be deallocated after table_deinit
+ * is called.
+ **/
+void table_init(struct table *tbl, struct fastbuf *out);
+void table_cleanup(struct table *tbl);
+
+/**
+ * table_start is called before the cells of the table are set. After the table_start is called, the user can
+ * call the table_set_* functions. The table_end_of_row function can be called after the table_start is called
+ * (but before the table_end is called)
+ **/
+void table_start(struct table *tbl);
+
+/**
+ * This function must be called after all the rows of the current table are printed. The table_set_*
+ * functions can be called in between table_start and table_end calls.
+ **/
+void table_end(struct table *tbl);
+
+/**
+ * Sets the order in which the columns are printed. The @col_order parameter is used until the table_end or
+ * table_cleanup is called. The table stores the pointer only and the memory pointed to by @col_order is
+ * allocated and deallocated by the caller.
+ **/
+void table_col_order(struct table *tbl, int *col_order, int col_order_size);
+
+/**
+ * Sets the order in which the columns are printed. The specification is a string with comma delimited column
+ * names.
+ **/
+int table_col_order_by_name(struct table *tbl, const char *col_order);
+
+/**
+ * Called when all the cells have filled values. The function the prints a table row into the output stream.
+ * The table row has newline at the end.
+ **/
+void table_end_row(struct table *tbl);
+
+/**
+ * Prints a string that is printf-like formated into a particular column. This function does not check the
+ * type of the column, i.e., it can be used to print double into an int column
+ **/
+void table_set_printf(struct table *tbl, int col, const char *fmt, ...) FORMAT_CHECK(printf, 3, 4);
+
+/**
+ * Appends a string that is printf-like formated to the last printed column. This function does not check the
+ * type of the column, i.e., it can be used to print double into an int column
+ **/
+void table_append_printf(struct table *tbl, const char *fmt, ...) FORMAT_CHECK(printf, 2, 3);
+
+/**
+ * Find the index of a column with name @col_name and returns it. Returns -1 if the column was not found.
+ **/
+int table_get_col_idx(struct table *tbl, const char *col_name);
+
+/**
+ * Returns comma-separated list of column names
+ **/
+const char * table_get_col_list(struct table *tbl);
+
+/**
+ * Opens a fastbuf stream that can be used for creating a cell content. The @sz parameter is the initial size
+ * allocated on the memory pool.
+ **/
+struct fastbuf *table_col_fbstart(struct table *tbl, int col);
+// FIXME: test table_col_fbstart/table_col_fbend
+
+/**
+ * Closes the stream that is used for printing of the last column
+ **/
+void table_col_fbend(struct table *tbl);
+
+/**
+ * Sets the callbacks in @tbl. The callbacks are stored the arg @callbacks.
+ **/
+void table_set_output_callbacks(struct table *tbl, struct table_output_callbacks *callbacks);
+
+
+/**
+ * Process the table one option and sets the values in @tbl according to the command-line parameters.
+ * The option has the following format: a) "<key>:<value>"; b) "<key>" (currently not used).
+ *
+ * Possible key-value pairs:
+ * header:[0|1] - 1 indicates that the header should be printed, 0 otherwise
+ * noheader - equivalent to header:0
+ * cols:<string-with-col-names> - comma-separated list of columns that will be printed (in the order specified on the cmd-line)
+ * fmt:[human|machine|...] - output type
+ * col-delim:<char> - column delimiter
+ *
+ * Returns NULL on success or an error string otherwise.
+ **/
+const char *table_set_option(struct table *tbl, const char *opt);
+const char *table_set_gary_options(struct table *tbl, char **gary_table_opts);
+
+extern struct table_output_callbacks table_fmt_human_readable;
+extern struct table_output_callbacks table_fmt_machine_readable;
+
+#define TABLE_SET_COL_PROTO(_name_, _type_) void table_set_##_name_(struct table *tbl, int col, _type_ val);\
+ void table_set_##_name_##_name(struct table *tbl, const char *col_name, _type_ val);\
+ void table_set_##_name_##_fmt(struct table *tbl, int col, const char *fmt, _type_ val) FORMAT_CHECK(printf, 3, 0);
+
+// table_set_<type>_fmt has one disadvantage: it is not possible to
+// check whether fmt contains format that contains formatting that is
+// compatible with _type_
+
+TABLE_SET_COL_PROTO(int, int);
+TABLE_SET_COL_PROTO(uint, uint);
+TABLE_SET_COL_PROTO(double, double);
+TABLE_SET_COL_PROTO(str, const char *);
+TABLE_SET_COL_PROTO(intmax, intmax_t);
+TABLE_SET_COL_PROTO(uintmax, uintmax_t);
+
+void table_set_bool(struct table *tbl, int col, uint val);
+void table_set_bool_name(struct table *tbl, const char *col_name, uint val);
+void table_set_bool_fmt(struct table *tbl, int col, const char *fmt, uint val);
+#undef TABLE_SET_COL_PROTO
+
+#define TABLE_APPEND_PROTO(_name_, _type_) void table_append_##_name_(struct table *tbl, _type_ val)
+TABLE_APPEND_PROTO(int, int);
+TABLE_APPEND_PROTO(uint, uint);
+TABLE_APPEND_PROTO(double, double);
+TABLE_APPEND_PROTO(str, const char *);
+TABLE_APPEND_PROTO(intmax, intmax_t);
+TABLE_APPEND_PROTO(uintmax, uintmax_t);
+void table_append_bool(struct table *tbl, int val);
+#undef TABLE_APPEND_PROTO
+
+#endif
--- /dev/null
+Run: ../obj/ucw/table-t
+Out <<EOF
+col3_bool
+ true
+ false
+ col0_str col2_uint col1_int col3_bool
+ sdsdf,aaaaa XXX-22222 10000 true
+ test,bbbbb 100 -100 false
+ sdsdf,aaaaa XXX-22222 10000 true
+ test,bbbbb 100 -100 false
+col3_bool
+ true
+ false
+col3_bool col0_str
+ true sdsdf,aaaaa
+ false test,bbbbb
+ col0_str col3_bool col2_uint
+ sdsdf,aaaaa true XXX-22222
+ test,bbbbb false 100
+ col0_str col3_bool col2_uint col0_str col3_bool col2_uint col0_str col3_bool col2_uint
+ sdsdf,aaaaa true XXX-22222 sdsdf,aaaaa true XXX-22222 sdsdf,aaaaa true XXX-22222
+ test,bbbbb false 100 test,bbbbb false 100 test,bbbbb false 100
+ col0_str col1_int col2_uint col3_bool col4_double
+ sdsdf,aaaaa 10000 XXX-22222 true AAA
+ test,bbbbb -100 100 false 1.50
+col0_int col1_any
+ -10 10000
+ -10 1.40
+10,20,30 1.40,1.50,1.60
+EOF