diff options
-rw-r--r-- | libsmartcols/src/Makemodule.am | 1 | ||||
-rw-r--r-- | libsmartcols/src/buffer.c | 12 | ||||
-rw-r--r-- | libsmartcols/src/calculate.c | 22 | ||||
-rw-r--r-- | libsmartcols/src/column.c | 1 | ||||
-rw-r--r-- | libsmartcols/src/grouping.c | 587 | ||||
-rw-r--r-- | libsmartcols/src/init.c | 1 | ||||
-rw-r--r-- | libsmartcols/src/libsmartcols.h.in | 12 | ||||
-rw-r--r-- | libsmartcols/src/libsmartcols.sym | 12 | ||||
-rw-r--r-- | libsmartcols/src/line.c | 23 | ||||
-rw-r--r-- | libsmartcols/src/print.c | 195 | ||||
-rw-r--r-- | libsmartcols/src/smartcolsP.h | 135 | ||||
-rw-r--r-- | libsmartcols/src/symbols.c | 138 | ||||
-rw-r--r-- | libsmartcols/src/table.c | 100 |
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; } |