summaryrefslogtreecommitdiffstats
path: root/libsmartcols
diff options
context:
space:
mode:
Diffstat (limited to 'libsmartcols')
-rw-r--r--libsmartcols/src/Makemodule.am1
-rw-r--r--libsmartcols/src/buffer.c12
-rw-r--r--libsmartcols/src/calculate.c22
-rw-r--r--libsmartcols/src/column.c1
-rw-r--r--libsmartcols/src/grouping.c587
-rw-r--r--libsmartcols/src/init.c1
-rw-r--r--libsmartcols/src/libsmartcols.h.in12
-rw-r--r--libsmartcols/src/libsmartcols.sym12
-rw-r--r--libsmartcols/src/line.c23
-rw-r--r--libsmartcols/src/print.c195
-rw-r--r--libsmartcols/src/smartcolsP.h135
-rw-r--r--libsmartcols/src/symbols.c138
-rw-r--r--libsmartcols/src/table.c100
13 files changed, 1177 insertions, 62 deletions
diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am
index 6dc4dd881..c458715f5 100644
--- a/libsmartcols/src/Makemodule.am
+++ b/libsmartcols/src/Makemodule.am
@@ -21,6 +21,7 @@ libsmartcols_la_SOURCES= \
libsmartcols/src/version.c \
libsmartcols/src/buffer.c \
libsmartcols/src/calculate.c \
+ libsmartcols/src/grouping.c \
libsmartcols/src/init.c
libsmartcols_la_LIBADD = $(LDADD) libcommon.la
diff --git a/libsmartcols/src/buffer.c b/libsmartcols/src/buffer.c
index 46f270d1f..20953a5dc 100644
--- a/libsmartcols/src/buffer.c
+++ b/libsmartcols/src/buffer.c
@@ -67,6 +67,18 @@ int buffer_append_data(struct libscols_buffer *buf, const char *str)
return 0;
}
+int buffer_append_ntimes(struct libscols_buffer *buf, size_t n, const char *str)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ int rc = buffer_append_data(buf, str);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
int buffer_set_data(struct libscols_buffer *buf, const char *str)
{
int rc = buffer_reset_data(buf);
diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c
index 8dd65a254..a1224adee 100644
--- a/libsmartcols/src/calculate.c
+++ b/libsmartcols/src/calculate.c
@@ -32,7 +32,6 @@ static void dbg_columns(struct libscols_table *tb)
dbg_column(tb, cl);
}
-
/*
* This function counts column width.
*
@@ -141,14 +140,21 @@ int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf)
struct libscols_iter itr;
size_t width = 0, width_min = 0; /* output width */
int stage, rc = 0;
- int extremes = 0;
+ int extremes = 0, group_ncolumns = 0;
size_t colsepsz;
- DBG(TAB, ul_debugobj(tb, "recounting widths (termwidth=%zu)", tb->termwidth));
+ DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth));
colsepsz = mbs_safe_width(colsep(tb));
+ if (has_groups(tb)) {
+ rc = scols_groups_calculate_grpset(tb);
+ if (rc)
+ goto done;
+ group_ncolumns = 1;
+ }
+
/* set basic columns width
*/
scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
@@ -157,6 +163,13 @@ int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf)
if (scols_column_is_hidden(cl))
continue;
+
+ /* we print groups chart only for the for the first tree column */
+ if (scols_column_is_tree(cl) && group_ncolumns == 1) {
+ cl->is_groups = 1;
+ group_ncolumns++;
+ }
+
rc = count_column_width(tb, cl, buf);
if (rc)
goto done;
@@ -165,7 +178,8 @@ int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf)
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 (cl->is_extreme)
+ extremes++;
}
if (!tb->is_term) {
diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c
index 53521f6ad..4b42938f6 100644
--- a/libsmartcols/src/column.c
+++ b/libsmartcols/src/column.c
@@ -110,6 +110,7 @@ struct libscols_column *scols_copy_column(const struct libscols_column *cl)
ret->width_hint = cl->width_hint;
ret->flags = cl->flags;
ret->is_extreme = cl->is_extreme;
+ ret->is_groups = cl->is_groups;
return ret;
err:
diff --git a/libsmartcols/src/grouping.c b/libsmartcols/src/grouping.c
new file mode 100644
index 000000000..63ed6c4b2
--- /dev/null
+++ b/libsmartcols/src/grouping.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
+ */
+#include "smartcolsP.h"
+
+/**
+ * SECTION: grouping
+ * @title: Grouping
+ * @short_description: lines grouing
+ *
+ * Lines groups manipulation API. The grouping API allows to create M:N
+ * relations between lines and on tree-like output it prints extra chart to
+ * visualize these relations. The group has unlimited number of members and
+ * group childs. See libsmartcols/sample/grouping* for more details.
+ */
+
+/* Private API */
+void scols_ref_group(struct libscols_group *gr)
+{
+ if (gr)
+ gr->refcount++;
+}
+
+void scols_group_remove_children(struct libscols_group *gr)
+{
+ if (!gr)
+ return;
+
+ while (!list_empty(&gr->gr_children)) {
+ struct libscols_line *ln = list_entry(gr->gr_children.next,
+ struct libscols_line, ln_children);
+
+ DBG(GROUP, ul_debugobj(gr, "remove child"));
+ list_del_init(&ln->ln_children);
+ scols_ref_group(ln->parent_group);
+ ln->parent_group = NULL;
+ scols_unref_line(ln);
+ }
+}
+
+void scols_group_remove_members(struct libscols_group *gr)
+{
+ if (!gr)
+ return;
+
+ while (!list_empty(&gr->gr_members)) {
+ struct libscols_line *ln = list_entry(gr->gr_members.next,
+ struct libscols_line, ln_groups);
+
+ DBG(GROUP, ul_debugobj(gr, "remove member [%p]", ln));
+ list_del_init(&ln->ln_groups);
+
+ scols_unref_group(ln->group);
+ ln->group->nmembers++;
+ ln->group = NULL;
+
+ scols_unref_line(ln);
+ }
+}
+
+/* note group has to be already without members to deallocate */
+void scols_unref_group(struct libscols_group *gr)
+{
+ if (gr && --gr->refcount <= 0) {
+ DBG(GROUP, ul_debugobj(gr, "dealloc"));
+ scols_group_remove_children(gr);
+ list_del(&gr->gr_groups);
+ free(gr);
+ return;
+ }
+}
+
+
+static void groups_fix_members_order(struct libscols_line *ln)
+{
+ struct libscols_iter itr;
+ struct libscols_line *child;
+
+ if (ln->group) {
+ list_add_tail(&ln->ln_groups, &ln->group->gr_members);
+ DBG(GROUP, ul_debugobj(ln->group, "fixing member line=%p [%zu/%zu]",
+ ln, ln->group->nmembers,
+ list_count_entries(&ln->group->gr_members)));
+ }
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_child(ln, &itr, &child) == 0)
+ groups_fix_members_order(child);
+
+ /*
+ * We modify gr_members list, so is_last_group_member() does not have
+ * to provide reliable answer, we need to verify by list_count_entries().
+ */
+ if (ln->group
+ && is_last_group_member(ln)
+ && ln->group->nmembers == list_count_entries(&ln->group->gr_members)) {
+
+ DBG(GROUP, ul_debugobj(ln->group, "fixing childs"));
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_group_child(ln, &itr, &child) == 0)
+ groups_fix_members_order(child);
+ }
+}
+
+void scols_groups_fix_members_order(struct libscols_table *tb)
+{
+ struct libscols_iter itr;
+ struct libscols_line *ln;
+ struct libscols_group *gr;
+
+ /* remove all from groups lists */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_group(tb, &itr, &gr) == 0) {
+ while (!list_empty(&gr->gr_members)) {
+ struct libscols_line *ln = list_entry(gr->gr_members.next,
+ struct libscols_line, ln_groups);
+ list_del_init(&ln->ln_groups);
+ }
+ }
+
+ /* add again to the groups list in order we walk in tree */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (ln->parent || ln->parent_group)
+ continue;
+ groups_fix_members_order(ln);
+ }
+
+ /* If group child is memeber of another group *
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_group(tb, &itr, &gr) == 0) {
+ struct libscols_iter xitr;
+ struct libscols_line *child;
+
+ scols_reset_iter(&xitr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_group_child(ln, &xitr, &child) == 0)
+ groups_fix_members_order(child);
+ }
+ */
+}
+
+static inline const char *group_state_to_string(int state)
+{
+ static const char *grpstates[] = {
+ [SCOLS_GSTATE_NONE] = "none",
+ [SCOLS_GSTATE_FIRST_MEMBER] = "1st-member",
+ [SCOLS_GSTATE_MIDDLE_MEMBER] = "middle-member",
+ [SCOLS_GSTATE_LAST_MEMBER] = "last-member",
+ [SCOLS_GSTATE_MIDDLE_CHILD] = "middle-child",
+ [SCOLS_GSTATE_LAST_CHILD] = "last-child",
+ [SCOLS_GSTATE_CONT_MEMBERS] = "continue-members",
+ [SCOLS_GSTATE_CONT_CHILDREN] = "continue-children"
+ };
+
+ assert(state >= 0);
+ assert((size_t) state < ARRAY_SIZE(grpstates));
+
+ return grpstates[state];
+};
+
+static void grpset_debug(struct libscols_table *tb, struct libscols_line *ln)
+{
+ size_t i;
+
+ for (i = 0; i < tb->grpset_size; i++) {
+ if (tb->grpset[i]) {
+ struct libscols_group *gr = tb->grpset[i];
+
+ if (ln)
+ DBG(LINE, ul_debugobj(ln, "grpset[%zu]: %p %s", i,
+ gr, group_state_to_string(gr->state)));
+ else
+ DBG(LINE, ul_debug("grpset[%zu]: %p %s", i,
+ gr, group_state_to_string(gr->state)));
+ } else if (ln) {
+ DBG(LINE, ul_debugobj(ln, "grpset[%zu]: free", i));
+ } else
+ DBG(LINE, ul_debug("grpset[%zu]: free", i));
+ }
+}
+static int group_state_for_line(struct libscols_group *gr, struct libscols_line *ln)
+{
+ if (gr->state == SCOLS_GSTATE_NONE &&
+ (ln->group != gr || !is_first_group_member(ln)))
+ /*
+ * NONE is possible to translate to FIRST_MEMBER only, and only if
+ * line group matches with the current group.
+ */
+ return SCOLS_GSTATE_NONE;
+
+ if (ln->group != gr && ln->parent_group != gr) {
+ /* Not our line, continue */
+ if (gr->state == SCOLS_GSTATE_FIRST_MEMBER ||
+ gr->state == SCOLS_GSTATE_MIDDLE_MEMBER ||
+ gr->state == SCOLS_GSTATE_CONT_MEMBERS)
+ return SCOLS_GSTATE_CONT_MEMBERS;
+
+ if (gr->state == SCOLS_GSTATE_LAST_MEMBER ||
+ gr->state == SCOLS_GSTATE_MIDDLE_CHILD ||
+ gr->state == SCOLS_GSTATE_CONT_CHILDREN)
+ return SCOLS_GSTATE_CONT_CHILDREN;
+
+ } else if (ln->group == gr && is_first_group_member(ln)) {
+ return SCOLS_GSTATE_FIRST_MEMBER;
+
+ } else if (ln->group == gr && is_last_group_member(ln)) {
+ return SCOLS_GSTATE_LAST_MEMBER;
+
+ } else if (ln->group == gr && is_group_member(ln)) {
+ return SCOLS_GSTATE_MIDDLE_MEMBER;
+
+ } else if (ln->parent_group == gr && is_last_group_child(ln)) {
+ return SCOLS_GSTATE_LAST_CHILD;
+
+ } else if (ln->parent_group == gr && is_group_child(ln)) {
+ return SCOLS_GSTATE_MIDDLE_CHILD;
+ }
+
+ return SCOLS_GSTATE_NONE;
+}
+
+/* For now we assume that each active group is just 3 columns width. Later we can make it dynamic...
+ */
+static void grpset_apply_group_state(struct libscols_group **xx, int state, struct libscols_group *gr)
+{
+ DBG(GROUP, ul_debugobj(gr, " appling state to grpset"));
+
+ /* gr->state holds the old state, @state is the new state
+ */
+ if (state == SCOLS_GSTATE_NONE)
+ xx[0] = xx[1] = xx[2] = NULL;
+ else
+ xx[0] = xx[1] = xx[2] = gr;
+ gr->state = state;
+}
+
+static struct libscols_group **grpset_locate_freespace(struct libscols_table *tb, size_t wanted, int prepend)
+{
+ size_t i, avail = 0;
+ struct libscols_group **tmp, **first = NULL;
+
+ if (!tb->grpset_size)
+ prepend = 0;
+
+ if (prepend) {
+ for (i = tb->grpset_size - 1; ; i--) {
+ if (tb->grpset[i] == NULL) {
+ first = &tb->grpset[i];
+ avail++;
+ } else
+ avail = 0;
+ if (avail == wanted)
+ goto done;
+ if (i == 0)
+ break;
+ }
+ } else {
+ for (i = 0; i < tb->grpset_size; i++) {
+ if (tb->grpset[i] == NULL) {
+ if (avail == 0)
+ first = &tb->grpset[i];
+ avail++;
+ } else
+ avail = 0;
+ if (avail == wanted)
+ goto done;
+ }
+ }
+
+ DBG(TAB, ul_debugobj(tb, " realocate grpset [sz: old=%zu, new=%zu]",
+ tb->grpset_size, tb->grpset_size + wanted));
+
+ tmp = realloc(tb->grpset, (tb->grpset_size + wanted) * sizeof(struct libscols_group *));
+ if (!tmp)
+ return NULL;
+
+ tb->grpset = tmp;
+
+ if (prepend) {
+ DBG(TAB, ul_debugobj(tb, " prepending free space"));
+ char *dest = (char *) tb->grpset;
+
+ memmove( dest + (wanted * sizeof(struct libscols_group *)),
+ tb->grpset,
+ tb->grpset_size * sizeof(struct libscols_group *));
+ first = tb->grpset;
+ } else {
+ first = tb->grpset + tb->grpset_size;
+ }
+
+ memset(first, 0, wanted * sizeof(struct libscols_group *));
+ tb->grpset_size += wanted;
+
+ grpset_debug(tb, NULL);
+done:
+ return first;
+}
+
+static struct libscols_group **grpset_locate_group(struct libscols_table *tb, struct libscols_group *gr)
+{
+ size_t i;
+
+ for (i = 0; i < tb->grpset_size; i++) {
+ if (gr == tb->grpset[i])
+ return &tb->grpset[i];
+ }
+
+ return NULL;
+}
+
+
+
+#define SCOLS_GRPSET_MINSZ 3
+
+static int grpset_update(struct libscols_table *tb, struct libscols_line *ln, struct libscols_group *gr)
+{
+ struct libscols_group **xx;
+ int state;
+
+ DBG(LINE, ul_debugobj(ln, " group [%p] grpset update", gr));
+
+ /* new state, note that gr->state still holds the original state */
+ state = group_state_for_line(gr, ln);
+ DBG(LINE, ul_debugobj(ln, " state old='%s', new='%s'",
+ group_state_to_string(gr->state),
+ group_state_to_string(state)));
+
+ if (state == SCOLS_GSTATE_FIRST_MEMBER && gr->state != SCOLS_GSTATE_NONE) {
+ DBG(LINE, ul_debugobj(ln, "wrong group initialization"));
+ abort();
+ }
+ if (state != SCOLS_GSTATE_NONE && gr->state == SCOLS_GSTATE_LAST_CHILD) {
+ DBG(LINE, ul_debugobj(ln, "wrong group termination"));
+ abort();
+ }
+ if (gr->state == SCOLS_GSTATE_LAST_MEMBER &&
+ !(state == SCOLS_GSTATE_LAST_CHILD ||
+ state == SCOLS_GSTATE_CONT_CHILDREN ||
+ state == SCOLS_GSTATE_MIDDLE_CHILD ||
+ state == SCOLS_GSTATE_NONE)) {
+ DBG(LINE, ul_debugobj(ln, "wrong group member->child order"));
+ abort();
+ }
+
+ /* should not happen; probably wrong line... */
+ if (gr->state == SCOLS_GSTATE_NONE && state == SCOLS_GSTATE_NONE)
+ return 0;
+
+ /* locate place in grpset where we draw the group */
+ if (!tb->grpset || gr->state == SCOLS_GSTATE_NONE)
+ xx = grpset_locate_freespace(tb, SCOLS_GRPSET_MINSZ, 1);
+ else
+ xx = grpset_locate_group(tb, gr);
+ if (!xx) {
+ DBG(LINE, ul_debugobj(ln, "failed to locate group or reallocate grpset"));
+ return -ENOMEM;
+ }
+
+ grpset_apply_group_state(xx, state, gr);
+ ON_DBG(LINE, grpset_debug(tb, ln));
+ return 0;
+}
+
+static int grpset_update_active_groups(struct libscols_table *tb, struct libscols_line *ln)
+{
+ int rc = 0;
+ size_t i;
+ struct libscols_group *last = NULL;
+
+ DBG(LINE, ul_debugobj(ln, " update for active groups"));
+
+ for (i = 0; i < tb->grpset_size; i++) {
+ struct libscols_group *gr = tb->grpset[i];
+
+ if (!gr || last == gr)
+ continue;
+ last = gr;
+ rc = grpset_update(tb, ln, gr);
+ if (rc)
+ break;
+ }
+
+ DBG(LINE, ul_debugobj(ln, " <- active groups updated [rc=%d]", rc));
+ return rc;
+}
+
+int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln)
+{
+ int rc = 0;
+
+ DBG(LINE, ul_debugobj(ln, " grpset update [line: group=%p, parent_group=%p",
+ ln->group, ln->parent_group));
+
+ rc = grpset_update_active_groups(tb, ln);
+ if (!rc && ln->group && ln->group->state == SCOLS_GSTATE_NONE) {
+ DBG(LINE, ul_debugobj(ln, " introduce a new group"));
+ rc = grpset_update(tb, ln, ln->group);
+ }
+ return rc;
+}
+
+static int groups_calculate_grpset(struct libscols_table *tb, struct libscols_line *ln)
+{
+ struct libscols_iter itr;
+ struct libscols_line *child;
+ int rc = 0;
+
+ DBG(LINE, ul_debugobj(ln, " grpset calculate"));
+
+ /* current line */
+ rc = scols_groups_update_grpset(tb, ln);
+ if (rc)
+ goto done;
+
+ /* line's children */
+ if (has_children(ln)) {
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_child(ln, &itr, &child) == 0) {
+ rc = groups_calculate_grpset(tb, child);
+ if (rc)
+ goto done;
+ }
+ }
+
+ /* group's children */
+ if (is_last_group_member(ln) && has_group_children(ln)) {
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_group_child(ln, &itr, &child) == 0) {
+ rc = groups_calculate_grpset(tb, child);
+ if (rc)
+ goto done;
+ }
+ }
+done:
+ return rc;
+}
+
+
+int scols_groups_calculate_grpset(struct libscols_table *tb)
+{
+ struct libscols_iter itr;
+ struct libscols_line *ln;
+ int rc = 0;
+
+ DBG(TAB, ul_debugobj(tb, "grpset calculate [top-level]"));
+
+ scols_groups_reset_state(tb);
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (ln->parent || ln->parent_group)
+ continue;
+ rc = groups_calculate_grpset(tb, ln);
+ if (rc)
+ break;
+ }
+
+ scols_groups_reset_state(tb);
+ return rc;
+}
+
+void scols_groups_reset_state(struct libscols_table *tb)
+{
+ struct libscols_iter itr;
+ struct libscols_group *gr;
+
+ DBG(TAB, ul_debugobj(tb, "reset groups states"));
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_group(tb, &itr, &gr) == 0)
+ gr->state = SCOLS_GSTATE_NONE;
+
+ if (tb->grpset)
+ memset(tb->grpset, 0, tb->grpset_size);
+}
+
+static void add_member(struct libscols_group *gr, struct libscols_line *ln)
+{
+ DBG(GROUP, ul_debugobj(gr, "add member"));
+ ln->group = gr;
+ gr->nmembers++;
+ scols_ref_group(gr);
+
+ list_add_tail(&ln->ln_groups, &gr->gr_members);
+ scols_ref_line(ln);
+}
+
+/**
+ * scols_table_group_lines:
+ * @tb: a pointer to a struct libscols_table instance
+ * @ln: new group member
+ * @member: group member
+ * @id: group identifier (unused, not implemented yet), use zero.
+ *
+ * This function add line @ln to group of lines represented by @member. If the
+ * group is not yet defined (@member is not member of any group) than a new one
+ * is allocated.
+ *
+ * The @ln maybe a NULL -- in this case only a new group is allocated if not
+ * defined yet.
+ *
+ * Note that the same line cannot be member of more groups (not implemented
+ * yet). The child of any group can be member of another group.
+ *
+ * The @id is not used for now, use 0. The plan is to use it to support
+ * multi-group membership in future.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_table_group_lines( struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_line *member,
+ __attribute__((__unused__)) int id)
+{
+ struct libscols_group *gr = NULL;
+
+ if (!tb || (!ln && !member))
+ return -EINVAL;
+ if (ln && member) {
+ if (ln->group && !member->group)
+ return -EINVAL;
+ if (ln->group && member->group && ln->group != member->group)
+ return -EINVAL;
+ }
+
+ gr = member->group;
+
+ /* create a new group */
+ if (!gr) {
+ gr = calloc(1, sizeof(*gr));
+ if (!gr)
+ return -ENOMEM;
+ DBG(GROUP, ul_debugobj(gr, "alloc"));
+ gr->refcount = 1;
+ INIT_LIST_HEAD(&gr->gr_members);
+ INIT_LIST_HEAD(&gr->gr_children);
+ INIT_LIST_HEAD(&gr->gr_groups);
+
+ /* add group to the table */
+ list_add_tail(&gr->gr_groups, &tb->tb_groups);
+
+ /* add the first member */
+ add_member(gr, member);
+ }
+
+ /* add to group */
+ if (ln && !ln->group)
+ add_member(gr, ln);
+
+ return 0;
+}
+
+/**
+ * scols_line_link_groups:
+ * @ln: line instance
+ * @member: group member
+ * @id: group identifier (unused, not implemented yet))
+ *
+ * Define @ln as child of group represented by group @member. The line @ln
+ * cannot be child of any other line. It's possible to create group->child or
+ * parent->child relationship, but no both for the same line (child).
+ *
+ * The @id is not used for now, use 0. The plan is to use it to support
+ * multi-group membership in future.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member,
+ __attribute__((__unused__)) int id)
+{
+ if (!ln || !member || !member->group || ln->parent)
+ return -EINVAL;
+
+ DBG(GROUP, ul_debugobj(member->group, "add child"));
+
+ list_add_tail(&ln->ln_children, &member->group->gr_children);
+ scols_ref_line(ln);
+
+ ln->parent_group = member->group;
+ scols_ref_group(member->group);
+
+ return 0;
+}
diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c
index 104e43b64..dfd7510dc 100644
--- a/libsmartcols/src/init.c
+++ b/libsmartcols/src/init.c
@@ -25,6 +25,7 @@ UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) =
{ "cell", SCOLS_DEBUG_CELL, "table cell utils" },
{ "col", SCOLS_DEBUG_COL, "cols utils" },
{ "help", SCOLS_DEBUG_HELP, "this help" },
+ { "group", SCOLS_DEBUG_GROUP, "lines grouping utils" },
{ "line", SCOLS_DEBUG_LINE, "table line utils" },
{ "tab", SCOLS_DEBUG_TAB, "table utils" },
{ NULL, 0, NULL }
diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in
index f8be0bc04..bd47f7898 100644
--- a/libsmartcols/src/libsmartcols.h.in
+++ b/libsmartcols/src/libsmartcols.h.in
@@ -129,6 +129,14 @@ 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);
+extern int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_middle_child(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,
@@ -314,6 +322,10 @@ extern int scols_table_print_range_to_string( struct libscols_table *tb,
struct libscols_line *end,
char **data);
+/* grouping.c */
+int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, int id);
+int scols_table_group_lines(struct libscols_table *tb, struct libscols_line *ln,
+ struct libscols_line *member, int id);
#ifdef __cplusplus
}
#endif
diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym
index 331a55554..e318c9054 100644
--- a/libsmartcols/src/libsmartcols.sym
+++ b/libsmartcols/src/libsmartcols.sym
@@ -182,3 +182,15 @@ SMARTCOLS_2.33 {
scols_column_set_json_type;
scols_column_get_json_type;
} SMARTCOLS_2.31;
+
+SMARTCOLS_2.34 {
+ scols_table_group_lines;
+ scols_line_link_group;
+ scols_symbols_set_group_vertical;
+ scols_symbols_set_group_horizontal;
+ scols_symbols_set_group_first_member;
+ scols_symbols_set_group_last_member;
+ scols_symbols_set_group_middle_member;
+ scols_symbols_set_group_last_child;
+ scols_symbols_set_group_middle_child;
+} SMARTCOLS_2.33;
diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c
index 60be2c135..351bed7d5 100644
--- a/libsmartcols/src/line.c
+++ b/libsmartcols/src/line.c
@@ -47,6 +47,7 @@ struct libscols_line *scols_new_line(void)
INIT_LIST_HEAD(&ln->ln_lines);
INIT_LIST_HEAD(&ln->ln_children);
INIT_LIST_HEAD(&ln->ln_branch);
+ INIT_LIST_HEAD(&ln->ln_groups);
return ln;
}
@@ -75,6 +76,8 @@ void scols_unref_line(struct libscols_line *ln)
DBG(CELL, ul_debugobj(ln, "dealloc"));
list_del(&ln->ln_lines);
list_del(&ln->ln_children);
+ list_del(&ln->ln_groups);
+ scols_unref_group(ln->group);
scols_line_free_cells(ln);
free(ln->color);
free(ln);
@@ -309,6 +312,26 @@ int scols_line_next_child(struct libscols_line *ln,
return rc;
}
+/* private API */
+int scols_line_next_group_child(struct libscols_line *ln,
+ struct libscols_iter *itr,
+ struct libscols_line **chld)
+{
+ int rc = 1;
+
+ if (!ln || !itr || !chld || !ln->group)
+ return -EINVAL;
+ *chld = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &ln->group->gr_children);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children);
+ rc = 0;
+ }
+
+ return rc;
+}
/**
* scols_line_is_ancestor:
diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c
index dc6637cea..4a45b41fb 100644
--- a/libsmartcols/src/print.c
+++ b/libsmartcols/src/print.c
@@ -33,9 +33,17 @@
* 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 branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-")
+#define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ")
+#define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-")
+
+#define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|")
+#define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-")
+#define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->")
+#define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->")
+#define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->")
+#define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-")
+#define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-")
#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \
((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " "))
@@ -44,7 +52,7 @@
/* returns pointer to the end of used data */
-static int line_ascii_art_to_buffer(struct libscols_table *tb,
+static int tree_ascii_art_to_buffer(struct libscols_table *tb,
struct libscols_line *ln,
struct libscols_buffer *buf)
{
@@ -57,7 +65,7 @@ static int line_ascii_art_to_buffer(struct libscols_table *tb,
if (!ln->parent)
return 0;
- rc = line_ascii_art_to_buffer(tb, ln->parent, buf);
+ rc = tree_ascii_art_to_buffer(tb, ln->parent, buf);
if (rc)
return rc;
@@ -69,6 +77,95 @@ static int line_ascii_art_to_buffer(struct libscols_table *tb,
return buffer_append_data(buf, art);
}
+static int grpset_is_empty( struct libscols_table *tb,
+ size_t idx,
+ size_t *rest)
+{
+ size_t i;
+
+ for (i = idx; i < tb->grpset_size; i++) {
+ if (tb->grpset[i] == NULL) {
+ if (rest)
+ (*rest)++;
+ } else
+ return 0;
+ }
+ return 1;
+}
+
+static int groups_ascii_art_to_buffer( struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_buffer *buf)
+{
+ int rc, filled = 0;
+ size_t i, rest = 0;
+ const char *filler = cellpadding_symbol(tb);
+
+ if (!has_groups(tb) || !tb->grpset)
+ return 0;
+
+ DBG(LINE, ul_debugobj(ln, "printing groups chart"));
+
+ rc = scols_groups_update_grpset(tb, ln);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < tb->grpset_size; i+=3) {
+ struct libscols_group *gr = tb->grpset[i];
+
+ if (!gr) {
+ buffer_append_ntimes(buf, 3, cellpadding_symbol(tb));
+ continue;
+ }
+
+ switch (gr->state) {
+ case SCOLS_GSTATE_FIRST_MEMBER:
+ buffer_append_data(buf, grp_m_first_symbol(tb));
+ break;
+ case SCOLS_GSTATE_MIDDLE_MEMBER:
+ buffer_append_data(buf, grp_m_middle_symbol(tb));
+ break;
+ case SCOLS_GSTATE_LAST_MEMBER:
+ buffer_append_data(buf, grp_m_last_symbol(tb));
+ break;
+ case SCOLS_GSTATE_CONT_MEMBERS:
+ buffer_append_data(buf, grp_vertical_symbol(tb));
+ buffer_append_ntimes(buf, 2, filler);
+ break;
+ case SCOLS_GSTATE_MIDDLE_CHILD:
+ buffer_append_data(buf, filler);
+ buffer_append_data(buf, grp_c_middle_symbol(tb));
+ if (grpset_is_empty(tb, i + 3, &rest)) {
+ buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb));
+ filled = 1;
+ }
+ filler = grp_horizontal_symbol(tb);
+ break;
+ case SCOLS_GSTATE_LAST_CHILD:
+ buffer_append_data(buf, cellpadding_symbol(tb));
+ buffer_append_data(buf, grp_c_last_symbol(tb));
+ if (grpset_is_empty(tb, i + 3, &rest)) {
+ buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb));
+ filled = 1;
+ }
+ filler = grp_horizontal_symbol(tb);
+ break;
+ case SCOLS_GSTATE_CONT_CHILDREN:
+ buffer_append_data(buf, filler);
+ buffer_append_data(buf, grp_vertical_symbol(tb));
+ buffer_append_data(buf, filler);
+ break;
+ }
+
+ if (filled)
+ break;
+ }
+
+ if (!filled)
+ buffer_append_data(buf, filler);
+ return 0;
+}
+
static int has_pending_data(struct libscols_table *tb)
{
struct libscols_column *cl;
@@ -107,7 +204,7 @@ static void print_empty_cell(struct libscols_table *tb,
if (art) {
/* whatever the rc, len_pad will be sensible */
- line_ascii_art_to_buffer(tb, ln, art);
+ tree_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);
@@ -458,19 +555,26 @@ int __cell_to_buffer(struct libscols_table *tb,
return buffer_set_data(buf, data);
/*
+ * Group stuff
+ */
+ if (!scols_table_is_json(tb) && cl->is_groups)
+ rc = groups_ascii_art_to_buffer(tb, ln, buf);
+
+ /*
* Tree stuff
*/
- if (ln->parent && !scols_table_is_json(tb)) {
- rc = line_ascii_art_to_buffer(tb, ln->parent, buf);
+ if (!rc && ln->parent && !scols_table_is_json(tb)) {
+ rc = tree_ascii_art_to_buffer(tb, ln->parent, buf);
if (!rc && is_last_child(ln))
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 && (ln->parent || cl->is_groups) && !scols_table_is_json(tb))
+ buffer_set_art_index(buf);
+
if (!rc)
rc = buffer_append_data(buf, data);
return rc;
@@ -490,8 +594,6 @@ static int print_line(struct libscols_table *tb,
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) {
@@ -651,7 +753,20 @@ int __scols_print_header(struct libscols_table *tb, struct libscols_buffer *buf)
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));
+
+ buffer_reset_data(buf);
+
+ if (cl->is_groups
+ && scols_table_is_tree(tb) && scols_column_is_tree(cl)) {
+ size_t i;
+ for (i = 0; i < tb->grpset_size + 1; i++) {
+ rc = buffer_append_data(buf, " ");
+ if (rc)
+ break;
+ }
+ }
+ if (!rc)
+ rc = buffer_append_data(buf, scols_cell_get_data(&cl->header));
if (!rc)
rc = print_data(tb, cl, NULL, &cl->header, buf);
}
@@ -664,8 +779,8 @@ int __scols_print_header(struct libscols_table *tb, struct libscols_buffer *buf)
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));
+ DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]",
+ tb->header_next, tb->termlines_used, rc));
return rc;
}
@@ -715,7 +830,9 @@ static int print_tree_line(struct libscols_table *tb,
int last,
int last_in_table)
{
- int rc;
+ int rc, children = 0, gr_children = 0;
+
+ DBG(LINE, ul_debugobj(ln, "printing line"));
/* print the line */
fput_line_open(tb);
@@ -723,27 +840,46 @@ static int print_tree_line(struct libscols_table *tb,
if (rc)
goto done;
- /* print children */
- if (!list_empty(&ln->ln_branch)) {
- struct list_head *p;
+ children = has_children(ln);
+ gr_children = is_last_group_member(ln) && has_group_children(ln);
+ if (children || gr_children)
fput_children_open(tb);
- /* print all children */
+ /* print children */
+ if (children) {
+ struct list_head *p;
+
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;
+ int last_child = !gr_children && 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);
+ /* print group's children */
+ if (gr_children) {
+ struct list_head *p;
+
+ list_for_each(p, &ln->group->gr_children) {
+ struct libscols_line *chld =
+ list_entry(p, struct libscols_line, ln_children);
+ int last_child = p->next == &ln->group->gr_children;
+
+ rc = print_tree_line(tb, chld, buf, last_child, last_in_table && last_child);
+ if (rc)
+ goto done;
+ }
}
- if (list_empty(&ln->ln_branch) || scols_table_is_json(tb))
+ if (children || gr_children)
+ fput_children_close(tb);
+
+ if ((!children && !gr_children) || scols_table_is_json(tb))
fput_line_close(tb, last, last_in_table);
done:
return rc;
@@ -757,17 +893,18 @@ int __scols_print_tree(struct libscols_table *tb, struct libscols_buffer *buf)
assert(tb);
- DBG(TAB, ul_debugobj(tb, "printing tree"));
+ DBG(TAB, ul_debugobj(tb, "----printing-tree-----"));
scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
- while (scols_table_next_line(tb, &itr, &ln) == 0)
+ 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)
+ if (ln->parent || ln->parent_group)
continue;
rc = print_tree_line(tb, ln, buf, ln == last, ln == last);
}
@@ -893,6 +1030,12 @@ int __scols_initialize_printing(struct libscols_table *tb, struct libscols_buffe
goto err;
}
+ /*
+ * Make sure groups members are in the same orders as the tree
+ */
+ if (has_groups(tb) && scols_table_is_tree(tb))
+ scols_groups_fix_members_order(tb);
+
if (tb->format == SCOLS_FMT_HUMAN) {
rc = __scols_calculate(tb, *buf);
if (rc != 0)
diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h
index 84f9b5be5..2bc62800d 100644
--- a/libsmartcols/src/smartcolsP.h
+++ b/libsmartcols/src/smartcolsP.h
@@ -29,6 +29,7 @@
#define SCOLS_DEBUG_TAB (1 << 4)
#define SCOLS_DEBUG_COL (1 << 5)
#define SCOLS_DEBUG_BUFF (1 << 6)
+#define SCOLS_DEBUG_GROUP (1 << 7)
#define SCOLS_DEBUG_ALL 0xFFFF
UL_DEBUG_DECLARE_MASK(libsmartcols);
@@ -53,9 +54,19 @@ struct libscols_iter {
*/
struct libscols_symbols {
int refcount;
- char *branch;
- char *vert;
- char *right;
+
+ char *tree_branch;
+ char *tree_vert;
+ char *tree_right;
+
+ char *group_vert;
+ char *group_horz;
+ char *group_first_member;
+ char *group_last_member;
+ char *group_middle_member;
+ char *group_last_child;
+ char *group_middle_child;
+
char *title_padding;
char *cell_padding;
};
@@ -89,7 +100,6 @@ struct libscols_column {
int json_type; /* SCOLS_JSON_* */
int flags;
- int is_extreme;
char *color; /* default column color */
char *safechars; /* do not encode this bytes */
@@ -113,11 +123,38 @@ struct libscols_column {
struct list_head cl_columns;
struct libscols_table *table;
+
+ unsigned int is_extreme : 1, /* extreme width in the column */
+ is_groups : 1; /* print group chart */
+
};
#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ")
#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n")
+enum {
+ SCOLS_GSTATE_NONE = 0, /* not activate yet */
+ SCOLS_GSTATE_FIRST_MEMBER,
+ SCOLS_GSTATE_MIDDLE_MEMBER,
+ SCOLS_GSTATE_LAST_MEMBER,
+ SCOLS_GSTATE_MIDDLE_CHILD,
+ SCOLS_GSTATE_LAST_CHILD,
+ SCOLS_GSTATE_CONT_MEMBERS,
+ SCOLS_GSTATE_CONT_CHILDREN
+};
+
+struct libscols_group {
+ int refcount;
+
+ size_t nmembers;
+
+ struct list_head gr_members; /* head of line->ln_group */
+ struct list_head gr_children; /* head of line->ln_children */
+ struct list_head gr_groups; /* member of table->tb_groups */
+
+ int state; /* SCOLS_GSTATE_* */
+};
+
/*
* Table line
*/
@@ -131,12 +168,14 @@ struct libscols_line {
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 list_head ln_group;
+ struct list_head ln_lines; /* member of table->tb_lines */
+ struct list_head ln_branch; /* head of line->ln_children */
+ struct list_head ln_children; /* member of line->ln_children or group->gr_children */
+ struct list_head ln_groups; /* member of group->gr_groups */
struct libscols_line *parent;
+ struct libscols_group *parent_group; /* for group childs */
+ struct libscols_group *group; /* for group members */
};
enum {
@@ -166,6 +205,11 @@ struct libscols_table {
struct list_head tb_columns;
struct list_head tb_lines;
+
+ struct list_head tb_groups; /* all defined groups */
+ struct libscols_group **grpset;
+ size_t grpset_size;
+
struct libscols_symbols *symbols;
struct libscols_cell title; /* optional table title (for humans) */
@@ -217,9 +261,22 @@ static inline int scols_iter_is_last(const struct libscols_iter *itr)
return itr->p == itr->head;
}
+/*
+ * line.c
+ */
+int scols_line_next_group_child(struct libscols_line *ln,
+ struct libscols_iter *itr,
+ struct libscols_line **chld);
/*
+ * table.c
+ */
+int scols_table_next_group(struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_group **gr);
+
+/*
* buffer.c
*/
struct libscols_buffer;
@@ -227,6 +284,7 @@ extern struct libscols_buffer *new_buffer(size_t sz);
extern void free_buffer(struct libscols_buffer *buf);
extern int buffer_reset_data(struct libscols_buffer *buf);
extern int buffer_append_data(struct libscols_buffer *buf, const char *str);
+extern int buffer_append_ntimes(struct libscols_buffer *buf, size_t n, const char *str);
extern int buffer_set_data(struct libscols_buffer *buf, const char *str);
extern void buffer_set_art_index(struct libscols_buffer *buf);
extern char *buffer_get_data(struct libscols_buffer *buf);
@@ -238,6 +296,18 @@ extern char *buffer_get_safe_data(struct libscols_table *tb,
extern size_t buffer_get_safe_art_size(struct libscols_buffer *buf);
/*
+ * grouping.c
+ */
+void scols_ref_group(struct libscols_group *gr);
+void scols_group_remove_children(struct libscols_group *gr);
+void scols_group_remove_members(struct libscols_group *gr);
+void scols_unref_group(struct libscols_group *gr);
+void scols_groups_fix_members_order(struct libscols_table *tb);
+int scols_groups_calculate_grpset(struct libscols_table *tb);
+int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln);
+void scols_groups_reset_state(struct libscols_table *tb);
+
+/*
* calculate.c
*/
extern int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf);
@@ -294,4 +364,53 @@ static inline int is_last_column(struct libscols_column *cl)
return 0;
}
+static inline int is_last_group_member(struct libscols_line *ln)
+{
+ if (!ln || !ln->group)
+ return 0;
+
+ return list_entry_is_last(&ln->ln_groups, &ln->group->gr_members);
+}
+
+static inline int is_first_group_member(struct libscols_line *ln)
+{
+ if (!ln || !ln->group)
+ return 0;
+
+ return list_entry_is_first(&ln->ln_groups, &ln->group->gr_members);
+}
+
+static inline int is_group_member(struct libscols_line *ln)
+{
+ return ln && ln->group;
+}
+
+static inline int is_last_group_child(struct libscols_line *ln)
+{
+ if (!ln || !ln->parent_group)
+ return 0;
+
+ return list_entry_is_last(&ln->ln_children, &ln->parent_group->gr_children);
+}
+
+static inline int is_group_child(struct libscols_line *ln)
+{
+ return ln && ln->parent_group;
+}
+
+static inline int has_groups(struct libscols_table *tb)
+{
+ return tb && !list_empty(&tb->tb_groups);
+}
+
+static inline int has_children(struct libscols_line *ln)
+{
+ return ln && !list_empty(&ln->ln_branch);
+}
+
+static inline int has_group_children(struct libscols_line *ln)
+{
+ return ln && ln->group && !list_empty(&ln->group->gr_children);
+}
+
#endif /* _LIBSMARTCOLS_PRIVATE_H */
diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c
index 6ddf1869b..a78489844 100644
--- a/libsmartcols/src/symbols.c
+++ b/libsmartcols/src/symbols.c
@@ -59,9 +59,16 @@ void scols_ref_symbols(struct libscols_symbols *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->tree_branch);
+ free(sy->tree_vert);
+ free(sy->tree_right);
+ free(sy->group_last_member);
+ free(sy->group_middle_member);
+ free(sy->group_first_member);
+ free(sy->group_vert);
+ free(sy->group_horz);
+ free(sy->group_last_child);
+ free(sy->group_middle_child);
free(sy->title_padding);
free(sy->cell_padding);
free(sy);
@@ -77,7 +84,7 @@ void scols_unref_symbols(struct libscols_symbols *sy)
*/
int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str)
{
- return strdup_to_struct_member(sy, branch, str);
+ return strdup_to_struct_member(sy, tree_branch, str);
}
/**
@@ -89,7 +96,7 @@ int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str)
*/
int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str)
{
- return strdup_to_struct_member(sy, vert, str);
+ return strdup_to_struct_member(sy, tree_vert, str);
}
/**
@@ -101,7 +108,7 @@ int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str)
*/
int scols_symbols_set_right(struct libscols_symbols *sy, const char *str)
{
- return strdup_to_struct_member(sy, right, str);
+ return strdup_to_struct_member(sy, tree_right, str);
}
/**
@@ -137,6 +144,105 @@ int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str)
return strdup_to_struct_member(sy, cell_padding, str);
}
+
+/**
+ * scols_symbols_set_group_vertical:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the vertival line
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_vert, str);
+}
+
+/**
+ * scols_symbols_set_group_horizontal:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the horizontal line
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_horz, str);
+}
+
+/**
+ * scols_symbols_set_group_first_member:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent first member
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_first_member, str);
+}
+
+/**
+ * scols_symbols_set_group_last_member:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent last member
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_last_member, str);
+}
+
+/**
+ * scols_symbols_set_group_middle:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent middle member
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_middle_member, str);
+}
+
+/**
+ * scols_symbols_set_group_last_child:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent last child
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_last_child, str);
+}
+
+/**
+ * scols_symbols_set_group_middle_child:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent last child
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_middle_child, str);
+}
+
/**
* scols_copy_symbols:
* @sy: a pointer to a struct libscols_symbols instance
@@ -156,11 +262,25 @@ struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy)
if (!ret)
return NULL;
- rc = scols_symbols_set_branch(ret, sy->branch);
+ rc = scols_symbols_set_branch(ret, sy->tree_branch);
+ if (!rc)
+ rc = scols_symbols_set_vertical(ret, sy->tree_vert);
+ if (!rc)
+ rc = scols_symbols_set_right(ret, sy->tree_right);
+ if (!rc)
+ rc = scols_symbols_set_group_vertical(ret, sy->group_vert);
+ if (!rc)
+ rc = scols_symbols_set_group_horizontal(ret, sy->group_horz);
+ if (!rc)
+ rc = scols_symbols_set_group_first_member(ret, sy->group_first_member);
+ if (!rc)
+ rc = scols_symbols_set_group_last_member(ret, sy->group_last_member);
+ if (!rc)
+ rc = scols_symbols_set_group_middle_member(ret, sy->group_middle_member);
if (!rc)
- rc = scols_symbols_set_vertical(ret, sy->vert);
+ rc = scols_symbols_set_group_middle_child(ret, sy->group_middle_child);
if (!rc)
- rc = scols_symbols_set_right(ret, sy->right);
+ rc = scols_symbols_set_group_last_child(ret, sy->group_last_child);
if (!rc)
rc = scols_symbols_set_title_padding(ret, sy->title_padding);
if (!rc)
diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c
index 14ff9de4b..e4f27a916 100644
--- a/libsmartcols/src/table.c
+++ b/libsmartcols/src/table.c
@@ -29,13 +29,17 @@
#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_VL "\342\224\244" /* U+2524 Vertical and left -| */
-#define UTF_H "\342\224\200" /* U+2500, Horizontal - */
-#define UTF_UR "\342\224\224" /* U+2514, Up and right '- */
-#define UTF_UL "\342\224\230" /* U+2518, Up and left -' */
-#define UTF_DL "\342\224\220" /* U+2510, Down and left -, */
+#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 '- */
+
+#define UTF_V3 "\342\224\206" /* U+2506 Triple Dash Vertical | */
+#define UTF_H3 "\342\224\210" /* U+2504 Triple Dash Horizontal - */
+#define UTF_DR "\342\224\214" /* U+250C Down and Right ,- */
+#define UTF_DH "\342\224\254" /* U+252C Down and Horizontal |' */
+
+#define UTF_TR "\342\226\266" /* U+25B6 Black Right-Pointing Triangle > */
#endif /* !HAVE_WIDECHAR */
#define is_last_column(_tb, _cl) \
@@ -79,6 +83,7 @@ struct libscols_table *scols_new_table(void)
INIT_LIST_HEAD(&tb->tb_lines);
INIT_LIST_HEAD(&tb->tb_columns);
+ INIT_LIST_HEAD(&tb->tb_groups);
DBG(TAB, ul_debugobj(tb, "alloc"));
ON_DBG(INIT, check_padding_debug(tb));
@@ -98,6 +103,17 @@ void scols_ref_table(struct libscols_table *tb)
tb->refcount++;
}
+static void scols_table_remove_groups(struct libscols_table *tb)
+{
+ while (!list_empty(&tb->tb_groups)) {
+ struct libscols_group *gr = list_entry(tb->tb_groups.next,
+ struct libscols_group, gr_groups);
+ scols_group_remove_children(gr);
+ scols_group_remove_members(gr);
+ scols_unref_group(gr);
+ }
+}
+
/**
* scols_unref_table:
* @tb: a pointer to a struct libscols_table instance
@@ -108,16 +124,40 @@ void scols_ref_table(struct libscols_table *tb)
void scols_unref_table(struct libscols_table *tb)
{
if (tb && (--tb->refcount <= 0)) {
- DBG(TAB, ul_debugobj(tb, "dealloc"));
+ DBG(TAB, ul_debugobj(tb, "dealloc <-"));
+ scols_table_remove_groups(tb);
scols_table_remove_lines(tb);
scols_table_remove_columns(tb);
scols_unref_symbols(tb->symbols);
scols_reset_cell(&tb->title);
+ free(tb->grpset);
free(tb->linesep);
free(tb->colsep);
free(tb->name);
free(tb);
+ DBG(TAB, ul_debug("<- done"));
+ }
+}
+
+/* Private API */
+int scols_table_next_group(struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_group **gr)
+{
+ int rc = 1;
+
+ if (!tb || !itr || !gr)
+ return -EINVAL;
+ *gr = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &tb->tb_groups);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *gr, struct libscols_group, gr_groups);
+ rc = 0;
}
+
+ return rc;
}
/**
@@ -801,15 +841,35 @@ int scols_table_set_default_symbols(struct libscols_table *tb)
#if defined(HAVE_WIDECHAR)
if (!scols_table_is_ascii(tb) &&
!strcmp(nl_langinfo(CODESET), "UTF-8")) {
+ /* tree chart */
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);
+ /* groups chart */
+ scols_symbols_set_group_horizontal(sy, UTF_H3);
+ scols_symbols_set_group_vertical(sy, UTF_V3);
+
+ scols_symbols_set_group_first_member(sy, UTF_DR UTF_H3 UTF_TR);
+ scols_symbols_set_group_last_member(sy, UTF_UR UTF_DH UTF_TR);
+ scols_symbols_set_group_middle_member(sy, UTF_VR UTF_H3 UTF_TR);
+ scols_symbols_set_group_last_child(sy, UTF_UR UTF_H3);
+ scols_symbols_set_group_middle_child(sy, UTF_VR UTF_H3);
} else
#endif
{
+ /* tree chart */
scols_symbols_set_branch(sy, "|-");
scols_symbols_set_vertical(sy, "| ");
scols_symbols_set_right(sy, "`-");
+ /* groups chart */
+ scols_symbols_set_group_horizontal(sy, "-");
+ scols_symbols_set_group_vertical(sy, "|");
+
+ scols_symbols_set_group_first_member(sy, ",->");
+ scols_symbols_set_group_last_member(sy, "'->");
+ scols_symbols_set_group_middle_member(sy, "|->");
+ scols_symbols_set_group_last_child(sy, "`-");
+ scols_symbols_set_group_middle_child(sy, "|-");
}
scols_symbols_set_title_padding(sy, " ");
scols_symbols_set_cell_padding(sy, " ");
@@ -1352,16 +1412,26 @@ static int sort_line_children(struct libscols_line *ln, struct libscols_column *
{
struct list_head *p;
- if (list_empty(&ln->ln_branch))
- return 0;
+ if (!list_empty(&ln->ln_branch)) {
+ 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);
+ }
+
+ if (is_first_group_member(ln)) {
+ list_for_each(p, &ln->group->gr_children) {
+ struct libscols_line *chld =
+ list_entry(p, struct libscols_line, ln_children);
+ sort_line_children(chld, cl);
+ }
- 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->group->gr_children, cells_cmp_wrapper_children, cl);
}
- list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl);
return 0;
}