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