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