2018-08-23 18:53:41 +03:00
/*
* 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/>.
*/
# include <config.h>
# include "vsh-table.h"
# include <stdarg.h>
# include <stddef.h>
# include <wchar.h>
# include <wctype.h>
# include "viralloc.h"
# include "virbuffer.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 ;
if ( ! arg ) {
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " ,
_ ( " Table row cannot be empty " ) ) ;
goto error ;
}
2020-09-23 23:06:18 +03:00
row = g_new0 ( vshTableRow , 1 ) ;
2018-08-23 18:53:41 +03:00
while ( arg ) {
char * tmp = NULL ;
2019-10-20 14:49:46 +03:00
tmp = g_strdup ( arg ) ;
2018-08-23 18:53:41 +03:00
if ( VIR_APPEND_ELEMENT ( row - > cells , row - > ncells , tmp ) < 0 ) {
VIR_FREE ( tmp ) ;
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 = NULL ;
vshTableRowPtr header = NULL ;
va_list ap ;
2020-09-23 23:06:18 +03:00
table = g_new0 ( vshTable , 1 ) ;
2018-08-23 18:53:41 +03:00
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
*
* Returns allocated string where all control and non - printable chars are
* replaced with \ x ? ? hex sequence , or NULL .
*/
static char *
vshTableSafeEncode ( const char * s , size_t * width )
{
const char * p = s ;
size_t sz = s ? strlen ( s ) : 0 ;
char * buf ;
char * ret ;
mbstate_t st ;
memset ( & st , 0 , sizeof ( st ) ) ;
2020-09-23 23:06:18 +03:00
buf = g_new0 ( char , ( sz * HEX_ENCODE_LENGTH ) + 1 ) ;
2018-08-23 18:53:41 +03:00
ret = buf ;
* width = 0 ;
while ( p & & * p ) {
if ( ( * p = = ' \\ ' & & * ( p + 1 ) = = ' x ' ) | |
2019-11-18 17:17:15 +03:00
g_ascii_iscntrl ( * p ) ) {
2019-11-13 16:53:42 +03:00
g_snprintf ( buf , HEX_ENCODE_LENGTH + 1 , " \\ x%02x " , * p ) ;
2018-08-23 18:53:41 +03:00
buf + = 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 .
*/
2019-11-18 17:14:47 +03:00
if ( ! g_ascii_isprint ( * p ) ) {
2019-11-13 16:53:42 +03:00
g_snprintf ( buf , HEX_ENCODE_LENGTH + 1 , " \\ x%02x " , * p ) ;
2018-08-23 18:53:41 +03:00
buf + = HEX_ENCODE_LENGTH ;
* width + = HEX_ENCODE_LENGTH ;
} else {
* buf + + = * p ;
( * width ) + + ;
}
} else if ( ! iswprint ( wc ) ) {
size_t i ;
for ( i = 0 ; i < len ; i + + ) {
2019-11-13 16:53:42 +03:00
g_snprintf ( buf , HEX_ENCODE_LENGTH + 1 , " \\ x%02x " , p [ i ] ) ;
2018-08-23 18:53:41 +03:00
buf + = HEX_ENCODE_LENGTH ;
* width + = HEX_ENCODE_LENGTH ;
}
} else {
memcpy ( buf , p , len ) ;
buf + = len ;
2020-01-17 16:40:54 +03:00
* width + = g_unichar_iszerowidth ( wc ) ? 0 : ( g_unichar_iswide ( wc ) ? 2 : 1 ) ;
2018-08-23 18:53:41 +03:00
}
p + = len ;
}
}
* buf = ' \0 ' ;
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 )
*
* Return 0 in case of success , - 1 otherwise .
*/
static int
vshTableGetColumnsWidths ( vshTablePtr table ,
size_t * maxwidths ,
size_t * * widths ,
bool header )
{
size_t i ;
i = header ? 0 : 1 ;
for ( ; i < table - > nrows ; i + + ) {
vshTableRowPtr row = table - > rows [ i ] ;
size_t j ;
for ( j = 0 ; j < row - > ncells ; j + + ) {
size_t size = 0 ;
/* need to replace nonprintable and control characters,
* because width of some of those characters ( e . g . \ t , \ v , \ b . . . )
* cannot be counted properly */
char * tmp = vshTableSafeEncode ( row - > cells [ j ] , & size ) ;
if ( ! tmp )
return - 1 ;
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 ] ) ;
2018-11-27 12:07:32 +03:00
if ( i < ( row - > ncells - 1 ) ) {
for ( j = 0 ; j < maxwidths [ i ] - widths [ i ] + 2 ; j + + )
virBufferAddChar ( buf , ' ' ) ;
}
2018-08-23 18:53:41 +03:00
}
virBufferAddChar ( 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 2 d 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 ;
2020-07-03 02:40:16 +03:00
g_auto ( virBuffer ) buf = VIR_BUFFER_INITIALIZER ;
2018-08-23 18:53:41 +03:00
char * ret = NULL ;
2020-09-23 23:06:18 +03:00
maxwidths = g_new0 ( size_t , table - > rows [ 0 ] - > ncells ) ;
2018-08-23 18:53:41 +03:00
2020-09-23 23:06:18 +03:00
widths = g_new0 ( size_t * , table - > nrows ) ;
2018-08-23 18:53:41 +03:00
/* retrieve widths of columns */
2020-09-23 23:06:18 +03:00
for ( i = 0 ; i < table - > nrows ; i + + )
widths [ i ] = g_new0 ( size_t , table - > rows [ 0 ] - > ncells ) ;
2018-08-23 18:53:41 +03:00
if ( vshTableGetColumnsWidths ( table , maxwidths , widths , header ) < 0 )
goto cleanup ;
if ( header ) {
/* print header */
vshTableRowPrint ( table - > rows [ 0 ] , maxwidths , widths [ 0 ] , & buf ) ;
/* print dividing line */
for ( i = 0 ; i < table - > rows [ 0 ] - > ncells ; i + + ) {
for ( j = 0 ; j < maxwidths [ i ] + 3 ; j + + )
virBufferAddChar ( & buf , ' - ' ) ;
}
virBufferAddChar ( & buf , ' \n ' ) ;
}
/* print content */
for ( i = 1 ; i < table - > nrows ; i + + )
vshTableRowPrint ( table - > rows [ i ] , maxwidths , widths [ i ] , & buf ) ;
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 returned in string to stdout .
2018-12-04 20:08:14 +03:00
* If effect on vshControl structure on printing function changes in future
2018-08-23 18:53:41 +03:00
* ( apart from quiet mode ) this code may need update
*/
void
vshTablePrintToStdout ( vshTablePtr table , vshControl * ctl )
{
bool header ;
char * out ;
header = ctl ? ! ctl - > quiet : true ;
out = vshTablePrintToString ( table , header ) ;
if ( out )
vshPrint ( ctl , " %s " , out ) ;
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 ) ;
}