[ovs-dev] [RFC v2] lib/table: add new flexible formatting
Aaron Conole
aconole at redhat.com
Wed Dec 7 15:55:54 UTC 2016
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 <aconole at redhat.com>
---
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
More information about the dev
mailing list