diff options
Diffstat (limited to 'libsmartcols/src')
-rw-r--r-- | libsmartcols/src/Makemodule.am | 58 | ||||
-rw-r--r-- | libsmartcols/src/cell.c | 257 | ||||
-rw-r--r-- | libsmartcols/src/column.c | 559 | ||||
-rw-r--r-- | libsmartcols/src/init.c | 61 | ||||
-rw-r--r-- | libsmartcols/src/iter.c | 74 | ||||
-rw-r--r-- | libsmartcols/src/libsmartcols.h.in | 321 | ||||
-rw-r--r-- | libsmartcols/src/libsmartcols.sym | 184 | ||||
-rw-r--r-- | libsmartcols/src/line.c | 517 | ||||
-rw-r--r-- | libsmartcols/src/smartcolsP.h | 216 | ||||
-rw-r--r-- | libsmartcols/src/symbols.c | 173 | ||||
-rw-r--r-- | libsmartcols/src/table.c | 1542 | ||||
-rw-r--r-- | libsmartcols/src/table_print.c | 1748 | ||||
-rw-r--r-- | libsmartcols/src/version.c | 62 |
13 files changed, 5772 insertions, 0 deletions
diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am new file mode 100644 index 0000000..665b2aa --- /dev/null +++ b/libsmartcols/src/Makemodule.am @@ -0,0 +1,58 @@ + + +# smartcols.h is generated, so it's stored in builddir! +smartcolsincdir = $(includedir)/libsmartcols +nodist_smartcolsinc_HEADERS = libsmartcols/src/libsmartcols.h + +usrlib_exec_LTLIBRARIES += libsmartcols.la +libsmartcols_la_SOURCES= \ + include/list.h \ + \ + libsmartcols/src/smartcolsP.h \ + libsmartcols/src/iter.c \ + libsmartcols/src/symbols.c \ + libsmartcols/src/cell.c \ + libsmartcols/src/column.c \ + libsmartcols/src/line.c \ + libsmartcols/src/table.c \ + libsmartcols/src/table_print.c \ + libsmartcols/src/version.c \ + libsmartcols/src/init.c + +libsmartcols_la_LIBADD = $(LDADD) libcommon.la + +libsmartcols_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + -I$(ul_libsmartcols_incdir) \ + -I$(top_srcdir)/libsmartcols/src + +EXTRA_libsmartcols_la_DEPENDENCIES = \ + libsmartcols/src/libsmartcols.sym + +libsmartcols_la_LDFLAGS = $(SOLIB_LDFLAGS) +if HAVE_VSCRIPT +libsmartcols_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libsmartcols/src/libsmartcols.sym +endif +libsmartcols_la_LDFLAGS += -version-info $(LIBSMARTCOLS_VERSION_INFO) + +EXTRA_DIST += \ + libsmartcols/src/libsmartcols.sym + +# move lib from $(usrlib_execdir) to $(libdir) if needed +install-exec-hook-libsmartcols: + if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libsmartcols.so"; then \ + $(MKDIR_P) $(DESTDIR)$(libdir); \ + mv $(DESTDIR)$(usrlib_execdir)/libsmartcols.so.* $(DESTDIR)$(libdir); \ + so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libsmartcols.so); \ + so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \ + (cd $(DESTDIR)$(usrlib_execdir) && \ + rm -f libsmartcols.so && \ + $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libsmartcols.so); \ + fi + +uninstall-hook-libsmartcols: + rm -f $(DESTDIR)$(libdir)/libsmartcols.so* + +INSTALL_EXEC_HOOKS += install-exec-hook-libsmartcols +UNINSTALL_HOOKS += uninstall-hook-libsmartcols diff --git a/libsmartcols/src/cell.c b/libsmartcols/src/cell.c new file mode 100644 index 0000000..0717a2d --- /dev/null +++ b/libsmartcols/src/cell.c @@ -0,0 +1,257 @@ +/* + * cell.c - functions for table handling at the cell level + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: cell + * @title: Cell + * @short_description: container for your data + * + * An API to access and modify per-cell data and information. Note that cell is + * always part of the line. If you destroy (un-reference) a line than it + * destroys all line cells too. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "smartcolsP.h" + +/* + * The cell has no ref-counting, free() and new() functions. All is + * handled by libscols_line. + */ + +/** + * scols_reset_cell: + * @ce: pointer to a struct libscols_cell instance + * + * Frees the cell's internal data and resets its status. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_reset_cell(struct libscols_cell *ce) +{ + if (!ce) + return -EINVAL; + + /*DBG(CELL, ul_debugobj(ce, "reset"));*/ + free(ce->data); + free(ce->color); + memset(ce, 0, sizeof(*ce)); + return 0; +} + +/** + * scols_cell_set_data: + * @ce: a pointer to a struct libscols_cell instance + * @data: data (used for scols_print_table()) + * + * Stores a copy of the @str in @ce, the old data are deallocated by free(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_data(struct libscols_cell *ce, const char *data) +{ + return strdup_to_struct_member(ce, data, data); +} + +/** + * scols_cell_refer_data: + * @ce: a pointer to a struct libscols_cell instance + * @data: data (used for scols_print_table()) + * + * Adds a reference to @str to @ce. The pointer is deallocated by + * scols_reset_cell() or scols_unref_line(). This function is mostly designed + * for situations when the data for the cell are already composed in allocated + * memory (e.g. asprintf()) to avoid extra unnecessary strdup(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_refer_data(struct libscols_cell *ce, char *data) +{ + if (!ce) + return -EINVAL; + free(ce->data); + ce->data = data; + return 0; +} + +/** + * scols_cell_get_data: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: data in @ce or NULL. + */ +const char *scols_cell_get_data(const struct libscols_cell *ce) +{ + return ce ? ce->data : NULL; +} + +/** + * scols_cell_set_userdata: + * @ce: a pointer to a struct libscols_cell instance + * @data: private user data + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_userdata(struct libscols_cell *ce, void *data) +{ + if (!ce) + return -EINVAL; + ce->userdata = data; + return 0; +} + +/** + * scols_cell_get_userdata + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: user data + */ +void *scols_cell_get_userdata(struct libscols_cell *ce) +{ + return ce->userdata; +} + +/** + * scols_cmpstr_cells: + * @a: pointer to cell + * @b: pointer to cell + * @data: unused pointer to private data (defined by API) + * + * Compares cells data by strcmp(). The function is designed for + * scols_column_set_cmpfunc() and scols_sort_table(). + * + * Returns: follows strcmp() return values. + */ +int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, + __attribute__((__unused__)) void *data) +{ + const char *adata, *bdata; + + if (a == b) + return 0; + + adata = scols_cell_get_data(a); + bdata = scols_cell_get_data(b); + + if (adata == NULL && bdata == NULL) + return 0; + if (adata == NULL) + return -1; + if (bdata == NULL) + return 1; + return strcmp(adata, bdata); +} + +/** + * scols_cell_set_color: + * @ce: a pointer to a struct libscols_cell instance + * @color: color name or ESC sequence + * + * Set the color of @ce to @color. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_color(struct libscols_cell *ce, const char *color) +{ + if (color && isalpha(*color)) { + color = color_sequence_from_colorname(color); + if (!color) + return -EINVAL; + } + return strdup_to_struct_member(ce, color, color); +} + +/** + * scols_cell_get_color: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: the current color of @ce. + */ +const char *scols_cell_get_color(const struct libscols_cell *ce) +{ + return ce->color; +} + +/** + * scols_cell_set_flags: + * @ce: a pointer to a struct libscols_cell instance + * @flags: SCOLS_CELL_FL_* flags + * + * Note that cells in the table are always aligned by column flags. The cell + * flags are used for table title only (now). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_flags(struct libscols_cell *ce, int flags) +{ + if (!ce) + return -EINVAL; + ce->flags = flags; + return 0; +} + +/** + * scols_cell_get_flags: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: the current flags + */ +int scols_cell_get_flags(const struct libscols_cell *ce) +{ + return ce->flags; +} + +/** + * scols_cell_get_alignment: + * @ce: a pointer to a struct libscols_cell instance + * + * Since: 2.30 + * + * Returns: SCOLS_CELL_FL_{RIGHT,CELNTER,LEFT} + */ +int scols_cell_get_alignment(const struct libscols_cell *ce) +{ + if (ce->flags & SCOLS_CELL_FL_RIGHT) + return SCOLS_CELL_FL_RIGHT; + else if (ce->flags & SCOLS_CELL_FL_CENTER) + return SCOLS_CELL_FL_CENTER; + + return SCOLS_CELL_FL_LEFT; /* default */ +} + +/** + * scols_cell_copy_content: + * @dest: a pointer to a struct libscols_cell instance + * @src: a pointer to an immutable struct libscols_cell instance + * + * Copy the contents of @src into @dest. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_copy_content(struct libscols_cell *dest, + const struct libscols_cell *src) +{ + int rc; + + rc = scols_cell_set_data(dest, scols_cell_get_data(src)); + if (!rc) + rc = scols_cell_set_color(dest, scols_cell_get_color(src)); + if (!rc) + dest->userdata = src->userdata; + + DBG(CELL, ul_debugobj(src, "copy")); + return rc; +} diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c new file mode 100644 index 0000000..53521f6 --- /dev/null +++ b/libsmartcols/src/column.c @@ -0,0 +1,559 @@ +/* + * column.c - functions for table handling at the column level + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: column + * @title: Column + * @short_description: defines output columns formats, headers, etc. + * + * An API to access and modify per-column data and information. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "mbsalign.h" + +#include "smartcolsP.h" + +/** + * scols_new_column: + * + * Allocates space for a new column. + * + * Returns: a pointer to a new struct libscols_column instance, NULL in case of an ENOMEM error. + */ +struct libscols_column *scols_new_column(void) +{ + struct libscols_column *cl; + + cl = calloc(1, sizeof(*cl)); + if (!cl) + return NULL; + DBG(COL, ul_debugobj(cl, "alloc")); + cl->refcount = 1; + INIT_LIST_HEAD(&cl->cl_columns); + return cl; +} + +/** + * scols_ref_column: + * @cl: a pointer to a struct libscols_column instance + * + * Increases the refcount of @cl. + */ +void scols_ref_column(struct libscols_column *cl) +{ + if (cl) + cl->refcount++; +} + +/** + * scols_unref_column: + * @cl: a pointer to a struct libscols_column instance + * + * Decreases the refcount of @cl. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_column(struct libscols_column *cl) +{ + if (cl && --cl->refcount <= 0) { + DBG(COL, ul_debugobj(cl, "dealloc")); + list_del(&cl->cl_columns); + scols_reset_cell(&cl->header); + free(cl->color); + free(cl->safechars); + free(cl->pending_data_buf); + free(cl); + } +} + +/** + * scols_copy_column: + * @cl: a pointer to a struct libscols_column instance + * + * Creates a new column and copies @cl's data over to it. + * + * Returns: a pointer to a new struct libscols_column instance. + */ +struct libscols_column *scols_copy_column(const struct libscols_column *cl) +{ + struct libscols_column *ret; + + if (!cl) + return NULL; + ret = scols_new_column(); + if (!ret) + return NULL; + + DBG(COL, ul_debugobj(cl, "copy")); + + if (scols_column_set_color(ret, cl->color)) + goto err; + if (scols_cell_copy_content(&ret->header, &cl->header)) + goto err; + + ret->width = cl->width; + ret->width_min = cl->width_min; + ret->width_max = cl->width_max; + ret->width_avg = cl->width_avg; + ret->width_hint = cl->width_hint; + ret->flags = cl->flags; + ret->is_extreme = cl->is_extreme; + + return ret; +err: + scols_unref_column(ret); + return NULL; +} + +/** + * scols_column_set_whint: + * @cl: a pointer to a struct libscols_column instance + * @whint: a width hint + * + * Sets the width hint of column @cl to @whint. See scols_table_new_column(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_whint(struct libscols_column *cl, double whint) +{ + if (!cl) + return -EINVAL; + + cl->width_hint = whint; + return 0; +} + +/** + * scols_column_get_whint: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The width hint of column @cl, a negative value in case of an error. + */ +double scols_column_get_whint(const struct libscols_column *cl) +{ + return cl->width_hint; +} + +/** + * scols_column_set_flags: + * @cl: a pointer to a struct libscols_column instance + * @flags: a flag mask + * + * Sets the flags of @cl to @flags. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_flags(struct libscols_column *cl, int flags) +{ + if (!cl) + return -EINVAL; + + if (cl->table) { + if (!(cl->flags & SCOLS_FL_TREE) && (flags & SCOLS_FL_TREE)) + cl->table->ntreecols++; + else if ((cl->flags & SCOLS_FL_TREE) && !(flags & SCOLS_FL_TREE)) + cl->table->ntreecols--; + } + + DBG(COL, ul_debugobj(cl, "setting flags from 0%x to 0%x", cl->flags, flags)); + cl->flags = flags; + return 0; +} + +/** + * scols_column_set_json_type: + * @cl: a pointer to a struct libscols_column instance + * @type: SCOLS_JSON_* type + * + * Sets the type used for JSON formatting, the default is SCOLS_JSON_STRING. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.33 + */ +int scols_column_set_json_type(struct libscols_column *cl, int type) +{ + if (!cl) + return -EINVAL; + + cl->json_type = type; + return 0; + +} + +/** + * scols_column_get_json_type: + * @cl: a pointer to a struct libscols_column instance + * + * Note that SCOLS_JSON_BOOLEAN interprets NULL, empty strings, '0', 'N' and + * 'n' as "false"; and everything else as "true". + * + * Returns: JSON type used for formatting or a negative value in case of an error. + * + * Since: 2.33 + */ +int scols_column_get_json_type(const struct libscols_column *cl) +{ + return cl ? cl->json_type : -EINVAL; +} + + +/** + * scols_column_get_table: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: pointer to the table where columns is used + */ +struct libscols_table *scols_column_get_table(const struct libscols_column *cl) +{ + return cl->table; +} + +/** + * scols_column_get_flags: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The flag mask of @cl, a negative value in case of an error. + */ +int scols_column_get_flags(const struct libscols_column *cl) +{ + return cl->flags; +} + +/** + * scols_column_get_header: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: A pointer to a struct libscols_cell instance, representing the + * header info of column @cl or NULL in case of an error. + */ +struct libscols_cell *scols_column_get_header(struct libscols_column *cl) +{ + return &cl->header; +} + +/** + * scols_column_set_color: + * @cl: a pointer to a struct libscols_column instance + * @color: color name or ESC sequence + * + * The default color for data cells and column header. + * + * If you want to set header specific color then use scols_column_get_header() + * and scols_cell_set_color(). + * + * If you want to set data cell specific color the use scols_line_get_cell() + + * scols_cell_set_color(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_color(struct libscols_column *cl, const char *color) +{ + if (color && isalpha(*color)) { + color = color_sequence_from_colorname(color); + if (!color) + return -EINVAL; + } + return strdup_to_struct_member(cl, color, color); +} + +/** + * scols_column_get_color: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The current color setting of the column @cl. + */ +const char *scols_column_get_color(const struct libscols_column *cl) +{ + return cl->color; +} + +/** + * scols_wrapnl_nextchunk: + * @cl: a pointer to a struct libscols_column instance + * @data: string + * @userdata: callback private data + * + * This is built-in function for scols_column_set_wrapfunc(). This function + * terminates the current chunk by \0 and returns pointer to the begin of + * the next chunk. The chunks are based on \n. + * + * For example for data "AAA\nBBB\nCCC" the next chunk is "BBB". + * + * Returns: next chunk + * + * Since: 2.29 + */ +char *scols_wrapnl_nextchunk(const struct libscols_column *cl __attribute__((unused)), + char *data, + void *userdata __attribute__((unused))) +{ + char *p = data ? strchr(data, '\n') : NULL; + + if (p) { + *p = '\0'; + return p + 1; + } + return NULL; +} + +/** + * scols_wrapnl_chunksize: + * @cl: a pointer to a struct libscols_column instance + * @data: string + * @userdata: callback private data + * + * Analyzes @data and returns size of the largest chunk. The chunks are based + * on \n. For example for data "AAA\nBBB\nCCCC" the largest chunk size is 4. + * + * Note that the size has to be based on number of terminal cells rather than + * bytes to support multu-byte output. + * + * Returns: size of the largest chunk. + * + * Since: 2.29 + */ +size_t scols_wrapnl_chunksize(const struct libscols_column *cl __attribute__((unused)), + const char *data, + void *userdata __attribute__((unused))) +{ + size_t sum = 0; + + while (data && *data) { + const char *p; + size_t sz; + + p = strchr(data, '\n'); + if (p) { + sz = mbs_safe_nwidth(data, p - data, NULL); + p++; + } else + sz = mbs_safe_width(data); + + sum = max(sum, sz); + data = p; + } + + return sum; +} + +/** + * scols_column_set_cmpfunc: + * @cl: column + * @cmp: pointer to compare function + * @data: private data for cmp function + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *, + struct libscols_cell *, + void *), + void *data) +{ + if (!cl) + return -EINVAL; + + cl->cmpfunc = cmp; + cl->cmpfunc_data = data; + return 0; +} + +/** + * scols_column_set_wrapfunc: + * @cl: a pointer to a struct libscols_column instance + * @wrap_chunksize: function to return size of the largest chink of data + * @wrap_nextchunk: function to return next zero terminated data + * @userdata: optional stuff for callbacks + * + * Extends SCOLS_FL_WRAP and allows to set custom wrap function. The default + * is to wrap by column size, but you can create functions to wrap for example + * after \n or after words, etc. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_column_set_wrapfunc(struct libscols_column *cl, + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, + void *), + char * (*wrap_nextchunk)(const struct libscols_column *, + char *, + void *), + void *userdata) +{ + if (!cl) + return -EINVAL; + + cl->wrap_nextchunk = wrap_nextchunk; + cl->wrap_chunksize = wrap_chunksize; + cl->wrapfunc_data = userdata; + return 0; +} + +/** + * scols_column_set_safechars: + * @cl: a pointer to a struct libscols_column instance + * @safe: safe characters (e.g. "\n\t") + * + * Use for bytes you don't want to encode on output. This is for example + * necessary if you want to use custom wrap function based on \n, in this case + * you have to set "\n" as a safe char. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_column_set_safechars(struct libscols_column *cl, const char *safe) +{ + return strdup_to_struct_member(cl, safechars, safe); +} + +/** + * scols_column_get_safechars: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: safe chars + * + * Since: 2.29 + */ +const char *scols_column_get_safechars(const struct libscols_column *cl) +{ + return cl->safechars; +} + +/** + * scols_column_get_width: + * @cl: a pointer to a struct libscols_column instance + * + * Important note: the column width is unknown until library starts printing + * (width is calculated before printing). The function is usable for example in + * nextchunk() callback specified by scols_column_set_wrapfunc(). + * + * See also scols_column_get_whint(), it returns wanted size (!= final size). + * + * Returns: column width + * + * Since: 2.29 + */ +size_t scols_column_get_width(const struct libscols_column *cl) +{ + return cl->width; +} + +/** + * scols_column_is_hidden: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag hidden. + * + * Returns: 0 or 1 + * + * Since: 2.27 + */ +int scols_column_is_hidden(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_HIDDEN ? 1 : 0; +} + +/** + * scols_column_is_trunc: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag trunc. + * + * Returns: 0 or 1 + */ +int scols_column_is_trunc(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_TRUNC ? 1 : 0; +} +/** + * scols_column_is_tree: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag tree. + * + * Returns: 0 or 1 + */ +int scols_column_is_tree(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_TREE ? 1 : 0; +} +/** + * scols_column_is_right: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag right. + * + * Returns: 0 or 1 + */ +int scols_column_is_right(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_RIGHT ? 1 : 0; +} +/** + * scols_column_is_strict_width: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag strict_width. + * + * Returns: 0 or 1 + */ +int scols_column_is_strict_width(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_STRICTWIDTH ? 1 : 0; +} +/** + * scols_column_is_noextremes: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag no_extremes. + * + * Returns: 0 or 1 + */ +int scols_column_is_noextremes(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_NOEXTREMES ? 1 : 0; +} +/** + * scols_column_is_wrap: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag wrap. + * + * Returns: 0 or 1 + * + * Since: 2.28 + */ +int scols_column_is_wrap(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_WRAP ? 1 : 0; +} +/** + * scols_column_is_customwrap: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: 0 or 1 + * + * Since: 2.29 + */ +int scols_column_is_customwrap(const struct libscols_column *cl) +{ + return (cl->flags & SCOLS_FL_WRAP) + && cl->wrap_chunksize + && cl->wrap_nextchunk ? 1 : 0; +} diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c new file mode 100644 index 0000000..104e43b --- /dev/null +++ b/libsmartcols/src/init.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: init + * @title: Library initialization + * @short_description: initialize debugging + * + * The library debug stuff. + */ + +#include <stdarg.h> + +#include "smartcolsP.h" + +UL_DEBUG_DEFINE_MASK(libsmartcols); +UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) = +{ + { "all", SCOLS_DEBUG_ALL, "info about all subsystems" }, + { "buff", SCOLS_DEBUG_BUFF, "output buffer utils" }, + { "cell", SCOLS_DEBUG_CELL, "table cell utils" }, + { "col", SCOLS_DEBUG_COL, "cols utils" }, + { "help", SCOLS_DEBUG_HELP, "this help" }, + { "line", SCOLS_DEBUG_LINE, "table line utils" }, + { "tab", SCOLS_DEBUG_TAB, "table utils" }, + { NULL, 0, NULL } +}; + +/** + * scols_init_debug: + * @mask: debug mask (0xffff to enable full debugging) + * + * If the @mask is not specified, then this function reads + * the LIBSMARTCOLS_DEBUG environment variable to get the mask. + * + * Already initialized debugging stuff cannot be changed. Calling + * this function twice has no effect. + */ +void scols_init_debug(int mask) +{ + if (libsmartcols_debug_mask) + return; + + __UL_INIT_DEBUG_FROM_ENV(libsmartcols, SCOLS_DEBUG_, mask, LIBSMARTCOLS_DEBUG); + + if (libsmartcols_debug_mask != SCOLS_DEBUG_INIT + && libsmartcols_debug_mask != (SCOLS_DEBUG_HELP|SCOLS_DEBUG_INIT)) { + const char *ver = NULL; + + scols_get_library_version(&ver); + + DBG(INIT, ul_debug("library debug mask: 0x%04x", libsmartcols_debug_mask)); + DBG(INIT, ul_debug("library version: %s", ver)); + } + ON_DBG(HELP, ul_debug_print_masks("LIBSMARTCOLS_DEBUG", + UL_DEBUG_MASKNAMES(libsmartcols))); +} diff --git a/libsmartcols/src/iter.c b/libsmartcols/src/iter.c new file mode 100644 index 0000000..91cc080 --- /dev/null +++ b/libsmartcols/src/iter.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: iter + * @title: Iterator + * @short_description: unified iterator + * + * The iterator keeps the direction and the last position + * for access to the internal library tables/lists. + */ + +#include <string.h> +#include <stdlib.h> + +#include "smartcolsP.h" + +/** + * scols_new_iter: + * @direction: SCOLS_INTER_{FOR,BACK}WARD direction + * + * Returns: newly allocated generic libmount iterator. + */ +struct libscols_iter *scols_new_iter(int direction) +{ + struct libscols_iter *itr = calloc(1, sizeof(*itr)); + if (!itr) + return NULL; + itr->direction = direction; + return itr; +} + +/** + * scols_free_iter: + * @itr: iterator pointer + * + * Deallocates the iterator. + */ +void scols_free_iter(struct libscols_iter *itr) +{ + free(itr); +} + +/** + * scols_reset_iter: + * @itr: iterator pointer + * @direction: SCOLS_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged + * + * Resets the iterator. + */ +void scols_reset_iter(struct libscols_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +/** + * scols_iter_get_direction: + * @itr: iterator pointer + * + * Returns: SCOLS_INTER_{FOR,BACK}WARD + */ +int scols_iter_get_direction(const struct libscols_iter *itr) +{ + return itr->direction; +} diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in new file mode 100644 index 0000000..f8be0bc --- /dev/null +++ b/libsmartcols/src/libsmartcols.h.in @@ -0,0 +1,321 @@ +/* + * Prints table or tree. + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef _LIBSMARTCOLS_H +#define _LIBSMARTCOLS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> + +/** + * LIBSMARTCOLS_VERSION: + * + * Library version string + */ +#define LIBSMARTCOLS_VERSION "@LIBSMARTCOLS_VERSION@" + +/** + * libscols_iter: + * + * Generic iterator + */ +struct libscols_iter; + +/** + * libscols_symbols: + * + * Symbol groups for printing tree hierarchies + */ +struct libscols_symbols; + +/** + * libscols_cell: + * + * A cell - the smallest library object + */ +struct libscols_cell; + +/** + * libscols_line: + * + * A line - an array of cells + */ +struct libscols_line; + +/** + * libscols_table: + * + * A table - The most abstract object, encapsulating lines, columns, symbols and cells + */ +struct libscols_table; + +/** + * libscols_column: + * + * A column - defines the number of columns and column names + */ +struct libscols_column; + +/* iter.c */ +enum { + + SCOLS_ITER_FORWARD = 0, + SCOLS_ITER_BACKWARD +}; + +/* + * Column flags + */ +enum { + SCOLS_FL_TRUNC = (1 << 0), /* truncate fields data if necessary */ + SCOLS_FL_TREE = (1 << 1), /* use tree "ascii art" */ + SCOLS_FL_RIGHT = (1 << 2), /* align to the right */ + SCOLS_FL_STRICTWIDTH = (1 << 3), /* don't reduce width if column is empty */ + SCOLS_FL_NOEXTREMES = (1 << 4), /* ignore extreme fields when count column width*/ + SCOLS_FL_HIDDEN = (1 << 5), /* maintain data, but don't print */ + SCOLS_FL_WRAP = (1 << 6) /* wrap long lines to multi-line cells */ +}; + +/* + * Column JSON types + */ +enum { + SCOLS_JSON_STRING = 0, /* default */ + SCOLS_JSON_NUMBER = 1, + SCOLS_JSON_BOOLEAN = 2 +}; + +/* + * Cell flags, see scols_cell_set_flags() before use + */ +enum { + /* alignment evaluated in order: right,center,left */ + SCOLS_CELL_FL_LEFT = 0, + SCOLS_CELL_FL_CENTER = (1 << 0), + SCOLS_CELL_FL_RIGHT = (1 << 1) +}; + +extern struct libscols_iter *scols_new_iter(int direction); +extern void scols_free_iter(struct libscols_iter *itr); +extern void scols_reset_iter(struct libscols_iter *itr, int direction); +extern int scols_iter_get_direction(const struct libscols_iter *itr); + +/* init.c */ +extern void scols_init_debug(int mask); + +/* version.c */ +extern int scols_parse_version_string(const char *ver_string); +extern int scols_get_library_version(const char **ver_string); + +/* symbols.c */ +extern struct libscols_symbols *scols_new_symbols(void); +extern void scols_ref_symbols(struct libscols_symbols *sy); +extern void scols_unref_symbols(struct libscols_symbols *sy); +extern struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy); +extern int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_right(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str); + +/* cell.c */ +extern int scols_reset_cell(struct libscols_cell *ce); +extern int scols_cell_copy_content(struct libscols_cell *dest, + const struct libscols_cell *src); +extern int scols_cell_set_data(struct libscols_cell *ce, const char *data); +extern int scols_cell_refer_data(struct libscols_cell *ce, char *data); +extern const char *scols_cell_get_data(const struct libscols_cell *ce); +extern int scols_cell_set_color(struct libscols_cell *ce, const char *color); +extern const char *scols_cell_get_color(const struct libscols_cell *ce); + +extern int scols_cell_set_flags(struct libscols_cell *ce, int flags); +extern int scols_cell_get_flags(const struct libscols_cell *ce); +extern int scols_cell_get_alignment(const struct libscols_cell *ce); + +extern void *scols_cell_get_userdata(struct libscols_cell *ce); +extern int scols_cell_set_userdata(struct libscols_cell *ce, void *data); + +extern int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, void *data); +/* column.c */ +extern int scols_column_is_tree(const struct libscols_column *cl); +extern int scols_column_is_trunc(const struct libscols_column *cl); +extern int scols_column_is_right(const struct libscols_column *cl); +extern int scols_column_is_strict_width(const struct libscols_column *cl); +extern int scols_column_is_hidden(const struct libscols_column *cl); +extern int scols_column_is_noextremes(const struct libscols_column *cl); +extern int scols_column_is_wrap(const struct libscols_column *cl); +extern int scols_column_is_customwrap(const struct libscols_column *cl); + +extern size_t scols_column_get_width(const struct libscols_column *cl); + +extern int scols_column_set_safechars(struct libscols_column *cl, const char *safe); +extern const char *scols_column_get_safechars(const struct libscols_column *cl); + +extern int scols_column_set_json_type(struct libscols_column *cl, int type); +extern int scols_column_get_json_type(const struct libscols_column *cl); + +extern int scols_column_set_flags(struct libscols_column *cl, int flags); +extern int scols_column_get_flags(const struct libscols_column *cl); +extern struct libscols_column *scols_new_column(void); +extern void scols_ref_column(struct libscols_column *cl); +extern void scols_unref_column(struct libscols_column *cl); +extern struct libscols_column *scols_copy_column(const struct libscols_column *cl); +extern int scols_column_set_whint(struct libscols_column *cl, double whint); +extern double scols_column_get_whint(const struct libscols_column *cl); +extern struct libscols_cell *scols_column_get_header(struct libscols_column *cl); +extern int scols_column_set_color(struct libscols_column *cl, const char *color); +extern const char *scols_column_get_color(const struct libscols_column *cl); +extern struct libscols_table *scols_column_get_table(const struct libscols_column *cl); + +extern int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *a, + struct libscols_cell *b, void *), + void *data); + +extern int scols_column_set_wrapfunc(struct libscols_column *cl, + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, void *), + char * (*wrap_nextchunk)(const struct libscols_column *, + char *, void *), + void *userdata); + +extern char *scols_wrapnl_nextchunk(const struct libscols_column *cl, char *data, void *userdata); +extern size_t scols_wrapnl_chunksize(const struct libscols_column *cl, const char *data, void *userdata); + +/* line.c */ +extern struct libscols_line *scols_new_line(void); +extern void scols_ref_line(struct libscols_line *ln); +extern void scols_unref_line(struct libscols_line *ln); +extern int scols_line_alloc_cells(struct libscols_line *ln, size_t n); +extern void scols_line_free_cells(struct libscols_line *ln); +extern int scols_line_set_userdata(struct libscols_line *ln, void *data); +extern void *scols_line_get_userdata(struct libscols_line *ln); +extern int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child); +extern int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child); +extern int scols_line_has_children(struct libscols_line *ln); +extern int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent); +extern int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, struct libscols_line **chld); +extern struct libscols_line *scols_line_get_parent(const struct libscols_line *ln); +extern int scols_line_set_color(struct libscols_line *ln, const char *color); +extern const char *scols_line_get_color(const struct libscols_line *ln); +extern size_t scols_line_get_ncells(const struct libscols_line *ln); +extern struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, size_t n); +extern struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl); +extern int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data); +extern int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data); +extern int scols_line_set_column_data(struct libscols_line *ln, struct libscols_column *cl, const char *data); +extern int scols_line_refer_column_data(struct libscols_line *ln, struct libscols_column *cl, char *data); +extern struct libscols_line *scols_copy_line(const struct libscols_line *ln); + +/* table */ +extern int scols_table_colors_wanted(const struct libscols_table *tb); +extern int scols_table_set_name(struct libscols_table *tb, const char *name); +extern const char *scols_table_get_name(const struct libscols_table *tb); +extern struct libscols_cell *scols_table_get_title(struct libscols_table *tb); +extern int scols_table_is_raw(const struct libscols_table *tb); +extern int scols_table_is_ascii(const struct libscols_table *tb); +extern int scols_table_is_json(const struct libscols_table *tb); +extern int scols_table_is_noheadings(const struct libscols_table *tb); +extern int scols_table_is_header_repeat(const struct libscols_table *tb); +extern int scols_table_is_empty(const struct libscols_table *tb); +extern int scols_table_is_export(const struct libscols_table *tb); +extern int scols_table_is_maxout(const struct libscols_table *tb); +extern int scols_table_is_nowrap(const struct libscols_table *tb); +extern int scols_table_is_nolinesep(const struct libscols_table *tb); +extern int scols_table_is_tree(const struct libscols_table *tb); +extern int scols_table_is_noencoding(const struct libscols_table *tb); + +extern int scols_table_enable_colors(struct libscols_table *tb, int enable); +extern int scols_table_enable_raw(struct libscols_table *tb, int enable); +extern int scols_table_enable_ascii(struct libscols_table *tb, int enable); +extern int scols_table_enable_json(struct libscols_table *tb, int enable); +extern int scols_table_enable_noheadings(struct libscols_table *tb, int enable); +extern int scols_table_enable_header_repeat(struct libscols_table *tb, int enable); +extern int scols_table_enable_export(struct libscols_table *tb, int enable); +extern int scols_table_enable_maxout(struct libscols_table *tb, int enable); +extern int scols_table_enable_nowrap(struct libscols_table *tb, int enable); +extern int scols_table_enable_nolinesep(struct libscols_table *tb, int enable); +extern int scols_table_enable_noencoding(struct libscols_table *tb, int enable); + +extern int scols_table_set_column_separator(struct libscols_table *tb, const char *sep); +extern int scols_table_set_line_separator(struct libscols_table *tb, const char *sep); + +extern struct libscols_table *scols_new_table(void); +extern void scols_ref_table(struct libscols_table *tb); +extern void scols_unref_table(struct libscols_table *tb); +extern int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_table_remove_column(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_table_remove_columns(struct libscols_table *tb); +extern int scols_table_move_column(struct libscols_table *tb, struct libscols_column *pre, struct libscols_column *cl); +extern struct libscols_column *scols_table_new_column(struct libscols_table *tb, const char *name, double whint, int flags); +extern int scols_table_next_column(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column **cl); +extern const char *scols_table_get_column_separator(const struct libscols_table *tb); +extern const char *scols_table_get_line_separator(const struct libscols_table *tb); +extern size_t scols_table_get_ncols(const struct libscols_table *tb); +extern size_t scols_table_get_nlines(const struct libscols_table *tb); +extern struct libscols_column *scols_table_get_column(struct libscols_table *tb, size_t n); +extern int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln); +extern int scols_table_remove_line(struct libscols_table *tb, struct libscols_line *ln); +extern void scols_table_remove_lines(struct libscols_table *tb); +extern int scols_table_next_line(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_line **ln); +extern struct libscols_line *scols_table_new_line(struct libscols_table *tb, struct libscols_line *parent); +extern struct libscols_line *scols_table_get_line(struct libscols_table *tb, size_t n); +extern struct libscols_table *scols_copy_table(struct libscols_table *tb); +extern int scols_table_set_symbols(struct libscols_table *tb, struct libscols_symbols *sy); +extern int scols_table_set_default_symbols(struct libscols_table *tb); +extern struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb); + +extern int scols_table_set_stream(struct libscols_table *tb, FILE *stream); +extern FILE *scols_table_get_stream(const struct libscols_table *tb); +extern int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce); + +extern int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_sort_table_by_tree(struct libscols_table *tb); +/* + * + */ +enum { + SCOLS_TERMFORCE_AUTO = 0, + SCOLS_TERMFORCE_NEVER, + SCOLS_TERMFORCE_ALWAYS +}; +extern int scols_table_set_termforce(struct libscols_table *tb, int force); +extern int scols_table_get_termforce(const struct libscols_table *tb); +extern int scols_table_set_termwidth(struct libscols_table *tb, size_t width); +extern size_t scols_table_get_termwidth(const struct libscols_table *tb); +extern int scols_table_set_termheight(struct libscols_table *tb, size_t height); +extern size_t scols_table_get_termheight(const struct libscols_table *tb); + + +/* table_print.c */ +extern int scols_print_table(struct libscols_table *tb); +extern int scols_print_table_to_string(struct libscols_table *tb, char **data); + +extern int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end); +extern int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSMARTCOLS_H */ diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym new file mode 100644 index 0000000..331a555 --- /dev/null +++ b/libsmartcols/src/libsmartcols.sym @@ -0,0 +1,184 @@ +/* + * symbols since util-linux 2.25 + * + * Copyright (C) 2014-2016 Karel Zak <kzak@redhat.com> + */ +SMARTCOLS_2.25 { +global: + scols_cell_copy_content; + scols_cell_get_color; + scols_cell_get_data; + scols_cell_get_userdata; + scols_cell_refer_data; + scols_cell_set_color; + scols_cell_set_data; + scols_cell_set_userdata; + scols_cmpstr_cells; + scols_column_get_color; + scols_column_get_flags; + scols_column_get_header; + scols_column_get_whint; + scols_column_is_noextremes; + scols_column_is_right; + scols_column_is_strict_width; + scols_column_is_tree; + scols_column_is_trunc; + scols_column_set_cmpfunc; + scols_column_set_color; + scols_column_set_flags; + scols_column_set_whint; + scols_copy_column; + scols_copy_line; + scols_copy_symbols; + scols_copy_table; + scols_free_iter; + scols_get_library_version; + scols_init_debug; + scols_iter_get_direction; + scols_line_add_child; + scols_line_alloc_cells; + scols_line_free_cells; + scols_line_get_cell; + scols_line_get_color; + scols_line_get_column_cell; + scols_line_get_ncells; + scols_line_get_parent; + scols_line_get_userdata; + scols_line_has_children; + scols_line_next_child; + scols_line_refer_data; + scols_line_remove_child; + scols_line_set_color; + scols_line_set_data; + scols_line_set_userdata; + scols_new_column; + scols_new_iter; + scols_new_line; + scols_new_symbols; + scols_new_table; + scols_parse_version_string; + scols_print_table; + scols_print_table_to_string; + scols_ref_column; + scols_ref_line; + scols_ref_symbols; + scols_ref_table; + scols_reset_cell; + scols_reset_iter; + scols_sort_table; + scols_symbols_set_branch; + scols_symbols_set_right; + scols_symbols_set_vertical; + scols_table_add_column; + scols_table_add_line; + scols_table_colors_wanted; + scols_table_enable_ascii; + scols_table_enable_colors; + scols_table_enable_export; + scols_table_enable_maxout; + scols_table_enable_noheadings; + scols_table_enable_raw; + scols_table_get_column; + scols_table_get_column_separator; + scols_table_get_line; + scols_table_get_line_separator; + scols_table_get_ncols; + scols_table_get_nlines; + scols_table_get_stream; + scols_table_is_ascii; + scols_table_is_empty; + scols_table_is_export; + scols_table_is_maxout; + scols_table_is_noheadings; + scols_table_is_raw; + scols_table_is_tree; + scols_table_new_column; + scols_table_new_line; + scols_table_next_column; + scols_table_next_line; + scols_table_reduce_termwidth; + scols_table_remove_column; + scols_table_remove_columns; + scols_table_remove_line; + scols_table_remove_lines; + scols_table_set_column_separator; + scols_table_set_line_separator; + scols_table_set_stream; + scols_table_set_symbols; + scols_unref_column; + scols_unref_line; + scols_unref_symbols; + scols_unref_table; +local: + *; +}; + +SMARTCOLS_2.27 { +global: + scols_column_is_hidden; + scols_table_enable_json; + scols_table_is_json; + scols_table_set_name; +} SMARTCOLS_2.25; + +SMARTCOLS_2.28 { +global: + scols_column_is_wrap; + scols_line_refer_column_data; + scols_line_set_column_data; + scols_symbols_set_title_padding; + scols_table_enable_nowrap; + scols_table_get_title; + scols_cell_get_flags; + scols_cell_set_flags; + scols_table_print_range; + scols_table_print_range_to_string; + scols_table_enable_nolinesep; +} SMARTCOLS_2.27; + +SMARTCOLS_2.29 { +global: + scols_column_get_safechars; + scols_column_get_table; + scols_column_get_width; + scols_column_is_customwrap; + scols_column_set_safechars; + scols_column_set_wrapfunc; + scols_symbols_set_cell_padding; + scols_table_get_name; + scols_table_get_symbols; + scols_table_get_termforce; + scols_table_get_termwidth; + scols_table_is_nolinesep; + scols_table_is_nowrap; + scols_table_set_default_symbols; + scols_table_set_termforce; + scols_table_set_termwidth; + scols_wrapnl_chunksize; + scols_wrapnl_nextchunk; +} SMARTCOLS_2.28; + + +SMARTCOLS_2.30 { +global: + scols_cell_get_alignment; + scols_table_move_column; + scols_sort_table_by_tree; + scols_line_is_ancestor; +} SMARTCOLS_2.29; + + +SMARTCOLS_2.31 { + scols_table_set_termheight; + scols_table_get_termheight; + scols_table_is_header_repeat; + scols_table_enable_header_repeat; + scols_table_enable_noencoding; + scols_table_is_noencoding; +} SMARTCOLS_2.30; + + +SMARTCOLS_2.33 { + scols_column_set_json_type; + scols_column_get_json_type; +} SMARTCOLS_2.31; diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c new file mode 100644 index 0000000..60be2c1 --- /dev/null +++ b/libsmartcols/src/line.c @@ -0,0 +1,517 @@ +/* + * line.c - functions for table handling at the line level + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: line + * @title: Line + * @short_description: cells container, also keeps tree (parent->child) information + * + * An API to access and modify per-line data and information. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "smartcolsP.h" + +/** + * scols_new_line: + * + * Note that the line is allocated without cells, the cells will be allocated + * later when you add the line to the table. If you want to use the line + * without table then you have to explicitly allocate the cells by + * scols_line_alloc_cells(). + * + * Returns: a pointer to a new struct libscols_line instance. + */ +struct libscols_line *scols_new_line(void) +{ + struct libscols_line *ln; + + ln = calloc(1, sizeof(*ln)); + if (!ln) + return NULL; + + DBG(LINE, ul_debugobj(ln, "alloc")); + ln->refcount = 1; + INIT_LIST_HEAD(&ln->ln_lines); + INIT_LIST_HEAD(&ln->ln_children); + INIT_LIST_HEAD(&ln->ln_branch); + return ln; +} + +/** + * scols_ref_line: + * @ln: a pointer to a struct libscols_line instance + * + * Increases the refcount of @ln. + */ +void scols_ref_line(struct libscols_line *ln) +{ + if (ln) + ln->refcount++; +} + +/** + * scols_unref_line: + * @ln: a pointer to a struct libscols_line instance + * + * Decreases the refcount of @ln. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_line(struct libscols_line *ln) +{ + if (ln && --ln->refcount <= 0) { + DBG(CELL, ul_debugobj(ln, "dealloc")); + list_del(&ln->ln_lines); + list_del(&ln->ln_children); + scols_line_free_cells(ln); + free(ln->color); + free(ln); + return; + } +} + +/** + * scols_line_free_cells: + * @ln: a pointer to a struct libscols_line instance + * + * Frees the allocated cells referenced to by @ln. + */ +void scols_line_free_cells(struct libscols_line *ln) +{ + size_t i; + + if (!ln || !ln->cells) + return; + + DBG(LINE, ul_debugobj(ln, "free cells")); + + for (i = 0; i < ln->ncells; i++) + scols_reset_cell(&ln->cells[i]); + + free(ln->cells); + ln->ncells = 0; + ln->cells = NULL; +} + +/** + * scols_line_alloc_cells: + * @ln: a pointer to a struct libscols_line instance + * @n: the number of elements + * + * Allocates space for @n cells. This function is optional, + * and libsmartcols automatically allocates necessary cells + * according to number of columns in the table when you add + * the line to the table. See scols_table_add_line(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_alloc_cells(struct libscols_line *ln, size_t n) +{ + struct libscols_cell *ce; + + if (!ln) + return -EINVAL; + if (ln->ncells == n) + return 0; + + if (!n) { + scols_line_free_cells(ln); + return 0; + } + + DBG(LINE, ul_debugobj(ln, "alloc %zu cells", n)); + + ce = realloc(ln->cells, n * sizeof(struct libscols_cell)); + if (!ce) + return -errno; + + if (n > ln->ncells) + memset(ce + ln->ncells, 0, + (n - ln->ncells) * sizeof(struct libscols_cell)); + + ln->cells = ce; + ln->ncells = n; + return 0; +} + +int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn) +{ + struct libscols_cell ce; + + if (!ln || newn >= ln->ncells || oldn >= ln->ncells) + return -EINVAL; + if (oldn == newn) + return 0; + + DBG(LINE, ul_debugobj(ln, "move cells[%zu] -> cells[%zu]", oldn, newn)); + + /* remember data from old position */ + memcpy(&ce, &ln->cells[oldn], sizeof(struct libscols_cell)); + + /* remove old position (move data behind oldn to oldn) */ + if (oldn + 1 < ln->ncells) + memmove(ln->cells + oldn, ln->cells + oldn + 1, + (ln->ncells - oldn - 1) * sizeof(struct libscols_cell)); + + /* create a space for new position */ + if (newn + 1 < ln->ncells) + memmove(ln->cells + newn + 1, ln->cells + newn, + (ln->ncells - newn - 1) * sizeof(struct libscols_cell)); + + /* copy original data to new position */ + memcpy(&ln->cells[newn], &ce, sizeof(struct libscols_cell)); + return 0; +} + +/** + * scols_line_set_userdata: + * @ln: a pointer to a struct libscols_line instance + * @data: user data + * + * Binds @data to @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_userdata(struct libscols_line *ln, void *data) +{ + if (!ln) + return -EINVAL; + ln->userdata = data; + return 0; +} + +/** + * scols_line_get_userdata: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: user data + */ +void *scols_line_get_userdata(struct libscols_line *ln) +{ + return ln->userdata; +} + +/** + * scols_line_remove_child: + * @ln: a pointer to a struct libscols_line instance + * @child: a pointer to a struct libscols_line instance + * + * Removes @child as a child of @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child) +{ + if (!ln || !child) + return -EINVAL; + + DBG(LINE, ul_debugobj(ln, "remove child")); + + list_del_init(&child->ln_children); + child->parent = NULL; + scols_unref_line(child); + + scols_unref_line(ln); + return 0; +} + +/** + * scols_line_add_child: + * @ln: a pointer to a struct libscols_line instance + * @child: a pointer to a struct libscols_line instance + * + * Sets @child as a child of @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child) +{ + if (!ln || !child) + return -EINVAL; + + DBG(LINE, ul_debugobj(ln, "add child")); + scols_ref_line(child); + scols_ref_line(ln); + + /* unref old<->parent */ + if (child->parent) + scols_line_remove_child(child->parent, child); + + /* new reference from parent to child */ + list_add_tail(&child->ln_children, &ln->ln_branch); + + /* new reference from child to parent */ + child->parent = ln; + return 0; +} + +/** + * scols_line_get_parent: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: a pointer to @ln's parent, NULL in case it has no parent or if there was an error. + */ +struct libscols_line *scols_line_get_parent(const struct libscols_line *ln) +{ + return ln ? ln->parent : NULL; +} + +/** + * scols_line_has_children: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: 1 if @ln has any children, otherwise 0. + */ +int scols_line_has_children(struct libscols_line *ln) +{ + return ln ? !list_empty(&ln->ln_branch) : 0; +} + +/** + * scols_line_next_child: + * @ln: a pointer to a struct libscols_line instance + * @itr: a pointer to a struct libscols_iter instance + * @chld: a pointer to a pointer to a struct libscols_line instance + * + * Finds the next child and returns a pointer to it via @chld. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld) +{ + int rc = 1; + + if (!ln || !itr || !chld) + return -EINVAL; + *chld = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &ln->ln_branch); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children); + rc = 0; + } + + return rc; +} + + +/** + * scols_line_is_ancestor: + * @ln: line + * @parent: potential parent + * + * The function is designed to detect circular dependencies between @ln and + * @parent. It checks if @ln is not any (grand) parent in the @parent's tree. + * + * Since: 2.30 + * + * Returns: 0 or 1 + */ +int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent) +{ + while (parent) { + if (parent == ln) + return 1; + parent = scols_line_get_parent(parent); + }; + return 0; +} + +/** + * scols_line_set_color: + * @ln: a pointer to a struct libscols_line instance + * @color: color name or ESC sequence + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_color(struct libscols_line *ln, const char *color) +{ + if (color && isalnum(*color)) { + color = color_sequence_from_colorname(color); + if (!color) + return -EINVAL; + } + return strdup_to_struct_member(ln, color, color); +} + +/** + * scols_line_get_color: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: @ln's color string, NULL in case of an error. + */ +const char *scols_line_get_color(const struct libscols_line *ln) +{ + return ln->color; +} + +/** + * scols_line_get_ncells: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: number of cells + */ +size_t scols_line_get_ncells(const struct libscols_line *ln) +{ + return ln->ncells; +} + +/** + * scols_line_get_cell: + * @ln: a pointer to a struct libscols_line instance + * @n: cell number to retrieve + * + * Returns: the @n-th cell in @ln, NULL in case of an error. + */ +struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, + size_t n) +{ + if (!ln || n >= ln->ncells) + return NULL; + return &ln->cells[n]; +} + +/** + * scols_line_get_column_cell: + * @ln: a pointer to a struct libscols_line instance + * @cl: pointer to cell + * + * Like scols_line_get_cell() by cell is referenced by column. + * + * Returns: the @n-th cell in @ln, NULL in case of an error. + */ +struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl) +{ + if (!ln || !cl) + return NULL; + + return scols_line_get_cell(ln, cl->seqnum); +} + +/** + * scols_line_set_data: + * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell, whose data is to be set + * @data: actual data to set + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, n); + + if (!ce) + return -EINVAL; + return scols_cell_set_data(ce, data); +} + +/** + * scols_line_set_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be set + * @data: actual data to set + * + * The same as scols_line_set_data() but cell is referenced by column object. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_line_set_column_data(struct libscols_line *ln, + struct libscols_column *cl, + const char *data) +{ + return scols_line_set_data(ln, cl->seqnum, data); +} + +/** + * scols_line_refer_data: + * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell which will refer to @data + * @data: actual data to refer to + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, n); + + if (!ce) + return -EINVAL; + return scols_cell_refer_data(ce, data); +} + +/** + * scols_line_refer_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be set + * @data: actual data to refer to + * + * The same as scols_line_refer_data() but cell is referenced by column object. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_line_refer_column_data(struct libscols_line *ln, + struct libscols_column *cl, + char *data) +{ + return scols_line_refer_data(ln, cl->seqnum, data); +} + +/** + * scols_copy_line: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: A newly allocated copy of @ln, NULL in case of an error. + */ +struct libscols_line *scols_copy_line(const struct libscols_line *ln) +{ + struct libscols_line *ret; + size_t i; + + if (!ln) + return NULL; + + ret = scols_new_line(); + if (!ret) + return NULL; + if (scols_line_set_color(ret, ln->color)) + goto err; + if (scols_line_alloc_cells(ret, ln->ncells)) + goto err; + + ret->userdata = ln->userdata; + ret->ncells = ln->ncells; + ret->seqnum = ln->seqnum; + + DBG(LINE, ul_debugobj(ln, "copy")); + + for (i = 0; i < ret->ncells; ++i) { + if (scols_cell_copy_content(&ret->cells[i], &ln->cells[i])) + goto err; + } + + return ret; +err: + scols_unref_line(ret); + return NULL; +} diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h new file mode 100644 index 0000000..f45ac3a --- /dev/null +++ b/libsmartcols/src/smartcolsP.h @@ -0,0 +1,216 @@ +/* + * smartcolsP.h - private library header file + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#ifndef _LIBSMARTCOLS_PRIVATE_H +#define _LIBSMARTCOLS_PRIVATE_H + +#include "c.h" +#include "list.h" +#include "strutils.h" +#include "color-names.h" +#include "debug.h" + +#include "libsmartcols.h" + +/* + * Debug + */ +#define SCOLS_DEBUG_HELP (1 << 0) +#define SCOLS_DEBUG_INIT (1 << 1) +#define SCOLS_DEBUG_CELL (1 << 2) +#define SCOLS_DEBUG_LINE (1 << 3) +#define SCOLS_DEBUG_TAB (1 << 4) +#define SCOLS_DEBUG_COL (1 << 5) +#define SCOLS_DEBUG_BUFF (1 << 6) +#define SCOLS_DEBUG_ALL 0xFFFF + +UL_DEBUG_DECLARE_MASK(libsmartcols); +#define DBG(m, x) __UL_DBG(libsmartcols, SCOLS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(libsmartcols, SCOLS_DEBUG_, m, x) +#define DBG_FLUSH __UL_DBG_FLUSH(libsmartcols, SCOLS_DEBUG_) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libsmartcols) +#include "debugobj.h" + +/* + * Generic iterator + */ +struct libscols_iter { + struct list_head *p; /* current position */ + struct list_head *head; /* start position */ + int direction; /* SCOLS_ITER_{FOR,BACK}WARD */ +}; + +/* + * Tree symbols + */ +struct libscols_symbols { + int refcount; + char *branch; + char *vert; + char *right; + char *title_padding; + char *cell_padding; +}; + +/* + * Table cells + */ +struct libscols_cell { + char *data; + char *color; + void *userdata; + int flags; +}; + +extern int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn); + +/* + * Table column + */ +struct libscols_column { + int refcount; /* reference counter */ + size_t seqnum; /* column index */ + + size_t width; /* real column width */ + size_t width_min; /* minimal width (usually header width) */ + size_t width_max; /* maximal width */ + size_t width_avg; /* average width, used to detect extreme fields */ + size_t width_treeart; /* size of the tree ascii art */ + double width_hint; /* hint (N < 1 is in percent of termwidth) */ + + int json_type; /* SCOLS_JSON_* */ + + int flags; + int is_extreme; + char *color; /* default column color */ + char *safechars; /* do not encode this bytes */ + + char *pending_data; + size_t pending_data_sz; + char *pending_data_buf; + + int (*cmpfunc)(struct libscols_cell *, + struct libscols_cell *, + void *); /* cells comparison function */ + void *cmpfunc_data; + + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, void *); + char *(*wrap_nextchunk)(const struct libscols_column *, + char *, void *); + void *wrapfunc_data; + + + struct libscols_cell header; + struct list_head cl_columns; + + struct libscols_table *table; +}; + +/* + * Table line + */ +struct libscols_line { + int refcount; + size_t seqnum; + + void *userdata; + char *color; /* default line color */ + + struct libscols_cell *cells; /* array with data */ + size_t ncells; /* number of cells */ + + struct list_head ln_lines; /* table lines */ + struct list_head ln_branch; /* begin of branch (head of ln_children) */ + struct list_head ln_children; + + struct libscols_line *parent; +}; + +enum { + SCOLS_FMT_HUMAN = 0, /* default, human readable */ + SCOLS_FMT_RAW, /* space separated */ + SCOLS_FMT_EXPORT, /* COLNAME="data" ... */ + SCOLS_FMT_JSON /* http://en.wikipedia.org/wiki/JSON */ +}; + +/* + * The table + */ +struct libscols_table { + int refcount; + char *name; /* optional table name (for JSON) */ + size_t ncols; /* number of columns */ + size_t ntreecols; /* number of columns with SCOLS_FL_TREE */ + size_t nlines; /* number of lines */ + size_t termwidth; /* terminal width (number of columns) */ + size_t termheight; /* terminal height (number of lines) */ + size_t termreduce; /* extra blank space */ + int termforce; /* SCOLS_TERMFORCE_* */ + FILE *out; /* output stream */ + + char *colsep; /* column separator */ + char *linesep; /* line separator */ + + struct list_head tb_columns; + struct list_head tb_lines; + struct libscols_symbols *symbols; + struct libscols_cell title; /* optional table title (for humans) */ + + int indent; /* indention counter */ + int indent_last_sep;/* last printed has been line separator */ + int format; /* SCOLS_FMT_* */ + + size_t termlines_used; /* printed line counter */ + size_t header_next; /* where repeat header */ + + /* flags */ + unsigned int ascii :1, /* don't use unicode */ + colors_wanted :1, /* enable colors */ + is_term :1, /* isatty() */ + padding_debug :1, /* output visible padding chars */ + maxout :1, /* maximize output */ + header_repeat :1, /* print header after libscols_table->termheight */ + header_printed :1, /* header already printed */ + priv_symbols :1, /* default private symbols */ + no_headings :1, /* don't print header */ + no_encode :1, /* don't care about control and non-printable chars */ + no_linesep :1, /* don't print line separator */ + no_wrap :1; /* never wrap lines */ +}; + +#define IS_ITER_FORWARD(_i) ((_i)->direction == SCOLS_ITER_FORWARD) +#define IS_ITER_BACKWARD(_i) ((_i)->direction == SCOLS_ITER_BACKWARD) + +#define SCOLS_ITER_INIT(itr, list) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (list)->next : (list)->prev; \ + (itr)->head = (list); \ + } while(0) + +#define SCOLS_ITER_ITERATE(itr, res, restype, member) \ + do { \ + res = list_entry((itr)->p, restype, member); \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (itr)->p->next : (itr)->p->prev; \ + } while(0) + + +static inline int scols_iter_is_last(const struct libscols_iter *itr) +{ + if (!itr || !itr->head || !itr->p) + return 0; + + return itr->p == itr->head; +} + +#endif /* _LIBSMARTCOLS_PRIVATE_H */ diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c new file mode 100644 index 0000000..6ddf186 --- /dev/null +++ b/libsmartcols/src/symbols.c @@ -0,0 +1,173 @@ +/* + * symbols.c - routines for symbol handling + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: symbols + * @title: Symbols + * @short_description: allows to overwrite default output chars (for ascii art) + * + * An API to access and modify data and information per symbol/symbol group. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "smartcolsP.h" + +/** + * scols_new_symbols: + * + * Returns: a pointer to a newly allocated struct libscols_symbols instance. + */ +struct libscols_symbols *scols_new_symbols(void) +{ + struct libscols_symbols *sy = calloc(1, sizeof(struct libscols_symbols)); + + if (!sy) + return NULL; + sy->refcount = 1; + return sy; +} + +/** + * scols_ref_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Increases the refcount of @sy. + */ +void scols_ref_symbols(struct libscols_symbols *sy) +{ + if (sy) + sy->refcount++; +} + +/** + * scols_unref_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Decreases the refcount of @sy. + */ +void scols_unref_symbols(struct libscols_symbols *sy) +{ + if (sy && --sy->refcount <= 0) { + free(sy->branch); + free(sy->vert); + free(sy->right); + free(sy->title_padding); + free(sy->cell_padding); + free(sy); + } +} + +/** + * scols_symbols_set_branch: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the branch part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, branch, str); +} + +/** + * scols_symbols_set_vertical: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertical part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, vert, str); +} + +/** + * scols_symbols_set_right: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the right part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_right(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, right, str); +} + +/** + * scols_symbols_set_title_padding: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the symbols which fill title output + * + * The current implementation uses only the first byte from the padding string. + * A multibyte chars are not supported yet. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, title_padding, str); +} + +/** + * scols_symbols_set_cell_padding: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the symbols which fill cells + * + * The padding char has to take up just one cell on the terminal. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, cell_padding, str); +} + +/** + * scols_copy_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Returns: a newly allocated copy of the @sy symbol group or NULL in case of an error. + */ +struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy) +{ + struct libscols_symbols *ret; + int rc; + + assert(sy); + if (!sy) + return NULL; + + ret = scols_new_symbols(); + if (!ret) + return NULL; + + rc = scols_symbols_set_branch(ret, sy->branch); + if (!rc) + rc = scols_symbols_set_vertical(ret, sy->vert); + if (!rc) + rc = scols_symbols_set_right(ret, sy->right); + if (!rc) + rc = scols_symbols_set_title_padding(ret, sy->title_padding); + if (!rc) + rc = scols_symbols_set_cell_padding(ret, sy->cell_padding); + if (!rc) + return ret; + + scols_unref_symbols(ret); + return NULL; +} diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c new file mode 100644 index 0000000..3914c88 --- /dev/null +++ b/libsmartcols/src/table.c @@ -0,0 +1,1542 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table + * @title: Table + * @short_description: container for rows and columns + * + * Table data manipulation API. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <termios.h> +#include <ctype.h> + +#include "nls.h" +#include "ttyutils.h" +#include "smartcolsP.h" + +#ifdef HAVE_WIDECHAR +#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ +#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ +#define UTF_H "\342\224\200" /* U+2500, Horizontal */ +#define UTF_UR "\342\224\224" /* U+2514, Up and right */ +#endif /* !HAVE_WIDECHAR */ + +#define is_last_column(_tb, _cl) \ + list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) + + +static void check_padding_debug(struct libscols_table *tb) +{ + const char *str; + + assert(libsmartcols_debug_mask); /* debug has to be enabled! */ + + str = getenv("LIBSMARTCOLS_DEBUG_PADDING"); + if (!str || (strcmp(str, "on") != 0 && strcmp(str, "1") != 0)) + return; + + DBG(INIT, ul_debugobj(tb, "padding debug: ENABLE")); + tb->padding_debug = 1; +} + +/** + * scols_new_table: + * + * Returns: A newly allocated table. + */ +struct libscols_table *scols_new_table(void) +{ + struct libscols_table *tb; + int c, l; + + tb = calloc(1, sizeof(struct libscols_table)); + if (!tb) + return NULL; + + tb->refcount = 1; + tb->out = stdout; + + get_terminal_dimension(&c, &l); + tb->termwidth = c > 0 ? c : 80; + tb->termheight = l > 0 ? l : 24; + + INIT_LIST_HEAD(&tb->tb_lines); + INIT_LIST_HEAD(&tb->tb_columns); + + DBG(TAB, ul_debugobj(tb, "alloc")); + ON_DBG(INIT, check_padding_debug(tb)); + + return tb; +} + +/** + * scols_ref_table: + * @tb: a pointer to a struct libscols_table instance + * + * Increases the refcount of @tb. + */ +void scols_ref_table(struct libscols_table *tb) +{ + if (tb) + tb->refcount++; +} + +/** + * scols_unref_table: + * @tb: a pointer to a struct libscols_table instance + * + * Decreases the refcount of @tb. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_table(struct libscols_table *tb) +{ + if (tb && (--tb->refcount <= 0)) { + DBG(TAB, ul_debugobj(tb, "dealloc")); + scols_table_remove_lines(tb); + scols_table_remove_columns(tb); + scols_unref_symbols(tb->symbols); + scols_reset_cell(&tb->title); + free(tb->linesep); + free(tb->colsep); + free(tb->name); + free(tb); + } +} + +/** + * scols_table_set_name: + * @tb: a pointer to a struct libscols_table instance + * @name: a name + * + * The table name is used for example for JSON top level object name. + * + * Returns: 0, a negative number in case of an error. + * + * Since: 2.27 + */ +int scols_table_set_name(struct libscols_table *tb, const char *name) +{ + return strdup_to_struct_member(tb, name, name); +} + +/** + * scols_table_get_name: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: The current name setting of the table @tb + * + * Since: 2.29 + */ +const char *scols_table_get_name(const struct libscols_table *tb) +{ + return tb->name; +} + +/** + * scols_table_get_title: + * @tb: a pointer to a struct libscols_table instance + * + * The returned pointer is possible to modify by cell functions. Note that + * title output alignment on non-tty is hardcoded to 80 output chars. For the + * regular terminal it's based on terminal width. + * + * Returns: Title of the table, or NULL in case of blank title. + * + * Since: 2.28 + */ +struct libscols_cell *scols_table_get_title(struct libscols_table *tb) +{ + return &tb->title; +} + +/** + * scols_table_add_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * + * Adds @cl to @tb's column list. The column cannot be shared between more + * tables. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_iter itr; + struct libscols_line *ln; + int rc = 0; + + if (!tb || !cl || cl->table) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols++; + + DBG(TAB, ul_debugobj(tb, "add column")); + list_add_tail(&cl->cl_columns, &tb->tb_columns); + cl->seqnum = tb->ncols++; + cl->table = tb; + scols_ref_column(cl); + + if (list_empty(&tb->tb_lines)) + return 0; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + /* Realloc line cell arrays + */ + while (scols_table_next_line(tb, &itr, &ln) == 0) { + rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + break; + } + + return rc; +} + +/** + * scols_table_remove_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * + * Removes @cl from @tb. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_remove_column(struct libscols_table *tb, + struct libscols_column *cl) +{ + if (!tb || !cl || !list_empty(&tb->tb_lines)) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols--; + + DBG(TAB, ul_debugobj(tb, "remove column")); + list_del_init(&cl->cl_columns); + tb->ncols--; + cl->table = NULL; + scols_unref_column(cl); + return 0; +} + +/** + * scols_table_remove_columns: + * @tb: a pointer to a struct libscols_table instance + * + * Removes all of @tb's columns. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_remove_columns(struct libscols_table *tb) +{ + if (!tb || !list_empty(&tb->tb_lines)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove all columns")); + while (!list_empty(&tb->tb_columns)) { + struct libscols_column *cl = list_entry(tb->tb_columns.next, + struct libscols_column, cl_columns); + scols_table_remove_column(tb, cl); + } + return 0; +} + +/** + * scols_table_move_column: + * @tb: table + * @pre: column before the column + * @cl: column to move + * + * Move the @cl behind @pre. If the @pre is NULL then the @col is the first + * column in the table. + * + * Since: 2.30 + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_move_column(struct libscols_table *tb, + struct libscols_column *pre, + struct libscols_column *cl) +{ + struct list_head *head; + struct libscols_iter itr; + struct libscols_column *p; + struct libscols_line *ln; + size_t n = 0, oldseq; + + if (!tb || !cl) + return -EINVAL; + + if (pre && pre->seqnum + 1 == cl->seqnum) + return 0; + if (pre == NULL && cl->seqnum == 0) + return 0; + + DBG(TAB, ul_debugobj(tb, "move column %zu behind %zu", + cl->seqnum, pre? pre->seqnum : 0)); + + list_del_init(&cl->cl_columns); /* remove from old position */ + + head = pre ? &pre->cl_columns : &tb->tb_columns; + list_add(&cl->cl_columns, head); /* add to the new place */ + + oldseq = cl->seqnum; + + /* fix seq. numbers */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &p) == 0) + p->seqnum = n++; + + /* move data in lines */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + scols_line_move_cells(ln, cl->seqnum, oldseq); + return 0; +} + +/** + * scols_table_new_column: + * @tb: table + * @name: column header + * @whint: column width hint (absolute width: N > 1; relative width: 0 < N < 1) + * @flags: flags integer + * + * This is shortcut for + * + * cl = scols_new_column(); + * scols_column_set_....(cl, ...); + * scols_table_add_column(tb, cl); + * + * The column width is possible to define by: + * + * @whint: 0 < N < 1 : relative width, percent of terminal width + * + * @whint: N >= 1 : absolute width, empty column will be truncated to + * the column header width if no specified STRICTWIDTH flag + * + * Note that if table has disabled "maxout" flag (disabled by default) than + * relative width is used as a hint only. It's possible that column will be + * narrow if the specified size is too large for column data. + * + * + * If the width of all columns is greater than terminal width then library + * tries to reduce width of the individual columns. It's done in three stages: + * + * #1 reduce columns with SCOLS_FL_TRUNC flag and with relative width if the + * width is greater than width defined by @whint (@whint * terminal_width) + * + * #2 reduce all columns with SCOLS_FL_TRUNC flag + * + * #3 reduce all columns with relative width + * + * The next stage is always used if the previous stage is unsuccessful. Note + * that SCOLS_FL_WRAP is interpreted as SCOLS_FL_TRUNC when calculate column + * width (if custom wrap function is not specified), but the final text is not + * truncated, but wrapped to multi-line cell. + * + * + * The column is necessary to address by sequential number. The first defined + * column has the colnum = 0. For example: + * + * scols_table_new_column(tab, "FOO", 0.5, 0); // colnum = 0 + * scols_table_new_column(tab, "BAR", 0.5, 0); // colnum = 1 + * . + * . + * scols_line_get_cell(line, 0); // FOO column + * scols_line_get_cell(line, 1); // BAR column + * + * Returns: newly allocated column + */ +struct libscols_column *scols_table_new_column(struct libscols_table *tb, + const char *name, + double whint, + int flags) +{ + struct libscols_column *cl; + struct libscols_cell *hr; + + if (!tb) + return NULL; + + DBG(TAB, ul_debugobj(tb, "new column name=%s, whint=%g, flags=%d", + name, whint, flags)); + cl = scols_new_column(); + if (!cl) + return NULL; + + /* set column name */ + hr = scols_column_get_header(cl); + if (!hr) + goto err; + if (scols_cell_set_data(hr, name)) + goto err; + + scols_column_set_whint(cl, whint); + scols_column_set_flags(cl, flags); + + if (scols_table_add_column(tb, cl)) /* this increments column ref-counter */ + goto err; + + scols_unref_column(cl); + return cl; +err: + scols_unref_column(cl); + return NULL; +} + +/** + * scols_table_next_column: + * @tb: a pointer to a struct libscols_table instance + * @itr: a pointer to a struct libscols_iter instance + * @cl: a pointer to a pointer to a struct libscols_column instance + * + * Returns the next column of @tb via @cl. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_next_column(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_column **cl) +{ + int rc = 1; + + if (!tb || !itr || !cl) + return -EINVAL; + *cl = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_columns); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *cl, struct libscols_column, cl_columns); + rc = 0; + } + + return rc; +} + +/** + * scols_table_get_ncols: + * @tb: table + * + * Returns: the ncols table member. + */ +size_t scols_table_get_ncols(const struct libscols_table *tb) +{ + return tb->ncols; +} + +/** + * scols_table_get_nlines: + * @tb: table + * + * Returns: the nlines table member. + */ +size_t scols_table_get_nlines(const struct libscols_table *tb) +{ + return tb->nlines; +} + +/** + * scols_table_set_stream: + * @tb: table + * @stream: output stream + * + * Sets the output stream for table @tb. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_set_stream(struct libscols_table *tb, FILE *stream) +{ + assert(tb); + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "setting alternative stream")); + tb->out = stream; + return 0; +} + +/** + * scols_table_get_stream: + * @tb: table + * + * Gets the output stream for table @tb. + * + * Returns: stream pointer, NULL in case of an error or an unset stream. + */ +FILE *scols_table_get_stream(const struct libscols_table *tb) +{ + return tb->out; +} + +/** + * scols_table_reduce_termwidth: + * @tb: table + * @reduce: width + * + * If necessary then libsmartcols use all terminal width, the @reduce setting + * provides extra space (for example for borders in ncurses applications). + * + * The @reduce must be smaller than terminal width, otherwise it's silently + * ignored. The reduction is not applied when STDOUT_FILENO is not terminal. + * + * Note that after output initialization (scols_table_print_* calls) the width + * will be reduced, this behavior affects subsequenced scols_table_get_termwidth() + * calls. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "reduce terminal width: %zu", reduce)); + tb->termreduce = reduce; + return 0; +} + +/** + * scols_table_get_column: + * @tb: table + * @n: number of column (0..N) + * + * Returns: pointer to column or NULL + */ +struct libscols_column *scols_table_get_column(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + if (!tb) + return NULL; + if (n >= tb->ncols) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (cl->seqnum == n) + return cl; + } + return NULL; +} + +/** + * scols_table_add_line: + * @tb: table + * @ln: line + * + * Note that this function calls scols_line_alloc_cells() if number + * of the cells in the line is too small for @tb. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) +{ + if (!tb || !ln) + return -EINVAL; + + if (tb->ncols > ln->ncells) { + int rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + return rc; + } + + DBG(TAB, ul_debugobj(tb, "add line")); + list_add_tail(&ln->ln_lines, &tb->tb_lines); + ln->seqnum = tb->nlines++; + scols_ref_line(ln); + return 0; +} + +/** + * scols_table_remove_line: + * @tb: table + * @ln: line + * + * Note that this function does not destroy the parent<->child relationship between lines. + * You have to call scols_line_remove_child() + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_remove_line(struct libscols_table *tb, + struct libscols_line *ln) +{ + if (!tb || !ln) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove line")); + list_del_init(&ln->ln_lines); + tb->nlines--; + scols_unref_line(ln); + return 0; +} + +/** + * scols_table_remove_lines: + * @tb: table + * + * This empties the table and also destroys all the parent<->child relationships. + */ +void scols_table_remove_lines(struct libscols_table *tb) +{ + if (!tb) + return; + + DBG(TAB, ul_debugobj(tb, "remove all lines")); + while (!list_empty(&tb->tb_lines)) { + struct libscols_line *ln = list_entry(tb->tb_lines.next, + struct libscols_line, ln_lines); + if (ln->parent) + scols_line_remove_child(ln->parent, ln); + scols_table_remove_line(tb, ln); + } +} + +/** + * scols_table_next_line: + * @tb: a pointer to a struct libscols_table instance + * @itr: a pointer to a struct libscols_iter instance + * @ln: a pointer to a pointer to a struct libscols_line instance + * + * Finds the next line and returns a pointer to it via @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_next_line(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_line **ln) +{ + int rc = 1; + + if (!tb || !itr || !ln) + return -EINVAL; + *ln = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_lines); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *ln, struct libscols_line, ln_lines); + rc = 0; + } + + return rc; +} + +/** + * scols_table_new_line: + * @tb: table + * @parent: parental line or NULL + * + * This is shortcut for + * + * ln = scols_new_line(); + * scols_table_add_line(tb, ln); + * scols_line_add_child(parent, ln); + * + * + * Returns: newly allocate line + */ +struct libscols_line *scols_table_new_line(struct libscols_table *tb, + struct libscols_line *parent) +{ + struct libscols_line *ln; + + if (!tb) + return NULL; + + ln = scols_new_line(); + if (!ln) + return NULL; + + if (scols_table_add_line(tb, ln)) + goto err; + if (parent) + scols_line_add_child(parent, ln); + + scols_unref_line(ln); /* ref-counter incremented by scols_table_add_line() */ + return ln; +err: + scols_unref_line(ln); + return NULL; +} + +/** + * scols_table_get_line: + * @tb: table + * @n: column number (0..N) + * + * Returns: a line or NULL + */ +struct libscols_line *scols_table_get_line(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_line *ln; + + if (!tb) + return NULL; + if (n >= tb->nlines) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->seqnum == n) + return ln; + } + return NULL; +} + +/** + * scols_copy_table: + * @tb: table + * + * Creates a new independent table copy, except struct libscols_symbols that + * are shared between the tables. + * + * Returns: a newly allocated copy of @tb + */ +struct libscols_table *scols_copy_table(struct libscols_table *tb) +{ + struct libscols_table *ret; + struct libscols_line *ln; + struct libscols_column *cl; + struct libscols_iter itr; + + if (!tb) + return NULL; + ret = scols_new_table(); + if (!ret) + return NULL; + + DBG(TAB, ul_debugobj(tb, "copy")); + + if (tb->symbols) + scols_table_set_symbols(ret, tb->symbols); + + /* columns */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + cl = scols_copy_column(cl); + if (!cl) + goto err; + if (scols_table_add_column(ret, cl)) + goto err; + scols_unref_column(cl); + } + + /* lines */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + struct libscols_line *newln = scols_copy_line(ln); + if (!newln) + goto err; + if (scols_table_add_line(ret, newln)) + goto err; + if (ln->parent) { + struct libscols_line *p = + scols_table_get_line(ret, ln->parent->seqnum); + if (p) + scols_line_add_child(p, newln); + } + scols_unref_line(newln); + } + + /* separators */ + if (scols_table_set_column_separator(ret, tb->colsep) || + scols_table_set_line_separator(ret, tb->linesep)) + goto err; + + return ret; +err: + scols_unref_table(ret); + return NULL; +} + +/** + * scols_table_set_default_symbols: + * @tb: table + * + * The library check the current environment to select ASCII or UTF8 symbols. + * This default behavior could be controlled by scols_table_enable_ascii(). + * + * Use scols_table_set_symbols() to unset symbols or use your own setting. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_default_symbols(struct libscols_table *tb) +{ + struct libscols_symbols *sy; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "setting default symbols")); + + sy = scols_new_symbols(); + if (!sy) + return -ENOMEM; + +#if defined(HAVE_WIDECHAR) + if (!scols_table_is_ascii(tb) && + !strcmp(nl_langinfo(CODESET), "UTF-8")) { + scols_symbols_set_branch(sy, UTF_VR UTF_H); + scols_symbols_set_vertical(sy, UTF_V " "); + scols_symbols_set_right(sy, UTF_UR UTF_H); + } else +#endif + { + scols_symbols_set_branch(sy, "|-"); + scols_symbols_set_vertical(sy, "| "); + scols_symbols_set_right(sy, "`-"); + } + scols_symbols_set_title_padding(sy, " "); + scols_symbols_set_cell_padding(sy, " "); + + rc = scols_table_set_symbols(tb, sy); + scols_unref_symbols(sy); + return rc; +} + + +/** + * scols_table_set_symbols: + * @tb: table + * @sy: symbols or NULL + * + * Add a reference to @sy from the table. The symbols are used by library to + * draw tree output. If no symbols are used for the table then library creates + * default temporary symbols to draw output by scols_table_set_default_symbols(). + * + * If @sy is NULL then remove reference from the currently used symbols. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_symbols(struct libscols_table *tb, + struct libscols_symbols *sy) +{ + if (!tb) + return -EINVAL; + + /* remove old */ + if (tb->symbols) { + DBG(TAB, ul_debugobj(tb, "remove symbols reference")); + scols_unref_symbols(tb->symbols); + tb->symbols = NULL; + } + + /* set new */ + if (sy) { /* ref user defined */ + DBG(TAB, ul_debugobj(tb, "set symbols")); + tb->symbols = sy; + scols_ref_symbols(sy); + } + return 0; +} + +/** + * scols_table_get_symbols: + * @tb: table + * + * Returns: pointer to symbols table. + * + * Since: 2.29 + */ +struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb) +{ + return tb->symbols; +} + +/** + * scols_table_enable_nolinesep: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable line separator printing. This is useful if you want to + * re-printing the same line more than once (e.g. progress bar). Don't use it + * if you're not sure. + * + * Note that for the last line in the table the separator is disabled at all. + * The library differentiate between table terminator and line terminator + * (although for standard output \n byte is used in both cases). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_nolinesep(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "nolinesep: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_linesep = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_nolinesep: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if line separator printing is disabled. + * + * Since: 2.29 + */ +int scols_table_is_nolinesep(const struct libscols_table *tb) +{ + return tb->no_linesep; +} + +/** + * scols_table_enable_colors: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable colors. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_colors(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "colors: %s", enable ? "ENABLE" : "DISABLE")); + tb->colors_wanted = enable; + return 0; +} + +/** + * scols_table_enable_raw: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable raw output format. The parsable output formats + * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_raw(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "raw: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_RAW; + else if (tb->format == SCOLS_FMT_RAW) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_json: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable JSON output format. The parsable output formats + * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.27 + */ +int scols_table_enable_json(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "json: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_JSON; + else if (tb->format == SCOLS_FMT_JSON) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_export: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable export output format (COLUMNAME="value" ...). + * The parsable output formats (export and raw) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_export(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "export: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_EXPORT; + else if (tb->format == SCOLS_FMT_EXPORT) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_ascii: + * @tb: table + * @enable: 1 or 0 + * + * The ASCII-only output is relevant for tree-like outputs. The library + * checks if the current environment is UTF8 compatible by default. This + * function overrides this check and force the library to use ASCII chars + * for the tree. + * + * If a custom libcols_symbols are specified (see scols_table_set_symbols() + * then ASCII flag setting is ignored. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_ascii(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "ascii: %s", enable ? "ENABLE" : "DISABLE")); + tb->ascii = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_noheadings: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable header line. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_noheadings(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "noheading: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_headings = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_header_repeat: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable header line repeat. The header line is printed only once by + * default. Note that the flag will be silently ignored and disabled if the + * output is not on terminal or output format is JSON, raw, etc. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.31 + */ +int scols_table_enable_header_repeat(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "header-repeat: %s", enable ? "ENABLE" : "DISABLE")); + tb->header_repeat = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_maxout: + * @tb: table + * @enable: 1 or 0 + * + * The extra space after last column is ignored by default. The output + * maximization use the extra space for all columns. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_maxout(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "maxout: %s", enable ? "ENABLE" : "DISABLE")); + tb->maxout = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_nowrap: + * @tb: table + * @enable: 1 or 0 + * + * Never continue on next line, remove last column(s) when too large, truncate last column. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.28 + */ +int scols_table_enable_nowrap(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "nowrap: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_wrap = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_nowrap: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if nowrap is enabled. + * + * Since: 2.29 + */ +int scols_table_is_nowrap(const struct libscols_table *tb) +{ + return tb->no_wrap; +} + +/** + * scols_table_enable_noencoding: + * @tb: table + * @enable: 1 or 0 + * + * The library encode non-printable and control chars by \xHEX by default. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.31 + */ +int scols_table_enable_noencoding(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "encoding: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_encode = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_noencoding: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if encoding is disabled. + * + * Since: 2.31 + */ +int scols_table_is_noencoding(const struct libscols_table *tb) +{ + return tb->no_encode; +} + +/** + * scols_table_colors_wanted: + * @tb: table + * + * Returns: 1 if colors are enabled. + */ +int scols_table_colors_wanted(const struct libscols_table *tb) +{ + return tb->colors_wanted; +} + +/** + * scols_table_is_empty: + * @tb: table + * + * Returns: 1 if the table is empty. + */ +int scols_table_is_empty(const struct libscols_table *tb) +{ + return !tb->nlines; +} + +/** + * scols_table_is_ascii: + * @tb: table + * + * Returns: 1 if ASCII tree is enabled. + */ +int scols_table_is_ascii(const struct libscols_table *tb) +{ + return tb->ascii; +} + +/** + * scols_table_is_noheadings: + * @tb: table + * + * Returns: 1 if header output is disabled. + */ +int scols_table_is_noheadings(const struct libscols_table *tb) +{ + return tb->no_headings; +} + +/** + * scols_table_is_header_repeat + * @tb: table + * + * Returns: 1 if header repeat is enabled. + * + * Since: 2.31 + */ +int scols_table_is_header_repeat(const struct libscols_table *tb) +{ + return tb->header_repeat; +} + +/** + * scols_table_is_export: + * @tb: table + * + * Returns: 1 if export output format is enabled. + */ +int scols_table_is_export(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_EXPORT; +} + +/** + * scols_table_is_raw: + * @tb: table + * + * Returns: 1 if raw output format is enabled. + */ +int scols_table_is_raw(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_RAW; +} + +/** + * scols_table_is_json: + * @tb: table + * + * Returns: 1 if JSON output format is enabled. + * + * Since: 2.27 + */ +int scols_table_is_json(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_JSON; +} + +/** + * scols_table_is_maxout + * @tb: table + * + * Returns: 1 if output maximization is enabled or 0 + */ +int scols_table_is_maxout(const struct libscols_table *tb) +{ + return tb->maxout; +} + +/** + * scols_table_is_tree: + * @tb: table + * + * Returns: returns 1 tree-like output is expected. + */ +int scols_table_is_tree(const struct libscols_table *tb) +{ + return tb->ntreecols > 0; +} + +/** + * scols_table_set_column_separator: + * @tb: table + * @sep: separator + * + * Sets the column separator of @tb to @sep. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_column_separator(struct libscols_table *tb, const char *sep) +{ + return strdup_to_struct_member(tb, colsep, sep); +} + +/** + * scols_table_set_line_separator: + * @tb: table + * @sep: separator + * + * Sets the line separator of @tb to @sep. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_line_separator(struct libscols_table *tb, const char *sep) +{ + return strdup_to_struct_member(tb, linesep, sep); +} + +/** + * scols_table_get_column_separator: + * @tb: table + * + * Returns: @tb column separator, NULL in case of an error + */ +const char *scols_table_get_column_separator(const struct libscols_table *tb) +{ + return tb->colsep; +} + +/** + * scols_table_get_line_separator: + * @tb: table + * + * Returns: @tb line separator, NULL in case of an error + */ +const char *scols_table_get_line_separator(const struct libscols_table *tb) +{ + return tb->linesep; +} +/* for lines in the struct libscols_line->ln_lines list */ +static int cells_cmp_wrapper_lines(struct list_head *a, struct list_head *b, void *data) +{ + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; + struct libscols_cell *ca, *cb; + + assert(a); + assert(b); + assert(cl); + + ra = list_entry(a, struct libscols_line, ln_lines); + rb = list_entry(b, struct libscols_line, ln_lines); + ca = scols_line_get_cell(ra, cl->seqnum); + cb = scols_line_get_cell(rb, cl->seqnum); + + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); +} + +/* for lines in the struct libscols_line->ln_children list */ +static int cells_cmp_wrapper_children(struct list_head *a, struct list_head *b, void *data) +{ + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; + struct libscols_cell *ca, *cb; + + assert(a); + assert(b); + assert(cl); + + ra = list_entry(a, struct libscols_line, ln_children); + rb = list_entry(b, struct libscols_line, ln_children); + ca = scols_line_get_cell(ra, cl->seqnum); + cb = scols_line_get_cell(rb, cl->seqnum); + + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); +} + + +static int sort_line_children(struct libscols_line *ln, struct libscols_column *cl) +{ + struct list_head *p; + + if (list_empty(&ln->ln_branch)) + return 0; + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } + + list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl); + return 0; +} + +/** + * scols_sort_table: + * @tb: table + * @cl: order by this column + * + * Orders the table by the column. See also scols_column_set_cmpfunc(). If the + * tree output is enabled then children in the tree are recursively sorted too. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl) +{ + if (!tb || !cl || !cl->cmpfunc) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table")); + list_sort(&tb->tb_lines, cells_cmp_wrapper_lines, cl); + + if (scols_table_is_tree(tb)) { + struct libscols_line *ln; + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + sort_line_children(ln, cl); + } + + return 0; +} + +static struct libscols_line *move_line_and_children(struct libscols_line *ln, struct libscols_line *pre) +{ + if (pre) { + list_del_init(&ln->ln_lines); /* remove from old position */ + list_add(&ln->ln_lines, &pre->ln_lines); /* add to the new place (behind @pre) */ + } + pre = ln; + + if (!list_empty(&ln->ln_branch)) { + struct list_head *p; + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + pre = move_line_and_children(chld, pre); + } + } + + return pre; +} + +/** + * scols_sort_table_by_tree: + * @tb: table + * + * Reorders lines in the table by parent->child relation. Note that order of + * the lines in the table is independent on the tree hierarchy. + * + * Since: 2.30 + * + * Returns: 0, a negative value in case of an error. + */ +int scols_sort_table_by_tree(struct libscols_table *tb) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table by tree")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent) + continue; + + move_line_and_children(ln, NULL); + } + + return 0; +} + + +/** + * scols_table_set_termforce: + * @tb: table + * @force: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} + * + * Forces library to use stdout as terminal, non-terminal or use automatic + * detection (default). + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_termforce(struct libscols_table *tb, int force) +{ + if (!tb) + return -EINVAL; + tb->termforce = force; + return 0; +} + +/** + * scols_table_get_termforce: + * @tb: table + * + * Returns: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} or a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_get_termforce(const struct libscols_table *tb) +{ + return tb->termforce; +} + +/** + * scols_table_set_termwidth + * @tb: table + * @width: terminal width + * + * The library automatically detects terminal width or defaults to 80 chars if + * detections is unsuccessful. This function override this behaviour. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_termwidth(struct libscols_table *tb, size_t width) +{ + DBG(TAB, ul_debugobj(tb, "set terminatl width: %zu", width)); + tb->termwidth = width; + return 0; +} + +/** + * scols_table_get_termwidth + * @tb: table + * + * Returns: terminal width. + */ +size_t scols_table_get_termwidth(const struct libscols_table *tb) +{ + return tb->termwidth; +} + +/** + * scols_table_set_termheight + * @tb: table + * @height: terminal height (number of lines) + * + * The library automatically detects terminal height or defaults to 24 lines if + * detections is unsuccessful. This function override this behaviour. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.31 + */ +int scols_table_set_termheight(struct libscols_table *tb, size_t height) +{ + DBG(TAB, ul_debugobj(tb, "set terminatl height: %zu", height)); + tb->termheight = height; + return 0; +} + +/** + * scols_table_get_termheight + * @tb: table + * + * Returns: terminal height (number of lines). + * + * Since: 2.31 + */ +size_t scols_table_get_termheight(const struct libscols_table *tb) +{ + return tb->termheight; +} diff --git a/libsmartcols/src/table_print.c b/libsmartcols/src/table_print.c new file mode 100644 index 0000000..a15bf90 --- /dev/null +++ b/libsmartcols/src/table_print.c @@ -0,0 +1,1748 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table_print + * @title: Table print + * @short_description: output functions + * + * Table output API. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <termios.h> +#include <ctype.h> + +#include "mbsalign.h" +#include "carefulputc.h" +#include "smartcolsP.h" + +#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") +#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") + +/* Fallback for symbols + * + * Note that by default library define all the symbols, but in case user does + * not define all symbols or if we extended the symbols struct then we need + * fallback to be more robust and backwardly compatible. + */ +#define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ") +#define branch_symbol(tb) ((tb)->symbols->branch ? (tb)->symbols->branch : "|-") +#define vertical_symbol(tb) ((tb)->symbols->vert ? (tb)->symbols->vert : "| ") +#define right_symbol(tb) ((tb)->symbols->right ? (tb)->symbols->right : "`-") + +#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \ + ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " ")) + +#define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used) + + +/* This is private struct to work with output data */ +struct libscols_buffer { + char *begin; /* begin of the buffer */ + char *cur; /* current end of the buffer */ + char *encdata; /* encoded buffer mbs_safe_encode() */ + + size_t bufsz; /* size of the buffer */ + size_t art_idx; /* begin of the tree ascii art or zero */ +}; + +static struct libscols_buffer *new_buffer(size_t sz) +{ + struct libscols_buffer *buf = malloc(sz + sizeof(struct libscols_buffer)); + + if (!buf) + return NULL; + + buf->cur = buf->begin = ((char *) buf) + sizeof(struct libscols_buffer); + buf->encdata = NULL; + buf->bufsz = sz; + + DBG(BUFF, ul_debugobj(buf, "alloc (size=%zu)", sz)); + return buf; +} + +static void free_buffer(struct libscols_buffer *buf) +{ + if (!buf) + return; + DBG(BUFF, ul_debugobj(buf, "dealloc")); + free(buf->encdata); + free(buf); +} + +static int buffer_reset_data(struct libscols_buffer *buf) +{ + if (!buf) + return -EINVAL; + + /*DBG(BUFF, ul_debugobj(buf, "reset data"));*/ + buf->begin[0] = '\0'; + buf->cur = buf->begin; + buf->art_idx = 0; + return 0; +} + +static int buffer_append_data(struct libscols_buffer *buf, const char *str) +{ + size_t maxsz, sz; + + if (!buf) + return -EINVAL; + if (!str || !*str) + return 0; + + sz = strlen(str); + maxsz = buf->bufsz - (buf->cur - buf->begin); + + if (maxsz <= sz) + return -EINVAL; + memcpy(buf->cur, str, sz + 1); + buf->cur += sz; + return 0; +} + +static int buffer_set_data(struct libscols_buffer *buf, const char *str) +{ + int rc = buffer_reset_data(buf); + return rc ? rc : buffer_append_data(buf, str); +} + +/* save the current buffer position to art_idx */ +static void buffer_set_art_index(struct libscols_buffer *buf) +{ + if (buf) { + buf->art_idx = buf->cur - buf->begin; + /*DBG(BUFF, ul_debugobj(buf, "art index: %zu", buf->art_idx));*/ + } +} + +static char *buffer_get_data(struct libscols_buffer *buf) +{ + return buf ? buf->begin : NULL; +} + +/* encode data by mbs_safe_encode() to avoid control and non-printable chars */ +static char *buffer_get_safe_data(struct libscols_table *tb, + struct libscols_buffer *buf, + size_t *cells, + const char *safechars) +{ + char *data = buffer_get_data(buf); + char *res = NULL; + + if (!data) + goto nothing; + + if (!buf->encdata) { + buf->encdata = malloc(mbs_safe_encode_size(buf->bufsz) + 1); + if (!buf->encdata) + goto nothing; + } + + if (tb->no_encode) { + *cells = mbs_safe_width(data); + strcpy(buf->encdata, data); + res = buf->encdata; + } else { + res = mbs_safe_encode_to_buffer(data, cells, buf->encdata, safechars); + } + + if (!res || !*cells || *cells == (size_t) -1) + goto nothing; + return res; +nothing: + *cells = 0; + return NULL; +} + +/* returns size in bytes of the ascii art (according to art_idx) in safe encoding */ +static size_t buffer_get_safe_art_size(struct libscols_buffer *buf) +{ + char *data = buffer_get_data(buf); + size_t bytes = 0; + + if (!data || !buf->art_idx) + return 0; + + mbs_safe_nwidth(data, buf->art_idx, &bytes); + return bytes; +} + +/* returns pointer to the end of used data */ +static int line_ascii_art_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf) +{ + const char *art; + int rc; + + assert(ln); + assert(buf); + + if (!ln->parent) + return 0; + + rc = line_ascii_art_to_buffer(tb, ln->parent, buf); + if (rc) + return rc; + + if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) + art = " "; + else + art = vertical_symbol(tb); + + return buffer_append_data(buf, art); +} + +static int is_last_column(struct libscols_column *cl) +{ + int rc = list_entry_is_last(&cl->cl_columns, &cl->table->tb_columns); + struct libscols_column *next; + + if (rc) + return 1; + + next = list_entry(cl->cl_columns.next, struct libscols_column, cl_columns); + if (next && scols_column_is_hidden(next) && is_last_column(next)) + return 1; + return 0; +} + + +static int has_pending_data(struct libscols_table *tb) +{ + struct libscols_column *cl; + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) + return 1; + } + return 0; +} + +/* print padding or ASCII-art instead of data of @cl */ +static void print_empty_cell(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + size_t bufsz) +{ + size_t len_pad = 0; /* in screen cells as opposed to bytes */ + + /* generate tree ASCII-art rather than padding */ + if (ln && scols_column_is_tree(cl)) { + if (!ln->parent) { + /* only print symbols->vert if followed by child */ + if (!list_empty(&ln->ln_branch)) { + fputs(vertical_symbol(tb), tb->out); + len_pad = mbs_safe_width(vertical_symbol(tb)); + } + } else { + /* use the same draw function as though we were intending to draw an L-shape */ + struct libscols_buffer *art = new_buffer(bufsz); + char *data; + + if (art) { + /* whatever the rc, len_pad will be sensible */ + line_ascii_art_to_buffer(tb, ln, art); + if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) + buffer_append_data(art, vertical_symbol(tb)); + data = buffer_get_safe_data(tb, art, &len_pad, NULL); + if (data && len_pad) + fputs(data, tb->out); + free_buffer(art); + } + } + } + + if (is_last_column(cl)) + return; + + /* fill rest of cell with space */ + for(; len_pad < cl->width; ++len_pad) + fputs(cellpadding_symbol(tb), tb->out); + + fputs(colsep(tb), tb->out); +} + + +static const char *get_cell_color(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) /* optional */ +{ + const char *color = NULL; + + if (tb && tb->colors_wanted) { + if (ce) + color = ce->color; + if (ln && !color) + color = ln->color; + if (!color) + color = cl->color; + } + return color; +} + +/* Fill the start of a line with padding (or with tree ascii-art). + * + * This is necessary after a long non-truncated column, as this requires the + * next column to be printed on the next line. For example (see 'DDD'): + * + * aaa bbb ccc ddd eee + * AAA BBB CCCCCCC + * DDD EEE + * ^^^^^^^^^^^^ + * new line padding + */ +static void print_newline_padding(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + size_t bufsz) +{ + size_t i; + + assert(tb); + assert(cl); + + fputs(linesep(tb), tb->out); /* line break */ + tb->termlines_used++; + + /* fill cells after line break */ + for (i = 0; i <= (size_t) cl->seqnum; i++) + print_empty_cell(tb, scols_table_get_column(tb, i), ln, bufsz); +} + +/* + * Pending data + * + * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is + * printed as usually and output is truncated to match column width. + * + * The rest of the long text is printed on next extra line(s). The extra lines + * don't exist in the table (not represented by libscols_line). The data for + * the extra lines are stored in libscols_column->pending_data_buf and the + * function print_line() adds extra lines until the buffer is not empty in all + * columns. + */ + +/* set data that will be printed by extra lines */ +static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz) +{ + char *p = NULL; + + if (data && *data) { + DBG(COL, ul_debugobj(cl, "setting pending data")); + assert(sz); + p = strdup(data); + if (!p) + return -ENOMEM; + } + + free(cl->pending_data_buf); + cl->pending_data_buf = p; + cl->pending_data_sz = sz; + cl->pending_data = cl->pending_data_buf; + return 0; +} + +/* the next extra line has been printed, move pending data cursor */ +static int step_pending_data(struct libscols_column *cl, size_t bytes) +{ + DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes)); + + if (bytes >= cl->pending_data_sz) + return set_pending_data(cl, NULL, 0); + + cl->pending_data += bytes; + cl->pending_data_sz -= bytes; + return 0; +} + +/* print next pending data for the column @cl */ +static int print_pending_data( + struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + size_t width = cl->width, bytes; + size_t len = width, i; + char *data; + char *nextchunk = NULL; + + if (!cl->pending_data) + return 0; + if (!width) + return -EINVAL; + + DBG(COL, ul_debugobj(cl, "printing pending data")); + + data = strdup(cl->pending_data); + if (!data) + goto err; + + if (scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + bytes = nextchunk - data; + + len = mbs_safe_nwidth(data, bytes, NULL); + } else + bytes = mbs_truncate(data, &len); + + if (bytes == (size_t) -1) + goto err; + + if (bytes) + step_pending_data(cl, bytes); + + if (color) + fputs(color, tb->out); + fputs(data, tb->out); + if (color) + fputs(UL_COLOR_RESET, tb->out); + free(data); + + if (is_last_column(cl)) + return 0; + + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); /* padding */ + + fputs(colsep(tb), tb->out); /* columns separator */ + return 0; +err: + free(data); + return -errno; +} + +static int print_data(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, /* optional */ + struct libscols_buffer *buf) +{ + size_t len = 0, i, width, bytes; + const char *color = NULL; + char *data, *nextchunk; + int is_last; + + assert(tb); + assert(cl); + + data = buffer_get_data(buf); + if (!data) + data = ""; + + is_last = is_last_column(cl); + + switch (tb->format) { + case SCOLS_FMT_RAW: + fputs_nonblank(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_EXPORT: + fprintf(tb->out, "%s=", scols_cell_get_data(&cl->header)); + fputs_quoted(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_JSON: + fputs_quoted_json_lower(scols_cell_get_data(&cl->header), tb->out); + fputs(":", tb->out); + switch (cl->json_type) { + case SCOLS_JSON_STRING: + if (!*data) + fputs("null", tb->out); + else + fputs_quoted_json(data, tb->out); + break; + case SCOLS_JSON_NUMBER: + if (!*data) + fputs("null", tb->out); + else + fputs(data, tb->out); + break; + case SCOLS_JSON_BOOLEAN: + fputs(!*data ? "false" : + *data == '0' ? "false" : + *data == 'N' || *data == 'n' ? "false" : "true", + tb->out); + break; + } + if (!is_last) + fputs(", ", tb->out); + return 0; + + case SCOLS_FMT_HUMAN: + break; /* continue below */ + } + + color = get_cell_color(tb, cl, ln, ce); + + /* Encode. Note that 'len' and 'width' are number of cells, not bytes. + */ + data = buffer_get_safe_data(tb, buf, &len, scols_column_get_safechars(cl)); + if (!data) + data = ""; + bytes = strlen(data); + width = cl->width; + + /* custom multi-line cell based */ + if (*data && scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + set_pending_data(cl, nextchunk, bytes - (nextchunk - data)); + bytes = nextchunk - data; + len = mbs_safe_nwidth(data, bytes, NULL); + } + + if (is_last + && len < width + && !scols_table_is_maxout(tb) + && !scols_column_is_right(cl)) + width = len; + + /* truncate data */ + if (len > width && scols_column_is_trunc(cl)) { + len = width; + bytes = mbs_truncate(data, &len); /* updates 'len' */ + } + + /* standard multi-line cell */ + if (len > width && scols_column_is_wrap(cl) + && !scols_column_is_customwrap(cl)) { + set_pending_data(cl, data, bytes); + + len = width; + bytes = mbs_truncate(data, &len); + if (bytes != (size_t) -1 && bytes > 0) + step_pending_data(cl, bytes); + } + + if (bytes == (size_t) -1) { + bytes = len = 0; + data = NULL; + } + + if (data) { + if (scols_column_is_right(cl)) { + if (color) + fputs(color, tb->out); + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + fputs(data, tb->out); + if (color) + fputs(UL_COLOR_RESET, tb->out); + len = width; + + } else if (color) { + char *p = data; + size_t art = buffer_get_safe_art_size(buf); + + /* we don't want to colorize tree ascii art */ + if (scols_column_is_tree(cl) && art && art < bytes) { + fwrite(p, 1, art, tb->out); + p += art; + } + + fputs(color, tb->out); + fputs(p, tb->out); + fputs(UL_COLOR_RESET, tb->out); + } else + fputs(data, tb->out); + } + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); /* padding */ + + if (is_last) + return 0; + + if (len > width && !scols_column_is_trunc(cl)) + print_newline_padding(tb, cl, ln, buf->bufsz); /* next column starts on next line */ + else + fputs(colsep(tb), tb->out); /* columns separator */ + + return 0; +} + +static int cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct libscols_buffer *buf) +{ + const char *data; + struct libscols_cell *ce; + int rc = 0; + + assert(tb); + assert(ln); + assert(cl); + assert(buf); + assert(cl->seqnum <= tb->ncols); + + buffer_reset_data(buf); + + ce = scols_line_get_cell(ln, cl->seqnum); + data = ce ? scols_cell_get_data(ce) : NULL; + if (!data) + return 0; + + if (!scols_column_is_tree(cl)) + return buffer_set_data(buf, data); + + /* + * Tree stuff + */ + if (ln->parent && !scols_table_is_json(tb)) { + rc = line_ascii_art_to_buffer(tb, ln->parent, buf); + + if (!rc && list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) + rc = buffer_append_data(buf, right_symbol(tb)); + else if (!rc) + rc = buffer_append_data(buf, branch_symbol(tb)); + if (!rc) + buffer_set_art_index(buf); + } + + if (!rc) + rc = buffer_append_data(buf, data); + return rc; +} + +static void fput_indent(struct libscols_table *tb) +{ + int i; + + for (i = 0; i <= tb->indent; i++) + fputs(" ", tb->out); +} + +static void fput_table_open(struct libscols_table *tb) +{ + tb->indent = 0; + + if (scols_table_is_json(tb)) { + fputc('{', tb->out); + fputs(linesep(tb), tb->out); + + fput_indent(tb); + fputs_quoted(tb->name, tb->out); + fputs(": [", tb->out); + fputs(linesep(tb), tb->out); + + tb->indent++; + tb->indent_last_sep = 1; + } +} + +static void fput_table_close(struct libscols_table *tb) +{ + tb->indent--; + + if (scols_table_is_json(tb)) { + fput_indent(tb); + fputc(']', tb->out); + tb->indent--; + fputs(linesep(tb), tb->out); + fputc('}', tb->out); + tb->indent_last_sep = 1; + } +} + +static void fput_children_open(struct libscols_table *tb) +{ + if (scols_table_is_json(tb)) { + fputc(',', tb->out); + fputs(linesep(tb), tb->out); + fput_indent(tb); + fputs("\"children\": [", tb->out); + } + /* between parent and child is separator */ + fputs(linesep(tb), tb->out); + tb->indent_last_sep = 1; + tb->indent++; + tb->termlines_used++; +} + +static void fput_children_close(struct libscols_table *tb) +{ + tb->indent--; + + if (scols_table_is_json(tb)) { + fput_indent(tb); + fputc(']', tb->out); + fputs(linesep(tb), tb->out); + tb->indent_last_sep = 1; + } +} + +static void fput_line_open(struct libscols_table *tb) +{ + if (scols_table_is_json(tb)) { + fput_indent(tb); + fputc('{', tb->out); + tb->indent_last_sep = 0; + } + tb->indent++; +} + +static void fput_line_close(struct libscols_table *tb, int last, int last_in_table) +{ + tb->indent--; + if (scols_table_is_json(tb)) { + if (tb->indent_last_sep) + fput_indent(tb); + fputs(last ? "}" : "},", tb->out); + if (!tb->no_linesep) + fputs(linesep(tb), tb->out); + + } else if (tb->no_linesep == 0 && last_in_table == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + tb->indent_last_sep = 1; +} + +/* + * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and + * control and non-printable characters can be encoded in the \x?? encoding. + */ +static int print_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf) +{ + int rc = 0, pending = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(ln); + + DBG(TAB, ul_debugobj(tb, "printing line")); + + /* regular line */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + rc = cell_to_buffer(tb, ln, cl, buf); + if (rc == 0) + rc = print_data(tb, cl, ln, + scols_line_get_cell(ln, cl->seqnum), + buf); + if (rc == 0 && cl->pending_data) + pending = 1; + } + + /* extra lines of the multi-line cells */ + while (rc == 0 && pending) { + pending = 0; + fputs(linesep(tb), tb->out); + tb->termlines_used++; + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) { + rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum)); + if (rc == 0 && cl->pending_data) + pending = 1; + } else + print_empty_cell(tb, cl, ln, buf->bufsz); + } + } + + return 0; +} + +static int print_title(struct libscols_table *tb) +{ + int rc, color = 0; + mbs_align_t align; + size_t width, len = 0, bufsz, titlesz; + char *title = NULL, *buf = NULL; + + assert(tb); + + if (!tb->title.data) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing title")); + + /* encode data */ + if (tb->no_encode) { + len = bufsz = strlen(tb->title.data) + 1; + buf = strdup(tb->title.data); + if (!buf) { + rc = -ENOMEM; + goto done; + } + } else { + bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1; + if (bufsz == 1) { + DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore")); + return 0; + } + buf = malloc(bufsz); + if (!buf) { + rc = -ENOMEM; + goto done; + } + + if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) || + !len || len == (size_t) -1) { + rc = -EINVAL; + goto done; + } + } + + /* truncate and align */ + width = tb->is_term ? tb->termwidth : 80; + titlesz = width + bufsz; + + title = malloc(titlesz); + if (!title) { + rc = -EINVAL; + goto done; + } + + switch (scols_cell_get_alignment(&tb->title)) { + case SCOLS_CELL_FL_RIGHT: + align = MBS_ALIGN_RIGHT; + break; + case SCOLS_CELL_FL_CENTER: + align = MBS_ALIGN_CENTER; + break; + case SCOLS_CELL_FL_LEFT: + default: + align = MBS_ALIGN_LEFT; + /* + * Don't print extra blank chars after the title if on left + * (that's same as we use for the last column in the table). + */ + if (len < width + && !scols_table_is_maxout(tb) + && isblank(*titlepadding_symbol(tb))) + width = len; + break; + + } + + /* copy from buf to title and align to width with title_padding */ + rc = mbsalign_with_padding(buf, title, titlesz, + &width, align, + 0, (int) *titlepadding_symbol(tb)); + + if (rc == -1) { + rc = -EINVAL; + goto done; + } + + if (tb->colors_wanted && tb->title.color) + color = 1; + if (color) + fputs(tb->title.color, tb->out); + + fputs(title, tb->out); + + if (color) + fputs(UL_COLOR_RESET, tb->out); + + fputc('\n', tb->out); + rc = 0; +done: + free(buf); + free(title); + DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc)); + return rc; +} + +static int print_header(struct libscols_table *tb, struct libscols_buffer *buf) +{ + int rc = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(tb); + + if ((tb->header_printed == 1 && tb->header_repeat == 0) || + scols_table_is_noheadings(tb) || + scols_table_is_export(tb) || + scols_table_is_json(tb) || + list_empty(&tb->tb_lines)) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing header")); + + /* set the width according to the size of the data */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + rc = buffer_set_data(buf, scols_cell_get_data(&cl->header)); + if (!rc) + rc = print_data(tb, cl, NULL, &cl->header, buf); + } + + if (rc == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + tb->header_printed = 1; + tb->header_next = tb->termlines_used + tb->termheight; + if (tb->header_repeat) + DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu]", + tb->header_next, tb->termlines_used)); + return rc; +} + + +static int print_range( struct libscols_table *tb, + struct libscols_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end) +{ + int rc = 0; + struct libscols_line *ln; + + assert(tb); + DBG(TAB, ul_debugobj(tb, "printing range")); + + while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { + + int last = scols_iter_is_last(itr); + + fput_line_open(tb); + rc = print_line(tb, ln, buf); + fput_line_close(tb, last, last); + + if (end && ln == end) + break; + + if (!last && want_repeat_header(tb)) + print_header(tb, buf); + } + + return rc; + +} + +static int print_table(struct libscols_table *tb, struct libscols_buffer *buf) +{ + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + return print_range(tb, buf, &itr, NULL); +} + + +static int print_tree_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_buffer *buf, + int last, + int last_in_table) +{ + int rc; + + /* print the line */ + fput_line_open(tb); + rc = print_line(tb, ln, buf); + if (rc) + goto done; + + /* print children */ + if (!list_empty(&ln->ln_branch)) { + struct list_head *p; + + fput_children_open(tb); + + /* print all children */ + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + int last_child = p->next == &ln->ln_branch; + + rc = print_tree_line(tb, chld, buf, last_child, last_in_table && last_child); + if (rc) + goto done; + } + + fput_children_close(tb); + } + + if (list_empty(&ln->ln_branch) || scols_table_is_json(tb)) + fput_line_close(tb, last, last_in_table); +done: + return rc; +} + +static int print_tree(struct libscols_table *tb, struct libscols_buffer *buf) +{ + int rc = 0; + struct libscols_line *ln, *last = NULL; + struct libscols_iter itr; + + assert(tb); + + DBG(TAB, ul_debugobj(tb, "printing tree")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + while (scols_table_next_line(tb, &itr, &ln) == 0) + if (!last || !ln->parent) + last = ln; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent) + continue; + rc = print_tree_line(tb, ln, buf, ln == last, ln == last); + } + + return rc; +} + +static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) +{ + if (scols_column_is_hidden(cl)) { + DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); + return; + } + + DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " + "hint=%d, avg=%zu, max=%zu, min=%zu, " + "extreme=%s %s", + + cl->header.data, cl->seqnum, cl->width, + cl->width_hint > 1 ? (int) cl->width_hint : + (int) (cl->width_hint * tb->termwidth), + cl->width_avg, + cl->width_max, + cl->width_min, + cl->is_extreme ? "yes" : "not", + cl->flags & SCOLS_FL_TRUNC ? "trunc" : "")); +} + +static void dbg_columns(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) + dbg_column(tb, cl); +} + + +/* + * This function counts column width. + * + * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function + * two times. The first pass counts the width and average width. If the column + * contains fields that are too large (a width greater than 2 * average) then + * the column is marked as "extreme". In the second pass all extreme fields + * are ignored and the column width is counted from non-extreme fields only. + */ +static int count_column_width(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_buffer *buf) +{ + struct libscols_line *ln; + struct libscols_iter itr; + int extreme_count = 0, rc = 0, no_header = 0; + size_t extreme_sum = 0; + + assert(tb); + assert(cl); + + cl->width = 0; + if (!cl->width_min) { + if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { + cl->width_min = (size_t) (cl->width_hint * tb->termwidth); + if (cl->width_min && !is_last_column(cl)) + cl->width_min--; + } + if (scols_cell_get_data(&cl->header)) { + size_t len = mbs_safe_width(scols_cell_get_data(&cl->header)); + cl->width_min = max(cl->width_min, len); + } else + no_header = 1; + + if (!cl->width_min) + cl->width_min = 1; + } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t len; + char *data; + + rc = cell_to_buffer(tb, ln, cl, buf); + if (rc) + goto done; + + data = buffer_get_data(buf); + + if (!data) + len = 0; + else if (scols_column_is_customwrap(cl)) + len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); + else + len = mbs_safe_width(data); + + if (len == (size_t) -1) /* ignore broken multibyte strings */ + len = 0; + cl->width_max = max(len, cl->width_max); + + if (cl->is_extreme && cl->width_avg && len > cl->width_avg * 2) + continue; + else if (scols_column_is_noextremes(cl)) { + extreme_sum += len; + extreme_count++; + } + cl->width = max(len, cl->width); + if (scols_column_is_tree(cl)) { + size_t treewidth = buffer_get_safe_art_size(buf); + cl->width_treeart = max(cl->width_treeart, treewidth); + } + } + + if (extreme_count && cl->width_avg == 0) { + cl->width_avg = extreme_sum / extreme_count; + if (cl->width_avg && cl->width_max > cl->width_avg * 2) + cl->is_extreme = 1; + } + + /* enlarge to minimal width */ + if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) + cl->width = cl->width_min; + + /* use absolute size for large columns */ + else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint + && cl->width_min < (size_t) cl->width_hint) + + cl->width = (size_t) cl->width_hint; + + + /* Column without header and data, set minimal size to zero (default is 1) */ + if (cl->width_max == 0 && no_header && cl->width_min == 1 && cl->width <= 1) + cl->width = cl->width_min = 0; + +done: + ON_DBG(COL, dbg_column(tb, cl)); + return rc; +} + +/* + * This is core of the scols_* voodoo... + */ +static int recount_widths(struct libscols_table *tb, struct libscols_buffer *buf) +{ + struct libscols_column *cl; + struct libscols_iter itr; + size_t width = 0, width_min = 0; /* output width */ + int stage, rc = 0; + int extremes = 0; + size_t colsepsz; + + + DBG(TAB, ul_debugobj(tb, "recounting widths (termwidth=%zu)", tb->termwidth)); + + colsepsz = mbs_safe_width(colsep(tb)); + + /* set basic columns width + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + int is_last; + + if (scols_column_is_hidden(cl)) + continue; + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + is_last = is_last_column(cl); + + width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ + width_min += cl->width_min + (is_last ? 0 : colsepsz); + extremes += cl->is_extreme; + } + + if (!tb->is_term) { + DBG(TAB, ul_debugobj(tb, " non-terminal output")); + goto done; + } + + /* be paranoid */ + if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (width_min > tb->termwidth + && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + width_min--; + cl->width_min--; + } + DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); + } + + /* reduce columns with extreme fields */ + if (width > tb->termwidth && extremes) { + DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t org_width; + + if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + org_width = cl->width; + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + if (org_width > cl->width) + width -= org_width - cl->width; + else + extremes--; /* hmm... nothing reduced */ + } + } + + if (width < tb->termwidth) { + if (extremes) { + DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); + + /* enlarge the first extreme column */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t add; + + if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + /* this column is too large, ignore? + if (cl->width_max - cl->width > + (tb->termwidth - width)) + continue; + */ + + add = tb->termwidth - width; + if (add && cl->width + add > cl->width_max) + add = cl->width_max - cl->width; + + cl->width += add; + width += add; + + if (width == tb->termwidth) + break; + } + } + + if (width < tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); + + /* try enlarging all columns */ + while (width < tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + cl->width++; + width++; + if (width == tb->termwidth) + break; + } + } + } else if (width < tb->termwidth) { + /* enlarge the last column */ + struct libscols_column *col = list_entry( + tb->tb_columns.prev, struct libscols_column, cl_columns); + + DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); + + if (!scols_column_is_right(col) && tb->termwidth - width > 0) { + col->width += tb->termwidth - width; + width = tb->termwidth; + } + } + } + + /* bad, we have to reduce output width, this is done in three stages: + * + * 1) trunc relative with trunc flag if the column width is greater than + * expected column width (it means "width_hint * terminal_width"). + * + * 2) trunc all with trunc flag + * + * 3) trunc relative without trunc flag + * + * Note that SCOLS_FL_WRAP (if no custom wrap function is specified) is + * interpreted as SCOLS_FL_TRUNC. + */ + for (stage = 1; width > tb->termwidth && stage <= 3; ) { + size_t org_width = width; + + DBG(TAB, ul_debugobj(tb, " reduce width - #%d stage (current=%zu, wanted=%zu)", + stage, width, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + int trunc_flag = 0; + + DBG(TAB, ul_debugobj(cl, " checking %s (width=%zu, treeart=%zu)", + cl->header.data, cl->width, cl->width_treeart)); + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + + /* never truncate if already minimal width */ + if (cl->width == cl->width_min) + continue; + + /* never truncate the tree */ + if (scols_column_is_tree(cl) && width <= cl->width_treeart) + continue; + + /* nothing to truncate */ + if (cl->width == 0 || width == 0) + continue; + + trunc_flag = scols_column_is_trunc(cl) + || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); + + switch (stage) { + /* #1 stage - trunc relative with TRUNC flag */ + case 1: + if (!trunc_flag) /* ignore: missing flag */ + break; + if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ + break; + if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) /* ignore: smaller than expected width */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (relative with flag)")); + cl->width--; + width--; + break; + + /* #2 stage - trunc all with TRUNC flag */ + case 2: + if (!trunc_flag) /* ignore: missing flag */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (all with flag)")); + cl->width--; + width--; + break; + + /* #3 stage - trunc relative without flag */ + case 3: + if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (relative without flag)")); + cl->width--; + width--; + break; + } + + /* hide zero width columns */ + if (cl->width == 0) + cl->flags |= SCOLS_FL_HIDDEN; + } + + /* the current stage is without effect, go to the next */ + if (org_width == width) + stage++; + } + + /* ignore last column(s) or force last column to be truncated if + * nowrap mode enabled */ + if (tb->no_wrap && width > tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + if (width - cl->width < tb->termwidth) { + size_t r = width - tb->termwidth; + + cl->flags |= SCOLS_FL_TRUNC; + cl->width -= r; + width -= r; + } else { + cl->flags |= SCOLS_FL_HIDDEN; + width -= cl->width + colsepsz; + } + } + } +done: + DBG(TAB, ul_debugobj(tb, " final width: %zu (rc=%d)", width, rc)); + ON_DBG(TAB, dbg_columns(tb)); + + return rc; +} + +static size_t strlen_line(struct libscols_line *ln) +{ + size_t i, sz = 0; + + assert(ln); + + for (i = 0; i < ln->ncells; i++) { + struct libscols_cell *ce = scols_line_get_cell(ln, i); + const char *data = ce ? scols_cell_get_data(ce) : NULL; + + sz += data ? strlen(data) : 0; + } + + return sz; +} + +static void cleanup_printing(struct libscols_table *tb, struct libscols_buffer *buf) +{ + if (!tb) + return; + + free_buffer(buf); + + if (tb->priv_symbols) { + scols_table_set_symbols(tb, NULL); + tb->priv_symbols = 0; + } +} + +static int initialize_printing(struct libscols_table *tb, struct libscols_buffer **buf) +{ + size_t bufsz, extra_bufsz = 0; + struct libscols_line *ln; + struct libscols_iter itr; + int rc; + + DBG(TAB, ul_debugobj(tb, "initialize printing")); + *buf = NULL; + + if (!tb->symbols) { + rc = scols_table_set_default_symbols(tb); + if (rc) + goto err; + tb->priv_symbols = 1; + } else + tb->priv_symbols = 0; + + if (tb->format == SCOLS_FMT_HUMAN) + tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 : + tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 : + isatty(STDOUT_FILENO); + + if (tb->is_term) { + size_t width = (size_t) scols_table_get_termwidth(tb); + + if (tb->termreduce > 0 && tb->termreduce < width) { + width -= tb->termreduce; + scols_table_set_termwidth(tb, width); + } + bufsz = width; + } else + bufsz = BUFSIZ; + + if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) + tb->header_repeat = 0; + + /* + * Estimate extra space necessary for tree, JSON or another output + * decoration. + */ + if (scols_table_is_tree(tb)) + extra_bufsz += tb->nlines * strlen(vertical_symbol(tb)); + + switch (tb->format) { + case SCOLS_FMT_RAW: + extra_bufsz += tb->ncols; /* separator between columns */ + break; + case SCOLS_FMT_JSON: + if (tb->format == SCOLS_FMT_JSON) + extra_bufsz += tb->nlines * 3; /* indention */ + /* fallthrough */ + case SCOLS_FMT_EXPORT: + { + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + extra_bufsz += strlen(scols_cell_get_data(&cl->header)); /* data */ + extra_bufsz += 2; /* separators */ + } + break; + } + case SCOLS_FMT_HUMAN: + break; + } + + /* + * Enlarge buffer if necessary, the buffer should be large enough to + * store line data and tree ascii art (or another decoration). + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t sz; + + sz = strlen_line(ln) + extra_bufsz; + if (sz > bufsz) + bufsz = sz; + } + + *buf = new_buffer(bufsz + 1); /* data + space for \0 */ + if (!*buf) { + rc = -ENOMEM; + goto err; + } + + if (tb->format == SCOLS_FMT_HUMAN) { + rc = recount_widths(tb, *buf); + if (rc != 0) + goto err; + } + + return 0; +err: + cleanup_printing(tb, *buf); + return rc; +} + +/** + * scola_table_print_range: + * @tb: table + * @start: first printed line or NULL to print from the begin of the table + * @end: last printed line or NULL to print all from start. + * + * If the start is the first line in the table than prints table header too. + * The header is printed only once. This does not work for trees. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end) +{ + struct libscols_buffer *buf = NULL; + struct libscols_iter itr; + int rc; + + if (scols_table_is_tree(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range from API")); + + rc = initialize_printing(tb, &buf); + if (rc) + return rc; + + if (start) { + itr.direction = SCOLS_ITER_FORWARD; + itr.head = &tb->tb_lines; + itr.p = &start->ln_lines; + } else + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + if (!start || itr.p == tb->tb_lines.next) { + rc = print_header(tb, buf); + if (rc) + goto done; + } + + rc = print_range(tb, buf, &itr, end); +done: + cleanup_printing(tb, buf); + return rc; +} + +/** + * scols_table_print_range_to_string: + * @tb: table + * @start: first printed line or NULL to print from the beginning of the table + * @end: last printed line or NULL to print all from start. + * @data: pointer to the beginning of a memory area to print to + * + * The same as scols_table_print_range(), but prints to @data instead of + * stream. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = scols_table_print_range(tb, start, end); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_table_print_range_to_string( + struct libscols_table *tb __attribute__((__unused__)), + struct libscols_line *start __attribute__((__unused__)), + struct libscols_line *end __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif + +static int __scols_print_table(struct libscols_table *tb, int *is_empty) +{ + int rc = 0; + struct libscols_buffer *buf = NULL; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing")); + if (is_empty) + *is_empty = 0; + + if (list_empty(&tb->tb_columns)) { + DBG(TAB, ul_debugobj(tb, "error -- no columns")); + return -EINVAL; + } + if (list_empty(&tb->tb_lines)) { + DBG(TAB, ul_debugobj(tb, "ignore -- no lines")); + if (is_empty) + *is_empty = 1; + return 0; + } + + tb->header_printed = 0; + rc = initialize_printing(tb, &buf); + if (rc) + return rc; + + fput_table_open(tb); + + if (tb->format == SCOLS_FMT_HUMAN) + print_title(tb); + + rc = print_header(tb, buf); + if (rc) + goto done; + + if (scols_table_is_tree(tb)) + rc = print_tree(tb, buf); + else + rc = print_table(tb, buf); + + fput_table_close(tb); +done: + cleanup_printing(tb, buf); + return rc; +} + +/** + * scols_print_table: + * @tb: table + * + * Prints the table to the output stream and terminate by \n. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_print_table(struct libscols_table *tb) +{ + int empty = 0; + int rc = __scols_print_table(tb, &empty); + + if (rc == 0 && !empty) + fputc('\n', tb->out); + return rc; +} + +/** + * scols_print_table_to_string: + * @tb: table + * @data: pointer to the beginning of a memory area to print to + * + * Prints the table to @data. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_print_table_to_string(struct libscols_table *tb, char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = __scols_print_table(tb, NULL); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_print_table_to_string( + struct libscols_table *tb __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif + diff --git a/libsmartcols/src/version.c b/libsmartcols/src/version.c new file mode 100644 index 0000000..e592ccc --- /dev/null +++ b/libsmartcols/src/version.c @@ -0,0 +1,62 @@ +/* + * version.c - Return the version of the library + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * See COPYING.libmount for the License of this software. + */ + +/** + * SECTION: version-utils + * @title: Version functions + * @short_description: functions to get the library version. + * + * Note that library version is not the same thing as SONAME version. The + * libsmarcols uses symbols versioning and SONAME is not modified for releases. + * + * The library version and symbols version follow util-linux package versioning. + */ + +#include <ctype.h> + +#include "smartcolsP.h" + +static const char *lib_version = LIBSMARTCOLS_VERSION; + +/** + * scols_parse_version_string: + * @ver_string: version string (e.g "2.18.0") + * + * Returns: release version code. + */ +int scols_parse_version_string(const char *ver_string) +{ + const char *cp; + int version = 0; + + assert(ver_string); + + for (cp = ver_string; *cp; cp++) { + if (*cp == '.') + continue; + if (!isdigit(*cp)) + break; + version = (version * 10) + (*cp - '0'); + } + return version; +} + +/** + * scols_get_library_version: + * @ver_string: return pointer to the static library version string if not NULL + * + * Returns: release version number. + */ +int scols_get_library_version(const char **ver_string) +{ + if (ver_string) + *ver_string = lib_version; + + return scols_parse_version_string(lib_version); +} + |