ovsdb-client: Break table formatting into new library.
[sliver-openvswitch.git] / lib / table.c
1 /*
2  * Copyright (c) 2009, 2010, 2011 Nicira Networks.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <config.h>
18
19 #include "table.h"
20
21 #include <assert.h>
22
23 #include "dynamic-string.h"
24 #include "json.h"
25 #include "ovsdb-data.h"
26 #include "util.h"
27
28 struct column {
29     char *heading;
30 };
31
32 static char *
33 cell_to_text(struct cell *cell, const struct table_style *style)
34 {
35     if (!cell->text) {
36         if (cell->json) {
37             if (style->cell_format == CF_JSON || !cell->type) {
38                 cell->text = json_to_string(cell->json, JSSF_SORT);
39             } else if (style->cell_format == CF_STRING) {
40                 struct ovsdb_datum datum;
41                 struct ovsdb_error *error;
42                 struct ds s;
43
44                 error = ovsdb_datum_from_json(&datum, cell->type, cell->json,
45                                               NULL);
46                 if (!error) {
47                     ds_init(&s);
48                     ovsdb_datum_to_string(&datum, cell->type, &s);
49                     ovsdb_datum_destroy(&datum, cell->type);
50                     cell->text = ds_steal_cstr(&s);
51                 } else {
52                     cell->text = json_to_string(cell->json, JSSF_SORT);
53                 }
54             } else {
55                 NOT_REACHED();
56             }
57         } else {
58             cell->text = xstrdup("");
59         }
60     }
61
62     return cell->text;
63 }
64
65 static void
66 cell_destroy(struct cell *cell)
67 {
68     free(cell->text);
69     json_destroy(cell->json);
70 }
71
72 /* Initializes 'table' as an empty table.
73  *
74  * The caller should then:
75  *
76  * 1. Call table_add_column() once for each column.
77  * 2. For each row:
78  *    2a. Call table_add_row().
79  *    2b. For each column in the cell, call table_add_cell() and fill in
80  *        the returned cell.
81  * 3. Call table_print() to print the final table.
82  * 4. Free the table with table_destroy().
83  */
84 void
85 table_init(struct table *table)
86 {
87     memset(table, 0, sizeof *table);
88 }
89
90 /* Destroys 'table' and frees all associated storage.  (However, the client
91  * owns the 'type' members pointed to by cells, so these are not destroyed.) */
92 void
93 table_destroy(struct table *table)
94 {
95     if (table) {
96         size_t i;
97
98         for (i = 0; i < table->n_columns; i++) {
99             free(table->columns[i].heading);
100         }
101         free(table->columns);
102
103         for (i = 0; i < table->n_columns * table->n_rows; i++) {
104             cell_destroy(&table->cells[i]);
105         }
106         free(table->cells);
107
108         free(table->caption);
109     }
110 }
111
112 /* Sets 'caption' as the caption for 'table'.
113  *
114  * 'table' takes ownership of 'caption'. */
115 void
116 table_set_caption(struct table *table, char *caption)
117 {
118     free(table->caption);
119     table->caption = caption;
120 }
121
122 /* Adds a new column to 'table' just to the right of any existing column, with
123  * 'heading' as a title for the column.  'heading' must be a valid printf()
124  * format specifier.
125  *
126  * Columns must be added before any data is put into 'table'. */
127 void
128 table_add_column(struct table *table, const char *heading, ...)
129 {
130     struct column *column;
131     va_list args;
132
133     assert(!table->n_rows);
134     if (table->n_columns >= table->allocated_columns) {
135         table->columns = x2nrealloc(table->columns, &table->allocated_columns,
136                                     sizeof *table->columns);
137     }
138     column = &table->columns[table->n_columns++];
139
140     va_start(args, heading);
141     column->heading = xvasprintf(heading, args);
142     va_end(args);
143 }
144
145 static struct cell *
146 table_cell__(const struct table *table, size_t row, size_t column)
147 {
148     return &table->cells[column + row * table->n_columns];
149 }
150
151 /* Adds a new row to 'table'.  The table's columns must already have been added
152  * with table_add_column().
153  *
154  * The row is initially empty; use table_add_cell() to start filling it in. */
155 void
156 table_add_row(struct table *table)
157 {
158     size_t x, y;
159
160     if (table->n_rows >= table->allocated_rows) {
161         table->cells = x2nrealloc(table->cells, &table->allocated_rows,
162                                   table->n_columns * sizeof *table->cells);
163     }
164
165     y = table->n_rows++;
166     table->current_column = 0;
167     for (x = 0; x < table->n_columns; x++) {
168         struct cell *cell = table_cell__(table, y, x);
169         memset(cell, 0, sizeof *cell);
170     }
171 }
172
173 /* Adds a new cell in the current row of 'table', which must have been added
174  * with table_add_row().  Cells are filled in the same order that the columns
175  * were added with table_add_column().
176  *
177  * The caller is responsible for filling in the returned cell, in one of two
178  * fashions:
179  *
180  *   - If the cell should contain an ovsdb_datum, formatted according to the
181  *     table style, then fill in the 'json' member with the JSON representation
182  *     of the datum and 'type' with its type.
183  *
184  *   - If the cell should contain a fixed text string, then the caller should
185  *     assign that string to the 'text' member.  This is undesirable if the
186  *     cell actually contains OVSDB data because 'text' cannot be formatted
187  *     according to the table style; it is always output verbatim.
188  */
189 struct cell *
190 table_add_cell(struct table *table)
191 {
192     size_t x, y;
193
194     assert(table->n_rows > 0);
195     assert(table->current_column < table->n_columns);
196
197     x = table->current_column++;
198     y = table->n_rows - 1;
199
200     return table_cell__(table, y, x);
201 }
202
203 static void
204 table_print_table_line__(struct ds *line)
205 {
206     puts(ds_cstr(line));
207     ds_clear(line);
208 }
209
210 static void
211 table_print_table__(const struct table *table, const struct table_style *style)
212 {
213     static int n = 0;
214     struct ds line = DS_EMPTY_INITIALIZER;
215     int *widths;
216     size_t x, y;
217
218     if (n++ > 0) {
219         putchar('\n');
220     }
221
222     if (table->caption) {
223         puts(table->caption);
224     }
225
226     widths = xmalloc(table->n_columns * sizeof *widths);
227     for (x = 0; x < table->n_columns; x++) {
228         const struct column *column = &table->columns[x];
229
230         widths[x] = strlen(column->heading);
231         for (y = 0; y < table->n_rows; y++) {
232             const char *text = cell_to_text(table_cell__(table, y, x), style);
233             size_t length = strlen(text);
234
235             if (length > widths[x]) {
236                 widths[x] = length;
237             }
238         }
239     }
240
241     if (style->headings) {
242         for (x = 0; x < table->n_columns; x++) {
243             const struct column *column = &table->columns[x];
244             if (x) {
245                 ds_put_char(&line, ' ');
246             }
247             ds_put_format(&line, "%-*s", widths[x], column->heading);
248         }
249         table_print_table_line__(&line);
250
251         for (x = 0; x < table->n_columns; x++) {
252             if (x) {
253                 ds_put_char(&line, ' ');
254             }
255             ds_put_char_multiple(&line, '-', widths[x]);
256         }
257         table_print_table_line__(&line);
258     }
259
260     for (y = 0; y < table->n_rows; y++) {
261         for (x = 0; x < table->n_columns; x++) {
262             const char *text = cell_to_text(table_cell__(table, y, x), style);
263             if (x) {
264                 ds_put_char(&line, ' ');
265             }
266             ds_put_format(&line, "%-*s", widths[x], text);
267         }
268         table_print_table_line__(&line);
269     }
270
271     ds_destroy(&line);
272     free(widths);
273 }
274
275 static void
276 table_escape_html_text__(const char *s, size_t n)
277 {
278     size_t i;
279
280     for (i = 0; i < n; i++) {
281         char c = s[i];
282
283         switch (c) {
284         case '&':
285             fputs("&amp;", stdout);
286             break;
287         case '<':
288             fputs("&lt;", stdout);
289             break;
290         case '>':
291             fputs("&gt;", stdout);
292             break;
293         case '"':
294             fputs("&quot;", stdout);
295             break;
296         default:
297             putchar(c);
298             break;
299         }
300     }
301 }
302
303 static void
304 table_print_html_cell__(const char *element, const char *content)
305 {
306     const char *p;
307
308     printf("    <%s>", element);
309     for (p = content; *p; ) {
310         struct uuid uuid;
311
312         if (uuid_from_string_prefix(&uuid, p)) {
313             printf("<a href=\"#%.*s\">%.*s</a>", UUID_LEN, p, 8, p);
314             p += UUID_LEN;
315         } else {
316             table_escape_html_text__(p, 1);
317             p++;
318         }
319     }
320     printf("</%s>\n", element);
321 }
322
323 static void
324 table_print_html__(const struct table *table, const struct table_style *style)
325 {
326     size_t x, y;
327
328     fputs("<table border=1>\n", stdout);
329
330     if (table->caption) {
331         table_print_html_cell__("caption", table->caption);
332     }
333
334     if (style->headings) {
335         fputs("  <tr>\n", stdout);
336         for (x = 0; x < table->n_columns; x++) {
337             const struct column *column = &table->columns[x];
338             table_print_html_cell__("th", column->heading);
339         }
340         fputs("  </tr>\n", stdout);
341     }
342
343     for (y = 0; y < table->n_rows; y++) {
344         fputs("  <tr>\n", stdout);
345         for (x = 0; x < table->n_columns; x++) {
346             const char *content;
347
348             content = cell_to_text(table_cell__(table, y, x), style);
349             if (!strcmp(table->columns[x].heading, "_uuid")) {
350                 fputs("    <td><a name=\"", stdout);
351                 table_escape_html_text__(content, strlen(content));
352                 fputs("\">", stdout);
353                 table_escape_html_text__(content, 8);
354                 fputs("</a></td>\n", stdout);
355             } else {
356                 table_print_html_cell__("td", content);
357             }
358         }
359         fputs("  </tr>\n", stdout);
360     }
361
362     fputs("</table>\n", stdout);
363 }
364
365 static void
366 table_print_csv_cell__(const char *content)
367 {
368     const char *p;
369
370     if (!strpbrk(content, "\n\",")) {
371         fputs(content, stdout);
372     } else {
373         putchar('"');
374         for (p = content; *p != '\0'; p++) {
375             switch (*p) {
376             case '"':
377                 fputs("\"\"", stdout);
378                 break;
379             default:
380                 putchar(*p);
381                 break;
382             }
383         }
384         putchar('"');
385     }
386 }
387
388 static void
389 table_print_csv__(const struct table *table, const struct table_style *style)
390 {
391     static int n = 0;
392     size_t x, y;
393
394     if (n++ > 0) {
395         putchar('\n');
396     }
397
398     if (table->caption) {
399         puts(table->caption);
400     }
401
402     if (style->headings) {
403         for (x = 0; x < table->n_columns; x++) {
404             const struct column *column = &table->columns[x];
405             if (x) {
406                 putchar(',');
407             }
408             table_print_csv_cell__(column->heading);
409         }
410         putchar('\n');
411     }
412
413     for (y = 0; y < table->n_rows; y++) {
414         for (x = 0; x < table->n_columns; x++) {
415             if (x) {
416                 putchar(',');
417             }
418             table_print_csv_cell__(cell_to_text(table_cell__(table, y, x),
419                                                 style));
420         }
421         putchar('\n');
422     }
423 }
424
425 static void
426 table_print_json__(const struct table *table, const struct table_style *style)
427 {
428     struct json *json, *headings, *data;
429     size_t x, y;
430     char *s;
431
432     json = json_object_create();
433     if (table->caption) {
434         json_object_put_string(json, "caption", table->caption);
435     }
436
437     headings = json_array_create_empty();
438     for (x = 0; x < table->n_columns; x++) {
439         const struct column *column = &table->columns[x];
440         json_array_add(headings, json_string_create(column->heading));
441     }
442     json_object_put(json, "headings", headings);
443
444     data = json_array_create_empty();
445     for (y = 0; y < table->n_rows; y++) {
446         struct json *row = json_array_create_empty();
447         for (x = 0; x < table->n_columns; x++) {
448             const struct cell *cell = table_cell__(table, y, x);
449             if (cell->text) {
450                 json_array_add(row, json_string_create(cell->text));
451             } else {
452                 json_array_add(row, json_clone(cell->json));
453             }
454         }
455         json_array_add(data, row);
456     }
457     json_object_put(json, "data", data);
458
459     s = json_to_string(json, style->json_flags);
460     json_destroy(json);
461     puts(s);
462     free(s);
463 }
464 \f
465 /* Parses 'format' as the argument to a --format command line option, updating
466  * 'style->format'. */
467 void
468 table_parse_format(struct table_style *style, const char *format)
469 {
470     if (!strcmp(format, "table")) {
471         style->format = TF_TABLE;
472     } else if (!strcmp(format, "html")) {
473         style->format = TF_HTML;
474     } else if (!strcmp(format, "csv")) {
475         style->format = TF_CSV;
476     } else if (!strcmp(format, "json")) {
477         style->format = TF_JSON;
478     } else {
479         ovs_fatal(0, "unknown output format \"%s\"", format);
480     }
481 }
482
483 /* Parses 'format' as the argument to a --data command line option, updating
484  * 'style->cell_format'. */
485 void
486 table_parse_cell_format(struct table_style *style, const char *format)
487 {
488     if (!strcmp(format, "string")) {
489         style->cell_format = CF_STRING;
490     } else if (!strcmp(format, "json")) {
491         style->cell_format = CF_JSON;
492     } else {
493         ovs_fatal(0, "unknown data format \"%s\"", format);
494     }
495 }
496
497 /* Outputs 'table' on stdout in the specified 'style'. */
498 void
499 table_print(const struct table *table, const struct table_style *style)
500 {
501     switch (style->format) {
502     case TF_TABLE:
503         table_print_table__(table, style);
504         break;
505
506     case TF_HTML:
507         table_print_html__(table, style);
508         break;
509
510     case TF_CSV:
511         table_print_csv__(table, style);
512         break;
513
514     case TF_JSON:
515         table_print_json__(table, style);
516         break;
517     }
518 }