]> mj.ucw.cz Git - libucw.git/commitdiff
Imported tableprinter module
authorMartin Mares <mj@ucw.cz>
Fri, 30 May 2014 11:53:22 +0000 (13:53 +0200)
committerMartin Mares <mj@ucw.cz>
Fri, 30 May 2014 11:53:22 +0000 (13:53 +0200)
Original version was developed in GigaMail repository by Robert
Kessl, but we agreed that it should be moved to LibUCW and maintained
there.

Further cleanup can be expected.

ucw/Makefile
ucw/table-test.c [new file with mode: 0644]
ucw/table-test.t [new file with mode: 0644]
ucw/table.c [new file with mode: 0644]
ucw/table.h [new file with mode: 0644]
ucw/table.t [new file with mode: 0644]

index 860a647a497d6852209afb47c0a829cc21aa3db8..527e2beba267cc6f6ea5425932a51a47f8c9e654 100644 (file)
@@ -37,7 +37,8 @@ LIBUCW_MODS= \
        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 \
@@ -67,7 +68,8 @@ LIBUCW_MAIN_INCLUDES= \
        daemon.h \
        signames.h \
        sighandler.h \
-       opt.h
+       opt.h \
+       table.h
 
 ifdef CONFIG_UCW_THREADS
 # Some modules require threading
@@ -130,6 +132,7 @@ endif
 $(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 \
@@ -137,7 +140,8 @@ TESTS+=$(addprefix $(o)/ucw/,regex.test unicode.test hash-test.test mempool.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
@@ -169,6 +173,8 @@ $(o)/ucw/crc.test: $(o)/ucw/crc-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)
diff --git a/ucw/table-test.c b/ucw/table-test.c
new file mode 100644 (file)
index 0000000..ee01954
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ *     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;
+}
diff --git a/ucw/table-test.t b/ucw/table-test.t
new file mode 100644 (file)
index 0000000..f2871a4
--- /dev/null
@@ -0,0 +1,88 @@
+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
diff --git a/ucw/table.c b/ucw/table.c
new file mode 100644 (file)
index 0000000..241c6df
--- /dev/null
@@ -0,0 +1,694 @@
+/*
+ *     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
diff --git a/ucw/table.h b/ucw/table.h
new file mode 100644 (file)
index 0000000..0a2b4db
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ *     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
diff --git a/ucw/table.t b/ucw/table.t
new file mode 100644 (file)
index 0000000..d5dc82a
--- /dev/null
@@ -0,0 +1,30 @@
+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