]>
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 | { | |
e51d0a1d BP |
217 | while (ds_last(line) == ' ') { |
218 | line->length--; | |
219 | } | |
3a3eb9da BP |
220 | puts(ds_cstr(line)); |
221 | ds_clear(line); | |
222 | } | |
223 | ||
3e78870d BP |
224 | static char * |
225 | table_format_timestamp__(void) | |
8f46c9bb | 226 | { |
2b31d8e7 | 227 | return xastrftime_msec("%Y-%m-%d %H:%M:%S.###", time_wall_msec(), true); |
8f46c9bb BP |
228 | } |
229 | ||
230 | static void | |
231 | table_print_timestamp__(const struct table *table) | |
232 | { | |
233 | if (table->timestamp) { | |
3e78870d | 234 | char *s = table_format_timestamp__(); |
8f46c9bb | 235 | puts(s); |
3e78870d | 236 | free(s); |
8f46c9bb BP |
237 | } |
238 | } | |
239 | ||
3a3eb9da BP |
240 | static void |
241 | table_print_table__(const struct table *table, const struct table_style *style) | |
242 | { | |
243 | static int n = 0; | |
244 | struct ds line = DS_EMPTY_INITIALIZER; | |
245 | int *widths; | |
246 | size_t x, y; | |
247 | ||
248 | if (n++ > 0) { | |
249 | putchar('\n'); | |
250 | } | |
251 | ||
8f46c9bb BP |
252 | table_print_timestamp__(table); |
253 | ||
3a3eb9da BP |
254 | if (table->caption) { |
255 | puts(table->caption); | |
256 | } | |
257 | ||
80f66ee0 | 258 | widths = xzalloc(table->n_columns * sizeof *widths); |
3a3eb9da BP |
259 | for (x = 0; x < table->n_columns; x++) { |
260 | const struct column *column = &table->columns[x]; | |
261 | ||
80f66ee0 | 262 | int w = 0; |
3a3eb9da BP |
263 | for (y = 0; y < table->n_rows; y++) { |
264 | const char *text = cell_to_text(table_cell__(table, y, x), style); | |
265 | size_t length = strlen(text); | |
266 | ||
80f66ee0 BP |
267 | if (length > w) { |
268 | w = length; | |
3a3eb9da BP |
269 | } |
270 | } | |
80f66ee0 BP |
271 | |
272 | int max = style->max_column_width; | |
273 | if (max > 0 && w > max) { | |
274 | w = max; | |
275 | } | |
276 | if (style->headings) { | |
277 | int min = strlen(column->heading); | |
278 | if (w < min) { | |
279 | w = min; | |
280 | } | |
281 | } | |
282 | widths[x] = w; | |
3a3eb9da BP |
283 | } |
284 | ||
285 | if (style->headings) { | |
286 | for (x = 0; x < table->n_columns; x++) { | |
287 | const struct column *column = &table->columns[x]; | |
288 | if (x) { | |
289 | ds_put_char(&line, ' '); | |
290 | } | |
291 | ds_put_format(&line, "%-*s", widths[x], column->heading); | |
292 | } | |
293 | table_print_table_line__(&line); | |
294 | ||
295 | for (x = 0; x < table->n_columns; x++) { | |
296 | if (x) { | |
297 | ds_put_char(&line, ' '); | |
298 | } | |
299 | ds_put_char_multiple(&line, '-', widths[x]); | |
300 | } | |
301 | table_print_table_line__(&line); | |
302 | } | |
303 | ||
304 | for (y = 0; y < table->n_rows; y++) { | |
305 | for (x = 0; x < table->n_columns; x++) { | |
306 | const char *text = cell_to_text(table_cell__(table, y, x), style); | |
307 | if (x) { | |
308 | ds_put_char(&line, ' '); | |
309 | } | |
80f66ee0 | 310 | ds_put_format(&line, "%-*.*s", widths[x], widths[x], text); |
3a3eb9da BP |
311 | } |
312 | table_print_table_line__(&line); | |
313 | } | |
314 | ||
315 | ds_destroy(&line); | |
316 | free(widths); | |
317 | } | |
318 | ||
c6a41252 BP |
319 | static void |
320 | table_print_list__(const struct table *table, const struct table_style *style) | |
321 | { | |
322 | static int n = 0; | |
323 | size_t x, y; | |
324 | ||
325 | if (n++ > 0) { | |
326 | putchar('\n'); | |
327 | } | |
328 | ||
8f46c9bb BP |
329 | table_print_timestamp__(table); |
330 | ||
c6a41252 BP |
331 | if (table->caption) { |
332 | puts(table->caption); | |
333 | } | |
334 | ||
335 | for (y = 0; y < table->n_rows; y++) { | |
336 | if (y > 0) { | |
337 | putchar('\n'); | |
338 | } | |
339 | for (x = 0; x < table->n_columns; x++) { | |
340 | const char *text = cell_to_text(table_cell__(table, y, x), style); | |
341 | if (style->headings) { | |
342 | printf("%-20s: ", table->columns[x].heading); | |
343 | } | |
344 | puts(text); | |
345 | } | |
346 | } | |
347 | } | |
348 | ||
3a3eb9da BP |
349 | static void |
350 | table_escape_html_text__(const char *s, size_t n) | |
351 | { | |
352 | size_t i; | |
353 | ||
354 | for (i = 0; i < n; i++) { | |
355 | char c = s[i]; | |
356 | ||
357 | switch (c) { | |
358 | case '&': | |
359 | fputs("&", stdout); | |
360 | break; | |
361 | case '<': | |
362 | fputs("<", stdout); | |
363 | break; | |
364 | case '>': | |
365 | fputs(">", stdout); | |
366 | break; | |
367 | case '"': | |
368 | fputs(""", stdout); | |
369 | break; | |
370 | default: | |
371 | putchar(c); | |
372 | break; | |
373 | } | |
374 | } | |
375 | } | |
376 | ||
377 | static void | |
378 | table_print_html_cell__(const char *element, const char *content) | |
379 | { | |
380 | const char *p; | |
381 | ||
382 | printf(" <%s>", element); | |
383 | for (p = content; *p; ) { | |
384 | struct uuid uuid; | |
385 | ||
386 | if (uuid_from_string_prefix(&uuid, p)) { | |
387 | printf("<a href=\"#%.*s\">%.*s</a>", UUID_LEN, p, 8, p); | |
388 | p += UUID_LEN; | |
389 | } else { | |
390 | table_escape_html_text__(p, 1); | |
391 | p++; | |
392 | } | |
393 | } | |
394 | printf("</%s>\n", element); | |
395 | } | |
396 | ||
397 | static void | |
398 | table_print_html__(const struct table *table, const struct table_style *style) | |
399 | { | |
400 | size_t x, y; | |
401 | ||
8f46c9bb BP |
402 | table_print_timestamp__(table); |
403 | ||
3a3eb9da BP |
404 | fputs("<table border=1>\n", stdout); |
405 | ||
406 | if (table->caption) { | |
407 | table_print_html_cell__("caption", table->caption); | |
408 | } | |
409 | ||
410 | if (style->headings) { | |
411 | fputs(" <tr>\n", stdout); | |
412 | for (x = 0; x < table->n_columns; x++) { | |
413 | const struct column *column = &table->columns[x]; | |
414 | table_print_html_cell__("th", column->heading); | |
415 | } | |
416 | fputs(" </tr>\n", stdout); | |
417 | } | |
418 | ||
419 | for (y = 0; y < table->n_rows; y++) { | |
420 | fputs(" <tr>\n", stdout); | |
421 | for (x = 0; x < table->n_columns; x++) { | |
422 | const char *content; | |
423 | ||
424 | content = cell_to_text(table_cell__(table, y, x), style); | |
425 | if (!strcmp(table->columns[x].heading, "_uuid")) { | |
426 | fputs(" <td><a name=\"", stdout); | |
427 | table_escape_html_text__(content, strlen(content)); | |
428 | fputs("\">", stdout); | |
429 | table_escape_html_text__(content, 8); | |
430 | fputs("</a></td>\n", stdout); | |
431 | } else { | |
432 | table_print_html_cell__("td", content); | |
433 | } | |
434 | } | |
435 | fputs(" </tr>\n", stdout); | |
436 | } | |
437 | ||
438 | fputs("</table>\n", stdout); | |
439 | } | |
440 | ||
441 | static void | |
442 | table_print_csv_cell__(const char *content) | |
443 | { | |
444 | const char *p; | |
445 | ||
446 | if (!strpbrk(content, "\n\",")) { | |
447 | fputs(content, stdout); | |
448 | } else { | |
449 | putchar('"'); | |
450 | for (p = content; *p != '\0'; p++) { | |
451 | switch (*p) { | |
452 | case '"': | |
453 | fputs("\"\"", stdout); | |
454 | break; | |
455 | default: | |
456 | putchar(*p); | |
457 | break; | |
458 | } | |
459 | } | |
460 | putchar('"'); | |
461 | } | |
462 | } | |
463 | ||
464 | static void | |
465 | table_print_csv__(const struct table *table, const struct table_style *style) | |
466 | { | |
467 | static int n = 0; | |
468 | size_t x, y; | |
469 | ||
470 | if (n++ > 0) { | |
471 | putchar('\n'); | |
472 | } | |
473 | ||
8f46c9bb BP |
474 | table_print_timestamp__(table); |
475 | ||
3a3eb9da BP |
476 | if (table->caption) { |
477 | puts(table->caption); | |
478 | } | |
479 | ||
480 | if (style->headings) { | |
481 | for (x = 0; x < table->n_columns; x++) { | |
482 | const struct column *column = &table->columns[x]; | |
483 | if (x) { | |
484 | putchar(','); | |
485 | } | |
486 | table_print_csv_cell__(column->heading); | |
487 | } | |
488 | putchar('\n'); | |
489 | } | |
490 | ||
491 | for (y = 0; y < table->n_rows; y++) { | |
492 | for (x = 0; x < table->n_columns; x++) { | |
493 | if (x) { | |
494 | putchar(','); | |
495 | } | |
496 | table_print_csv_cell__(cell_to_text(table_cell__(table, y, x), | |
497 | style)); | |
498 | } | |
499 | putchar('\n'); | |
500 | } | |
501 | } | |
502 | ||
503 | static void | |
504 | table_print_json__(const struct table *table, const struct table_style *style) | |
505 | { | |
506 | struct json *json, *headings, *data; | |
507 | size_t x, y; | |
3a3eb9da BP |
508 | |
509 | json = json_object_create(); | |
510 | if (table->caption) { | |
511 | json_object_put_string(json, "caption", table->caption); | |
512 | } | |
8f46c9bb | 513 | if (table->timestamp) { |
3e78870d | 514 | char *s = table_format_timestamp__(); |
8f46c9bb | 515 | json_object_put_string(json, "time", s); |
3e78870d | 516 | free(s); |
8f46c9bb | 517 | } |
3a3eb9da BP |
518 | |
519 | headings = json_array_create_empty(); | |
520 | for (x = 0; x < table->n_columns; x++) { | |
521 | const struct column *column = &table->columns[x]; | |
522 | json_array_add(headings, json_string_create(column->heading)); | |
523 | } | |
524 | json_object_put(json, "headings", headings); | |
525 | ||
526 | data = json_array_create_empty(); | |
527 | for (y = 0; y < table->n_rows; y++) { | |
528 | struct json *row = json_array_create_empty(); | |
529 | for (x = 0; x < table->n_columns; x++) { | |
530 | const struct cell *cell = table_cell__(table, y, x); | |
531 | if (cell->text) { | |
532 | json_array_add(row, json_string_create(cell->text)); | |
8d56fd26 | 533 | } else if (cell->json) { |
3a3eb9da | 534 | json_array_add(row, json_clone(cell->json)); |
8d56fd26 BP |
535 | } else { |
536 | json_array_add(row, json_null_create()); | |
3a3eb9da BP |
537 | } |
538 | } | |
539 | json_array_add(data, row); | |
540 | } | |
541 | json_object_put(json, "data", data); | |
542 | ||
71f21279 | 543 | char *s = json_to_string(json, style->json_flags); |
3a3eb9da BP |
544 | json_destroy(json); |
545 | puts(s); | |
546 | free(s); | |
547 | } | |
548 | \f | |
549 | /* Parses 'format' as the argument to a --format command line option, updating | |
550 | * 'style->format'. */ | |
551 | void | |
552 | table_parse_format(struct table_style *style, const char *format) | |
553 | { | |
554 | if (!strcmp(format, "table")) { | |
555 | style->format = TF_TABLE; | |
c6a41252 BP |
556 | } else if (!strcmp(format, "list")) { |
557 | style->format = TF_LIST; | |
3a3eb9da BP |
558 | } else if (!strcmp(format, "html")) { |
559 | style->format = TF_HTML; | |
560 | } else if (!strcmp(format, "csv")) { | |
561 | style->format = TF_CSV; | |
562 | } else if (!strcmp(format, "json")) { | |
563 | style->format = TF_JSON; | |
564 | } else { | |
565 | ovs_fatal(0, "unknown output format \"%s\"", format); | |
566 | } | |
567 | } | |
568 | ||
569 | /* Parses 'format' as the argument to a --data command line option, updating | |
570 | * 'style->cell_format'. */ | |
571 | void | |
572 | table_parse_cell_format(struct table_style *style, const char *format) | |
573 | { | |
574 | if (!strcmp(format, "string")) { | |
575 | style->cell_format = CF_STRING; | |
c6a41252 BP |
576 | } else if (!strcmp(format, "bare")) { |
577 | style->cell_format = CF_BARE; | |
3a3eb9da BP |
578 | } else if (!strcmp(format, "json")) { |
579 | style->cell_format = CF_JSON; | |
580 | } else { | |
581 | ovs_fatal(0, "unknown data format \"%s\"", format); | |
582 | } | |
583 | } | |
584 | ||
585 | /* Outputs 'table' on stdout in the specified 'style'. */ | |
586 | void | |
587 | table_print(const struct table *table, const struct table_style *style) | |
588 | { | |
589 | switch (style->format) { | |
590 | case TF_TABLE: | |
591 | table_print_table__(table, style); | |
592 | break; | |
593 | ||
c6a41252 BP |
594 | case TF_LIST: |
595 | table_print_list__(table, style); | |
596 | break; | |
597 | ||
3a3eb9da BP |
598 | case TF_HTML: |
599 | table_print_html__(table, style); | |
600 | break; | |
601 | ||
602 | case TF_CSV: | |
603 | table_print_csv__(table, style); | |
604 | break; | |
605 | ||
606 | case TF_JSON: | |
607 | table_print_json__(table, style); | |
608 | break; | |
609 | } | |
610 | } | |
bcb58ce0 LR |
611 | |
612 | void | |
613 | table_usage(void) | |
614 | { | |
615 | printf("\nOutput formatting options:\n" | |
616 | " -f, --format=FORMAT set output formatting to FORMAT\n" | |
617 | " (\"table\", \"html\", \"csv\", " | |
618 | "or \"json\")\n" | |
619 | " -d, --data=FORMAT set table cell output formatting to\n" | |
620 | " FORMAT (\"string\", \"bare\", " | |
621 | "or \"json\")\n" | |
622 | " --no-headings omit table heading row\n" | |
623 | " --pretty pretty-print JSON in output\n" | |
624 | " --bare equivalent to " | |
625 | "\"--format=list --data=bare --no-headings\"\n"); | |
626 | } |