]>
git.ipfire.org Git - thirdparty/util-linux.git/blob - libsmartcols/src/table.c
2 * table.c - functions handling the data at the table level
4 * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
5 * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
6 * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com>
8 * This file may be redistributed under the terms of the
9 * GNU Lesser General Public License.
15 * @short_description: container for rows and columns
17 * Table data manipulation API.
29 #include "smartcolsP.h"
32 #define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */
33 #define UTF_VR "\342\224\234" /* U+251C, Vertical and right */
34 #define UTF_H "\342\224\200" /* U+2500, Horizontal */
35 #define UTF_UR "\342\224\224" /* U+2514, Up and right */
36 #endif /* !HAVE_WIDECHAR */
38 #define is_last_column(_tb, _cl) \
39 list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns)
45 * Returns: A newly allocated table.
47 struct libscols_table
*scols_new_table(void)
49 struct libscols_table
*tb
;
51 tb
= calloc(1, sizeof(struct libscols_table
));
58 INIT_LIST_HEAD(&tb
->tb_lines
);
59 INIT_LIST_HEAD(&tb
->tb_columns
);
61 DBG(TAB
, ul_debugobj(tb
, "alloc"));
67 * @tb: a pointer to a struct libscols_table instance
69 * Increases the refcount of @tb.
71 void scols_ref_table(struct libscols_table
*tb
)
79 * @tb: a pointer to a struct libscols_table instance
81 * Decreases the refcount of @tb. When the count falls to zero, the instance
82 * is automatically deallocated.
84 void scols_unref_table(struct libscols_table
*tb
)
86 if (tb
&& (--tb
->refcount
<= 0)) {
87 DBG(TAB
, ul_debugobj(tb
, "dealloc"));
88 scols_table_remove_lines(tb
);
89 scols_table_remove_columns(tb
);
90 scols_unref_symbols(tb
->symbols
);
91 scols_reset_cell(&tb
->title
);
101 * scols_table_set_name:
102 * @tb: a pointer to a struct libscols_table instance
105 * The table name is used for example for JSON top level object name.
107 * Returns: 0, a negative number in case of an error.
111 int scols_table_set_name(struct libscols_table
*tb
, const char *str
)
113 return strdup_to_struct_member(tb
, name
, str
);
117 * scols_table_get_title:
118 * @tb: a pointer to a struct libscols_table instance
120 * Returns: Title of the table, or %NULL in case of blank title.
124 struct libscols_cell
*scols_table_get_title(struct libscols_table
*tb
)
130 * scols_table_add_column:
131 * @tb: a pointer to a struct libscols_table instance
132 * @cl: a pointer to a struct libscols_column instance
134 * Adds @cl to @tb's column list. The column cannot be shared between more
137 * Returns: 0, a negative number in case of an error.
139 int scols_table_add_column(struct libscols_table
*tb
, struct libscols_column
*cl
)
141 if (!tb
|| !cl
|| !list_empty(&tb
->tb_lines
) || cl
->table
)
144 if (cl
->flags
& SCOLS_FL_TREE
)
147 DBG(TAB
, ul_debugobj(tb
, "add column %p", cl
));
148 list_add_tail(&cl
->cl_columns
, &tb
->tb_columns
);
149 cl
->seqnum
= tb
->ncols
++;
151 scols_ref_column(cl
);
155 * Currently it's possible to add/remove columns only if the table is
156 * empty (see list_empty(tb->tb_lines) above). It would be nice to
157 * enlarge/reduce lines cells[] always when we add/remove a new column.
163 * scols_table_remove_column:
164 * @tb: a pointer to a struct libscols_table instance
165 * @cl: a pointer to a struct libscols_column instance
167 * Removes @cl from @tb.
169 * Returns: 0, a negative number in case of an error.
171 int scols_table_remove_column(struct libscols_table
*tb
,
172 struct libscols_column
*cl
)
174 if (!tb
|| !cl
|| !list_empty(&tb
->tb_lines
))
177 if (cl
->flags
& SCOLS_FL_TREE
)
180 DBG(TAB
, ul_debugobj(tb
, "remove column %p", cl
));
181 list_del_init(&cl
->cl_columns
);
184 scols_unref_column(cl
);
189 * scols_table_remove_columns:
190 * @tb: a pointer to a struct libscols_table instance
192 * Removes all of @tb's columns.
194 * Returns: 0, a negative number in case of an error.
196 int scols_table_remove_columns(struct libscols_table
*tb
)
198 if (!tb
|| !list_empty(&tb
->tb_lines
))
201 DBG(TAB
, ul_debugobj(tb
, "remove all columns"));
202 while (!list_empty(&tb
->tb_columns
)) {
203 struct libscols_column
*cl
= list_entry(tb
->tb_columns
.next
,
204 struct libscols_column
, cl_columns
);
205 scols_table_remove_column(tb
, cl
);
212 * scols_table_new_column:
214 * @name: column header
215 * @whint: column width hint (absolute width: N > 1; relative width: N < 1)
216 * @flags: flags integer
218 * This is shortcut for
220 * cl = scols_new_column();
221 * scols_column_set_....(cl, ...);
222 * scols_table_add_column(tb, cl);
224 * The column width is possible to define by:
226 * @whint = 0..1 : relative width, percent of terminal width
228 * @whint = 1..N : absolute width, empty column will be truncated to
229 * the column header width if no specified STRICTWIDTH flag
231 * Note that if table has disabled "maxout" flag (disabled by default) than
232 * relative width is used as a hint only. It's possible that column will be
233 * narrow if the specified size is too large for column data.
235 * The column is necessary to address by sequential number. The first defined
236 * column has the colnum = 0. For example:
238 * scols_table_new_column(tab, "FOO", 0.5, 0); // colnum = 0
239 * scols_table_new_column(tab, "BAR", 0.5, 0); // colnum = 1
242 * scols_line_get_cell(line, 0); // FOO column
243 * scols_line_get_cell(line, 1); // BAR column
245 * Returns: newly allocated column
247 struct libscols_column
*scols_table_new_column(struct libscols_table
*tb
,
252 struct libscols_column
*cl
;
253 struct libscols_cell
*hr
;
258 DBG(TAB
, ul_debugobj(tb
, "new column name=%s, whint=%g, flags=%d",
259 name
, whint
, flags
));
260 cl
= scols_new_column();
264 /* set column name */
265 hr
= scols_column_get_header(cl
);
268 if (scols_cell_set_data(hr
, name
))
271 scols_column_set_whint(cl
, whint
);
272 scols_column_set_flags(cl
, flags
);
274 if (scols_table_add_column(tb
, cl
)) /* this increments column ref-counter */
277 scols_unref_column(cl
);
280 scols_unref_column(cl
);
285 * scols_table_next_column:
286 * @tb: a pointer to a struct libscols_table instance
287 * @itr: a pointer to a struct libscols_iter instance
288 * @cl: a pointer to a pointer to a struct libscols_column instance
290 * Returns the next column of @tb via @cl.
292 * Returns: 0, a negative value in case of an error.
294 int scols_table_next_column(struct libscols_table
*tb
,
295 struct libscols_iter
*itr
,
296 struct libscols_column
**cl
)
300 if (!tb
|| !itr
|| !cl
)
305 SCOLS_ITER_INIT(itr
, &tb
->tb_columns
);
306 if (itr
->p
!= itr
->head
) {
307 SCOLS_ITER_ITERATE(itr
, *cl
, struct libscols_column
, cl_columns
);
316 * scols_table_get_ncols:
319 * Returns: the ncols table member, a negative number in case of an error.
321 int scols_table_get_ncols(struct libscols_table
*tb
)
323 return tb
? (int)tb
->ncols
: -EINVAL
;
327 * scols_table_get_nlines:
330 * Returns: the nlines table member, a negative number in case of an error.
332 int scols_table_get_nlines(struct libscols_table
*tb
)
334 return tb
? (int)tb
->nlines
: -EINVAL
;
338 * scols_table_set_stream:
340 * @stream: output stream
342 * Sets the output stream for table @tb.
344 * Returns: 0, a negative number in case of an error.
346 int scols_table_set_stream(struct libscols_table
*tb
, FILE *stream
)
352 DBG(TAB
, ul_debugobj(tb
, "setting alternative stream"));
358 * scols_table_get_stream:
361 * Gets the output stream for table @tb.
363 * Returns: stream pointer, NULL in case of an error or an unset stream.
365 FILE *scols_table_get_stream(struct libscols_table
*tb
)
367 return tb
? tb
->out
: NULL
;
371 * scols_table_reduce_termwidth:
375 * If necessary then libsmartcols use all terminal width, the @reduce setting
376 * provides extra space (for example for borders in ncurses applications).
378 * The @reduce must be smaller than terminal width, otherwise it's silently
379 * ignored. The reduction is not applied when STDOUT_FILENO is not terminal.
381 * Returns: 0, a negative value in case of an error.
383 int scols_table_reduce_termwidth(struct libscols_table
*tb
, size_t reduce
)
388 DBG(TAB
, ul_debugobj(tb
, "reduce terminal width: %zu", reduce
));
389 tb
->termreduce
= reduce
;
394 * scols_table_get_column:
396 * @n: number of column (0..N)
398 * Returns: pointer to column or NULL
400 struct libscols_column
*scols_table_get_column(struct libscols_table
*tb
,
403 struct libscols_iter itr
;
404 struct libscols_column
*cl
;
411 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
412 while (scols_table_next_column(tb
, &itr
, &cl
) == 0) {
420 * scols_table_add_line:
424 * Note that this function calls scols_line_alloc_cells() if number
425 * of the cells in the line is too small for @tb.
427 * Returns: 0, a negative value in case of an error.
429 int scols_table_add_line(struct libscols_table
*tb
, struct libscols_line
*ln
)
434 if (tb
->ncols
> ln
->ncells
) {
435 int rc
= scols_line_alloc_cells(ln
, tb
->ncols
);
440 DBG(TAB
, ul_debugobj(tb
, "add line %p", ln
));
441 list_add_tail(&ln
->ln_lines
, &tb
->tb_lines
);
442 ln
->seqnum
= tb
->nlines
++;
448 * scols_table_remove_line:
452 * Note that this function does not destroy the parent<->child relationship between lines.
453 * You have to call scols_line_remove_child()
455 * Returns: 0, a negative value in case of an error.
457 int scols_table_remove_line(struct libscols_table
*tb
,
458 struct libscols_line
*ln
)
463 DBG(TAB
, ul_debugobj(tb
, "remove line %p", ln
));
464 list_del_init(&ln
->ln_lines
);
466 scols_unref_line(ln
);
471 * scols_table_remove_lines:
474 * This empties the table and also destroys all the parent<->child relationships.
476 void scols_table_remove_lines(struct libscols_table
*tb
)
482 DBG(TAB
, ul_debugobj(tb
, "remove all lines"));
483 while (!list_empty(&tb
->tb_lines
)) {
484 struct libscols_line
*ln
= list_entry(tb
->tb_lines
.next
,
485 struct libscols_line
, ln_lines
);
487 scols_line_remove_child(ln
->parent
, ln
);
488 scols_table_remove_line(tb
, ln
);
493 * scols_table_next_line:
494 * @tb: a pointer to a struct libscols_table instance
495 * @itr: a pointer to a struct libscols_iter instance
496 * @ln: a pointer to a pointer to a struct libscols_line instance
498 * Finds the next line and returns a pointer to it via @ln.
500 * Returns: 0, a negative value in case of an error.
502 int scols_table_next_line(struct libscols_table
*tb
,
503 struct libscols_iter
*itr
,
504 struct libscols_line
**ln
)
508 if (!tb
|| !itr
|| !ln
)
513 SCOLS_ITER_INIT(itr
, &tb
->tb_lines
);
514 if (itr
->p
!= itr
->head
) {
515 SCOLS_ITER_ITERATE(itr
, *ln
, struct libscols_line
, ln_lines
);
523 * scols_table_new_line:
525 * @parent: parental line or NULL
527 * This is shortcut for
529 * ln = scols_new_line();
530 * scols_table_add_line(tb, ln);
531 * scols_line_add_child(parent, ln);
534 * Returns: newly allocate line
536 struct libscols_line
*scols_table_new_line(struct libscols_table
*tb
,
537 struct libscols_line
*parent
)
539 struct libscols_line
*ln
;
541 if (!tb
|| !tb
->ncols
)
544 ln
= scols_new_line();
548 if (scols_table_add_line(tb
, ln
))
551 scols_line_add_child(parent
, ln
);
553 scols_unref_line(ln
); /* ref-counter incremented by scols_table_add_line() */
556 scols_unref_line(ln
);
561 * scols_table_get_line:
563 * @n: column number (0..N)
565 * Returns: a line or NULL
567 struct libscols_line
*scols_table_get_line(struct libscols_table
*tb
,
570 struct libscols_iter itr
;
571 struct libscols_line
*ln
;
578 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
579 while (scols_table_next_line(tb
, &itr
, &ln
) == 0) {
590 * Creates a new independent table copy, except struct libscols_symbols that
591 * are shared between the tables.
593 * Returns: a newly allocated copy of @tb
595 struct libscols_table
*scols_copy_table(struct libscols_table
*tb
)
597 struct libscols_table
*ret
;
598 struct libscols_line
*ln
;
599 struct libscols_column
*cl
;
600 struct libscols_iter itr
;
604 ret
= scols_new_table();
608 DBG(TAB
, ul_debugobj(tb
, "copy into %p", ret
));
611 scols_table_set_symbols(ret
, tb
->symbols
);
614 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
615 while (scols_table_next_column(tb
, &itr
, &cl
) == 0) {
616 cl
= scols_copy_column(cl
);
619 if (scols_table_add_column(ret
, cl
))
621 scols_unref_column(cl
);
625 scols_reset_iter(&itr
, SCOLS_ITER_FORWARD
);
626 while (scols_table_next_line(tb
, &itr
, &ln
) == 0) {
627 struct libscols_line
*newln
= scols_copy_line(ln
);
630 if (scols_table_add_line(ret
, newln
))
633 struct libscols_line
*p
=
634 scols_table_get_line(ret
, ln
->parent
->seqnum
);
636 scols_line_add_child(p
, newln
);
638 scols_unref_line(newln
);
642 if (scols_table_set_column_separator(ret
, tb
->colsep
) ||
643 scols_table_set_line_separator(ret
, tb
->linesep
))
648 scols_unref_table(ret
);
653 * scols_table_set_symbols:
655 * @sy: symbols or NULL
657 * Add a reference to @sy from the table. The symbols are used by library to
658 * draw tree output. If no symbols are specified then library checks the
659 * current environment to select ASCII or UTF8 symbols. This default behavior
660 * could be controlled by scols_table_enable_ascii().
662 * Returns: 0, a negative value in case of an error.
664 int scols_table_set_symbols(struct libscols_table
*tb
,
665 struct libscols_symbols
*sy
)
670 DBG(TAB
, ul_debugobj(tb
, "setting alternative symbols %p", sy
));
672 if (tb
->symbols
) /* unref old */
673 scols_unref_symbols(tb
->symbols
);
674 if (sy
) { /* ref user defined */
676 scols_ref_symbols(sy
);
677 } else { /* default symbols */
678 tb
->symbols
= scols_new_symbols();
681 #if defined(HAVE_WIDECHAR)
682 if (!scols_table_is_ascii(tb
) &&
683 !strcmp(nl_langinfo(CODESET
), "UTF-8")) {
684 scols_symbols_set_branch(tb
->symbols
, UTF_VR UTF_H
);
685 scols_symbols_set_vertical(tb
->symbols
, UTF_V
" ");
686 scols_symbols_set_right(tb
->symbols
, UTF_UR UTF_H
);
690 scols_symbols_set_branch(tb
->symbols
, "|-");
691 scols_symbols_set_vertical(tb
->symbols
, "| ");
692 scols_symbols_set_right(tb
->symbols
, "`-");
694 scols_symbols_set_title_padding(tb
->symbols
, " ");
701 * scols_table_enable_nolinesep
705 * Enable/disable line separator printing. This is useful if you want to
706 * re-printing the same line more than once (e.g. progress bar). Don't use it
707 * if you're not sure.
709 * Returns: 0 on success, negative number in case of an error.
711 int scols_table_enable_nolinesep(struct libscols_table
*tb
, int enable
)
716 DBG(TAB
, ul_debugobj(tb
, "nolinesep: %s", enable
? "ENABLE" : "DISABLE"));
717 tb
->no_linesep
= enable
;
722 * scols_table_enable_colors:
726 * Enable/disable colors.
728 * Returns: 0 on success, negative number in case of an error.
730 int scols_table_enable_colors(struct libscols_table
*tb
, int enable
)
735 DBG(TAB
, ul_debugobj(tb
, "colors: %s", enable
? "ENABLE" : "DISABLE"));
736 tb
->colors_wanted
= enable
;
741 * scols_table_enable_raw:
745 * Enable/disable raw output format. The parsable output formats
746 * (export, raw, JSON, ...) are mutually exclusive.
748 * Returns: 0 on success, negative number in case of an error.
750 int scols_table_enable_raw(struct libscols_table
*tb
, int enable
)
755 DBG(TAB
, ul_debugobj(tb
, "raw: %s", enable
? "ENABLE" : "DISABLE"));
757 tb
->format
= SCOLS_FMT_RAW
;
758 else if (tb
->format
== SCOLS_FMT_RAW
)
764 * scols_table_enable_json:
768 * Enable/disable JSON output format. The parsable output formats
769 * (export, raw, JSON, ...) are mutually exclusive.
771 * Returns: 0 on success, negative number in case of an error.
775 int scols_table_enable_json(struct libscols_table
*tb
, int enable
)
780 DBG(TAB
, ul_debugobj(tb
, "json: %s", enable
? "ENABLE" : "DISABLE"));
782 tb
->format
= SCOLS_FMT_JSON
;
783 else if (tb
->format
== SCOLS_FMT_JSON
)
789 * scols_table_enable_export:
793 * Enable/disable export output format (COLUMNAME="value" ...).
794 * The parsable output formats (export and raw) are mutually exclusive.
796 * Returns: 0 on success, negative number in case of an error.
798 int scols_table_enable_export(struct libscols_table
*tb
, int enable
)
803 DBG(TAB
, ul_debugobj(tb
, "export: %s", enable
? "ENABLE" : "DISABLE"));
805 tb
->format
= SCOLS_FMT_EXPORT
;
806 else if (tb
->format
== SCOLS_FMT_EXPORT
)
812 * scols_table_enable_ascii:
816 * The ASCII-only output is relevant for tree-like outputs. The library
817 * checks if the current environment is UTF8 compatible by default. This
818 * function overrides this check and force the library to use ASCII chars
821 * If a custom libcols_symbols are specified (see scols_table_set_symbols()
822 * then ASCII flag setting is ignored.
824 * Returns: 0 on success, negative number in case of an error.
826 int scols_table_enable_ascii(struct libscols_table
*tb
, int enable
)
831 DBG(TAB
, ul_debugobj(tb
, "ascii: %s", enable
? "ENABLE" : "DISABLE"));
832 tb
->ascii
= enable
? 1 : 0;
837 * scols_table_enable_noheadings:
841 * Enable/disable header line.
843 * Returns: 0 on success, negative number in case of an error.
845 int scols_table_enable_noheadings(struct libscols_table
*tb
, int enable
)
849 DBG(TAB
, ul_debugobj(tb
, "noheading: %s", enable
? "ENABLE" : "DISABLE"));
850 tb
->no_headings
= enable
? 1 : 0;
855 * scols_table_enable_maxout:
859 * The extra space after last column is ignored by default. The output
860 * maximization use the extra space for all columns.
862 * Returns: 0 on success, negative number in case of an error.
864 int scols_table_enable_maxout(struct libscols_table
*tb
, int enable
)
868 DBG(TAB
, ul_debugobj(tb
, "maxout: %s", enable
? "ENABLE" : "DISABLE"));
869 tb
->maxout
= enable
? 1 : 0;
874 * scols_table_enable_nowrap:
878 * Never continue on next line, remove last column(s) when too large, truncate last column.
880 * Returns: 0 on success, negative number in case of an error.
884 int scols_table_enable_nowrap(struct libscols_table
*tb
, int enable
)
888 DBG(TAB
, ul_debugobj(tb
, "nowrap: %s", enable
? "ENABLE" : "DISABLE"));
889 tb
->no_wrap
= enable
? 1 : 0;
894 * scols_table_colors_wanted:
897 * Returns: 1 if colors are enabled.
899 int scols_table_colors_wanted(struct libscols_table
*tb
)
901 return tb
&& tb
->colors_wanted
;
905 * scols_table_is_empty:
908 * Returns: 1 if the table is empty.
910 int scols_table_is_empty(struct libscols_table
*tb
)
912 return !tb
|| !tb
->nlines
;
916 * scols_table_is_ascii:
919 * Returns: 1 if ASCII tree is enabled.
921 int scols_table_is_ascii(struct libscols_table
*tb
)
923 return tb
&& tb
->ascii
;
927 * scols_table_is_noheadings:
930 * Returns: 1 if header output is disabled.
932 int scols_table_is_noheadings(struct libscols_table
*tb
)
934 return tb
&& tb
->no_headings
;
938 * scols_table_is_export:
941 * Returns: 1 if export output format is enabled.
943 int scols_table_is_export(struct libscols_table
*tb
)
945 return tb
&& tb
->format
== SCOLS_FMT_EXPORT
;
949 * scols_table_is_raw:
952 * Returns: 1 if raw output format is enabled.
954 int scols_table_is_raw(struct libscols_table
*tb
)
956 return tb
&& tb
->format
== SCOLS_FMT_RAW
;
960 * scols_table_is_json:
963 * Returns: 1 if JSON output format is enabled.
967 int scols_table_is_json(struct libscols_table
*tb
)
969 return tb
&& tb
->format
== SCOLS_FMT_JSON
;
974 * scols_table_is_maxout
977 * Returns: 1 if output maximization is enabled, negative value in case of an error.
979 int scols_table_is_maxout(struct libscols_table
*tb
)
981 return tb
&& tb
->maxout
;
985 * scols_table_is_tree:
988 * Returns: returns 1 tree-like output is expected.
990 int scols_table_is_tree(struct libscols_table
*tb
)
992 return tb
&& tb
->ntreecols
> 0;
996 * scols_table_set_column_separator:
1000 * Sets the column separator of @tb to @sep.
1001 * Please note that @sep should always take up a single cell in the output.
1003 * Returns: 0, a negative value in case of an error.
1005 int scols_table_set_column_separator(struct libscols_table
*tb
, const char *sep
)
1007 return strdup_to_struct_member(tb
, colsep
, sep
);
1011 * scols_table_set_line_separator:
1015 * Sets the line separator of @tb to @sep.
1017 * Returns: 0, a negative value in case of an error.
1019 int scols_table_set_line_separator(struct libscols_table
*tb
, const char *sep
)
1021 return strdup_to_struct_member(tb
, linesep
, sep
);
1025 * scols_table_get_column_separator:
1028 * Returns: @tb column separator, NULL in case of an error
1030 char *scols_table_get_column_separator(struct libscols_table
*tb
)
1038 * scols_table_get_line_separator:
1041 * Returns: @tb line separator, NULL in case of an error
1043 char *scols_table_get_line_separator(struct libscols_table
*tb
)
1051 static int cells_cmp_wrapper(struct list_head
*a
, struct list_head
*b
, void *data
)
1053 struct libscols_column
*cl
= (struct libscols_column
*) data
;
1054 struct libscols_line
*ra
, *rb
;
1055 struct libscols_cell
*ca
, *cb
;
1061 ra
= list_entry(a
, struct libscols_line
, ln_lines
);
1062 rb
= list_entry(b
, struct libscols_line
, ln_lines
);
1063 ca
= scols_line_get_cell(ra
, cl
->seqnum
);
1064 cb
= scols_line_get_cell(rb
, cl
->seqnum
);
1066 return cl
->cmpfunc(ca
, cb
, cl
->cmpfunc_data
);
1072 * @cl: order by this column
1074 * Orders the table by the column. See also scols_column_set_cmpfunc().
1076 * Returns: 0, a negative value in case of an error.
1078 int scols_sort_table(struct libscols_table
*tb
, struct libscols_column
*cl
)
1080 if (!tb
|| !cl
|| !cl
->cmpfunc
)
1083 DBG(TAB
, ul_debugobj(tb
, "sorting table"));
1084 list_sort(&tb
->tb_lines
, cells_cmp_wrapper
, cl
);