]>
Commit | Line | Data |
---|---|---|
6dbe3af9 KZ |
1 | /* |
2 | * Copyright (c) 1989, 1993, 1994 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
7d07df62 KZ |
5 | * Copyright (C) 2017 Karel Zak <kzak@redhat.com> |
6 | * | |
6dbe3af9 KZ |
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 | */ | |
6dbe3af9 KZ |
35 | #include <sys/types.h> |
36 | #include <sys/ioctl.h> | |
37 | ||
38 | #include <ctype.h> | |
6dbe3af9 | 39 | #include <stdio.h> |
fd6b7a7f | 40 | #include <unistd.h> |
6dbe3af9 KZ |
41 | #include <stdlib.h> |
42 | #include <string.h> | |
a4cc8dfe SK |
43 | #include <errno.h> |
44 | #include <getopt.h> | |
6dbe3af9 | 45 | |
3b56eea7 | 46 | #include "nls.h" |
eb76ca98 | 47 | #include "c.h" |
9ea8ded3 | 48 | #include "widechar.h" |
a29e40ca | 49 | #include "xalloc.h" |
02b77f7b | 50 | #include "strutils.h" |
b87cbe84 | 51 | #include "closestream.h" |
3b56eea7 | 52 | #include "ttyutils.h" |
11a1092a | 53 | #include "strv.h" |
be03f652 | 54 | #include "optutils.h" |
dda229c7 | 55 | #include "mbsalign.h" |
eb63b9b8 | 56 | |
4762ae9d | 57 | #include "libsmartcols.h" |
eb63b9b8 | 58 | |
739e58ff | 59 | #define TABCHAR_CELLS 8 |
683ddbd5 | 60 | |
7d07df62 KZ |
61 | enum { |
62 | COLUMN_MODE_FILLCOLS = 0, | |
63 | COLUMN_MODE_FILLROWS, | |
64 | COLUMN_MODE_TABLE, | |
65 | COLUMN_MODE_SIMPLE | |
66 | }; | |
67 | ||
68 | struct column_control { | |
2593c139 | 69 | int mode; /* COLUMN_MODE_* */ |
0784187d | 70 | size_t termwidth; |
2593c139 | 71 | |
4762ae9d KZ |
72 | struct libscols_table *tab; |
73 | ||
7e947f48 KZ |
74 | char **tab_colnames; /* array with column names */ |
75 | const char *tab_name; /* table name */ | |
76 | const char *tab_order; /* --table-order */ | |
77 | ||
1ae24ec2 KZ |
78 | const char *tab_colright; /* --table-right */ |
79 | const char *tab_coltrunc; /* --table-trunc */ | |
80 | const char *tab_colnoextrem; /* --table-noextreme */ | |
68916af3 | 81 | const char *tab_colwrap; /* --table-wrap */ |
9624f615 | 82 | const char *tab_colhide; /* --table-hide */ |
11a1092a | 83 | |
90852c3e KZ |
84 | const char *tree; |
85 | const char *tree_id; | |
86 | const char *tree_parent; | |
87 | ||
4762ae9d KZ |
88 | wchar_t *input_separator; |
89 | const char *output_separator; | |
90 | ||
0784187d KZ |
91 | wchar_t **ents; /* input entries */ |
92 | size_t nents; /* number of entries */ | |
93 | size_t maxlength; /* longest input record (line) */ | |
7d07df62 | 94 | |
9dbe8e1c | 95 | unsigned int greedy :1, |
d9eddf72 | 96 | json :1, |
785e5436 | 97 | header_repeat :1, |
2698f9ba | 98 | tab_empty_lines :1, /* --table-empty-lines */ |
785e5436 | 99 | tab_noheadings :1; |
4762ae9d | 100 | }; |
7d07df62 | 101 | |
8f1be588 | 102 | static size_t width(const wchar_t *str) |
683ddbd5 | 103 | { |
8f1be588 | 104 | size_t width = 0; |
683ddbd5 KZ |
105 | |
106 | for (; *str != '\0'; str++) { | |
8f1be588 KZ |
107 | #ifdef HAVE_WIDECHAR |
108 | int x = wcwidth(*str); /* don't use wcswidth(), need to ignore non-printable */ | |
683ddbd5 KZ |
109 | if (x > 0) |
110 | width += x; | |
683ddbd5 | 111 | #else |
683ddbd5 KZ |
112 | if (isprint(*str)) |
113 | width++; | |
8f1be588 | 114 | #endif |
683ddbd5 KZ |
115 | } |
116 | return width; | |
117 | } | |
683ddbd5 | 118 | |
37d84d6d KZ |
119 | static wchar_t *mbs_to_wcs(const char *s) |
120 | { | |
8f1be588 | 121 | #ifdef HAVE_WIDECHAR |
37d84d6d KZ |
122 | ssize_t n; |
123 | wchar_t *wcs; | |
124 | ||
125 | n = mbstowcs((wchar_t *)0, s, 0); | |
126 | if (n < 0) | |
127 | return NULL; | |
86399c33 | 128 | wcs = xcalloc((n + 1) * sizeof(wchar_t), 1); |
37d84d6d KZ |
129 | n = mbstowcs(wcs, s, n + 1); |
130 | if (n < 0) { | |
131 | free(wcs); | |
132 | return NULL; | |
133 | } | |
134 | return wcs; | |
8f1be588 | 135 | #else |
8bc5195b | 136 | return xstrdup(s); |
37d84d6d | 137 | #endif |
8f1be588 | 138 | } |
37d84d6d | 139 | |
4762ae9d KZ |
140 | static char *wcs_to_mbs(const wchar_t *s) |
141 | { | |
142 | #ifdef HAVE_WIDECHAR | |
143 | size_t n; | |
144 | char *str; | |
145 | ||
146 | n = wcstombs(NULL, s, 0); | |
147 | if (n == (size_t) -1) | |
148 | return NULL; | |
149 | ||
86399c33 | 150 | str = xcalloc(n + 1, 1); |
4762ae9d KZ |
151 | if (wcstombs(str, s, n) == (size_t) -1) { |
152 | free(str); | |
153 | return NULL; | |
154 | } | |
155 | return str; | |
156 | #else | |
8bc5195b | 157 | return xstrdup(s); |
4762ae9d KZ |
158 | #endif |
159 | } | |
160 | ||
073abd4c SK |
161 | static wchar_t *local_wcstok(struct column_control const *const ctl, wchar_t *p, |
162 | wchar_t **state) | |
4762ae9d KZ |
163 | { |
164 | wchar_t *result = NULL; | |
165 | ||
073abd4c | 166 | if (ctl->greedy) |
ccedd0d7 | 167 | #ifdef HAVE_WIDECHAR |
073abd4c | 168 | return wcstok(p, ctl->input_separator, state); |
ccedd0d7 | 169 | #else |
073abd4c | 170 | return strtok_r(p, ctl->input_separator, state); |
ccedd0d7 | 171 | #endif |
4762ae9d | 172 | if (!p) { |
325bfd53 | 173 | if (!*state) |
4762ae9d KZ |
174 | return NULL; |
175 | p = *state; | |
176 | } | |
177 | result = p; | |
ccedd0d7 | 178 | #ifdef HAVE_WIDECHAR |
073abd4c | 179 | p = wcspbrk(result, ctl->input_separator); |
ccedd0d7 | 180 | #else |
073abd4c | 181 | p = strpbrk(result, ctl->input_separator); |
ccedd0d7 | 182 | #endif |
4762ae9d KZ |
183 | if (!p) |
184 | *state = NULL; | |
185 | else { | |
186 | *p = '\0'; | |
187 | *state = p + 1; | |
188 | } | |
189 | return result; | |
190 | } | |
191 | ||
dbed2f8c | 192 | static char **split_or_error(const char *str, const char *errmsg) |
11a1092a | 193 | { |
dbed2f8c KZ |
194 | char **res = strv_split(str, ","); |
195 | if (!res) { | |
11a1092a KZ |
196 | if (errno == ENOMEM) |
197 | err_oom(); | |
dbed2f8c | 198 | errx(EXIT_FAILURE, "%s: '%s'", errmsg, str); |
11a1092a | 199 | } |
dbed2f8c | 200 | return res; |
11a1092a KZ |
201 | } |
202 | ||
4762ae9d KZ |
203 | static void init_table(struct column_control *ctl) |
204 | { | |
205 | scols_init_debug(0); | |
206 | ||
207 | ctl->tab = scols_new_table(); | |
208 | if (!ctl->tab) | |
209 | err(EXIT_FAILURE, _("failed to allocate output table")); | |
210 | ||
211 | scols_table_set_column_separator(ctl->tab, ctl->output_separator); | |
9dbe8e1c KZ |
212 | if (ctl->json) { |
213 | scols_table_enable_json(ctl->tab, 1); | |
214 | scols_table_set_name(ctl->tab, ctl->tab_name ? : "table"); | |
00e8e677 KZ |
215 | } else |
216 | scols_table_enable_noencoding(ctl->tab, 1); | |
217 | ||
11a1092a KZ |
218 | if (ctl->tab_colnames) { |
219 | char **name; | |
220 | ||
221 | STRV_FOREACH(name, ctl->tab_colnames) | |
222 | scols_table_new_column(ctl->tab, *name, 0, 0); | |
d9eddf72 KZ |
223 | if (ctl->header_repeat) |
224 | scols_table_enable_header_repeat(ctl->tab, 1); | |
785e5436 | 225 | scols_table_enable_noheadings(ctl->tab, !!ctl->tab_noheadings); |
11a1092a KZ |
226 | } else |
227 | scols_table_enable_noheadings(ctl->tab, 1); | |
4762ae9d KZ |
228 | } |
229 | ||
dbed2f8c KZ |
230 | static struct libscols_column *string_to_column(struct column_control *ctl, const char *str) |
231 | { | |
232 | uint32_t colnum = 0; | |
233 | ||
234 | if (isdigit_string(str)) | |
235 | colnum = strtou32_or_err(str, _("failed to parse column")) - 1; | |
236 | else { | |
237 | char **name; | |
238 | ||
239 | STRV_FOREACH(name, ctl->tab_colnames) { | |
240 | if (strcasecmp(*name, str) == 0) | |
241 | break; | |
242 | colnum++; | |
243 | } | |
244 | if (!name || !*name) | |
245 | errx(EXIT_FAILURE, _("undefined column name '%s'"), str); | |
246 | } | |
247 | ||
248 | return scols_table_get_column(ctl->tab, colnum); | |
249 | } | |
250 | ||
c728e000 KZ |
251 | static struct libscols_column *get_last_visible_column(struct column_control *ctl) |
252 | { | |
253 | struct libscols_iter *itr; | |
254 | struct libscols_column *cl, *last = NULL; | |
255 | ||
256 | itr = scols_new_iter(SCOLS_ITER_FORWARD); | |
257 | if (!itr) | |
258 | err_oom(); | |
259 | ||
260 | while (scols_table_next_column(ctl->tab, itr, &cl) == 0) { | |
261 | if (scols_column_get_flags(cl) & SCOLS_FL_HIDDEN) | |
262 | continue; | |
263 | last = cl; | |
264 | } | |
265 | ||
266 | scols_free_iter(itr); | |
267 | return last; | |
268 | } | |
3ba01db0 | 269 | |
dbed2f8c KZ |
270 | static int column_set_flag(struct libscols_column *cl, int fl) |
271 | { | |
272 | int cur = scols_column_get_flags(cl); | |
273 | ||
274 | return scols_column_set_flags(cl, cur | fl); | |
275 | } | |
276 | ||
3ba01db0 KZ |
277 | static void apply_columnflag_from_list(struct column_control *ctl, const char *list, |
278 | int flag, const char *errmsg) | |
279 | { | |
280 | char **all = split_or_error(list, errmsg); | |
281 | char **one; | |
b5de9e69 | 282 | int unnamed = 0; |
3ba01db0 KZ |
283 | |
284 | STRV_FOREACH(one, all) { | |
b5de9e69 KZ |
285 | struct libscols_column *cl; |
286 | ||
287 | if (flag == SCOLS_FL_HIDDEN && strcmp(*one, "-") == 0) { | |
288 | unnamed = 1; | |
289 | continue; | |
290 | } | |
291 | cl = string_to_column(ctl, *one); | |
3ba01db0 KZ |
292 | if (cl) |
293 | column_set_flag(cl, flag); | |
294 | } | |
295 | strv_free(all); | |
b5de9e69 KZ |
296 | |
297 | /* apply flag to all columns without name */ | |
298 | if (unnamed) { | |
299 | struct libscols_iter *itr; | |
300 | struct libscols_column *cl; | |
301 | ||
302 | itr = scols_new_iter(SCOLS_ITER_FORWARD); | |
303 | if (!itr) | |
304 | err_oom(); | |
305 | ||
306 | while (scols_table_next_column(ctl->tab, itr, &cl) == 0) { | |
307 | struct libscols_cell *ce = scols_column_get_header(cl); | |
308 | ||
309 | if (ce == NULL || scols_cell_get_data(ce) == NULL) | |
310 | column_set_flag(cl, flag); | |
311 | } | |
312 | scols_free_iter(itr); | |
313 | } | |
3ba01db0 KZ |
314 | } |
315 | ||
166271a9 KZ |
316 | static void reorder_table(struct column_control *ctl) |
317 | { | |
318 | struct libscols_column **wanted, *last = NULL; | |
319 | size_t i, count = 0; | |
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")); | |
322 | char **one; | |
323 | ||
324 | wanted = xcalloc(ncols, sizeof(struct libscols_column *)); | |
325 | ||
326 | STRV_FOREACH(one, order) { | |
327 | struct libscols_column *cl = string_to_column(ctl, *one); | |
328 | if (cl) | |
329 | wanted[count++] = cl; | |
330 | } | |
331 | ||
332 | for (i = 0; i < count; i++) { | |
333 | scols_table_move_column(ctl->tab, last, wanted[i]); | |
334 | last = wanted[i]; | |
335 | } | |
d6580917 KZ |
336 | |
337 | free(wanted); | |
338 | strv_free(order); | |
166271a9 KZ |
339 | } |
340 | ||
90852c3e KZ |
341 | static void create_tree(struct column_control *ctl) |
342 | { | |
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; | |
348 | ||
349 | if (!cl_p || !cl_i || !cl_tree) | |
350 | return; /* silently ignore the tree request */ | |
351 | ||
352 | column_set_flag(cl_tree, SCOLS_FL_TREE); | |
353 | ||
354 | itr_p = scols_new_iter(SCOLS_ITER_FORWARD); | |
355 | itr_i = scols_new_iter(SCOLS_ITER_FORWARD); | |
356 | if (!itr_p || !itr_i) | |
357 | err_oom(); | |
358 | ||
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; | |
364 | ||
365 | if (!id) | |
366 | continue; | |
367 | ||
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) { | |
371 | const char *parent; | |
372 | ||
373 | ce = scols_line_get_column_cell(ln, cl_p); | |
374 | parent = ce ? scols_cell_get_data(ce) : NULL; | |
375 | ||
376 | if (!parent) | |
377 | continue; | |
c467fdaf | 378 | if (strcmp(id, parent) != 0) |
b0f00a94 | 379 | continue; |
c467fdaf | 380 | if (scols_line_is_ancestor(ln, ln_i)) |
b0f00a94 | 381 | continue; |
c467fdaf | 382 | scols_line_add_child(ln_i, ln); |
90852c3e KZ |
383 | } |
384 | } | |
385 | ||
386 | scols_free_iter(itr_p); | |
387 | scols_free_iter(itr_i); | |
388 | } | |
389 | ||
dbed2f8c KZ |
390 | static void modify_table(struct column_control *ctl) |
391 | { | |
3ba01db0 | 392 | scols_table_set_termwidth(ctl->tab, ctl->termwidth); |
bd6b5a64 | 393 | scols_table_set_termforce(ctl->tab, SCOLS_TERMFORCE_ALWAYS); |
3ba01db0 | 394 | |
3ba01db0 KZ |
395 | if (ctl->tab_colright) |
396 | apply_columnflag_from_list(ctl, ctl->tab_colright, | |
397 | SCOLS_FL_RIGHT, _("failed to parse --table-right list")); | |
398 | ||
3ba01db0 KZ |
399 | if (ctl->tab_coltrunc) |
400 | apply_columnflag_from_list(ctl, ctl->tab_coltrunc, | |
401 | SCOLS_FL_TRUNC , _("failed to parse --table-trunc list")); | |
1ae24ec2 KZ |
402 | |
403 | if (ctl->tab_colnoextrem) | |
404 | apply_columnflag_from_list(ctl, ctl->tab_colnoextrem, | |
405 | SCOLS_FL_NOEXTREMES , _("failed to parse --table-noextreme list")); | |
68916af3 KZ |
406 | |
407 | if (ctl->tab_colwrap) | |
408 | apply_columnflag_from_list(ctl, ctl->tab_colwrap, | |
409 | SCOLS_FL_WRAP , _("failed to parse --table-wrap list")); | |
410 | ||
9624f615 KZ |
411 | if (ctl->tab_colhide) |
412 | apply_columnflag_from_list(ctl, ctl->tab_colhide, | |
413 | SCOLS_FL_HIDDEN , _("failed to parse --table-hide list")); | |
166271a9 | 414 | |
c728e000 KZ |
415 | if (!ctl->tab_colnoextrem) { |
416 | struct libscols_column *cl = get_last_visible_column(ctl); | |
417 | if (cl) | |
418 | column_set_flag(cl, SCOLS_FL_NOEXTREMES); | |
419 | } | |
420 | ||
90852c3e KZ |
421 | if (ctl->tree) |
422 | create_tree(ctl); | |
166271a9 KZ |
423 | |
424 | /* This must be the last step! */ | |
425 | if (ctl->tab_order) | |
426 | reorder_table(ctl); | |
dbed2f8c KZ |
427 | } |
428 | ||
166271a9 | 429 | |
4762ae9d KZ |
430 | static int add_line_to_table(struct column_control *ctl, wchar_t *wcs) |
431 | { | |
432 | wchar_t *wcdata, *sv = NULL; | |
433 | size_t n = 0; | |
434 | struct libscols_line *ln = NULL; | |
435 | ||
436 | if (!ctl->tab) | |
437 | init_table(ctl); | |
438 | ||
073abd4c | 439 | while ((wcdata = local_wcstok(ctl, wcs, &sv))) { |
4762ae9d KZ |
440 | char *data; |
441 | ||
cb3fdf2a KZ |
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, | |
447 | n + 1); | |
4762ae9d | 448 | scols_table_new_column(ctl->tab, NULL, 0, 0); |
cb3fdf2a | 449 | } |
4762ae9d KZ |
450 | if (!ln) { |
451 | ln = scols_table_new_line(ctl->tab, NULL); | |
452 | if (!ln) | |
453 | err(EXIT_FAILURE, _("failed to allocate output line")); | |
454 | } | |
5c7b67fb | 455 | |
4762ae9d KZ |
456 | data = wcs_to_mbs(wcdata); |
457 | if (!data) | |
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")); | |
461 | n++; | |
462 | wcs = NULL; | |
463 | } | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
2698f9ba KZ |
468 | static int add_emptyline_to_table(struct column_control *ctl) |
469 | { | |
470 | if (!ctl->tab) | |
471 | init_table(ctl); | |
472 | ||
473 | if (!scols_table_new_line(ctl->tab, NULL)) | |
474 | err(EXIT_FAILURE, _("failed to allocate output line")); | |
475 | ||
476 | return 0; | |
477 | } | |
478 | ||
37d84d6d KZ |
479 | static int read_input(struct column_control *ctl, FILE *fp) |
480 | { | |
481 | char *buf = NULL; | |
482 | size_t bufsz = 0; | |
483 | size_t maxents = 0; | |
651c5d42 | 484 | int rc = 0; |
37d84d6d | 485 | |
5c7b67fb | 486 | /* Read input */ |
37d84d6d KZ |
487 | do { |
488 | char *str, *p; | |
4762ae9d | 489 | wchar_t *wcs = NULL; |
37d84d6d KZ |
490 | size_t len; |
491 | ||
492 | if (getline(&buf, &bufsz, fp) < 0) { | |
493 | if (feof(fp)) | |
494 | break; | |
4762ae9d | 495 | err(EXIT_FAILURE, _("read failed")); |
37d84d6d KZ |
496 | } |
497 | str = (char *) skip_space(buf); | |
498 | if (str) { | |
499 | p = strchr(str, '\n'); | |
500 | if (p) | |
501 | *p = '\0'; | |
502 | } | |
2698f9ba KZ |
503 | if (!str || !*str) { |
504 | if (ctl->mode == COLUMN_MODE_TABLE && ctl->tab_empty_lines) | |
505 | add_emptyline_to_table(ctl); | |
37d84d6d | 506 | continue; |
2698f9ba | 507 | } |
37d84d6d | 508 | |
651c5d42 | 509 | wcs = mbs_to_wcs(buf); |
dda229c7 KZ |
510 | if (!wcs) { |
511 | /* | |
512 | * Convert broken sequences to \x<hex> and continue. | |
513 | */ | |
514 | size_t tmpsz = 0; | |
651c5d42 | 515 | char *tmp = mbs_invalid_encode(buf, &tmpsz); |
dda229c7 KZ |
516 | |
517 | if (!tmp) | |
518 | err(EXIT_FAILURE, _("read failed")); | |
519 | wcs = mbs_to_wcs(tmp); | |
520 | free(tmp); | |
521 | } | |
4762ae9d | 522 | |
37d84d6d KZ |
523 | switch (ctl->mode) { |
524 | case COLUMN_MODE_TABLE: | |
4762ae9d KZ |
525 | rc = add_line_to_table(ctl, wcs); |
526 | free(wcs); | |
527 | break; | |
37d84d6d KZ |
528 | |
529 | case COLUMN_MODE_FILLCOLS: | |
530 | case COLUMN_MODE_FILLROWS: | |
531 | if (ctl->nents <= maxents) { | |
532 | maxents += 1000; | |
533 | ctl->ents = xrealloc(ctl->ents, | |
534 | maxents * sizeof(wchar_t *)); | |
535 | } | |
4762ae9d | 536 | ctl->ents[ctl->nents] = wcs; |
37d84d6d KZ |
537 | len = width(ctl->ents[ctl->nents]); |
538 | if (ctl->maxlength < len) | |
539 | ctl->maxlength = len; | |
540 | ctl->nents++; | |
541 | break; | |
dd45b90e KZ |
542 | default: |
543 | free(wcs); | |
544 | break; | |
37d84d6d | 545 | } |
4762ae9d | 546 | } while (rc == 0); |
37d84d6d | 547 | |
4762ae9d | 548 | return rc; |
37d84d6d KZ |
549 | } |
550 | ||
551 | ||
552 | static void columnate_fillrows(struct column_control *ctl) | |
553 | { | |
554 | size_t chcnt, col, cnt, endcol, numcols; | |
555 | wchar_t **lp; | |
556 | ||
739e58ff | 557 | ctl->maxlength = (ctl->maxlength + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1); |
37d84d6d KZ |
558 | numcols = ctl->termwidth / ctl->maxlength; |
559 | endcol = ctl->maxlength; | |
f4d37838 | 560 | for (chcnt = col = 0, lp = ctl->ents; /* nothing */; ++lp) { |
37d84d6d KZ |
561 | fputws(*lp, stdout); |
562 | chcnt += width(*lp); | |
563 | if (!--ctl->nents) | |
564 | break; | |
565 | if (++col == numcols) { | |
566 | chcnt = col = 0; | |
567 | endcol = ctl->maxlength; | |
568 | putwchar('\n'); | |
569 | } else { | |
739e58ff | 570 | while ((cnt = ((chcnt + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1))) <= endcol) { |
37d84d6d KZ |
571 | putwchar('\t'); |
572 | chcnt = cnt; | |
573 | } | |
574 | endcol += ctl->maxlength; | |
575 | } | |
576 | } | |
577 | if (chcnt) | |
578 | putwchar('\n'); | |
579 | } | |
580 | ||
581 | static void columnate_fillcols(struct column_control *ctl) | |
582 | { | |
583 | size_t base, chcnt, cnt, col, endcol, numcols, numrows, row; | |
584 | ||
739e58ff | 585 | ctl->maxlength = (ctl->maxlength + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1); |
37d84d6d KZ |
586 | numcols = ctl->termwidth / ctl->maxlength; |
587 | if (!numcols) | |
588 | numcols = 1; | |
589 | numrows = ctl->nents / numcols; | |
590 | if (ctl->nents % numcols) | |
591 | ++numrows; | |
592 | ||
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) | |
599 | break; | |
739e58ff | 600 | while ((cnt = ((chcnt + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1))) <= endcol) { |
37d84d6d KZ |
601 | putwchar('\t'); |
602 | chcnt = cnt; | |
603 | } | |
604 | endcol += ctl->maxlength; | |
605 | } | |
606 | putwchar('\n'); | |
607 | } | |
608 | } | |
609 | ||
610 | static void simple_print(struct column_control *ctl) | |
611 | { | |
612 | int cnt; | |
613 | wchar_t **lp; | |
614 | ||
615 | for (cnt = ctl->nents, lp = ctl->ents; cnt--; ++lp) { | |
616 | fputws(*lp, stdout); | |
617 | putwchar('\n'); | |
618 | } | |
619 | } | |
620 | ||
fa2cd89a | 621 | static void __attribute__((__noreturn__)) usage(void) |
a4cc8dfe | 622 | { |
fa2cd89a | 623 | FILE *out = stdout; |
a4cc8dfe | 624 | |
3eed42f3 | 625 | fputs(USAGE_HEADER, out); |
4ce393f4 | 626 | fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name); |
451dbcfa BS |
627 | |
628 | fputs(USAGE_SEPARATOR, out); | |
629 | fputs(_("Columnate lists.\n"), out); | |
630 | ||
3eed42f3 | 631 | fputs(USAGE_OPTIONS, out); |
d7a3bf94 | 632 | fputs(_(" -t, --table create a table\n"), out); |
7e947f48 KZ |
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); | |
01e335c9 | 635 | fputs(_(" -N, --table-columns <names> comma separated columns names\n"), out); |
7e947f48 | 636 | fputs(_(" -E, --table-noextreme <columns> don't count long text from the columns to column width\n"), out); |
785e5436 | 637 | fputs(_(" -d, --table-noheadings don't print header\n"), out); |
d9eddf72 | 638 | fputs(_(" -e, --table-header-repeat repeat header for each page\n"), out); |
7e947f48 | 639 | fputs(_(" -H, --table-hide <columns> don't print the columns\n"), out); |
01e335c9 | 640 | fputs(_(" -R, --table-right <columns> right align text in these columns\n"), out); |
1ae24ec2 | 641 | fputs(_(" -T, --table-truncate <columns> truncate text in the columns when necessary\n"), out); |
68916af3 | 642 | fputs(_(" -W, --table-wrap <columns> wrap text in the columns when necessary\n"), out); |
2698f9ba | 643 | fputs(_(" -L, --table-empty-lines don't ignore empty lines\n"), out); |
7e947f48 KZ |
644 | fputs(_(" -J, --json use JSON output format for table\n"), out); |
645 | ||
74cc7c25 | 646 | fputs(USAGE_SEPARATOR, out); |
90852c3e KZ |
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); | |
650 | ||
7e947f48 | 651 | fputs(USAGE_SEPARATOR, out); |
d7a3bf94 | 652 | fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out); |
7e947f48 KZ |
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); | |
d7a3bf94 | 655 | fputs(_(" -x, --fillrows fill rows before columns\n"), out); |
7e947f48 | 656 | |
dda229c7 | 657 | |
3eed42f3 | 658 | fputs(USAGE_SEPARATOR, out); |
f45f3ec3 RM |
659 | printf(USAGE_HELP_OPTIONS(34)); |
660 | printf(USAGE_MAN_TAIL("column(1)")); | |
a4cc8dfe | 661 | |
fa2cd89a | 662 | exit(EXIT_SUCCESS); |
a4cc8dfe | 663 | } |
1df29104 SK |
664 | |
665 | int main(int argc, char **argv) | |
6dbe3af9 | 666 | { |
2593c139 KZ |
667 | struct column_control ctl = { |
668 | .mode = COLUMN_MODE_FILLCOLS, | |
bd6b5a64 KZ |
669 | .greedy = 1, |
670 | .termwidth = (size_t) -1 | |
2593c139 | 671 | }; |
7d07df62 | 672 | |
be03f652 | 673 | int c; |
daf093d2 | 674 | unsigned int eval = 0; /* exit value */ |
daf093d2 SK |
675 | |
676 | static const struct option longopts[] = | |
677 | { | |
11a1092a KZ |
678 | { "columns", required_argument, NULL, 'c' }, /* deprecated */ |
679 | { "fillrows", no_argument, NULL, 'x' }, | |
680 | { "help", no_argument, NULL, 'h' }, | |
7e947f48 | 681 | { "json", no_argument, NULL, 'J' }, |
11a1092a KZ |
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' }, | |
01e335c9 | 686 | { "table-columns", required_argument, NULL, 'N' }, |
7e947f48 KZ |
687 | { "table-hide", required_argument, NULL, 'H' }, |
688 | { "table-name", required_argument, NULL, 'n' }, | |
689 | { "table-noextreme", required_argument, NULL, 'E' }, | |
785e5436 | 690 | { "table-noheadings", no_argument, NULL, 'd' }, |
7e947f48 | 691 | { "table-order", required_argument, NULL, 'O' }, |
01e335c9 | 692 | { "table-right", required_argument, NULL, 'R' }, |
3ba01db0 | 693 | { "table-truncate", required_argument, NULL, 'T' }, |
68916af3 | 694 | { "table-wrap", required_argument, NULL, 'W' }, |
2698f9ba | 695 | { "table-empty-lines", no_argument, NULL, 'L' }, |
d9eddf72 | 696 | { "table-header-repeat", no_argument, NULL, 'e' }, |
90852c3e KZ |
697 | { "tree", required_argument, NULL, 'r' }, |
698 | { "tree-id", required_argument, NULL, 'i' }, | |
699 | { "tree-parent", required_argument, NULL, 'p' }, | |
11a1092a | 700 | { "version", no_argument, NULL, 'V' }, |
87918040 | 701 | { NULL, 0, NULL, 0 }, |
daf093d2 | 702 | }; |
be03f652 KZ |
703 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
704 | { 'J','x' }, | |
705 | { 't','x' }, | |
706 | { 0 } | |
707 | }; | |
708 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
daf093d2 | 709 | |
7eda085c KZ |
710 | setlocale(LC_ALL, ""); |
711 | bindtextdomain(PACKAGE, LOCALEDIR); | |
712 | textdomain(PACKAGE); | |
2c308875 | 713 | close_stdout_atexit(); |
6dbe3af9 | 714 | |
4762ae9d KZ |
715 | ctl.output_separator = " "; |
716 | ctl.input_separator = mbs_to_wcs("\t "); | |
6dbe3af9 | 717 | |
2698f9ba | 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) { |
be03f652 KZ |
719 | |
720 | err_exclusive_options(c, longopts, excl, excl_st); | |
721 | ||
722 | switch(c) { | |
7e947f48 KZ |
723 | case 'c': |
724 | ctl.termwidth = strtou32_or_err(optarg, _("invalid columns argument")); | |
725 | break; | |
785e5436 KZ |
726 | case 'd': |
727 | ctl.tab_noheadings = 1; | |
728 | break; | |
1ae24ec2 KZ |
729 | case 'E': |
730 | ctl.tab_colnoextrem = optarg; | |
731 | break; | |
d9eddf72 KZ |
732 | case 'e': |
733 | ctl.header_repeat = 1; | |
734 | break; | |
7e947f48 KZ |
735 | case 'H': |
736 | ctl.tab_colhide = optarg; | |
737 | break; | |
90852c3e KZ |
738 | case 'i': |
739 | ctl.tree_id = optarg; | |
740 | break; | |
9dbe8e1c KZ |
741 | case 'J': |
742 | ctl.json = 1; | |
743 | ctl.mode = COLUMN_MODE_TABLE; | |
744 | break; | |
2698f9ba KZ |
745 | case 'L': |
746 | ctl.tab_empty_lines = 1; | |
747 | break; | |
11a1092a | 748 | case 'N': |
dbed2f8c | 749 | ctl.tab_colnames = split_or_error(optarg, _("failed to parse column names")); |
11a1092a | 750 | break; |
7e947f48 KZ |
751 | case 'n': |
752 | ctl.tab_name = optarg; | |
9624f615 | 753 | break; |
7e947f48 KZ |
754 | case 'O': |
755 | ctl.tab_order = optarg; | |
1ae90932 | 756 | break; |
7e947f48 KZ |
757 | case 'o': |
758 | ctl.output_separator = optarg; | |
6dbe3af9 | 759 | break; |
90852c3e KZ |
760 | case 'p': |
761 | ctl.tree_parent = optarg; | |
762 | break; | |
dbed2f8c KZ |
763 | case 'R': |
764 | ctl.tab_colright = optarg; | |
765 | break; | |
90852c3e KZ |
766 | case 'r': |
767 | ctl.tree = optarg; | |
768 | break; | |
6dbe3af9 | 769 | case 's': |
4762ae9d KZ |
770 | free(ctl.input_separator); |
771 | ctl.input_separator = mbs_to_wcs(optarg); | |
772 | ctl.greedy = 0; | |
6dbe3af9 | 773 | break; |
7e947f48 KZ |
774 | case 'T': |
775 | ctl.tab_coltrunc = optarg; | |
47bd8ddc | 776 | break; |
6dbe3af9 | 777 | case 't': |
7d07df62 | 778 | ctl.mode = COLUMN_MODE_TABLE; |
6dbe3af9 | 779 | break; |
68916af3 KZ |
780 | case 'W': |
781 | ctl.tab_colwrap = optarg; | |
782 | break; | |
6dbe3af9 | 783 | case 'x': |
7d07df62 | 784 | ctl.mode = COLUMN_MODE_FILLROWS; |
6dbe3af9 | 785 | break; |
2c308875 KZ |
786 | |
787 | case 'h': | |
788 | usage(); | |
789 | case 'V': | |
790 | print_version(EXIT_SUCCESS); | |
6dbe3af9 | 791 | default: |
677ec86c | 792 | errtryhelp(EXIT_FAILURE); |
be03f652 | 793 | } |
1df29104 | 794 | } |
6dbe3af9 KZ |
795 | argc -= optind; |
796 | argv += optind; | |
797 | ||
bd6b5a64 KZ |
798 | if (ctl.termwidth == (size_t) -1) |
799 | ctl.termwidth = get_terminal_width(80); | |
800 | ||
90852c3e KZ |
801 | if (ctl.tree) { |
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")); | |
806 | } | |
807 | ||
7e947f48 KZ |
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-*")); | |
813 | ||
9dbe8e1c | 814 | if (ctl.tab_colnames == NULL && ctl.json) |
2483c4c9 | 815 | errx(EXIT_FAILURE, _("option --table-columns required for --json")); |
9dbe8e1c | 816 | |
6dbe3af9 | 817 | if (!*argv) |
d6b63b9f | 818 | eval += read_input(&ctl, stdin); |
1df29104 | 819 | else |
1ae90932 | 820 | for (; *argv; ++argv) { |
a46e644e KZ |
821 | FILE *fp; |
822 | ||
1ae90932 | 823 | if ((fp = fopen(*argv, "r")) != NULL) { |
d6b63b9f | 824 | eval += read_input(&ctl, fp); |
daf093d2 | 825 | fclose(fp); |
1ae90932 SK |
826 | } else { |
827 | warn("%s", *argv); | |
daf093d2 | 828 | eval += EXIT_FAILURE; |
1ae90932 | 829 | } |
6dbe3af9 KZ |
830 | } |
831 | ||
4762ae9d KZ |
832 | if (ctl.mode != COLUMN_MODE_TABLE) { |
833 | if (!ctl.nents) | |
834 | exit(eval); | |
835 | if (ctl.maxlength >= ctl.termwidth) | |
836 | ctl.mode = COLUMN_MODE_SIMPLE; | |
837 | } | |
7d07df62 KZ |
838 | |
839 | switch (ctl.mode) { | |
840 | case COLUMN_MODE_TABLE: | |
da06d421 KZ |
841 | if (ctl.tab && scols_table_get_nlines(ctl.tab)) { |
842 | modify_table(&ctl); | |
843 | eval = scols_print_table(ctl.tab); | |
844 | } | |
7d07df62 KZ |
845 | break; |
846 | case COLUMN_MODE_FILLCOLS: | |
2593c139 | 847 | columnate_fillcols(&ctl); |
7d07df62 KZ |
848 | break; |
849 | case COLUMN_MODE_FILLROWS: | |
2593c139 | 850 | columnate_fillrows(&ctl); |
7d07df62 KZ |
851 | break; |
852 | case COLUMN_MODE_SIMPLE: | |
d6b63b9f | 853 | simple_print(&ctl); |
7d07df62 KZ |
854 | break; |
855 | } | |
dcbca568 | 856 | |
7d07df62 | 857 | return eval == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
6dbe3af9 | 858 | } |