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(wchar_t *p
, const wchar_t *separator
, int greedy
, wchar_t **state
)
163 wchar_t *result
= NULL
;
167 return wcstok(p
, separator
, state
);
169 return strtok_r(p
, separator
, state
);
178 p
= wcspbrk(result
, separator
);
180 p
= strpbrk(result
, separator
);
191 static char **split_or_error(const char *str
, const char *errmsg
)
193 char **res
= strv_split(str
, ",");
197 errx(EXIT_FAILURE
, "%s: '%s'", errmsg
, str
);
202 static void init_table(struct column_control
*ctl
)
206 ctl
->tab
= scols_new_table();
208 err(EXIT_FAILURE
, _("failed to allocate output table"));
210 scols_table_set_column_separator(ctl
->tab
, ctl
->output_separator
);
212 scols_table_enable_json(ctl
->tab
, 1);
213 scols_table_set_name(ctl
->tab
, ctl
->tab_name
? : "table");
215 scols_table_enable_noencoding(ctl
->tab
, 1);
217 if (ctl
->tab_colnames
) {
220 STRV_FOREACH(name
, ctl
->tab_colnames
)
221 scols_table_new_column(ctl
->tab
, *name
, 0, 0);
222 if (ctl
->header_repeat
)
223 scols_table_enable_header_repeat(ctl
->tab
, 1);
224 scols_table_enable_noheadings(ctl
->tab
, !!ctl
->tab_noheadings
);
226 scols_table_enable_noheadings(ctl
->tab
, 1);
229 static struct libscols_column
*string_to_column(struct column_control
*ctl
, const char *str
)
233 if (isdigit_string(str
))
234 colnum
= strtou32_or_err(str
, _("failed to parse column")) - 1;
238 STRV_FOREACH(name
, ctl
->tab_colnames
) {
239 if (strcasecmp(*name
, str
) == 0)
244 errx(EXIT_FAILURE
, _("undefined column name '%s'"), str
);
247 return scols_table_get_column(ctl
->tab
, colnum
);
250 static struct libscols_column
*get_last_visible_column(struct column_control
*ctl
)
252 struct libscols_iter
*itr
;
253 struct libscols_column
*cl
, *last
= NULL
;
255 itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
259 while (scols_table_next_column(ctl
->tab
, itr
, &cl
) == 0) {
260 if (scols_column_get_flags(cl
) & SCOLS_FL_HIDDEN
)
265 scols_free_iter(itr
);
269 static int column_set_flag(struct libscols_column
*cl
, int fl
)
271 int cur
= scols_column_get_flags(cl
);
273 return scols_column_set_flags(cl
, cur
| fl
);
276 static void apply_columnflag_from_list(struct column_control
*ctl
, const char *list
,
277 int flag
, const char *errmsg
)
279 char **all
= split_or_error(list
, errmsg
);
283 STRV_FOREACH(one
, all
) {
284 struct libscols_column
*cl
;
286 if (flag
== SCOLS_FL_HIDDEN
&& strcmp(*one
, "-") == 0) {
290 cl
= string_to_column(ctl
, *one
);
292 column_set_flag(cl
, flag
);
296 /* apply flag to all columns without name */
298 struct libscols_iter
*itr
;
299 struct libscols_column
*cl
;
301 itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
305 while (scols_table_next_column(ctl
->tab
, itr
, &cl
) == 0) {
306 struct libscols_cell
*ce
= scols_column_get_header(cl
);
308 if (ce
== NULL
|| scols_cell_get_data(ce
) == NULL
)
309 column_set_flag(cl
, flag
);
311 scols_free_iter(itr
);
315 static void reorder_table(struct column_control
*ctl
)
317 struct libscols_column
**wanted
, *last
= NULL
;
319 size_t ncols
= scols_table_get_ncols(ctl
->tab
);
320 char **order
= split_or_error(ctl
->tab_order
, _("failed to parse --table-order list"));
323 wanted
= xcalloc(ncols
, sizeof(struct libscols_column
*));
325 STRV_FOREACH(one
, order
) {
326 struct libscols_column
*cl
= string_to_column(ctl
, *one
);
328 wanted
[count
++] = cl
;
331 for (i
= 0; i
< count
; i
++) {
332 scols_table_move_column(ctl
->tab
, last
, wanted
[i
]);
340 static void create_tree(struct column_control
*ctl
)
342 struct libscols_column
*cl_tree
= string_to_column(ctl
, ctl
->tree
);
343 struct libscols_column
*cl_p
= string_to_column(ctl
, ctl
->tree_parent
);
344 struct libscols_column
*cl_i
= string_to_column(ctl
, ctl
->tree_id
);
345 struct libscols_iter
*itr_p
, *itr_i
;
346 struct libscols_line
*ln_i
;
348 if (!cl_p
|| !cl_i
|| !cl_tree
)
349 return; /* silently ignore the tree request */
351 column_set_flag(cl_tree
, SCOLS_FL_TREE
);
353 itr_p
= scols_new_iter(SCOLS_ITER_FORWARD
);
354 itr_i
= scols_new_iter(SCOLS_ITER_FORWARD
);
355 if (!itr_p
|| !itr_i
)
358 /* scan all lines for ID */
359 while (scols_table_next_line(ctl
->tab
, itr_i
, &ln_i
) == 0) {
360 struct libscols_line
*ln
;
361 struct libscols_cell
*ce
= scols_line_get_column_cell(ln_i
, cl_i
);
362 const char *id
= ce
? scols_cell_get_data(ce
) : NULL
;
367 /* see if the ID is somewhere used in parent column */
368 scols_reset_iter(itr_p
, SCOLS_ITER_FORWARD
);
369 while (scols_table_next_line(ctl
->tab
, itr_p
, &ln
) == 0) {
372 ce
= scols_line_get_column_cell(ln
, cl_p
);
373 parent
= ce
? scols_cell_get_data(ce
) : NULL
;
377 if (strcmp(id
, parent
) != 0)
379 if (scols_line_is_ancestor(ln
, ln_i
))
381 scols_line_add_child(ln_i
, ln
);
385 scols_free_iter(itr_p
);
386 scols_free_iter(itr_i
);
389 static void modify_table(struct column_control
*ctl
)
391 scols_table_set_termwidth(ctl
->tab
, ctl
->termwidth
);
392 scols_table_set_termforce(ctl
->tab
, SCOLS_TERMFORCE_ALWAYS
);
394 if (ctl
->tab_colright
)
395 apply_columnflag_from_list(ctl
, ctl
->tab_colright
,
396 SCOLS_FL_RIGHT
, _("failed to parse --table-right list"));
398 if (ctl
->tab_coltrunc
)
399 apply_columnflag_from_list(ctl
, ctl
->tab_coltrunc
,
400 SCOLS_FL_TRUNC
, _("failed to parse --table-trunc list"));
402 if (ctl
->tab_colnoextrem
)
403 apply_columnflag_from_list(ctl
, ctl
->tab_colnoextrem
,
404 SCOLS_FL_NOEXTREMES
, _("failed to parse --table-noextreme list"));
406 if (ctl
->tab_colwrap
)
407 apply_columnflag_from_list(ctl
, ctl
->tab_colwrap
,
408 SCOLS_FL_WRAP
, _("failed to parse --table-wrap list"));
410 if (ctl
->tab_colhide
)
411 apply_columnflag_from_list(ctl
, ctl
->tab_colhide
,
412 SCOLS_FL_HIDDEN
, _("failed to parse --table-hide list"));
414 if (!ctl
->tab_colnoextrem
) {
415 struct libscols_column
*cl
= get_last_visible_column(ctl
);
417 column_set_flag(cl
, SCOLS_FL_NOEXTREMES
);
423 /* This must be the last step! */
429 static int add_line_to_table(struct column_control
*ctl
, wchar_t *wcs
)
431 wchar_t *wcdata
, *sv
= NULL
;
433 struct libscols_line
*ln
= NULL
;
438 while ((wcdata
= local_wcstok(wcs
, ctl
->input_separator
, ctl
->greedy
, &sv
))) {
441 if (scols_table_get_ncols(ctl
->tab
) < n
+ 1) {
442 if (scols_table_is_json(ctl
->tab
))
443 errx(EXIT_FAILURE
, _("line %zu: for JSON the name of the "
444 "column %zu is required"),
445 scols_table_get_nlines(ctl
->tab
) + 1,
447 scols_table_new_column(ctl
->tab
, NULL
, 0, 0);
450 ln
= scols_table_new_line(ctl
->tab
, NULL
);
452 err(EXIT_FAILURE
, _("failed to allocate output line"));
455 data
= wcs_to_mbs(wcdata
);
457 err(EXIT_FAILURE
, _("failed to allocate output data"));
458 if (scols_line_refer_data(ln
, n
, data
))
459 err(EXIT_FAILURE
, _("failed to add output data"));
467 static int add_emptyline_to_table(struct column_control
*ctl
)
472 if (!scols_table_new_line(ctl
->tab
, NULL
))
473 err(EXIT_FAILURE
, _("failed to allocate output line"));
478 static int read_input(struct column_control
*ctl
, FILE *fp
)
491 if (getline(&buf
, &bufsz
, fp
) < 0) {
494 err(EXIT_FAILURE
, _("read failed"));
496 str
= (char *) skip_space(buf
);
498 p
= strchr(str
, '\n');
503 if (ctl
->mode
== COLUMN_MODE_TABLE
&& ctl
->tab_empty_lines
)
504 add_emptyline_to_table(ctl
);
508 wcs
= mbs_to_wcs(buf
);
511 * Convert broken sequences to \x<hex> and continue.
514 char *tmp
= mbs_invalid_encode(buf
, &tmpsz
);
517 err(EXIT_FAILURE
, _("read failed"));
518 wcs
= mbs_to_wcs(tmp
);
523 case COLUMN_MODE_TABLE
:
524 rc
= add_line_to_table(ctl
, wcs
);
528 case COLUMN_MODE_FILLCOLS
:
529 case COLUMN_MODE_FILLROWS
:
530 if (ctl
->nents
<= maxents
) {
532 ctl
->ents
= xrealloc(ctl
->ents
,
533 maxents
* sizeof(wchar_t *));
535 ctl
->ents
[ctl
->nents
] = wcs
;
536 len
= width(ctl
->ents
[ctl
->nents
]);
537 if (ctl
->maxlength
< len
)
538 ctl
->maxlength
= len
;
551 static void columnate_fillrows(struct column_control
*ctl
)
553 size_t chcnt
, col
, cnt
, endcol
, numcols
;
556 ctl
->maxlength
= (ctl
->maxlength
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1);
557 numcols
= ctl
->termwidth
/ ctl
->maxlength
;
558 endcol
= ctl
->maxlength
;
559 for (chcnt
= col
= 0, lp
= ctl
->ents
; /* nothing */; ++lp
) {
564 if (++col
== numcols
) {
566 endcol
= ctl
->maxlength
;
569 while ((cnt
= ((chcnt
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1))) <= endcol
) {
573 endcol
+= ctl
->maxlength
;
580 static void columnate_fillcols(struct column_control
*ctl
)
582 size_t base
, chcnt
, cnt
, col
, endcol
, numcols
, numrows
, row
;
584 ctl
->maxlength
= (ctl
->maxlength
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1);
585 numcols
= ctl
->termwidth
/ ctl
->maxlength
;
588 numrows
= ctl
->nents
/ numcols
;
589 if (ctl
->nents
% numcols
)
592 for (row
= 0; row
< numrows
; ++row
) {
593 endcol
= ctl
->maxlength
;
594 for (base
= row
, chcnt
= col
= 0; col
< numcols
; ++col
) {
595 fputws(ctl
->ents
[base
], stdout
);
596 chcnt
+= width(ctl
->ents
[base
]);
597 if ((base
+= numrows
) >= ctl
->nents
)
599 while ((cnt
= ((chcnt
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1))) <= endcol
) {
603 endcol
+= ctl
->maxlength
;
609 static void simple_print(struct column_control
*ctl
)
614 for (cnt
= ctl
->nents
, lp
= ctl
->ents
; cnt
--; ++lp
) {
620 static void __attribute__((__noreturn__
)) usage(void)
624 fputs(USAGE_HEADER
, out
);
625 fprintf(out
, _(" %s [options] [<file>...]\n"), program_invocation_short_name
);
627 fputs(USAGE_SEPARATOR
, out
);
628 fputs(_("Columnate lists.\n"), out
);
630 fputs(USAGE_OPTIONS
, out
);
631 fputs(_(" -t, --table create a table\n"), out
);
632 fputs(_(" -n, --table-name <name> table name for JSON output\n"), out
);
633 fputs(_(" -O, --table-order <columns> specify order of output columns\n"), out
);
634 fputs(_(" -N, --table-columns <names> comma separated columns names\n"), out
);
635 fputs(_(" -E, --table-noextreme <columns> don't count long text from the columns to column width\n"), out
);
636 fputs(_(" -d, --table-noheadings don't print header\n"), out
);
637 fputs(_(" -e, --table-header-repeat repeat header for each page\n"), out
);
638 fputs(_(" -H, --table-hide <columns> don't print the columns\n"), out
);
639 fputs(_(" -R, --table-right <columns> right align text in these columns\n"), out
);
640 fputs(_(" -T, --table-truncate <columns> truncate text in the columns when necessary\n"), out
);
641 fputs(_(" -W, --table-wrap <columns> wrap text in the columns when necessary\n"), out
);
642 fputs(_(" -L, --table-empty-lines don't ignore empty lines\n"), out
);
643 fputs(_(" -J, --json use JSON output format for table\n"), out
);
645 fputs(USAGE_SEPARATOR
, out
);
646 fputs(_(" -r, --tree <column> column to use tree-like output for the table\n"), out
);
647 fputs(_(" -i, --tree-id <column> line ID to specify child-parent relation\n"), out
);
648 fputs(_(" -p, --tree-parent <column> parent to specify child-parent relation\n"), out
);
650 fputs(USAGE_SEPARATOR
, out
);
651 fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out
);
652 fputs(_(" -o, --output-separator <string> columns separator for table output (default is two spaces)\n"), out
);
653 fputs(_(" -s, --separator <string> possible table delimiters\n"), out
);
654 fputs(_(" -x, --fillrows fill rows before columns\n"), out
);
657 fputs(USAGE_SEPARATOR
, out
);
658 printf(USAGE_HELP_OPTIONS(34));
659 printf(USAGE_MAN_TAIL("column(1)"));
664 int main(int argc
, char **argv
)
666 struct column_control ctl
= {
667 .mode
= COLUMN_MODE_FILLCOLS
,
669 .termwidth
= (size_t) -1
673 unsigned int eval
= 0; /* exit value */
675 static const struct option longopts
[] =
677 { "columns", required_argument
, NULL
, 'c' }, /* deprecated */
678 { "fillrows", no_argument
, NULL
, 'x' },
679 { "help", no_argument
, NULL
, 'h' },
680 { "json", no_argument
, NULL
, 'J' },
681 { "output-separator", required_argument
, NULL
, 'o' },
682 { "output-width", required_argument
, NULL
, 'c' },
683 { "separator", required_argument
, NULL
, 's' },
684 { "table", no_argument
, NULL
, 't' },
685 { "table-columns", required_argument
, NULL
, 'N' },
686 { "table-hide", required_argument
, NULL
, 'H' },
687 { "table-name", required_argument
, NULL
, 'n' },
688 { "table-noextreme", required_argument
, NULL
, 'E' },
689 { "table-noheadings", no_argument
, NULL
, 'd' },
690 { "table-order", required_argument
, NULL
, 'O' },
691 { "table-right", required_argument
, NULL
, 'R' },
692 { "table-truncate", required_argument
, NULL
, 'T' },
693 { "table-wrap", required_argument
, NULL
, 'W' },
694 { "table-empty-lines", no_argument
, NULL
, 'L' },
695 { "table-header-repeat", no_argument
, NULL
, 'e' },
696 { "tree", required_argument
, NULL
, 'r' },
697 { "tree-id", required_argument
, NULL
, 'i' },
698 { "tree-parent", required_argument
, NULL
, 'p' },
699 { "version", no_argument
, NULL
, 'V' },
700 { NULL
, 0, NULL
, 0 },
702 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
707 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
709 setlocale(LC_ALL
, "");
710 bindtextdomain(PACKAGE
, LOCALEDIR
);
712 close_stdout_atexit();
714 ctl
.output_separator
= " ";
715 ctl
.input_separator
= mbs_to_wcs("\t ");
717 while ((c
= getopt_long(argc
, argv
, "c:dE:eH:hi:JLN:n:O:o:p:R:r:s:T:tVW:x", longopts
, NULL
)) != -1) {
719 err_exclusive_options(c
, longopts
, excl
, excl_st
);
723 ctl
.termwidth
= strtou32_or_err(optarg
, _("invalid columns argument"));
726 ctl
.tab_noheadings
= 1;
729 ctl
.tab_colnoextrem
= optarg
;
732 ctl
.header_repeat
= 1;
735 ctl
.tab_colhide
= optarg
;
738 ctl
.tree_id
= optarg
;
742 ctl
.mode
= COLUMN_MODE_TABLE
;
745 ctl
.tab_empty_lines
= 1;
748 ctl
.tab_colnames
= split_or_error(optarg
, _("failed to parse column names"));
751 ctl
.tab_name
= optarg
;
754 ctl
.tab_order
= optarg
;
757 ctl
.output_separator
= optarg
;
760 ctl
.tree_parent
= optarg
;
763 ctl
.tab_colright
= optarg
;
769 free(ctl
.input_separator
);
770 ctl
.input_separator
= mbs_to_wcs(optarg
);
774 ctl
.tab_coltrunc
= optarg
;
777 ctl
.mode
= COLUMN_MODE_TABLE
;
780 ctl
.tab_colwrap
= optarg
;
783 ctl
.mode
= COLUMN_MODE_FILLROWS
;
789 print_version(EXIT_SUCCESS
);
791 errtryhelp(EXIT_FAILURE
);
797 if (ctl
.termwidth
== (size_t) -1)
798 ctl
.termwidth
= get_terminal_width(80);
801 ctl
.mode
= COLUMN_MODE_TABLE
;
802 if (!ctl
.tree_parent
|| !ctl
.tree_id
)
803 errx(EXIT_FAILURE
, _("options --tree-id and --tree-parent are "
804 "required for tree formatting"));
807 if (ctl
.mode
!= COLUMN_MODE_TABLE
808 && (ctl
.tab_order
|| ctl
.tab_name
|| ctl
.tab_colwrap
||
809 ctl
.tab_colhide
|| ctl
.tab_coltrunc
|| ctl
.tab_colnoextrem
||
810 ctl
.tab_colright
|| ctl
.tab_colnames
))
811 errx(EXIT_FAILURE
, _("option --table required for all --table-*"));
813 if (ctl
.tab_colnames
== NULL
&& ctl
.json
)
814 errx(EXIT_FAILURE
, _("option --table-columns required for --json"));
817 eval
+= read_input(&ctl
, stdin
);
819 for (; *argv
; ++argv
) {
822 if ((fp
= fopen(*argv
, "r")) != NULL
) {
823 eval
+= read_input(&ctl
, fp
);
827 eval
+= EXIT_FAILURE
;
831 if (ctl
.mode
!= COLUMN_MODE_TABLE
) {
834 if (ctl
.maxlength
>= ctl
.termwidth
)
835 ctl
.mode
= COLUMN_MODE_SIMPLE
;
839 case COLUMN_MODE_TABLE
:
840 if (ctl
.tab
&& scols_table_get_nlines(ctl
.tab
)) {
842 eval
= scols_print_table(ctl
.tab
);
845 case COLUMN_MODE_FILLCOLS
:
846 columnate_fillcols(&ctl
);
848 case COLUMN_MODE_FILLROWS
:
849 columnate_fillrows(&ctl
);
851 case COLUMN_MODE_SIMPLE
:
856 return eval
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;