/* * table.c - functions handling the data at the table level * * Copyright (C) 2010-2014 Karel Zak * Copyright (C) 2014 Ondrej Oprala * * This file may be redistributed under the terms of the * GNU Lesser General Public License. */ /** * SECTION: table * @title: Table * @short_description: table API * * Table manipulation API. */ #include #include #include #include #include #include "nls.h" #include "widechar.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) /** * scols_new_table: * @syms: tree symbols or NULL for default * * Note that this function add a new reference to @syms. * * Returns: A newly allocated table. */ struct libscols_table *scols_new_table(struct libscols_symbols *syms) { struct libscols_table *tb; tb = calloc(1, sizeof(struct libscols_table)); if (!tb) return NULL; tb->refcount = 1; tb->out = stdout; INIT_LIST_HEAD(&tb->tb_lines); INIT_LIST_HEAD(&tb->tb_columns); if (syms && scols_table_set_symbols(tb, syms) != 0) goto err; return tb; err: scols_unref_table(tb); return NULL; } /** * 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. */ void scols_unref_table(struct libscols_table *tb) { if (tb && (--tb->refcount <= 0)) { scols_table_remove_lines(tb); scols_table_remove_columns(tb); scols_unref_symbols(tb->symbols); free(tb); } } /** * scols_table_add_column: * @tb: a pointer to a struct libscols_table instance * @cl: a pointer to a struct libscols_column instance * @flags: a flag mask integer * * Adds @cl to @tb's column list, setting the appropriate flags to @flags. * * Returns: 0, a negative number in case of an error. */ int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl, int flags) { assert(tb); assert(cl); if (!tb || !cl || !list_empty(&tb->tb_lines)) return -EINVAL; if (flags & SCOLS_FL_TREE) scols_table_set_tree(tb, 1); list_add_tail(&cl->cl_columns, &tb->tb_columns); cl->seqnum = tb->ncols++; scols_ref_column(cl); /* TODO: * * Currently it's possible to add/remove columns only if the table is * empty (see list_empty(tb->tb_lines) above). It would be nice to * enlarge/reduce lines cells[] always when we add/remove a new column. */ return 0; } /** * 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) { assert(tb); assert(cl); if (!tb || !cl || !list_empty(&tb->tb_lines)) return -EINVAL; list_del_init(&cl->cl_columns); tb->ncols--; 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) { assert(tb); if (!tb || !list_empty(&tb->tb_lines)) return -EINVAL; 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_new_column: * @tb: table * @name: column header * @whint: column width hint (absolute width: N > 1; relative width: 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 three ways: * * @whint = 0..1 : relative width, percent of terminal width * * @whint = 1..N : absolute width, empty colum will be truncated to * the column header width * * @whint = 1..N * * 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; assert (tb); if (!tb) return NULL; 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, flags)) /* 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, a negative number in case of an error. */ int scols_table_get_ncols(struct libscols_table *tb) { assert(tb); return tb ? tb->ncols : -EINVAL; } /* * scols_table_get_nlines: * @tb: table * * Returns: the nlines table member, a negative number in case of an error. */ int scols_table_get_nlines(struct libscols_table *tb) { assert(tb); return tb ? tb->nlines : -EINVAL; } /** * 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; 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(struct libscols_table *tb) { assert(tb); return tb ? tb->out: NULL; } /** * scols_table_reduce_termwidth: * @tb: table * @reduce: width * * Reduce the output width to @reduce. * * Returns: 0, a negative value in case of an error. */ int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce) { assert(tb); if (!tb) return -EINVAL; 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; assert(tb); 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) { assert(tb); assert(ln); if (!tb || !ln) return -EINVAL; if (tb->ncols > ln->ncells) { int rc = scols_line_alloc_cells(ln, tb->ncols); if (rc) return rc; } 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) { assert(tb); assert(ln); if (!tb || !ln) return -EINVAL; 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) { assert(tb); if (!tb) return; 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; assert(tb); assert(tb->ncols); if (!tb || !tb->ncols) 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) * * This is a shortcut for * * ln = scols_new_line(); * scols_line_set_....(cl, ...); * scols_table_add_line(tb, ln); * * Returns: a newly allocate line */ struct libscols_line *scols_table_get_line(struct libscols_table *tb, size_t n) { struct libscols_iter itr; struct libscols_line *ln; assert(tb); 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; assert(tb); if (!tb) return NULL; ret = scols_new_table(tb->symbols); if (!ret) return NULL; /* 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, tb->tree ? SCOLS_FL_TREE : 0)) 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); } return ret; err: scols_unref_table(ret); return NULL; } /** * scols_table_set_symbols: * @tb: table * @sy: symbols * * Returns: 0, a negative value in case of an error. */ int scols_table_set_symbols(struct libscols_table *tb, struct libscols_symbols *sy) { assert(tb); if (!tb) return -EINVAL; if (tb->symbols) /* unref old */ scols_unref_symbols(tb->symbols); if (sy) { /* ref user defined */ tb->symbols = sy; scols_ref_symbols(sy); } else { /* default symbols */ tb->symbols = scols_new_symbols(); if (!tb->symbols) return -ENOMEM; #if defined(HAVE_WIDECHAR) if (!scols_table_is_ascii(tb) && !strcmp(nl_langinfo(CODESET), "UTF-8")) { scols_symbols_set_branch(tb->symbols, UTF_VR UTF_H); scols_symbols_set_vertical(tb->symbols, UTF_V " "); scols_symbols_set_right(tb->symbols, UTF_UR UTF_H); } else #endif { scols_symbols_set_branch(tb->symbols, "|-"); scols_symbols_set_vertical(tb->symbols, "| "); scols_symbols_set_right(tb->symbols, "`-"); } } return 0; } /** * 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) { assert(tb); if (!tb) return -EINVAL; tb->colors_wanted = enable; return 0; } /** * scols_table_set_raw: * @tb: table * @enable: 1 or 0 * * Enable/disable raw * * Returns: 0 on success, negative number in case of an error. */ int scols_table_set_raw(struct libscols_table *tb, int enable) { assert(tb); if (!tb) return -EINVAL; tb->raw = enable; return 0; } /** * scols_table_set_ascii: * @tb: table * @enable: 1 or 0 * * Enable/disable ascii * * Returns: 0 on success, negative number in case of an error. */ int scols_table_set_ascii(struct libscols_table *tb, int enable) { assert(tb); if (!tb) return -EINVAL; tb->ascii = enable; return 0; } /** * scols_table_set_no_headings: * @tb: table * @enable: 1 or 0 * * Enable/disable no_headings * * Returns: 0 on success, negative number in case of an error. */ int scols_table_set_no_headings(struct libscols_table *tb, int enable) { assert(tb); if (!tb) return -EINVAL; tb->no_headings = enable; return 0; } /** * scols_table_set_export: * @tb: table * @enable: 1 or 0 * * Enable/disable export * * Returns: 0 on success, negative number in case of an error. */ int scols_table_set_export(struct libscols_table *tb, int enable) { assert(tb); if (!tb) return -EINVAL; tb->export = enable; return 0; } /** * scols_table_set_max: * @tb: table * @enable: 1 or 0 * * Enable/disable max * * Returns: 0 on success, negative number in case of an error. */ int scols_table_set_max(struct libscols_table *tb, int enable) { assert(tb); if (!tb) return -EINVAL; tb->max = enable; return 0; } /** * scols_table_set_tree: * @tb: table * @enable: 1 or 0 * * Enable/disable tree * * Returns: 0 on success, negative number in case of an error. */ int scols_table_set_tree(struct libscols_table *tb, int enable) { assert(tb); if (!tb) return -EINVAL; tb->tree = enable; return 0; } /** * scols_table_colors_wanted: * @tb: table * * Gets the value of the colors_wanted flag. * * Returns: colors_wanted flag value, negative value in case of an error. */ int scols_table_colors_wanted(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->colors_wanted; } /** * scols_table_is_empty: * @tb: table * * Returns: 1 if empty, 0 if not (or in case of an error). */ int scols_table_is_empty(struct libscols_table *tb) { return !tb || !tb->nlines; } /** * scols_table_is_raw: * @tb: table * * Gets the value of the raw flag. * * Returns: raw flag value, negative value in case of an error. */ int scols_table_is_raw(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->raw; } /** * scols_table_is_ascii: * @tb: table * * Gets the value of the ascii flag. * * Returns: ascii flag value, negative value in case of an error. */ int scols_table_is_ascii(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->ascii; } /** * scols_table_is_no_headings: * @tb: table * * Gets the value of the no_headings flag. * * Returns: no_headings flag value, negative value in case of an error. */ int scols_table_is_no_headings(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->no_headings; } /** * scols_table_is_export: * @tb: table * * Gets the value of the export flag. * * Returns: export flag value, negative value in case of an error. */ int scols_table_is_export(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->export; } /** * scols_table_is_max: * @tb: table * * Gets the value of the max flag. * * Returns: max flag value, negative value in case of an error. */ int scols_table_is_max(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->max; } /** * scols_table_is_tree: * @tb: table * * Gets the value of the tree flag. * * Returns: tree flag value, negative value in case of an error. */ int scols_table_is_tree(struct libscols_table *tb) { assert(tb); if (!tb) return -EINVAL; return tb->tree; }