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"
56 #include "libsmartcols.h"
58 #define TABCHAR_CELLS 8
61 COLUMN_MODE_FILLCOLS
= 0,
67 struct column_control
{
68 int mode
; /* COLUMN_MODE_* */
71 struct libscols_table
*tab
;
73 char **tab_colnames
; /* array with column names */
74 const char *tab_name
; /* table name */
75 const char *tab_colright
; /* non-parsed --table-right */
77 wchar_t *input_separator
;
78 const char *output_separator
;
80 wchar_t **ents
; /* input entries */
81 size_t nents
; /* number of entries */
82 size_t maxlength
; /* longest input record (line) */
84 unsigned int greedy
:1,
88 static size_t width(const wchar_t *str
)
92 for (; *str
!= '\0'; str
++) {
94 int x
= wcwidth(*str
); /* don't use wcswidth(), need to ignore non-printable */
105 static wchar_t *mbs_to_wcs(const char *s
)
111 n
= mbstowcs((wchar_t *)0, s
, 0);
114 wcs
= xcalloc((n
+ 1) * sizeof(wchar_t), 1);
115 n
= mbstowcs(wcs
, s
, n
+ 1);
126 static char *wcs_to_mbs(const wchar_t *s
)
132 n
= wcstombs(NULL
, s
, 0);
133 if (n
== (size_t) -1)
136 str
= xcalloc(n
+ 1, 1);
137 if (wcstombs(str
, s
, n
) == (size_t) -1) {
147 static wchar_t *local_wcstok(wchar_t *p
, const wchar_t *separator
, int greedy
, wchar_t **state
)
149 wchar_t *result
= NULL
;
152 return wcstok(p
, separator
, state
);
154 if (!*state
|| !**state
)
159 p
= wcspbrk(result
, separator
);
169 static char **split_or_error(const char *str
, const char *errmsg
)
171 char **res
= strv_split(str
, ",");
175 errx(EXIT_FAILURE
, "%s: '%s'", errmsg
, str
);
180 static void init_table(struct column_control
*ctl
)
184 ctl
->tab
= scols_new_table();
186 err(EXIT_FAILURE
, _("failed to allocate output table"));
188 scols_table_set_column_separator(ctl
->tab
, ctl
->output_separator
);
190 scols_table_enable_json(ctl
->tab
, 1);
191 scols_table_set_name(ctl
->tab
, ctl
->tab_name
? : "table");
193 if (ctl
->tab_colnames
) {
196 STRV_FOREACH(name
, ctl
->tab_colnames
)
197 scols_table_new_column(ctl
->tab
, *name
, 0, 0);
199 scols_table_enable_noheadings(ctl
->tab
, 1);
202 static struct libscols_column
*string_to_column(struct column_control
*ctl
, const char *str
)
206 if (isdigit_string(str
))
207 colnum
= strtou32_or_err(str
, _("failed to parse column")) - 1;
211 STRV_FOREACH(name
, ctl
->tab_colnames
) {
212 if (strcasecmp(*name
, str
) == 0)
217 errx(EXIT_FAILURE
, _("undefined column name '%s'"), str
);
220 return scols_table_get_column(ctl
->tab
, colnum
);
223 static int column_set_flag(struct libscols_column
*cl
, int fl
)
225 int cur
= scols_column_get_flags(cl
);
227 return scols_column_set_flags(cl
, cur
| fl
);
230 static void modify_table(struct column_control
*ctl
)
232 /* align text in columns to right */
233 if (ctl
->tab_colright
) {
234 char **allright
= split_or_error(ctl
->tab_colright
, _("failed to parse --table-colright list"));
237 STRV_FOREACH(right
, allright
) {
238 struct libscols_column
*cl
= string_to_column(ctl
, *right
);
240 column_set_flag(cl
, SCOLS_FL_RIGHT
);
246 static int add_line_to_table(struct column_control
*ctl
, wchar_t *wcs
)
248 wchar_t *wcdata
, *sv
= NULL
;
250 struct libscols_line
*ln
= NULL
;
255 while ((wcdata
= local_wcstok(wcs
, ctl
->input_separator
, ctl
->greedy
, &sv
))) {
258 if (scols_table_get_ncols(ctl
->tab
) < n
+ 1)
259 scols_table_new_column(ctl
->tab
, NULL
, 0, 0);
261 ln
= scols_table_new_line(ctl
->tab
, NULL
);
263 err(EXIT_FAILURE
, _("failed to allocate output line"));
265 data
= wcs_to_mbs(wcdata
);
267 err(EXIT_FAILURE
, _("failed to allocate output data"));
268 if (scols_line_refer_data(ln
, n
, data
))
269 err(EXIT_FAILURE
, _("failed to add output data"));
277 static int read_input(struct column_control
*ctl
, FILE *fp
)
289 if (getline(&buf
, &bufsz
, fp
) < 0) {
292 err(EXIT_FAILURE
, _("read failed"));
294 str
= (char *) skip_space(buf
);
296 p
= strchr(str
, '\n');
303 wcs
= mbs_to_wcs(str
);
305 err(EXIT_FAILURE
, _("read failed"));
308 case COLUMN_MODE_TABLE
:
309 rc
= add_line_to_table(ctl
, wcs
);
313 case COLUMN_MODE_FILLCOLS
:
314 case COLUMN_MODE_FILLROWS
:
315 if (ctl
->nents
<= maxents
) {
317 ctl
->ents
= xrealloc(ctl
->ents
,
318 maxents
* sizeof(wchar_t *));
320 ctl
->ents
[ctl
->nents
] = wcs
;
321 len
= width(ctl
->ents
[ctl
->nents
]);
322 if (ctl
->maxlength
< len
)
323 ctl
->maxlength
= len
;
333 static void columnate_fillrows(struct column_control
*ctl
)
335 size_t chcnt
, col
, cnt
, endcol
, numcols
;
338 ctl
->maxlength
= (ctl
->maxlength
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1);
339 numcols
= ctl
->termwidth
/ ctl
->maxlength
;
340 endcol
= ctl
->maxlength
;
341 for (chcnt
= col
= 0, lp
= ctl
->ents
;; ++lp
) {
346 if (++col
== numcols
) {
348 endcol
= ctl
->maxlength
;
351 while ((cnt
= ((chcnt
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1))) <= endcol
) {
355 endcol
+= ctl
->maxlength
;
362 static void columnate_fillcols(struct column_control
*ctl
)
364 size_t base
, chcnt
, cnt
, col
, endcol
, numcols
, numrows
, row
;
366 ctl
->maxlength
= (ctl
->maxlength
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1);
367 numcols
= ctl
->termwidth
/ ctl
->maxlength
;
370 numrows
= ctl
->nents
/ numcols
;
371 if (ctl
->nents
% numcols
)
374 for (row
= 0; row
< numrows
; ++row
) {
375 endcol
= ctl
->maxlength
;
376 for (base
= row
, chcnt
= col
= 0; col
< numcols
; ++col
) {
377 fputws(ctl
->ents
[base
], stdout
);
378 chcnt
+= width(ctl
->ents
[base
]);
379 if ((base
+= numrows
) >= ctl
->nents
)
381 while ((cnt
= ((chcnt
+ TABCHAR_CELLS
) & ~(TABCHAR_CELLS
- 1))) <= endcol
) {
385 endcol
+= ctl
->maxlength
;
391 static void simple_print(struct column_control
*ctl
)
396 for (cnt
= ctl
->nents
, lp
= ctl
->ents
; cnt
--; ++lp
) {
402 static void __attribute__((__noreturn__
)) usage(int rc
)
404 FILE *out
= rc
== EXIT_FAILURE
? stderr
: stdout
;
406 fputs(USAGE_HEADER
, out
);
407 fprintf(out
, _(" %s [options] [<file>...]\n"), program_invocation_short_name
);
409 fputs(USAGE_SEPARATOR
, out
);
410 fputs(_("Columnate lists.\n"), out
);
412 fputs(USAGE_OPTIONS
, out
);
413 fputs(_(" -J, --json use JSON output format for table\n"), out
);
414 fputs(_(" -t, --table create a table\n"), out
);
415 fputs(_(" -N, --table-colnames <names> comma separated columns names\n"), out
);
416 fputs(_(" -R, --table-colright <columns> right align text in these columns\n"), out
);
417 fputs(_(" -n, --table-name <name> table name for JSON output\n"), out
);
418 fputs(_(" -s, --separator <string> possible table delimiters\n"), out
);
419 fputs(_(" -o, --output-separator <string> columns separator for table output\n"
420 " (default is two spaces)\n"), out
);
421 fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out
);
422 fputs(_(" -x, --fillrows fill rows before columns\n"), out
);
423 fputs(USAGE_SEPARATOR
, out
);
424 fputs(USAGE_HELP
, out
);
425 fputs(USAGE_VERSION
, out
);
426 fprintf(out
, USAGE_MAN_TAIL("column(1)"));
431 int main(int argc
, char **argv
)
433 struct column_control ctl
= {
434 .mode
= COLUMN_MODE_FILLCOLS
,
440 unsigned int eval
= 0; /* exit value */
442 static const struct option longopts
[] =
444 { "columns", required_argument
, NULL
, 'c' }, /* deprecated */
445 { "json", no_argument
, NULL
, 'J' },
446 { "fillrows", no_argument
, NULL
, 'x' },
447 { "help", no_argument
, NULL
, 'h' },
448 { "output-separator", required_argument
, NULL
, 'o' },
449 { "output-width", required_argument
, NULL
, 'c' },
450 { "separator", required_argument
, NULL
, 's' },
451 { "table", no_argument
, NULL
, 't' },
452 { "table-colnames", required_argument
, NULL
, 'N' },
453 { "table-colright", required_argument
, NULL
, 'R' },
454 { "table-name", required_argument
, NULL
, 'n' },
455 { "version", no_argument
, NULL
, 'V' },
456 { NULL
, 0, NULL
, 0 },
458 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
463 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
465 setlocale(LC_ALL
, "");
466 bindtextdomain(PACKAGE
, LOCALEDIR
);
468 atexit(close_stdout
);
470 ctl
.termwidth
= get_terminal_width(80);
471 ctl
.output_separator
= " ";
472 ctl
.input_separator
= mbs_to_wcs("\t ");
474 while ((c
= getopt_long(argc
, argv
, "hVc:Jn:N:R:s:txo:", longopts
, NULL
)) != -1) {
476 err_exclusive_options(c
, longopts
, excl
, excl_st
);
481 ctl
.mode
= COLUMN_MODE_TABLE
;
484 ctl
.tab_name
= optarg
;
487 ctl
.tab_colnames
= split_or_error(optarg
, _("failed to parse column names"));
493 printf(UTIL_LINUX_VERSION
);
496 ctl
.termwidth
= strtou32_or_err(optarg
, _("invalid columns argument"));
499 ctl
.tab_colright
= optarg
;
502 free(ctl
.input_separator
);
503 ctl
.input_separator
= mbs_to_wcs(optarg
);
507 ctl
.output_separator
= optarg
;
510 ctl
.mode
= COLUMN_MODE_TABLE
;
513 ctl
.mode
= COLUMN_MODE_FILLROWS
;
516 errtryhelp(EXIT_FAILURE
);
522 if (ctl
.tab_colnames
== NULL
&& ctl
.json
)
523 errx(EXIT_FAILURE
, _("option --table-colnames required for --json"));
526 eval
+= read_input(&ctl
, stdin
);
528 for (; *argv
; ++argv
) {
531 if ((fp
= fopen(*argv
, "r")) != NULL
) {
532 eval
+= read_input(&ctl
, fp
);
536 eval
+= EXIT_FAILURE
;
540 if (ctl
.mode
!= COLUMN_MODE_TABLE
) {
543 if (ctl
.maxlength
>= ctl
.termwidth
)
544 ctl
.mode
= COLUMN_MODE_SIMPLE
;
548 case COLUMN_MODE_TABLE
:
550 eval
= scols_print_table(ctl
.tab
);
552 case COLUMN_MODE_FILLCOLS
:
553 columnate_fillcols(&ctl
);
555 case COLUMN_MODE_FILLROWS
:
556 columnate_fillrows(&ctl
);
558 case COLUMN_MODE_SIMPLE
:
563 return eval
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;