summaryrefslogtreecommitdiffstats
path: root/libsmartcols
diff options
context:
space:
mode:
authorKarel Zak2018-12-07 11:54:39 +0100
committerKarel Zak2018-12-07 12:33:34 +0100
commitd52f5542b03d31a0b55b3a68588e9395b2877f31 (patch)
tree4f17e4353d4a013138f54f15a21546b91dd3f69e /libsmartcols
parentinclude/list: add list_entry_is_first() and list_count_entries() (diff)
downloadkernel-qcow2-util-linux-d52f5542b03d31a0b55b3a68588e9395b2877f31.tar.gz
kernel-qcow2-util-linux-d52f5542b03d31a0b55b3a68588e9395b2877f31.tar.xz
kernel-qcow2-util-linux-d52f5542b03d31a0b55b3a68588e9395b2877f31.zip
libsmartcols: add lines grouping support
For some use-case we need to describe M:N relation between output lines. The nice examples are RAIDs or multi-path devices in lsblk output. NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT loop0 7:0 0 955.7M 0 loop ┌┈▶ ├─test-thin-metadata 253:0 0 2M 0 dm └┬▶ └─test-thin-data 253:1 0 953.7M 0 dm └┈┈test-thin-pool 253:2 0 953.7M 0 dm In this example two line (test-thin-metadata and test-thin-data) are parents for another line (test-thin-pool). The new API uses term "group" for parental line -- the number of group members is unlimited and every group has at least one child. It's possible that group's child is member of another group: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT loop0 7:0 0 955.7M 0 loop ┌┈▶ ├─test-thin-metadata 253:0 0 2M 0 dm └┬▶ └─test-thin-data 253:1 0 953.7M 0 dm ┌┈▶ └┈┈test-thin-pool 253:2 0 953.7M 0 dm ┆ └─test-thin 253:3 0 190.8M 0 dm └┬▶ loop1 7:1 0 190.8M 0 loop └┈┈┈┈┈test-thin-extsnap 253:4 0 190.8M 0 dm For now multi-group relation is unsupported and one line can be member of one group only. The library API and printing code is ready to support this feature, but not sure if we really need it. All what is necessary is to create array of groups in the line struct. Note that grouping is independent on standard parent->child relations between lines and grouping can connect arbitrary lines. The restriction is only that group child cannot be child of another line or child of another group. These cross reference are (and probably will be) impossible. The patch is relative large, but easy to review. Changes: * add new UTF symbols * add scols_symbols_set_group_* public API to modify new symbols * add struct libscols_group, used only internally * add "grpset" array to table struct -- the array is used to keep position of the group in the output. Every active group uses three items in the grpset. If there is more overlapping groups than bigger grpset is allocated. Signed-off-by: Karel Zak <kzak@redhat.com>
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;
}