[libvirt] [PATCH v4 1/3] vsh: Add API for printing tables.

Simon Kobyda skobyda at redhat.com
Thu Aug 23 07:49:34 UTC 2018


On Wed, 2018-08-22 at 19:42 +0200, Simon Kobyda wrote:
> It solves problems with alignment of columns. Width of each column
> is calculated by its biggest cell. Should solve unicode bug.
> In future, it may be implemented in virsh, virt-admin...
> 
> This API has 5 public functions:
> - vshTableNew - adds new table and defines its header
> - vshTableRowAppend - appends new row (for same number of columns as
> in
> header)
> - vshTablePrintToStdout
> - vshTablePrintToString
> - vshTableFree
> 
> https://bugzilla.redhat.com/show_bug.cgi?id=1574624
> https://bugzilla.redhat.com/show_bug.cgi?id=1584630
> 
> Signed-off-by: Simon Kobyda <skobyda at redhat.com>
> ---
>  tools/Makefile.am |   4 +-
>  tools/vsh-table.c | 483
> ++++++++++++++++++++++++++++++++++++++++++++++
>  tools/vsh-table.h |  42 ++++
>  3 files changed, 528 insertions(+), 1 deletion(-)
>  create mode 100644 tools/vsh-table.c
>  create mode 100644 tools/vsh-table.h
> 
> diff --git a/tools/Makefile.am b/tools/Makefile.am
> index 1452d984a0..f069167acc 100644
> --- a/tools/Makefile.am
> +++ b/tools/Makefile.am
> @@ -144,7 +144,9 @@ libvirt_shell_la_LIBADD = \
>  		$(READLINE_LIBS) \
>  		../gnulib/lib/libgnu.la \
>  		$(NULL)
> -libvirt_shell_la_SOURCES = vsh.c vsh.h
> +libvirt_shell_la_SOURCES = \
> +		vsh.c vsh.h \
> +		vsh-table.c vsh-table.h
>  
>  virt_host_validate_SOURCES = \
>  		virt-host-validate.c \
> diff --git a/tools/vsh-table.c b/tools/vsh-table.c
> new file mode 100644
> index 0000000000..6e1793e4e3
> --- /dev/null
> +++ b/tools/vsh-table.c
> @@ -0,0 +1,483 @@
> +/*
> + * vsh-table.c: table printing helper
> + *
> + * Copyright (C) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later
> version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library.  If not, see
> + * <http://www.gnu.org/licenses/>.
> + *
> + * Authors:
> + *   Simon Kobyda <skobyda at redhat.com>
> + *
> + */
> +
> +#include <config.h>
> +#include "vsh-table.h"
> +
> +#include <string.h>
> +#include <stdarg.h>
> +#include <stddef.h>
> +#include <wchar.h>
> +#include <wctype.h>
> +#include <uniwidth.h>
> +#include "c-ctype.h"
> +
> +#include "viralloc.h"
> +#include "virbuffer.h"
> +#include "virstring.h"
> +#include "virsh-util.h"
> +
> +#define HEX_ENCODE_LENGTH 4 /* represents length of '\xNN' */
> +
> +struct _vshTableRow {
> +    char **cells;
> +    size_t ncells;
> +};
> +
> +struct _vshTable {
> +    vshTableRowPtr *rows;
> +    size_t nrows;
> +};
> +
> +static void
> +vshTableRowFree(vshTableRowPtr row)
> +{
> +    size_t i;
> +
> +    if (!row)
> +        return;
> +
> +    for (i = 0; i < row->ncells; i++)
> +        VIR_FREE(row->cells[i]);
> +
> +    VIR_FREE(row->cells);
> +    VIR_FREE(row);
> +}
> +
> +void
> +vshTableFree(vshTablePtr table)
> +{
> +    size_t i;
> +
> +    if (!table)
> +        return;
> +
> +    for (i = 0; i < table->nrows; i++)
> +        vshTableRowFree(table->rows[i]);
> +    VIR_FREE(table->rows);
> +    VIR_FREE(table);
> +}
> +
> +/**
> + * vshTableRowNew:
> + * @arg: the first argument.
> + * @ap: list of variadic arguments
> + *
> + * Create a new row in the table. Each argument passed
> + * represents a cell in the row.
> + * Return: pointer to vshTableRowPtr row or NULL.
> + */
> +static vshTableRowPtr
> +vshTableRowNew(const char *arg, va_list ap)
> +{
> +    vshTableRowPtr row = NULL;
> +    char *tmp = NULL;
> +
> +    if (!arg) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                        _("Table row cannot be empty"));
> +        goto error;
> +    }
> +
> +    if (VIR_ALLOC(row) < 0)
> +        goto error;
> +
> +    while (arg) {
> +        if (VIR_STRDUP(tmp, arg) < 0)
> +            goto error;
> +
> +        if (VIR_APPEND_ELEMENT(row->cells, row->ncells, tmp) < 0)
> +            goto error;
> +
> +        arg = va_arg(ap, const char *);
> +    }
> +
> +    return row;
> +
> + error:
> +    vshTableRowFree(row);
> +    return NULL;
> +}
> +
> +/**
> + * vshTableNew:
> + * @arg: List of column names (NULL terminated)
> + *
> + * Create a new table.
> + *
> + * Returns: pointer to table or NULL.
> + */
> +vshTablePtr
> +vshTableNew(const char *arg, ...)
> +{
> +    vshTablePtr table;
> +    vshTableRowPtr header = NULL;
> +    va_list ap;
> +
> +    if (VIR_ALLOC(table) < 0)
> +        goto error;
> +
> +    va_start(ap, arg);
> +    header = vshTableRowNew(arg, ap);
> +    va_end(ap);
> +
> +    if (!header)
> +        goto error;
> +
> +    if (VIR_APPEND_ELEMENT(table->rows, table->nrows, header) < 0)
> +        goto error;
> +
> +    return table;
> + error:
> +    vshTableRowFree(header);
> +    vshTableFree(table);
> +    return NULL;
> +}
> +
> +/**
> + * vshTableRowAppend:
> + * @table: table to append to
> + * @arg: cells of the row (NULL terminated)
> + *
> + * Append new row into the @table. The number of cells in the row
> has
> + * to be equal to the number of cells in the table header.
> + *
> + * Returns: 0 if succeeded, -1 if failed.
> + */
> +int
> +vshTableRowAppend(vshTablePtr table, const char *arg, ...)
> +{
> +    vshTableRowPtr row = NULL;
> +    size_t ncolumns = table->rows[0]->ncells;
> +    va_list ap;
> +    int ret = -1;
> +
> +    va_start(ap, arg);
> +    row = vshTableRowNew(arg, ap);
> +    va_end(ap);
> +
> +    if (!row)
> +        goto cleanup;
> +
> +    if (ncolumns != row->ncells) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                        _("Incorrect number of cells in a table
> row"));
> +        goto cleanup;
> +    }
> +
> +    if (VIR_APPEND_ELEMENT(table->rows, table->nrows, row) < 0)
> +        goto cleanup;
> +
> +    ret = 0;
> + cleanup:
> +    vshTableRowFree(row);
> +    return ret;
> +}
> +
> +/**
> + * Function pulled from util-linux
> + *
> + * Function's name in util-linux: mbs_safe_encode_to_buffer
> + *
> + * Copy @s to @buf and replace control and non-printable chars with
> + * \x?? hex sequence. The @width returns number of cells. The
> @safechars
> + * are not encoded.
> + *
> + * The @buf has to be big enough to store
> mbs_safe_encode_size(strlen(s)))
> + * bytes.
> + */
> +static char *
> +vshTableSafeEncodeToBuffer(const char *s, size_t *width, char *buf,
> const char *safechars)
> +{
> +    const char *p = s;
> +    char *r;
> +    size_t sz = s ? strlen(s) : 0;
> +
> +    mbstate_t st;
> +    memset(&st, 0, sizeof(st));
> +    if (!sz || !buf)
> +        return NULL;
> +
> +    r = buf;
> +    *width = 0;
> +
> +    while (p && *p) {
> +        if (safechars && strchr(safechars, *p)) {
> +            *r++ = *p++;
> +            continue;
> +        }
> +
> +        if ((*p == '\\' && *(p + 1) == 'x') ||
> +            c_iscntrl((unsigned char) *p)) {
> +            snprintf(r, HEX_ENCODE_LENGTH + 1, "\\x%02x", (unsigned
> char) *p);
> +            r += HEX_ENCODE_LENGTH;
> +            *width += HEX_ENCODE_LENGTH;
> +            p++;
> +        } else {
> +            wchar_t wc;
> +            size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
> +
> +            if (len == 0)
> +                break;		/* end of string */
> +
> +            if (len == (size_t) -1 || len == (size_t) -2) {
> +                len = 1;
> +                /*
> +                 * Not valid multibyte sequence -- maybe it's
> +                 * printable char according to the current locales.
> +                 */
> +                if (!c_isprint((unsigned char) *p)) {
> +                    snprintf(r, HEX_ENCODE_LENGTH + 1, "\\x%02x",
> (unsigned char) *p);
> +                    r += HEX_ENCODE_LENGTH;
> +                    *width += HEX_ENCODE_LENGTH;
> +                } else {
> +                    (*width)++;
> +                    *r++ = *p;
> +                }
> +            } else if (!iswprint(wc)) {
> +                size_t i;
> +                for (i = 0; i < len; i++) {
> +                    snprintf(r, HEX_ENCODE_LENGTH + 1, "\\x%02x",
> (unsigned char) p[i]);
> +                    r += HEX_ENCODE_LENGTH;
> +                    *width += HEX_ENCODE_LENGTH;
> +                }
> +            } else {
> +                memcpy(r, p, len);
> +                r += len;
> +                *width += wcwidth(wc);
> +            }
> +            p += len;
> +        }
> +    }
> +
> +    *r = '\0';
> +    return buf;
> +}
> +
> +/**
> + * Function pulled from util-linux
> + *
> + * Function's name in util-linux: mbs_safe_encode_size
> + */
> +static size_t
> +vshTableSafeEncodeSize(size_t bytes)
> +{
> +    return (bytes * HEX_ENCODE_LENGTH) + 1;
> +}
> +
> +/**
> + * Function pulled from util-linux
> + * Function's name in util-linux: mbs_safe_encode
> + *
> + * Returns allocated string where all control and non-printable
> chars are
> + * replaced with \x?? hex sequence.
> + */
> +static char *
> +vshTableSafeEncode(const char *s, size_t *width)
> +{
> +    size_t sz = s ? strlen(s) : 0;
> +    char *buf, *ret = NULL;
> +
> +    if (!sz)
> +        return NULL;
> +    if (VIR_ALLOC_N(buf, vshTableSafeEncodeSize(sz)) == 0)
> +        ret = vshTableSafeEncodeToBuffer(s, width, buf, NULL);
> +    if (!ret)
> +        VIR_FREE(buf);
> +    return ret;
> +}
> +
> +/**
> + * vshTableGetColumnsWidths:
> + * @table: table
> + * @maxwidths: maximum count of characters for each columns
> + * @widths: count of characters for each cell in the table
> + *
> + * Fill passed @maxwidths and @widths arrays with maximum number
> + * of characters for columns and number of character per each
> + * table cell, respectively.
> + *
> + * Handle unicode strings (user must have multibyte locale)
> + */
> +static int
> +vshTableGetColumnsWidths(vshTablePtr table,
> +                         size_t *maxwidths,
> +                         size_t **widths,
> +                         bool header)
> +{
> +    size_t i;
> +    size_t j;
> +
> +    if (header)
> +        i = 0;
> +    else
> +        i = 1;
> +    for (; i < table->nrows; i++) {
> +        vshTableRowPtr row = table->rows[i];
> +
> +        for (j = 0; j < row->ncells; j++) {
> +            size_t size = 0;
> +            char *tmp = vshTableSafeEncode(row->cells[j], &size);
> +            VIR_FREE(row->cells[j]);
> +            row->cells[j] = tmp;
> +            widths[i][j] = size;
> +
> +            if (widths[i][j] > maxwidths[j])
> +                maxwidths[j] = widths[i][j];
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +/**
> + * vshTableRowPrint:
> + * @row: table to append to
> + * @maxwidths: maximum count of characters for each columns
> + * @widths: count of character for each cell in this row
> + * @buf: buffer to store table (only if @toStdout == true)
> + */
> +static void
> +vshTableRowPrint(vshTableRowPtr row,
> +                 size_t *maxwidths,
> +                 size_t *widths,
> +                 virBufferPtr buf)
> +{
> +    size_t i;
> +    size_t j;
> +
> +    for (i = 0; i < row->ncells; i++) {
> +        virBufferAsprintf(buf, " %s", row->cells[i]);
> +
> +        for (j = 0; j < maxwidths[i] - widths[i] + 2; j++)
> +            virBufferAddStr(buf, " ");
> +    }
> +    virBufferAddStr(buf, "\n");
> +}
> +
> +/**
> + * vshTablePrint:
> + * @table: table to print
> + * @header: whetever to print to header (true) or not (false)
> + * this argument is relevant only if @ctl == NULL
> + *
> + * Get table. To get an alignment of columns right, function
> + * fills 2d array @widths with count of characters in each cell and
> + * array @maxwidths maximum count of character in each column.
> + * Function then prints tables header and content.
> + *
> + * Return string containing table, or NULL
> + */
> +static char *
> +vshTablePrint(vshTablePtr table, bool header)
> +{
> +    size_t i;
> +    size_t j;
> +    size_t *maxwidths;
> +    size_t **widths;
> +    virBuffer buf = VIR_BUFFER_INITIALIZER;
> +    char *ret = NULL;
> +
> +    if (VIR_ALLOC_N(maxwidths, table->rows[0]->ncells))
> +        goto cleanup;
> +
> +    if (VIR_ALLOC_N(widths, table->nrows))
> +        goto cleanup;
> +
> +    /* retrieve widths of columns */
> +    for (i = 0; i < table->nrows; i++) {
> +        if (VIR_ALLOC_N(widths[i], table->rows[0]->ncells))
> +            goto cleanup;
> +    }
> +
> +    if (vshTableGetColumnsWidths(table, maxwidths, widths, header) <
> 0)
> +        goto cleanup;
> +
> +    if (header) {
> +        /* print header */
> +        VIR_WARNINGS_NO_PRINTF
> +        vshTableRowPrint(table->rows[0], maxwidths, widths[0],
> &buf);
> +        VIR_WARNINGS_RESET
> +
> +        /* print dividing line  */
> +        for (i = 0; i < table->rows[0]->ncells; i++) {
> +            for (j = 0; j < maxwidths[i] + 3; j++)
> +                    virBufferAddStr(&buf, "-");
> +        }
> +            virBufferAddStr(&buf, "\n");
> +    }
> +    /* print content */
> +    for (i = 1; i < table->nrows; i++) {
> +        VIR_WARNINGS_NO_PRINTF
> +        vshTableRowPrint(table->rows[i], maxwidths, widths[i],
> &buf);
> +        VIR_WARNINGS_RESET
> +    }
> +
> +    ret = virBufferContentAndReset(&buf);
> +
> + cleanup:
> +    VIR_FREE(maxwidths);
> +    for (i = 0; i < table->nrows; i++)
> +        VIR_FREE(widths[i]);
> +    VIR_FREE(widths);
> +    return ret;
> +}
> +
> +
> +/**
> + * vshTablePrintToStdout:
> + * @table: table to print
> + * @ctl virtshell control structure
> + *
> + * Print table to stdout.
> + *
> + */
> +void
> +vshTablePrintToStdout(vshTablePtr table, vshControl *ctl)
> +{
> +    bool header;
> +    char *out;
> +    if (ctl)
> +        header = !ctl->quiet;
> +    else
> +        header = true;
> +
> +    out = vshTablePrintToString(table, header);
> +    if (out)
> +        vshPrint(ctl, "%s", out);

Oops, here i forgot to free, my bad :).

    VIR_FREE(out);

> +}
> +
> +/**
> + * vshTablePrintToString:
> + * @table: table to print
> + * @header: whetever to print to header (true) or not (false)
> + *
> + * Return string containing table, or NULL if table was printed to
> + * stdout. User will have to free returned string.
> + */
> +char *
> +vshTablePrintToString(vshTablePtr table, bool header)
> +{
> +    return vshTablePrint(table, header);
> +}
> diff --git a/tools/vsh-table.h b/tools/vsh-table.h
> new file mode 100644
> index 0000000000..e4e9582b9f
> --- /dev/null
> +++ b/tools/vsh-table.h
> @@ -0,0 +1,42 @@
> +/*
> + * vsh-table.h: table printing helper
> + *
> + * Copyright (C) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later
> version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library.  If not, see
> + * <http://www.gnu.org/licenses/>.
> + *
> + * Authors:
> + *   Simon Kobyda <skobyda at redhat.com>
> + *
> + */
> +
> +#ifndef VSH_TABLE_H
> +# define VSH_TABLE_H
> +
> +# include "vsh.h"
> +
> +/* forward declarations */
> +typedef struct _vshTable vshTable;
> +typedef struct _vshTableRow vshTableRow;
> +typedef vshTable *vshTablePtr;
> +typedef vshTableRow *vshTableRowPtr;
> +
> +void vshTableFree(vshTablePtr table);
> +vshTablePtr vshTableNew(const char *format, ...);
> +int vshTableRowAppend(vshTablePtr table, const char *arg, ...);
> +void vshTablePrintToStdout(vshTablePtr table, vshControl *ctl);
> +char *vshTablePrintToString(vshTablePtr table, bool header);
> +
> +#endif /* VSH_TABLE_H */




More information about the libvir-list mailing list