diff options
author | Karel Zak | 2018-12-07 11:54:39 +0100 |
---|---|---|
committer | Karel Zak | 2018-12-07 12:33:34 +0100 |
commit | d52f5542b03d31a0b55b3a68588e9395b2877f31 (patch) | |
tree | 4f17e4353d4a013138f54f15a21546b91dd3f69e /libsmartcols/src/print.c | |
parent | include/list: add list_entry_is_first() and list_count_entries() (diff) | |
download | kernel-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/src/print.c')
-rw-r--r-- | libsmartcols/src/print.c | 195 |
1 files changed, 169 insertions, 26 deletions
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) |