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