Adds a new --format="xx={0},..." to the table library, which allows
users to make table output formats to match their own needs.Signed-off-by: Aaron Conole <[email protected]> --- v1 url: https://mail.openvswitch.org/pipermail/ovs-dev/2016-November/325235.html TODO: Still needs unit tests to check the format strings One thing I want to do is put in the idea of string length to the grammar. Probably will be a few other additions (ex: text which is omitted when the corresponding column value is empty). NOTE: Submitted for early feedback lib/table.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/table.h | 6 +- lib/table.man | 17 ++++++ 3 files changed, 213 insertions(+), 2 deletions(-) diff --git a/lib/table.c b/lib/table.c index 9158499..ca92848 100644 --- a/lib/table.c +++ b/lib/table.c @@ -532,6 +532,191 @@ table_print_json__(const struct table *table, const struct table_style *style) free(s); } +/* Dynamic format parser. */ + +struct table_output_action { + struct ds *print_value; /* When !NULL, the text here is output. */ + size_t column_id; /* When print_value is NULL, this is the + * column to print, instead. */ + enum table_format cell_style; /* TF_*. */ +}; + +/* This compiles an input string into a series out output actions for the + * table formatting routines. */ +static size_t +table_print_compile_format(const char *format, + struct table_output_action **output, + const struct table *table) +{ + struct table_output_action *actions; + char escape_vals[UCHAR_MAX] = {0}; + bool escape_on_br = false; + struct ds *action = NULL; + const char *c = format; + bool escape = false; + size_t i = 0; + + if (!format || !table) { + return 0; + } + + actions = xmalloc(sizeof(struct table_output_action)); + + memset(escape_vals, '?', sizeof(escape_vals)); + escape_vals['a'] = '\a'; + escape_vals['b'] = '\b'; + escape_vals['n'] = '\n'; + escape_vals['r'] = '\r'; + escape_vals['t'] = '\t'; + + for (; c; c++) { + if (!action) { + action = xmalloc(sizeof(struct ds)); + ds_init(action); + } + + switch (*c) { + /* default case - accumulate more in the action buffer */ + default: + { + size_t ch = *c; + if (escape) { + if (ch >= sizeof(escape_vals)) { + ch = 0; + } + ch = escape_vals[ch]; + escape = false; + } + + ds_put_char(action, ch); + } + break; + + /* This is a pretty standard 'escape' character. */ + case '\\': + if (!escape) { + escape = true; + } else { + ds_put_char(action, *c); + escape = false; + } + break; + + /* The special state - start a new action block. This printing + * state machine will try to have as few special states as possible, + * so the 'start' state is really just 'complete the previous block' + * state. Unlike another block completion state, this state will + * not create a column lookup action. */ + case '{': + if (escape) { + ds_put_char(action, *c); + escape = false; + escape_on_br = true; + } else { + actions[i++].print_value = action; + action = NULL; + actions = xrealloc(actions, (i+1) * sizeof(actions[0])); + } + break; + + /* The other special state - create a column lookup. See the + * previous comments on the '{' case. This is similar, except it + * does create a column lookup action. */ + case '}': + if (escape || escape_on_br) { + ds_put_char(action, *c); + escape = false; + escape_on_br = false; + } else { + /* allow for ':' to be a special character. */ + struct table_output_action *action_to_modify; + char *ch = strchr(ds_cstr(action), ':'); + enum table_format fmt = TF_TABLE; + + action_to_modify = &actions[i++]; + if (ch) { + struct table_style format_extractor; + *ch++ = 0; + table_parse_format(&format_extractor, ch); + fmt = format_extractor.format; + } + + /* find the header. If not found, we fail. */ + size_t hdrid; + for (hdrid = 0; hdrid < table->n_columns + && strcmp(ds_cstr(action), + table->columns[hdrid].heading); + ++hdrid) { + /* skip forward until we find the header. */ + } + + if (hdrid >= table->n_columns) { + char *end; + hdrid = strtoul(ds_cstr(action), &end, 10); + if (*end != '\0') { + ovs_fatal(0, "Unknown column specified \"%s\"", + ds_cstr(action)); + } + } + + action_to_modify->print_value = NULL; + action_to_modify->cell_style = fmt; + action_to_modify->column_id = hdrid; + actions = xrealloc(actions, (i+1) * sizeof(actions[0])); + ds_clear(action); + break; + } + } + if (!(*c)) { + actions[i++].print_value = action; + break; + } + } + + *output = actions; + return i; +} + +static void +table_print_by_fmt__(const struct table *table, + const struct table_style *style) +{ + struct table_output_action *actions; + size_t n_actions; + + n_actions = table_print_compile_format(style->format_string, &actions, + table); + + for (size_t row = 0; row < table->n_rows; ++row) { + for (size_t action = 0; action < n_actions; ++action) { + if (actions[action].print_value) { + fputs(ds_cstr(actions[action].print_value), stdout); + } else { + struct cell *cell = table_cell__(table, row, + actions[action].column_id); + const char *str = cell_to_text(cell, style); + + switch (actions[action].cell_style) + { + case TF_TABLE: + case TF_LIST: + case TF_JSON: + case TF_FORMATTED: + default: + fputs(str, stdout); + break; + case TF_CSV: + table_print_csv_cell__(str); + break; + case TF_HTML: + table_escape_html_text__(str, strlen(str)); + break; + } + } + } + } +} + /* Parses 'format' as the argument to a --format command line option, updating * 'style->format'. */ void @@ -547,6 +732,9 @@ table_parse_format(struct table_style *style, const char *format) style->format = TF_CSV; } else if (!strcmp(format, "json")) { style->format = TF_JSON; + } else if (strchr(format, '{')) { + style->format = TF_FORMATTED; + style->format_string = xstrdup(format); } else { ovs_fatal(0, "unknown output format \"%s\"", format); } @@ -592,5 +780,9 @@ table_print(const struct table *table, const struct table_style *style) case TF_JSON: table_print_json__(table, style); break; + + case TF_FORMATTED: + table_print_by_fmt__(table, style); + break; } } diff --git a/lib/table.h b/lib/table.h index 85b8156..0880273 100644 --- a/lib/table.h +++ b/lib/table.h @@ -64,7 +64,8 @@ enum table_format { TF_LIST, /* One cell per line, one row per paragraph. */ TF_HTML, /* HTML table. */ TF_CSV, /* Comma-separated lines. */ - TF_JSON /* JSON. */ + TF_JSON, /* JSON. */ + TF_FORMATTED /* A format string was specified. */ }; enum cell_format { @@ -78,9 +79,10 @@ struct table_style { enum cell_format cell_format; /* CF_*. */ bool headings; /* Include headings? */ int json_flags; /* CF_JSON: Flags for json_to_string(). */ + char *format_string; /* Valid only with TF_FORMATTED format. */ }; -#define TABLE_STYLE_DEFAULT { TF_TABLE, CF_STRING, true, JSSF_SORT } +#define TABLE_STYLE_DEFAULT { TF_TABLE, CF_STRING, true, JSSF_SORT, NULL } #define TABLE_OPTION_ENUMS \ OPT_NO_HEADINGS, \ diff --git a/lib/table.man b/lib/table.man index a8f1094..d47fc71 100644 --- a/lib/table.man +++ b/lib/table.man @@ -32,6 +32,23 @@ that represent OVSDB data or data types are expressed in the format described in the OVSDB specification; other cells are simply expressed as text strings. .RE +.IP "\fBFORMAT\fR" +\fBFORMAT\fR will emit each row of the table according to the format +string specified. The format string is comprised of either literal +text, or replacement fields delimited by braces \fB{\fIfield\fR}\fR. +Fields may be either a numeric value (which will be the ordinal +position of the corresponding heading, index starting at 0), or the +heading name. +Additionally, the cell data emitted can be modified with an additional +modification hint, corresponding to a cell output format. To specify +the hint, simply put a ':' after the heading, followed by the cell +format string. +.RS +\fIExample\fR: \fB\-f "elemt0={0:json},elemt1={heading3}"\fR will emit +rows as "elemt0=" followed by the cell corresponding to the first +heading in json format, followed by ",elemt1=", followed by the cell +corresponding to "heading3" +.RE .RE . .IP "\fB\-d \fIformat\fR" -- 2.7.4 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
