1 /* $OpenBSD: column.c,v 1.27 2022/12/26 19:16:00 jmc Exp $ */
2 /* $NetBSD: column.c,v 1.4 1995/09/02 05:53:03 jtc Exp $ */
3
4 /*
5 * Copyright (c) 1989, 1993, 1994
6 * The Regents of the University of California. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 #include <sys/types.h>
34 #include <sys/ioctl.h>
35
36 #include <ctype.h>
37 #include <err.h>
38 #include <limits.h>
39 #include <locale.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <wchar.h>
45 #include <wctype.h>
46
47 void c_columnate(void);
48 void *ereallocarray(void *, size_t, size_t);
49 void input(FILE *);
50 void maketbl(void);
51 void print(void);
52 void r_columnate(void);
53 __dead void usage(void);
54
55 struct field {
56 char *content;
57 int width;
58 };
59
60 int termwidth; /* default terminal width */
61 int entries; /* number of records */
62 int eval; /* exit value */
63 int *maxwidths; /* longest record per column */
64 struct field **table; /* one array of pointers per line */
65 wchar_t *separator = L"\t "; /* field separator for table option */
66
67 int
main(int argc,char * argv[])68 main(int argc, char *argv[])
69 {
70 struct winsize win;
71 FILE *fp;
72 int ch, tflag, xflag;
73 char *p;
74 const char *errstr;
75
76 setlocale(LC_CTYPE, "");
77
78 termwidth = 0;
79 if ((p = getenv("COLUMNS")) != NULL)
80 termwidth = strtonum(p, 1, INT_MAX, NULL);
81 if (termwidth == 0 && ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
82 win.ws_col > 0)
83 termwidth = win.ws_col;
84 if (termwidth == 0)
85 termwidth = 80;
86
87 if (pledge("stdio rpath", NULL) == -1)
88 err(1, "pledge");
89
90 tflag = xflag = 0;
91 while ((ch = getopt(argc, argv, "c:s:tx")) != -1) {
92 switch(ch) {
93 case 'c':
94 termwidth = strtonum(optarg, 1, INT_MAX, &errstr);
95 if (errstr != NULL)
96 errx(1, "%s: %s", errstr, optarg);
97 break;
98 case 's':
99 if ((separator = reallocarray(NULL, strlen(optarg) + 1,
100 sizeof(*separator))) == NULL)
101 err(1, NULL);
102 if (mbstowcs(separator, optarg, strlen(optarg) + 1) ==
103 (size_t) -1)
104 err(1, "sep");
105 break;
106 case 't':
107 tflag = 1;
108 break;
109 case 'x':
110 xflag = 1;
111 break;
112 default:
113 usage();
114 }
115 }
116
117 if (!tflag)
118 separator = L"";
119 argv += optind;
120
121 if (*argv == NULL) {
122 input(stdin);
123 } else {
124 for (; *argv; ++argv) {
125 if ((fp = fopen(*argv, "r"))) {
126 input(fp);
127 (void)fclose(fp);
128 } else {
129 warn("%s", *argv);
130 eval = 1;
131 }
132 }
133 }
134
135 if (pledge("stdio", NULL) == -1)
136 err(1, "pledge");
137
138 if (!entries)
139 return eval;
140
141 if (tflag)
142 maketbl();
143 else if (*maxwidths >= termwidth)
144 print();
145 else if (xflag)
146 c_columnate();
147 else
148 r_columnate();
149 return eval;
150 }
151
152 #define INCR_NEXTTAB(x) (x = (x + 8) & ~7)
153 void
c_columnate(void)154 c_columnate(void)
155 {
156 int col, numcols;
157 struct field **row;
158
159 INCR_NEXTTAB(*maxwidths);
160 if ((numcols = termwidth / *maxwidths) == 0)
161 numcols = 1;
162 for (col = 0, row = table;; ++row) {
163 fputs((*row)->content, stdout);
164 if (!--entries)
165 break;
166 if (++col == numcols) {
167 col = 0;
168 putchar('\n');
169 } else {
170 while (INCR_NEXTTAB((*row)->width) <= *maxwidths)
171 putchar('\t');
172 }
173 }
174 putchar('\n');
175 }
176
177 void
r_columnate(void)178 r_columnate(void)
179 {
180 int base, col, numcols, numrows, row;
181
182 INCR_NEXTTAB(*maxwidths);
183 if ((numcols = termwidth / *maxwidths) == 0)
184 numcols = 1;
185 numrows = entries / numcols;
186 if (entries % numcols)
187 ++numrows;
188
189 for (base = row = 0; row < numrows; base = ++row) {
190 for (col = 0; col < numcols; ++col, base += numrows) {
191 fputs(table[base]->content, stdout);
192 if (base + numrows >= entries)
193 break;
194 while (INCR_NEXTTAB(table[base]->width) <= *maxwidths)
195 putchar('\t');
196 }
197 putchar('\n');
198 }
199 }
200
201 void
print(void)202 print(void)
203 {
204 int row;
205
206 for (row = 0; row < entries; row++)
207 puts(table[row]->content);
208 }
209
210
211 void
maketbl(void)212 maketbl(void)
213 {
214 struct field **row;
215 int col;
216
217 for (row = table; entries--; ++row) {
218 for (col = 0; (*row)[col + 1].content != NULL; ++col)
219 printf("%s%*s ", (*row)[col].content,
220 maxwidths[col] - (*row)[col].width, "");
221 puts((*row)[col].content);
222 }
223 }
224
225 #define DEFNUM 1000
226 #define DEFCOLS 25
227
228 void
input(FILE * fp)229 input(FILE *fp)
230 {
231 static int maxentry = 0;
232 static int maxcols = 0;
233 static struct field *cols = NULL;
234 int col, width, twidth;
235 size_t blen;
236 ssize_t llen;
237 char *p, *s, *buf = NULL;
238 wchar_t wc;
239 int wlen;
240
241 while ((llen = getline(&buf, &blen, fp)) > -1) {
242 if (buf[llen - 1] == '\n')
243 buf[llen - 1] = '\0';
244
245 p = buf;
246 for (col = 0;; col++) {
247
248 /* Skip lines containing nothing but whitespace. */
249
250 for (s = p; (wlen = mbtowc(&wc, s, MB_CUR_MAX)) > 0;
251 s += wlen)
252 if (!iswspace(wc))
253 break;
254 if (*s == '\0')
255 break;
256
257 /* Skip leading, multiple, and trailing separators. */
258
259 while ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) > 0 &&
260 wcschr(separator, wc) != NULL)
261 p += wlen;
262 if (*p == '\0')
263 break;
264
265 /*
266 * Found a non-empty field.
267 * Remember the start and measure the width.
268 */
269
270 s = p;
271 width = 0;
272 while (*p != '\0') {
273 if ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) == -1) {
274 width++;
275 p++;
276 continue;
277 }
278 if (wcschr(separator, wc) != NULL)
279 break;
280 if (*p == '\t')
281 INCR_NEXTTAB(width);
282 else {
283 width += (twidth = wcwidth(wc)) == -1 ?
284 1 : twidth;
285 }
286 p += wlen;
287 }
288
289 if (col + 1 >= maxcols) {
290 if (maxcols > INT_MAX - DEFCOLS)
291 err(1, "too many columns");
292 maxcols += DEFCOLS;
293 cols = ereallocarray(cols, maxcols,
294 sizeof(*cols));
295 maxwidths = ereallocarray(maxwidths, maxcols,
296 sizeof(*maxwidths));
297 memset(maxwidths + col, 0,
298 DEFCOLS * sizeof(*maxwidths));
299 }
300
301 /*
302 * Remember the width of the field,
303 * NUL-terminate and remember the content,
304 * and advance beyond the separator, if any.
305 */
306
307 cols[col].width = width;
308 if (maxwidths[col] < width)
309 maxwidths[col] = width;
310 if (*p != '\0') {
311 *p = '\0';
312 p += wlen;
313 }
314 if ((cols[col].content = strdup(s)) == NULL)
315 err(1, NULL);
316 }
317 if (col == 0)
318 continue;
319
320 /* Found a non-empty line; remember it. */
321
322 if (entries == maxentry) {
323 if (maxentry > INT_MAX - DEFNUM)
324 errx(1, "too many input lines");
325 maxentry += DEFNUM;
326 table = ereallocarray(table, maxentry, sizeof(*table));
327 }
328 table[entries] = ereallocarray(NULL, col + 1,
329 sizeof(*(table[entries])));
330 table[entries][col].content = NULL;
331 while (col--)
332 table[entries][col] = cols[col];
333 entries++;
334 }
335 }
336
337 void *
ereallocarray(void * ptr,size_t nmemb,size_t size)338 ereallocarray(void *ptr, size_t nmemb, size_t size)
339 {
340 if ((ptr = reallocarray(ptr, nmemb, size)) == NULL)
341 err(1, NULL);
342 return ptr;
343 }
344
345 __dead void
usage(void)346 usage(void)
347 {
348 (void)fprintf(stderr,
349 "usage: column [-tx] [-c columns] [-s sep] [file ...]\n");
350 exit(1);
351 }
352