2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
5 * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 #include <sys/types.h>
36 #include <sys/ioctl.h>
51 #include "closestream.h"
57 #include "libsmartcols.h"
59 #define TABCHAR_CELLS 8
62 COLUMN_MODE_FILLCOLS
= 0,
68 struct column_control
{
69 int mode
; /* COLUMN_MODE_* */
72 struct libscols_table
*tab
;
74 char **tab_colnames
; /* array with column names */
75 const char *tab_name
; /* table name */
76 const char *tab_order
; /* --table-order */
78 const char *tab_colright
; /* --table-right */
79 const char *tab_coltrunc
; /* --table-trunc */
80 const char *tab_colnoextrem
; /* --table-noextreme */
81 const char *tab_colwrap
; /* --table-wrap */
82 const char *tab_colhide
; /* --table-hide */
86 const char *tree_parent
;
88 wchar_t *input_separator
;
89 const char *output_separator
;
91 wchar_t **ents
; /* input entries */
92 size_t nents
; /* number of entries */
93 size_t maxlength
; /* longest input record (line) */
95 unsigned int greedy
:1,
98 tab_empty_lines
:1, /* --table-empty-lines */
102 static size_t width(const wchar_t *str
)
106 for (; *str
!= '\0'; str
++) {
108 int x
= wcwidth(*str
); /* don't use wcswidth(), need to ignore non-printable */
119 static wchar_t *mbs_to_wcs(const char *s
)
125 n
= mbstowcs((wchar_t *)0, s
, 0);
128 wcs
= xcalloc((n
+ 1) * sizeof(wchar_t), 1);
129 n
= mbstowcs(wcs
, s
, n
+ 1);
140 static char *wcs_to_mbs(const wchar_t *s
)
146 n
= wcstombs(NULL
, s
, 0);
147 if (n
== (size_t) -1)
150 str
= xcalloc(n
+ 1, 1);
151 if (wcstombs(str
, s
, n
) == (size_t) -1) {
161 static wchar_t *local_wcstok(struct column_control
const *const ctl
, wchar_t *p
,
164 wchar_t *result
= NULL
;
168 return wcstok(p
, ctl
->input_separator
, state
);
170 return strtok_r(p
, ctl
->input_separator
, state
);
179 p
= wcspbrk(result
, ctl
->input_separator
);
181 p
= strpbrk(result
, ctl
->input_separator
);
192 static char **split_or_error(const char *str
, const char *errmsg
)
194 char **res
= strv_split(str
, ",");
198 errx(EXIT_FAILURE
, "%s: '%s'", errmsg
, str
);
203 static void init_table(struct column_control
*ctl
)
207 ctl
->tab
= scols_new_table();
209 err(EXIT_FAILURE
, _("failed to allocate output table"));
211 scols_table_set_column_separator(ctl
->tab
, ctl
->output_separator
);
213 scols_table_enable_json(ctl
->tab
, 1);
214 scols_table_set_name(ctl
->tab
, ctl
->tab_name
? : "table");
216 scols_table_enable_noencoding(ctl
->tab
, 1);
218 if (ctl
->tab_colnames
) {
221 STRV_FOREACH(name
, ctl
->tab_colnames
)
222 scols_table_new_column(ctl
->tab
, *name
, 0, 0);
223 if (ctl
->header_repeat
)
224 scols_table_enable_header_repeat(ctl
->tab
, 1);
225 scols_table_enable_noheadings(ctl
->tab
, !!ctl
->tab_noheadings
);
227 scols_table_enable_noheadings(ctl
->tab
, 1);
230 static struct libscols_column
*string_to_column(struct column_control
*ctl
, const char *str
)
234 if (isdigit_string(str
))
235 colnum
= strtou32_or_err(str
, _("failed to parse column")) - 1;
239 STRV_FOREACH(name
, ctl
->tab_colnames
) {
240 if (strcasecmp(*name
, str
) == 0)
245 errx(EXIT_FAILURE
, _("undefined column name '%s'"), str
);
248 return scols_table_get_column(ctl
->tab
, colnum
);
251 static struct libscols_column
*get_last_visible_column(struct column_control
*ctl
)
253 struct libscols_iter
*itr
;
254 struct libscols_column
*cl
, *last
= NULL
;
256 itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
260 while (scols_table_next_column(ctl
->tab
, itr
, &cl
) == 0) {
261 if (scols_column_get_flags(cl
) & SCOLS_FL_HIDDEN
)
266 scols_free_iter(itr
);
270 static int column_set_flag(struct libscols_column
*cl
, int fl
)
272 int cur
= scols_column_get_flags(cl
);
274 return scols_column_set_flags(cl
, cur
| fl
);
277 static void apply_columnflag_from_list(struct column_control
*ctl
, const char *list
,
278 int flag
, const char *errmsg
)
280 char **all
= split_or_error(list
, errmsg
);
284 STRV_FOREACH(one
, all
) {
285 struct libscols_column
*cl
;
287 if (flag
== SCOLS_FL_HIDDEN
&& strcmp(*one
, "-") == 0) {
291 cl
= string_to_column(ctl
, *one
);
293 column_set_flag(cl
, flag
);
297 /* apply flag to all columns without name */
299 struct libscols_iter
*itr
;
300 struct libscols_column
*cl
;
302 itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
306 while (scols_table_next_column(ctl
->tab
, itr
, &cl
) == 0) {
307 struct libscols_cell
*ce
= scols_column_get_header(cl
);
309 if (ce
== NULL
|| scols_cell_get_data(ce
) == NULL
)
310 column_set_flag(cl
, flag
);
312 scols_free_iter(itr
);
316 static void reorder_table(struct column_control
*ctl
)
318 struct libscols_column
**wanted
, *last
= NULL
;
320 size_t ncols
= scols_table_get_ncols(ctl
->tab
);
321 char **order
= split_or_error(ctl
->tab_order
, _("failed to parse --table-order list"));
324 wanted
= xcalloc(ncols
, sizeof(struct libscols_column
*));
326 STRV_FOREACH(one
, order
) {
327 struct libscols_column
*cl
= string_to_column(ctl
, *one
);
329 wanted
[count
++] = cl
;
332 for (i
= 0; i
< count
; i
++) {
333 scols_table_move_column(ctl
->tab
, last
, wanted
[i
]);
341 static void create_tree(struct column_control
*ctl
)
343 struct libscols_column
*cl_tree
= string_to_column(ctl
, ctl
->tree
);
344 struct libscols_column
*cl_p
= string_to_column(ctl
, ctl
->tree_parent
);
345 struct libscols_column
*cl_i
= string_to_column(ctl
, ctl
->tree_id
);
346 struct libscols_iter
*itr_p
, *itr_i
;
347 struct libscols_line
*ln_i
;
349 if (!cl_p
|| !cl_i
|| !cl_tree
)
350 return; /* silently ignore the tree request */
352 column_set_flag(cl_tree
, SCOLS_FL_TREE
);
354 itr_p
= scols_new_iter(SCOLS_ITER_FORWARD
);
355 itr_i
= scols_new_iter(SCOLS_ITER_FORWARD
);
356 if (!itr_p
|| !itr_i
)
359 /* scan all lines for ID */
360 while (scols_table_next_line(ctl
->tab
, itr_i
, &ln_i
) == 0) {
361 struct libscols_line
*ln
;
362 struct libscols_cell
*ce
= scols_line_get_column_cell(ln_i
, cl_i
);
363 const char *id
= ce
? scols_cell_get_data(ce
) : NULL
;
368 /* see if the ID is somewhere used in parent column */
369 scols_reset_iter(itr_p
, SCOLS_ITER_FORWARD
);
370 while (scols_table_next_line(ctl
->tab
, itr_p
, &ln
) == 0) {
373 ce
= scols_line_get_column_cell(ln
, cl_p
);
374 parent
= ce
? scols_cell_get_data(ce
) : NULL
;
378 if (strcmp(id
, parent
) != 0)
380 if (scols_line_is_ancestor(ln
, ln_i
))
382 scols_line_add_child(ln_i
, ln
);
386 scols_free_iter(itr_p
);
387 scols_free_iter(itr_i
);
390 static void modify_table(struct column_control
*ctl
)
392 scols_table_set_termwidth(ctl
->tab
, ctl
->termwidth
);
393 scols_table_set_termforce(ctl
->tab
, SCOLS_TERMFORCE_ALWAYS
);
395 if (ctl
->tab_colright
)
396 apply_columnflag_from_list(ctl
, ctl
->tab_colright
,
397 SCOLS_FL_RIGHT
, _("failed to parse --table-right list"));
399 if (ctl
->tab_coltrunc
)
400 apply_columnflag_from_list(ctl
, ctl
->tab_coltrunc
,
401 SCOLS_FL_TRUNC
, _("failed to parse --table-trunc list"));
403 if (ctl
->tab_colnoextrem
)
404 apply_columnflag_from_list(ctl
, ctl
->tab_colnoextrem
,
405 SCOLS_FL_NOEXTREMES
, _("failed to parse --table-noextreme list"));
407 if (ctl
->tab_colwrap
)
408 apply_columnflag_from_list(ctl
, ctl
->tab_colwrap
,
409 SCOLS_FL_WRAP
, _("failed to parse --table-wrap list"));
411 if (ctl
->tab_colhide
)
412 apply_columnflag_from_list(ctl
, ctl
->tab_colhide
,
413 SCOLS_FL_HIDDEN
, _("failed to parse --table-hide list"));
415 if (!ctl
->tab_colnoextrem
) {
416 struct libscols_column
*cl
= get_last_visible_column(ctl
);
418 column_set_flag(cl
, SCOLS_FL_NOEXTREMES
);
424 /* This must be the last step! */
430 static int add_line_to_table(struct column_control
*ctl
, wchar_t *wcs
)
432 wchar_t *wcdata
, *sv
= NULL
;
434 struct libscols_line
*ln
= NULL
;
439 while ((wcdata
= local_wcstok(ctl
, wcs
, &sv
))) {
442 if (scols_table_get_ncols(ctl
->tab
) < n
+ 1) {
443 if (scols_table_is_json(ctl
->tab
))
444 errx(EXIT_FAILURE
, _("line %zu: for JSON the name of the "
445 "column %zu is required"),
446 scols_table_get_nlines(ctl
->tab
) + 1,
448 scols_table_new_column(ctl
->tab
, NULL
, 0, 0);
451 ln
= scols_table_new_line(ctl
->tab
, NULL
);
453 err(EXIT_FAILURE
, _("failed to allocate output line"));
456 data
= wcs_to_mbs(wcdata
);
458 err(EXIT_FAILURE
, _("failed to allocate output data"));
459 if (scols_line_refer_data(ln
, n
, data
))
460 err(EXIT_FAILURE
, _("failed to add output data"));
468 static int add_emptyline_to_table(struct column_control
*ctl
)
473 if (!scols_table_new_line(ctl
->tab
, NULL
))
474 err(EXIT_FAILURE
, _("failed to allocate output line"));
479 static int read_input(struct column_control
*ctl
, FILE *fp
)
492 if (getline(&buf
, &bufsz
, fp
) < 0) {
495 err(EXIT_FAILURE
, _("read failed"));
497 str
= (char *) skip_space(buf
);
499 p
= strchr(str
, '\n');
504 if (ctl
->mode
== COLUMN_MODE_TABLE
&& ctl
->tab_empty_lines
)
505 add_emptyline_to_table(ctl
);
509 wcs
= mbs_to_wcs(buf
);
512 * Convert broken sequences to \x<hex> and continue.
515 char *tmp
= mbs_invalid_encode(buf
, &tmpsz
);
518 err(EXIT_FAILURE
, _("read failed"));
519 wcs
= mbs_to_wcs(tmp
);
524 case COLUMN_MODE_TABLE
:
525 rc
= add_line_to_table(ctl
, wcs
);
529 case COLUMN_MODE_FILLCOLS
:
530 case COLUMN_MODE_FILLROWS
:
531 if (ctl
->nents
<= maxents
) {
533 ctl
->ents
= xrealloc(ctl
->ents
,
534 maxents
* sizeof(wchar_t *));
536 ctl
->ents
[ctl
->nents
] = wcs
;
537 len
= width(ctl
->ents
[ctl
->nents
]);
538 if (ctl
->maxlength
< len
)
539 ctl
->maxlength
= len
;
552 static void columnate_fillrows(struct column_control
*ctl
)
554 size_t chcnt
, col
, cnt
, endcol
, numcols
;
557 ctl
->maxlength
= (ctl
->maxlength
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1);
558 numcols
= ctl
->termwidth
/ ctl
->maxlength
;
559 endcol
= ctl
->maxlength
;
560 for (chcnt
= col
= 0, lp
= ctl
->ents
; /* nothing */; ++lp
) {
565 if (++col
== numcols
) {
567 endcol
= ctl
->maxlength
;
570 while ((cnt
= ((chcnt
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1))) <= endcol
) {
574 endcol
+= ctl
->maxlength
;
581 static void columnate_fillcols(struct column_control
*ctl
)
583 size_t base
, chcnt
, cnt
, col
, endcol
, numcols
, numrows
, row
;
585 ctl
->maxlength
= (ctl
->maxlength
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1);
586 numcols
= ctl
->termwidth
/ ctl
->maxlength
;
589 numrows
= ctl
->nents
/ numcols
;
590 if (ctl
->nents
% numcols
)
593 for (row
= 0; row
< numrows
; ++row
) {
594 endcol
= ctl
->maxlength
;
595 for (base
= row
, chcnt
= col
= 0; col
< numcols
; ++col
) {
596 fputws(ctl
->ents
[base
], stdout
);
597 chcnt
+= width(ctl
->ents
[base
]);
598 if ((base
+= numrows
) >= ctl
->nents
)
600 while ((cnt
= ((chcnt
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1))) <= endcol
) {
604 endcol
+= ctl
->maxlength
;
610 static void simple_print(struct column_control
*ctl
)
615 for (cnt
= ctl
->nents
, lp
= ctl
->ents
; cnt
--; ++lp
) {
621 static void __attribute__((__noreturn__
)) usage(void)
625 fputs(USAGE_HEADER
, out
);
626 fprintf(out
, _(" %s [options] [<file>...]\n"), program_invocation_short_name
);
628 fputs(USAGE_SEPARATOR
, out
);
629 fputs(_("Columnate lists.\n"), out
);
631 fputs(USAGE_OPTIONS
, out
);
632 fputs(_(" -t, --table create a table\n"), out
);
633 fputs(_(" -n, --table-name <name> table name for JSON output\n"), out
);
634 fputs(_(" -O, --table-order <columns> specify order of output columns\n"), out
);
635 fputs(_(" -N, --table-columns <names> comma separated columns names\n"), out
);
636 fputs(_(" -E, --table-noextreme <columns> don't count long text from the columns to column width\n"), out
);
637 fputs(_(" -d, --table-noheadings don't print header\n"), out
);
638 fputs(_(" -e, --table-header-repeat repeat header for each page\n"), out
);
639 fputs(_(" -H, --table-hide <columns> don't print the columns\n"), out
);
640 fputs(_(" -R, --table-right <columns> right align text in these columns\n"), out
);
641 fputs(_(" -T, --table-truncate <columns> truncate text in the columns when necessary\n"), out
);
642 fputs(_(" -W, --table-wrap <columns> wrap text in the columns when necessary\n"), out
);
643 fputs(_(" -L, --table-empty-lines don't ignore empty lines\n"), out
);
644 fputs(_(" -J, --json use JSON output format for table\n"), out
);
646 fputs(USAGE_SEPARATOR
, out
);
647 fputs(_(" -r, --tree <column> column to use tree-like output for the table\n"), out
);
648 fputs(_(" -i, --tree-id <column> line ID to specify child-parent relation\n"), out
);
649 fputs(_(" -p, --tree-parent <column> parent to specify child-parent relation\n"), out
);
651 fputs(USAGE_SEPARATOR
, out
);
652 fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out
);
653 fputs(_(" -o, --output-separator <string> columns separator for table output (default is two spaces)\n"), out
);
654 fputs(_(" -s, --separator <string> possible table delimiters\n"), out
);
655 fputs(_(" -x, --fillrows fill rows before columns\n"), out
);
658 fputs(USAGE_SEPARATOR
, out
);
659 printf(USAGE_HELP_OPTIONS(34));
660 printf(USAGE_MAN_TAIL("column(1)"));
665 int main(int argc
, char **argv
)
667 struct column_control ctl
= {
668 .mode
= COLUMN_MODE_FILLCOLS
,
670 .termwidth
= (size_t) -1
674 unsigned int eval
= 0; /* exit value */
676 static const struct option longopts
[] =
678 { "columns", required_argument
, NULL
, 'c' }, /* deprecated */
679 { "fillrows", no_argument
, NULL
, 'x' },
680 { "help", no_argument
, NULL
, 'h' },
681 { "json", no_argument
, NULL
, 'J' },
682 { "output-separator", required_argument
, NULL
, 'o' },
683 { "output-width", required_argument
, NULL
, 'c' },
684 { "separator", required_argument
, NULL
, 's' },
685 { "table", no_argument
, NULL
, 't' },
686 { "table-columns", required_argument
, NULL
, 'N' },
687 { "table-hide", required_argument
, NULL
, 'H' },
688 { "table-name", required_argument
, NULL
, 'n' },
689 { "table-noextreme", required_argument
, NULL
, 'E' },
690 { "table-noheadings", no_argument
, NULL
, 'd' },
691 { "table-order", required_argument
, NULL
, 'O' },
692 { "table-right", required_argument
, NULL
, 'R' },
693 { "table-truncate", required_argument
, NULL
, 'T' },
694 { "table-wrap", required_argument
, NULL
, 'W' },
695 { "table-empty-lines", no_argument
, NULL
, 'L' },
696 { "table-header-repeat", no_argument
, NULL
, 'e' },
697 { "tree", required_argument
, NULL
, 'r' },
698 { "tree-id", required_argument
, NULL
, 'i' },
699 { "tree-parent", required_argument
, NULL
, 'p' },
700 { "version", no_argument
, NULL
, 'V' },
701 { NULL
, 0, NULL
, 0 },
703 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
708 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
710 setlocale(LC_ALL
, "");
711 bindtextdomain(PACKAGE
, LOCALEDIR
);
713 close_stdout_atexit();
715 ctl
.output_separator
= " ";
716 ctl
.input_separator
= mbs_to_wcs("\t ");
718 while ((c
= getopt_long(argc
, argv
, "c:dE:eH:hi:JLN:n:O:o:p:R:r:s:T:tVW:x", longopts
, NULL
)) != -1) {
720 err_exclusive_options(c
, longopts
, excl
, excl_st
);
724 ctl
.termwidth
= strtou32_or_err(optarg
, _("invalid columns argument"));
727 ctl
.tab_noheadings
= 1;
730 ctl
.tab_colnoextrem
= optarg
;
733 ctl
.header_repeat
= 1;
736 ctl
.tab_colhide
= optarg
;
739 ctl
.tree_id
= optarg
;
743 ctl
.mode
= COLUMN_MODE_TABLE
;
746 ctl
.tab_empty_lines
= 1;
749 ctl
.tab_colnames
= split_or_error(optarg
, _("failed to parse column names"));
752 ctl
.tab_name
= optarg
;
755 ctl
.tab_order
= optarg
;
758 ctl
.output_separator
= optarg
;
761 ctl
.tree_parent
= optarg
;
764 ctl
.tab_colright
= optarg
;
770 free(ctl
.input_separator
);
771 ctl
.input_separator
= mbs_to_wcs(optarg
);
775 ctl
.tab_coltrunc
= optarg
;
778 ctl
.mode
= COLUMN_MODE_TABLE
;
781 ctl
.tab_colwrap
= optarg
;
784 ctl
.mode
= COLUMN_MODE_FILLROWS
;
790 print_version(EXIT_SUCCESS
);
792 errtryhelp(EXIT_FAILURE
);
798 if (ctl
.termwidth
== (size_t) -1)
799 ctl
.termwidth
= get_terminal_width(80);
802 ctl
.mode
= COLUMN_MODE_TABLE
;
803 if (!ctl
.tree_parent
|| !ctl
.tree_id
)
804 errx(EXIT_FAILURE
, _("options --tree-id and --tree-parent are "
805 "required for tree formatting"));
808 if (ctl
.mode
!= COLUMN_MODE_TABLE
809 && (ctl
.tab_order
|| ctl
.tab_name
|| ctl
.tab_colwrap
||
810 ctl
.tab_colhide
|| ctl
.tab_coltrunc
|| ctl
.tab_colnoextrem
||
811 ctl
.tab_colright
|| ctl
.tab_colnames
))
812 errx(EXIT_FAILURE
, _("option --table required for all --table-*"));
814 if (ctl
.tab_colnames
== NULL
&& ctl
.json
)
815 errx(EXIT_FAILURE
, _("option --table-columns required for --json"));
818 eval
+= read_input(&ctl
, stdin
);
820 for (; *argv
; ++argv
) {
823 if ((fp
= fopen(*argv
, "r")) != NULL
) {
824 eval
+= read_input(&ctl
, fp
);
828 eval
+= EXIT_FAILURE
;
832 if (ctl
.mode
!= COLUMN_MODE_TABLE
) {
835 if (ctl
.maxlength
>= ctl
.termwidth
)
836 ctl
.mode
= COLUMN_MODE_SIMPLE
;
840 case COLUMN_MODE_TABLE
:
841 if (ctl
.tab
&& scols_table_get_nlines(ctl
.tab
)) {
843 eval
= scols_print_table(ctl
.tab
);
846 case COLUMN_MODE_FILLCOLS
:
847 columnate_fillcols(&ctl
);
849 case COLUMN_MODE_FILLROWS
:
850 columnate_fillrows(&ctl
);
852 case COLUMN_MODE_SIMPLE
:
857 return eval
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;