xref: /openbsd/usr.bin/column/column.c (revision 4cfece93)
1 /*	$OpenBSD: column.c,v 1.26 2018/06/22 12:27:00 rob 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
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
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
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
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
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
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 remeber 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 *
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
346 usage(void)
347 {
348 	(void)fprintf(stderr,
349 	    "usage: column [-tx] [-c columns] [-s sep] [file ...]\n");
350 	exit(1);
351 }
352