]> git.ipfire.org Git - thirdparty/util-linux.git/blame_incremental - text-utils/column.c
taskset: Accept 0 pid for current process
[thirdparty/util-linux.git] / text-utils / column.c
... / ...
CommitLineData
1/*
2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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.
22 *
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
33 * SUCH DAMAGE.
34 */
35#include <sys/types.h>
36#include <sys/ioctl.h>
37
38#include <ctype.h>
39#include <errno.h>
40#include <stdbool.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45#include <getopt.h>
46
47#include "nls.h"
48#include "c.h"
49#include "widechar.h"
50#include "xalloc.h"
51#include "strutils.h"
52#include "closestream.h"
53#include "ttyutils.h"
54#include "strv.h"
55#include "optutils.h"
56#include "mbsalign.h"
57
58#include "libsmartcols.h"
59
60#define TABCHAR_CELLS 8
61
62enum {
63 COLUMN_MODE_FILLCOLS = 0,
64 COLUMN_MODE_FILLROWS,
65 COLUMN_MODE_TABLE,
66 COLUMN_MODE_SIMPLE
67};
68
69struct column_control {
70 int mode; /* COLUMN_MODE_* */
71 size_t termwidth; /* -1 uninilialized, 0 unlimited, >0 width (default is 80) */
72
73 struct libscols_table *tab;
74
75 char **tab_colnames; /* array with column names */
76 const char *tab_name; /* table name */
77 const char *tab_order; /* --table-order */
78
79 char **tab_columns; /* array from --table-column */
80
81 const char *tab_colright; /* --table-right */
82 const char *tab_coltrunc; /* --table-trunc */
83 const char *tab_colnoextrem; /* --table-noextreme */
84 const char *tab_colwrap; /* --table-wrap */
85 const char *tab_colhide; /* --table-hide */
86
87 const char *tree;
88 const char *tree_id;
89 const char *tree_parent;
90
91 wchar_t *input_separator;
92 const char *output_separator;
93
94 wchar_t **ents; /* input entries */
95 size_t nents; /* number of entries */
96 size_t maxlength; /* longest input record (line) */
97 size_t maxncols; /* maximal number of input columns */
98 size_t mincolsep; /* minimal spaces between columns */
99
100 bool greedy,
101 json,
102 header_repeat,
103 hide_unnamed,
104 maxout : 1,
105 keep_empty_lines, /* --keep-empty-lines */
106 tab_noheadings,
107 use_spaces;
108};
109
110typedef enum {
111 ANSI_CHR = 'A',
112 ANSI_ESC = 0x1b,
113 ANSI_SGR = '[',
114 ANSI_OSC = ']',
115 ANSI_LNK = '8',
116 ANSI_LBL = 0x7,
117 ANSI_LSP = ';',
118 ANSI_LSG = 'M',
119 ANSI_END = '\\'
120} ansi_esc_states;
121
122/**
123 * Count how many characters are non-printable due to ANSI X3.41 escape codes.
124 *
125 * It detects and count Fe Escape and OSC 8 links sequences. These sequences contains
126 * characters that normally are printable, but due to being part of a escape sequence
127 * are ignored when displayed in console terminals.
128 */
129static inline size_t ansi_esc_width(ansi_esc_states *state, size_t *found, const wchar_t *str, int chw)
130{
131 switch (*state) {
132 case ANSI_CHR:
133 // ANSI X3.41 escape sequences begin with ESC ( written as \x1b \033 or ^[ )
134 if (*str == 0x1b)
135 *state = ANSI_ESC;
136 // Ignore 1 byte C1 control codes (0x80–0x9F) due to conflict with UTF-8 and CP-1252
137 return 0;
138 case ANSI_ESC:
139 // Fe escape sequences allows the range 0x40 to 0x5f
140 switch (*str) {
141 case '[': // CSI - Control Sequence Introducer
142 *state = ANSI_SGR;
143 break;
144 case ']': // OSC - Operating System Command
145 *state = ANSI_OSC;
146 break;
147 case '_': // APC - Application Program Command
148 case 'P': // DCS - Device Control String
149 case '^': // PM - Privacy Message
150 *state = ANSI_END;
151 break;
152 default:
153 *state = ANSI_CHR;
154 return 0;
155 }
156 *found = 1;
157 return 0;
158 case ANSI_SGR:
159 *found += chw;
160 // Fe escape sequences allows the range 0x30-0x3f
161 // However SGR (Select Graphic Rendition) only uses: 0-9 ';' ':'
162 if (*str >= '0' && *str <= '?')
163 return 0;
164 // Fe ends with the range 0x40-0x7e but SGR ends with 'm'
165 if (*str <= '@' && *str >= '~')
166 *found = 0;
167 break;
168 case ANSI_OSC:
169 *found += chw;
170 if (*str == ANSI_LNK) // OSC8-Link
171 *state = ANSI_LNK;
172 else
173 *state = ANSI_END; // other command sequences are ignored
174 return 0;
175 case ANSI_LNK: // OSC8 Terminal Hiperlink Sequence
176 switch (*str) {
177 case 0x7: // Separated by BEL
178 *state = ANSI_LBL; //# \e]8;;LINK\aTEXT\e]8;;\a #
179 break;
180 case 0x1b: // OSC8-Link separated by ESC-BACKSLASH
181 *found += 2;
182 *state = ANSI_LBL; //# \e]8;;LINK\e\\TEXT\e]8;;\e\\ #
183 break;
184 default:
185 *found += chw;
186 }
187 return 0; // ignore link width
188 case ANSI_LBL:
189 if (*str == 0x1b) { // Link label goes until ESC BACKSLASH
190 *found += chw;
191 *state = ANSI_LSP;
192 }
193 return 0;
194 case ANSI_LSP:
195 *found += chw;
196 if (*str == '[') // SGR FG/BG colors nested inside OSC8-Link sequence
197 *state = ANSI_LSG;
198 else
199 *state = ANSI_END; //# Link label ends with \e[8;;\e\\ #
200 return 0;
201 case ANSI_LSG: //# \e]8;;LINK\e\\\e[1;34mTEXT\e[0m\e]8;;\e\\ #
202 *found += chw;
203 if (*str < '0' || *str > '?') // SGR color sequence ends with 'm'
204 *state = ANSI_LBL;
205 return 0;
206 case ANSI_END:
207 switch (*str) {
208 case 0x1b: // APC/OSC8-Links ends with ESC-BACKSLASH
209 *found += chw;
210 break;
211 case 0x7: // APC/OSC/OSC8-Links ends with BEL
212#ifdef HAVE_WIDECHAR
213 case 0x9c: // APC/DCS/DM ends with ST (String Terminator)
214#endif
215 break;
216 default:
217 *found += chw;
218 return 0;
219 }
220 }
221 size_t res = *found;
222 *state = ANSI_CHR;
223 *found = 0;
224 return res;
225}
226
227static size_t width(const wchar_t *str)
228{
229 size_t count = 0;
230 size_t found = 0;
231 ansi_esc_states state = ANSI_CHR;
232
233 for (; *str != '\0'; str++) {
234#ifdef HAVE_WIDECHAR
235 int x = wcwidth(*str); /* don't use wcswidth(), need to ignore non-printable */
236#else
237 int x = isprint(*str) ? 1 : 0;
238#endif
239 int chw = x > 0 ? x : 0;
240 size_t nonpr = ansi_esc_width(&state, &found, str, chw);
241 count += chw - nonpr;
242 }
243 return count;
244}
245
246static wchar_t *mbs_to_wcs(const char *s)
247{
248#ifdef HAVE_WIDECHAR
249 ssize_t n;
250 wchar_t *wcs;
251
252 n = mbstowcs((wchar_t *)0, s, 0);
253 if (n < 0)
254 return NULL;
255 wcs = xcalloc((n + 1) * sizeof(wchar_t), 1);
256 n = mbstowcs(wcs, s, n + 1);
257 if (n < 0) {
258 free(wcs);
259 return NULL;
260 }
261 return wcs;
262#else
263 return xstrdup(s);
264#endif
265}
266
267static char *wcs_to_mbs(const wchar_t *s)
268{
269#ifdef HAVE_WIDECHAR
270 size_t n;
271 char *str;
272
273 n = wcstombs(NULL, s, 0);
274 if (n == (size_t) -1)
275 return NULL;
276
277 str = xcalloc(n + 1, 1);
278 if (wcstombs(str, s, n) == (size_t) -1) {
279 free(str);
280 return NULL;
281 }
282 return str;
283#else
284 return xstrdup(s);
285#endif
286}
287
288static wchar_t *local_wcstok(struct column_control const *const ctl, wchar_t *p,
289 wchar_t **state)
290{
291 wchar_t *result = NULL;
292
293 if (ctl->greedy)
294#ifdef HAVE_WIDECHAR
295 return wcstok(p, ctl->input_separator, state);
296#else
297 return strtok_r(p, ctl->input_separator, state);
298#endif
299 if (!p) {
300 if (!*state)
301 return NULL;
302 p = *state;
303 }
304 result = p;
305#ifdef HAVE_WIDECHAR
306 p = wcspbrk(result, ctl->input_separator);
307#else
308 p = strpbrk(result, ctl->input_separator);
309#endif
310 if (!p)
311 *state = NULL;
312 else {
313 *p = '\0';
314 *state = p + 1;
315 }
316 return result;
317}
318
319static char **split_or_error(const char *str, const char *errmsg)
320{
321 char **res = ul_strv_split(str, ",");
322 if (!res) {
323 if (errno == ENOMEM)
324 err_oom();
325 if (errmsg)
326 errx(EXIT_FAILURE, "%s: '%s'", errmsg, str);
327 else
328 return NULL;
329 }
330 return res;
331}
332
333static void init_table(struct column_control *ctl)
334{
335 scols_init_debug(0);
336
337 ctl->tab = scols_new_table();
338 if (!ctl->tab)
339 err(EXIT_FAILURE, _("failed to allocate output table"));
340
341 scols_table_set_column_separator(ctl->tab, ctl->output_separator);
342 if (ctl->json) {
343 scols_table_enable_json(ctl->tab, 1);
344 scols_table_set_name(ctl->tab, ctl->tab_name ? : "table");
345 } else
346 scols_table_enable_noencoding(ctl->tab, 1);
347
348 scols_table_enable_maxout(ctl->tab, ctl->maxout ? 1 : 0);
349
350 if (ctl->tab_columns) {
351 char **opts;
352
353 UL_STRV_FOREACH(opts, ctl->tab_columns) {
354 struct libscols_column *cl;
355
356 cl = scols_table_new_column(ctl->tab, NULL, 0, 0);
357 scols_column_set_properties(cl, *opts);
358 }
359
360 } else if (ctl->tab_colnames) {
361 char **name;
362
363 UL_STRV_FOREACH(name, ctl->tab_colnames)
364 scols_table_new_column(ctl->tab, *name, 0, 0);
365 } else
366 scols_table_enable_noheadings(ctl->tab, 1);
367
368 if (ctl->tab_colnames || ctl->tab_columns) {
369 if (ctl->header_repeat)
370 scols_table_enable_header_repeat(ctl->tab, 1);
371 scols_table_enable_noheadings(ctl->tab, !!ctl->tab_noheadings);
372 }
373
374}
375
376static struct libscols_column *get_last_visible_column(struct column_control *ctl, int n)
377{
378 struct libscols_iter *itr;
379 struct libscols_column *cl, *res = NULL;
380
381 itr = scols_new_iter(SCOLS_ITER_BACKWARD);
382 if (!itr)
383 err_oom();
384
385 while (scols_table_next_column(ctl->tab, itr, &cl) == 0) {
386 if (scols_column_get_flags(cl) & SCOLS_FL_HIDDEN)
387 continue;
388 if (n == 0) {
389 res = cl;
390 break;
391 }
392 n--;
393 }
394
395 scols_free_iter(itr);
396 return res;
397}
398
399static struct libscols_column *string_to_column(struct column_control *ctl, const char *str)
400{
401 struct libscols_column *cl;
402
403 if (isdigit_string(str)) {
404 uint32_t n = strtou32_or_err(str, _("failed to parse column")) - 1;
405
406 cl = scols_table_get_column(ctl->tab, n);
407 } else if (strcmp(str, "-1") == 0)
408 cl = get_last_visible_column(ctl, 0);
409 else
410 cl = scols_table_get_column_by_name(ctl->tab, str);
411
412 if (!cl)
413 errx(EXIT_FAILURE, _("undefined column name '%s'"), str);
414
415 return cl;
416}
417
418static int column_set_flag(struct libscols_column *cl, int fl)
419{
420 int cur = scols_column_get_flags(cl);
421
422 return scols_column_set_flags(cl, cur | fl);
423}
424
425static int has_unnamed(const char *list)
426{
427 char **all, **one;
428 int rc = 0;
429
430 if (!list)
431 return 0;
432 if (strcmp(list, "-") == 0)
433 return 1;
434 if (!strchr(list, ','))
435 return 0;
436
437 all = split_or_error(list, NULL);
438 if (all) {
439 UL_STRV_FOREACH(one, all) {
440 if (strcmp(*one, "-") == 0) {
441 rc = 1;
442 break;
443 }
444 }
445 ul_strv_free(all);
446 }
447
448 return rc;
449}
450
451static void apply_columnflag_from_list(struct column_control *ctl, const char *list,
452 int flag, const char *errmsg)
453{
454 char **all;
455 char **one;
456 int unnamed = 0;
457 struct libscols_column *cl;
458
459 /* apply to all */
460 if (list && strcmp(list, "0") == 0) {
461 struct libscols_iter *itr;
462
463 itr = scols_new_iter(SCOLS_ITER_FORWARD);
464 if (!itr)
465 err_oom();
466
467 while (scols_table_next_column(ctl->tab, itr, &cl) == 0)
468 column_set_flag(cl, flag);
469 scols_free_iter(itr);
470 return;
471 }
472
473 all = split_or_error(list, errmsg);
474
475 /* apply to columns specified by name */
476 UL_STRV_FOREACH(one, all) {
477 int low = 0, up = 0;
478
479 if (strcmp(*one, "-") == 0) {
480 unnamed = 1;
481 continue;
482 }
483
484 /* parse range (N-M) */
485 if (strchr(*one, '-') && ul_parse_range(*one, &low, &up, 0) == 0) {
486 for (; low <= up; low++) {
487 if (low < 0)
488 cl = get_last_visible_column(ctl, (low * -1) -1);
489 else
490 cl = scols_table_get_column(ctl->tab, low-1);
491 if (cl)
492 column_set_flag(cl, flag);
493 }
494 continue;
495 }
496
497 /* one item in the list */
498 cl = string_to_column(ctl, *one);
499 if (cl)
500 column_set_flag(cl, flag);
501 }
502 ul_strv_free(all);
503
504 /* apply flag to all columns without name */
505 if (unnamed) {
506 struct libscols_iter *itr;
507
508 itr = scols_new_iter(SCOLS_ITER_FORWARD);
509 if (!itr)
510 err_oom();
511
512 while (scols_table_next_column(ctl->tab, itr, &cl) == 0) {
513 if (!scols_column_get_name(cl))
514 column_set_flag(cl, flag);
515 }
516 scols_free_iter(itr);
517 }
518}
519
520static void reorder_table(struct column_control *ctl)
521{
522 struct libscols_column **wanted, *last = NULL;
523 size_t i, count = 0;
524 size_t ncols = scols_table_get_ncols(ctl->tab);
525 char **order = split_or_error(ctl->tab_order, _("failed to parse --table-order list"));
526 char **one;
527
528 wanted = xcalloc(ncols, sizeof(struct libscols_column *));
529
530 UL_STRV_FOREACH(one, order) {
531 struct libscols_column *cl = string_to_column(ctl, *one);
532 if (cl)
533 wanted[count++] = cl;
534 }
535
536 for (i = 0; i < count; i++) {
537 scols_table_move_column(ctl->tab, last, wanted[i]);
538 last = wanted[i];
539 }
540
541 free(wanted);
542 ul_strv_free(order);
543}
544
545static void create_tree(struct column_control *ctl)
546{
547 struct libscols_column *cl_tree = string_to_column(ctl, ctl->tree);
548 struct libscols_column *cl_p = string_to_column(ctl, ctl->tree_parent);
549 struct libscols_column *cl_i = string_to_column(ctl, ctl->tree_id);
550 struct libscols_iter *itr_p, *itr_i;
551 struct libscols_line *ln_i;
552
553 if (!cl_p || !cl_i || !cl_tree)
554 return; /* silently ignore the tree request */
555
556 column_set_flag(cl_tree, SCOLS_FL_TREE);
557
558 itr_p = scols_new_iter(SCOLS_ITER_FORWARD);
559 itr_i = scols_new_iter(SCOLS_ITER_FORWARD);
560 if (!itr_p || !itr_i)
561 err_oom();
562
563 /* scan all lines for ID */
564 while (scols_table_next_line(ctl->tab, itr_i, &ln_i) == 0) {
565 struct libscols_line *ln;
566 struct libscols_cell *ce = scols_line_get_column_cell(ln_i, cl_i);
567 const char *id = ce ? scols_cell_get_data(ce) : NULL;
568
569 if (!id)
570 continue;
571
572 /* see if the ID is somewhere used in parent column */
573 scols_reset_iter(itr_p, SCOLS_ITER_FORWARD);
574 while (scols_table_next_line(ctl->tab, itr_p, &ln) == 0) {
575 const char *parent;
576
577 ce = scols_line_get_column_cell(ln, cl_p);
578 parent = ce ? scols_cell_get_data(ce) : NULL;
579
580 if (!parent)
581 continue;
582 if (strcmp(id, parent) != 0)
583 continue;
584 if (scols_line_is_ancestor(ln, ln_i))
585 continue;
586 scols_line_add_child(ln_i, ln);
587 }
588 }
589
590 scols_free_iter(itr_p);
591 scols_free_iter(itr_i);
592}
593
594static void modify_table(struct column_control *ctl)
595{
596 if (ctl->termwidth > 0) {
597 scols_table_set_termwidth(ctl->tab, ctl->termwidth);
598 scols_table_set_termforce(ctl->tab, SCOLS_TERMFORCE_ALWAYS);
599 }
600
601 if (ctl->tab_colhide)
602 apply_columnflag_from_list(ctl, ctl->tab_colhide,
603 SCOLS_FL_HIDDEN , _("failed to parse --table-hide list"));
604
605 if (ctl->tab_colright)
606 apply_columnflag_from_list(ctl, ctl->tab_colright,
607 SCOLS_FL_RIGHT, _("failed to parse --table-right list"));
608
609 if (ctl->tab_coltrunc)
610 apply_columnflag_from_list(ctl, ctl->tab_coltrunc,
611 SCOLS_FL_TRUNC , _("failed to parse --table-trunc list"));
612
613 if (ctl->tab_colnoextrem)
614 apply_columnflag_from_list(ctl, ctl->tab_colnoextrem,
615 SCOLS_FL_NOEXTREMES , _("failed to parse --table-noextreme list"));
616
617 if (ctl->tab_colwrap)
618 apply_columnflag_from_list(ctl, ctl->tab_colwrap,
619 SCOLS_FL_WRAP , _("failed to parse --table-wrap list"));
620
621 if (!ctl->tab_colnoextrem) {
622 struct libscols_column *cl = get_last_visible_column(ctl, 0);
623 if (cl)
624 column_set_flag(cl, SCOLS_FL_NOEXTREMES);
625 }
626
627 if (ctl->tree)
628 create_tree(ctl);
629
630 /* This must be the last step! */
631 if (ctl->tab_order)
632 reorder_table(ctl);
633}
634
635
636static int add_line_to_table(struct column_control *ctl, wchar_t *wcs0)
637{
638 wchar_t *sv = NULL, *wcs = wcs0, *all = NULL;
639 size_t n = 0;
640 struct libscols_line *ln = NULL;
641
642
643 if (!ctl->tab)
644 init_table(ctl);
645
646 if (ctl->maxncols) {
647 all = wcsdup(wcs0);
648 if (!all)
649 err(EXIT_FAILURE, _("failed to allocate input line"));
650 }
651
652 do {
653 char *data;
654 wchar_t *wcdata = local_wcstok(ctl, wcs, &sv);
655
656 if (!wcdata)
657 break;
658
659 if (ctl->maxncols && n + 1 == ctl->maxncols) {
660 /* Use rest of the string as column data */
661 size_t skip = wcdata - wcs0;
662 wcdata = all + skip;
663 }
664
665 if (scols_table_get_ncols(ctl->tab) < n + 1) {
666 if (scols_table_is_json(ctl->tab) && !ctl->hide_unnamed)
667 errx(EXIT_FAILURE, _("line %zu: for JSON the name of the "
668 "column %zu is required"),
669 scols_table_get_nlines(ctl->tab) + 1,
670 n + 1);
671 scols_table_new_column(ctl->tab, NULL, 0,
672 ctl->hide_unnamed ? SCOLS_FL_HIDDEN : 0);
673 }
674 if (!ln) {
675 ln = scols_table_new_line(ctl->tab, NULL);
676 if (!ln)
677 err(EXIT_FAILURE, _("failed to allocate output line"));
678 }
679
680 data = wcs_to_mbs(wcdata);
681 if (!data)
682 err(EXIT_FAILURE, _("failed to allocate output data"));
683 if (scols_line_refer_data(ln, n, data))
684 err(EXIT_FAILURE, _("failed to add output data"));
685 n++;
686 wcs = NULL;
687 if (ctl->maxncols && n == ctl->maxncols)
688 break;
689 } while (1);
690
691 free(all);
692 return 0;
693}
694
695static int add_emptyline_to_table(struct column_control *ctl)
696{
697 if (!ctl->tab)
698 init_table(ctl);
699
700 if (!scols_table_new_line(ctl->tab, NULL))
701 err(EXIT_FAILURE, _("failed to allocate output line"));
702
703 return 0;
704}
705
706static void add_entry(struct column_control *ctl, size_t *maxents, wchar_t *wcs)
707{
708 if (ctl->nents <= *maxents) {
709 *maxents += 1000;
710 ctl->ents = xreallocarray(ctl->ents, *maxents, sizeof(wchar_t *));
711 }
712 ctl->ents[ctl->nents] = wcs;
713 ctl->nents++;
714}
715
716static int read_input(struct column_control *ctl, FILE *fp)
717{
718 wchar_t *empty = NULL;
719 char *buf = NULL;
720 size_t bufsz = 0;
721 size_t maxents = 0;
722 int rc = 0;
723
724 /* Read input */
725 do {
726 char *str, *p;
727 wchar_t *wcs = NULL;
728 size_t len;
729
730 if (getline(&buf, &bufsz, fp) < 0) {
731 if (feof(fp))
732 break;
733 err(EXIT_FAILURE, _("read failed"));
734 }
735 str = (char *) skip_space(buf);
736 if (str) {
737 p = strchr(str, '\n');
738 if (p)
739 *p = '\0';
740 }
741 if (!str || !*str) {
742 if (ctl->keep_empty_lines) {
743 if (ctl->mode == COLUMN_MODE_TABLE) {
744 add_emptyline_to_table(ctl);
745 } else {
746 if (!empty)
747 empty = mbs_to_wcs("");
748 add_entry(ctl, &maxents, empty);
749 }
750 }
751 continue;
752 }
753
754 wcs = mbs_to_wcs(buf);
755 if (!wcs) {
756 /*
757 * Convert broken sequences to \x<hex> and continue.
758 */
759 size_t tmpsz = 0;
760 char *tmp = mbs_invalid_encode(buf, &tmpsz);
761
762 if (!tmp)
763 err(EXIT_FAILURE, _("read failed"));
764 wcs = mbs_to_wcs(tmp);
765 free(tmp);
766 }
767
768 switch (ctl->mode) {
769 case COLUMN_MODE_TABLE:
770 rc = add_line_to_table(ctl, wcs);
771 free(wcs);
772 break;
773
774 case COLUMN_MODE_FILLCOLS:
775 case COLUMN_MODE_FILLROWS:
776 add_entry(ctl, &maxents, wcs);
777 len = width(wcs);
778 if (ctl->maxlength < len)
779 ctl->maxlength = len;
780 break;
781 default:
782 free(wcs);
783 break;
784 }
785 } while (rc == 0);
786
787 free(buf);
788
789 return rc;
790}
791
792
793static void columnate_fillrows(struct column_control *ctl)
794{
795 size_t chcnt, col, cnt, endcol, numcols, remains;
796 wchar_t **lp;
797
798 if (ctl->use_spaces)
799 ctl->maxlength += ctl->mincolsep;
800 else
801 ctl->maxlength = (ctl->maxlength + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1);
802 numcols = ctl->termwidth / ctl->maxlength;
803 remains = ctl->termwidth % ctl->maxlength;
804 if (ctl->use_spaces && remains + ctl->mincolsep >= ctl->maxlength)
805 numcols++;
806 endcol = ctl->maxlength;
807 for (chcnt = col = 0, lp = ctl->ents; /* nothing */; ++lp) {
808 fputws(*lp, stdout);
809 chcnt += width(*lp);
810 if (!--ctl->nents)
811 break;
812 if (++col == numcols) {
813 chcnt = col = 0;
814 endcol = ctl->maxlength;
815 putwchar('\n');
816 } else {
817 if (ctl->use_spaces) {
818 while (chcnt < endcol) {
819 putwchar(' ');
820 chcnt++;
821 }
822 } else {
823 while ((cnt = ((chcnt + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1))) <= endcol) {
824 putwchar('\t');
825 chcnt = cnt;
826 }
827 }
828 endcol += ctl->maxlength;
829 }
830 }
831 if (chcnt)
832 putwchar('\n');
833}
834
835static void columnate_fillcols(struct column_control *ctl)
836{
837 size_t base, chcnt, cnt, col, endcol, numcols, numrows, row, remains;
838
839 if (ctl->use_spaces)
840 ctl->maxlength += ctl->mincolsep;
841 else
842 ctl->maxlength = (ctl->maxlength + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1);
843 numcols = ctl->termwidth / ctl->maxlength;
844 remains = ctl->termwidth % ctl->maxlength;
845 if (!numcols)
846 numcols = 1;
847 if (ctl->use_spaces && remains + ctl->mincolsep >= ctl->maxlength)
848 numcols++;
849 numrows = ctl->nents / numcols;
850 if (ctl->nents % numcols)
851 ++numrows;
852
853 for (row = 0; row < numrows; ++row) {
854 endcol = ctl->maxlength;
855 for (base = row, chcnt = col = 0; col < numcols; ++col) {
856 fputws(ctl->ents[base], stdout);
857 chcnt += width(ctl->ents[base]);
858 if ((base += numrows) >= ctl->nents)
859 break;
860 if (ctl->use_spaces) {
861 while (chcnt < endcol) {
862 putwchar(' ');
863 chcnt++;
864 }
865 } else {
866 while ((cnt = ((chcnt + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1))) <= endcol) {
867 putwchar('\t');
868 chcnt = cnt;
869 }
870 }
871 endcol += ctl->maxlength;
872 }
873 putwchar('\n');
874 }
875}
876
877static void simple_print(struct column_control *ctl)
878{
879 int cnt;
880 wchar_t **lp;
881
882 for (cnt = ctl->nents, lp = ctl->ents; cnt--; ++lp) {
883 fputws(*lp, stdout);
884 putwchar('\n');
885 }
886}
887
888static void __attribute__((__noreturn__)) usage(void)
889{
890 FILE *out = stdout;
891
892 fputs(USAGE_HEADER, out);
893 fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
894
895 fputs(USAGE_SEPARATOR, out);
896 fputs(_("Columnate lists.\n"), out);
897
898 fputs(USAGE_OPTIONS, out);
899 fputs(_(" -t, --table create a table\n"), out);
900 fputs(_(" -n, --table-name <name> table name for JSON output\n"), out);
901 fputs(_(" -O, --table-order <columns> specify order of output columns\n"), out);
902 fputs(_(" -C, --table-column <properties> define column\n"), out);
903 fputs(_(" -N, --table-columns <names> comma separated columns names\n"), out);
904 fputs(_(" -l, --table-columns-limit <num> maximal number of input columns\n"), out);
905 fputs(_(" -E, --table-noextreme <columns> don't count long text from the columns to column width\n"), out);
906 fputs(_(" -d, --table-noheadings don't print header\n"), out);
907 fputs(_(" -m, --table-maxout fill all available space\n"), out);
908 fputs(_(" -e, --table-header-repeat repeat header for each page\n"), out);
909 fputs(_(" -H, --table-hide <columns> don't print the columns\n"), out);
910 fputs(_(" -R, --table-right <columns> right align text in these columns\n"), out);
911 fputs(_(" -T, --table-truncate <columns> truncate text in the columns when necessary\n"), out);
912 fputs(_(" -W, --table-wrap <columns> wrap text in the columns when necessary\n"), out);
913 fputs(_(" -L, --keep-empty-lines don't ignore empty lines\n"), out);
914 fputs(_(" -J, --json use JSON output format for table\n"), out);
915
916 fputs(USAGE_SEPARATOR, out);
917 fputs(_(" -r, --tree <column> column to use tree-like output for the table\n"), out);
918 fputs(_(" -i, --tree-id <column> line ID to specify child-parent relation\n"), out);
919 fputs(_(" -p, --tree-parent <column> parent to specify child-parent relation\n"), out);
920
921 fputs(USAGE_SEPARATOR, out);
922 fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out);
923 fputs(_(" -o, --output-separator <string> columns separator for table output (default is two spaces)\n"), out);
924 fputs(_(" -s, --separator <string> possible table delimiters\n"), out);
925 fputs(_(" -x, --fillrows fill rows before columns\n"), out);
926 fputs(_(" -S, --use-spaces <number> minimal whitespaces between columns (no tabs)\n"), out);
927
928
929 fputs(USAGE_SEPARATOR, out);
930 fprintf(out, USAGE_HELP_OPTIONS(34));
931 fprintf(out, USAGE_MAN_TAIL("column(1)"));
932
933 exit(EXIT_SUCCESS);
934}
935
936int main(int argc, char **argv)
937{
938 struct column_control ctl = {
939 .mode = COLUMN_MODE_FILLCOLS,
940 .greedy = 1,
941 .termwidth = (size_t) -1
942 };
943
944 int c;
945 unsigned int eval = 0; /* exit value */
946
947 static const struct option longopts[] =
948 {
949 { "columns", required_argument, NULL, 'c' }, /* deprecated */
950 { "fillrows", no_argument, NULL, 'x' },
951 { "help", no_argument, NULL, 'h' },
952 { "json", no_argument, NULL, 'J' },
953 { "keep-empty-lines", no_argument, NULL, 'L' },
954 { "output-separator", required_argument, NULL, 'o' },
955 { "output-width", required_argument, NULL, 'c' },
956 { "separator", required_argument, NULL, 's' },
957 { "table", no_argument, NULL, 't' },
958 { "table-columns", required_argument, NULL, 'N' },
959 { "table-column", required_argument, NULL, 'C' },
960 { "table-columns-limit", required_argument, NULL, 'l' },
961 { "table-hide", required_argument, NULL, 'H' },
962 { "table-name", required_argument, NULL, 'n' },
963 { "table-maxout", no_argument, NULL, 'm' },
964 { "table-noextreme", required_argument, NULL, 'E' },
965 { "table-noheadings", no_argument, NULL, 'd' },
966 { "table-order", required_argument, NULL, 'O' },
967 { "table-right", required_argument, NULL, 'R' },
968 { "table-truncate", required_argument, NULL, 'T' },
969 { "table-wrap", required_argument, NULL, 'W' },
970 { "table-empty-lines", no_argument, NULL, 'L' }, /* deprecated */
971 { "table-header-repeat", no_argument, NULL, 'e' },
972 { "tree", required_argument, NULL, 'r' },
973 { "tree-id", required_argument, NULL, 'i' },
974 { "tree-parent", required_argument, NULL, 'p' },
975 { "use-spaces", required_argument, NULL, 'S' },
976 { "version", no_argument, NULL, 'V' },
977 { NULL, 0, NULL, 0 },
978 };
979 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
980 { 'C','N' },
981 { 'J','x' },
982 { 't','x' },
983 { 0 }
984 };
985 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
986
987 setlocale(LC_ALL, "");
988 bindtextdomain(PACKAGE, LOCALEDIR);
989 textdomain(PACKAGE);
990 close_stdout_atexit();
991
992 ctl.output_separator = " ";
993 ctl.input_separator = mbs_to_wcs("\t ");
994
995 while ((c = getopt_long(argc, argv, "C:c:dE:eH:hi:Jl:LN:n:mO:o:p:R:r:S:s:T:tVW:x", longopts, NULL)) != -1) {
996
997 err_exclusive_options(c, longopts, excl, excl_st);
998
999 switch(c) {
1000 case 'C':
1001 if (ul_strv_extend(&ctl.tab_columns, optarg))
1002 err_oom();
1003 break;
1004 case 'c':
1005 if (strcmp(optarg, "unlimited") == 0)
1006 ctl.termwidth = 0;
1007 else
1008 ctl.termwidth = strtou32_or_err(optarg, _("invalid columns argument"));
1009 break;
1010 case 'd':
1011 ctl.tab_noheadings = 1;
1012 break;
1013 case 'E':
1014 ctl.tab_colnoextrem = optarg;
1015 break;
1016 case 'e':
1017 ctl.header_repeat = 1;
1018 break;
1019 case 'H':
1020 ctl.tab_colhide = optarg;
1021 ctl.hide_unnamed = has_unnamed(ctl.tab_colhide);
1022 break;
1023 case 'i':
1024 ctl.tree_id = optarg;
1025 break;
1026 case 'J':
1027 ctl.json = 1;
1028 ctl.mode = COLUMN_MODE_TABLE;
1029 break;
1030 case 'L':
1031 ctl.keep_empty_lines = 1;
1032 break;
1033 case 'l':
1034 ctl.maxncols = strtou32_or_err(optarg, _("invalid columns limit argument"));
1035 if (ctl.maxncols == 0)
1036 errx(EXIT_FAILURE, _("columns limit must be greater than zero"));
1037 break;
1038 case 'N':
1039 ctl.tab_colnames = split_or_error(optarg, _("failed to parse column names"));
1040 break;
1041 case 'n':
1042 ctl.tab_name = optarg;
1043 break;
1044 case 'm':
1045 ctl.maxout = 1;
1046 break;
1047 case 'O':
1048 ctl.tab_order = optarg;
1049 break;
1050 case 'o':
1051 ctl.output_separator = optarg;
1052 break;
1053 case 'p':
1054 ctl.tree_parent = optarg;
1055 break;
1056 case 'R':
1057 ctl.tab_colright = optarg;
1058 break;
1059 case 'r':
1060 ctl.tree = optarg;
1061 break;
1062 case 'S':
1063 ctl.use_spaces = 1;
1064 ctl.mincolsep = strtou32_or_err(optarg, _("invalid spaces argument"));
1065 break;
1066 case 's':
1067 free(ctl.input_separator);
1068 ctl.input_separator = mbs_to_wcs(optarg);
1069 if (!ctl.input_separator)
1070 err(EXIT_FAILURE, _("failed to parse input separator"));
1071 ctl.greedy = 0;
1072 break;
1073 case 'T':
1074 ctl.tab_coltrunc = optarg;
1075 break;
1076 case 't':
1077 ctl.mode = COLUMN_MODE_TABLE;
1078 break;
1079 case 'W':
1080 ctl.tab_colwrap = optarg;
1081 break;
1082 case 'x':
1083 ctl.mode = COLUMN_MODE_FILLROWS;
1084 break;
1085
1086 case 'h':
1087 usage();
1088 case 'V':
1089 print_version(EXIT_SUCCESS);
1090 default:
1091 errtryhelp(EXIT_FAILURE);
1092 }
1093 }
1094 argc -= optind;
1095 argv += optind;
1096
1097 if (ctl.termwidth == (size_t) -1)
1098 ctl.termwidth = get_terminal_width(80);
1099
1100 if (ctl.tree) {
1101 ctl.mode = COLUMN_MODE_TABLE;
1102 if (!ctl.tree_parent || !ctl.tree_id)
1103 errx(EXIT_FAILURE, _("options --tree-id and --tree-parent are "
1104 "required for tree formatting"));
1105 }
1106
1107 if (ctl.mode != COLUMN_MODE_TABLE
1108 && (ctl.tab_order || ctl.tab_name || ctl.tab_colwrap ||
1109 ctl.tab_colhide || ctl.tab_coltrunc || ctl.tab_colnoextrem ||
1110 ctl.tab_colright || ctl.tab_colnames || ctl.tab_columns))
1111 errx(EXIT_FAILURE, _("option --table required for all --table-*"));
1112
1113 if (!ctl.tab_colnames && !ctl.tab_columns && ctl.json)
1114 errx(EXIT_FAILURE, _("option --table-columns or --table-column required for --json"));
1115
1116 if (!*argv)
1117 eval += read_input(&ctl, stdin);
1118 else
1119 for (; *argv; ++argv) {
1120 FILE *fp;
1121
1122 if ((fp = fopen(*argv, "r")) != NULL) {
1123 eval += read_input(&ctl, fp);
1124 fclose(fp);
1125 } else {
1126 warn("%s", *argv);
1127 eval += EXIT_FAILURE;
1128 }
1129 }
1130
1131 if (ctl.mode != COLUMN_MODE_TABLE) {
1132 if (!ctl.nents)
1133 exit(eval);
1134 if (ctl.maxlength >= ctl.termwidth)
1135 ctl.mode = COLUMN_MODE_SIMPLE;
1136 }
1137
1138 switch (ctl.mode) {
1139 case COLUMN_MODE_TABLE:
1140 if (ctl.tab && scols_table_get_nlines(ctl.tab)) {
1141 modify_table(&ctl);
1142 eval = scols_print_table(ctl.tab);
1143
1144 scols_unref_table(ctl.tab);
1145 if (ctl.tab_colnames)
1146 ul_strv_free(ctl.tab_colnames);
1147 if (ctl.tab_columns)
1148 ul_strv_free(ctl.tab_columns);
1149 }
1150 break;
1151 case COLUMN_MODE_FILLCOLS:
1152 columnate_fillcols(&ctl);
1153 break;
1154 case COLUMN_MODE_FILLROWS:
1155 columnate_fillrows(&ctl);
1156 break;
1157 case COLUMN_MODE_SIMPLE:
1158 simple_print(&ctl);
1159 break;
1160 }
1161
1162 free(ctl.input_separator);
1163
1164 return eval == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
1165}