xref: /openbsd-src/usr.bin/column/column.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: column.c,v 1.25 2016/09/04 20:33:36 martijn 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 *ecalloc(size_t, size_t);
50 void  input(FILE *);
51 void  maketbl(void);
52 void  print(void);
53 void  r_columnate(void);
54 __dead void usage(void);
55 
56 struct field {
57 	char *content;
58 	int width;
59 };
60 
61 int termwidth;			/* default terminal width */
62 int entries;			/* number of records */
63 int eval;			/* exit value */
64 int *maxwidths;			/* longest record per column */
65 struct field **table;		/* one array of pointers per line */
66 wchar_t *separator = L"\t ";	/* field separator for table option */
67 
68 int
69 main(int argc, char *argv[])
70 {
71 	struct winsize win;
72 	FILE *fp;
73 	int ch, tflag, xflag;
74 	char *p;
75 	const char *errstr;
76 
77 	setlocale(LC_CTYPE, "");
78 
79 	termwidth = 0;
80 	if ((p = getenv("COLUMNS")) != NULL)
81 		termwidth = strtonum(p, 1, INT_MAX, NULL);
82 	if (termwidth == 0 && ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
83 	    win.ws_col > 0)
84 		termwidth = win.ws_col;
85 	if (termwidth == 0)
86 		termwidth = 80;
87 
88 	if (pledge("stdio rpath", NULL) == -1)
89 		err(1, "pledge");
90 
91 	tflag = xflag = 0;
92 	while ((ch = getopt(argc, argv, "c:s:tx")) != -1) {
93 		switch(ch) {
94 		case 'c':
95 			termwidth = strtonum(optarg, 1, INT_MAX, &errstr);
96 			if (errstr != NULL)
97 				errx(1, "%s: %s", errstr, optarg);
98 			break;
99 		case 's':
100 			if ((separator = reallocarray(NULL, strlen(optarg) + 1,
101 			    sizeof(*separator))) == NULL)
102 				err(1, NULL);
103 			if (mbstowcs(separator, optarg, strlen(optarg) + 1) ==
104 			    (size_t) -1)
105 				err(1, "sep");
106 			break;
107 		case 't':
108 			tflag = 1;
109 			break;
110 		case 'x':
111 			xflag = 1;
112 			break;
113 		default:
114 			usage();
115 		}
116 	}
117 
118 	if (!tflag)
119 		separator = L"";
120 	argv += optind;
121 
122 	if (*argv == NULL) {
123 		input(stdin);
124 	} else {
125 		for (; *argv; ++argv) {
126 			if ((fp = fopen(*argv, "r"))) {
127 				input(fp);
128 				(void)fclose(fp);
129 			} else {
130 				warn("%s", *argv);
131 				eval = 1;
132 			}
133 		}
134 	}
135 
136 	if (pledge("stdio", NULL) == -1)
137 		err(1, "pledge");
138 
139 	if (!entries)
140 		return eval;
141 
142 	if (tflag)
143 		maketbl();
144 	else if (*maxwidths >= termwidth)
145 		print();
146 	else if (xflag)
147 		c_columnate();
148 	else
149 		r_columnate();
150 	return eval;
151 }
152 
153 #define	INCR_NEXTTAB(x)	(x = (x + 8) & ~7)
154 void
155 c_columnate(void)
156 {
157 	int col, numcols;
158 	struct field **row;
159 
160 	INCR_NEXTTAB(*maxwidths);
161 	if ((numcols = termwidth / *maxwidths) == 0)
162 		numcols = 1;
163 	for (col = 0, row = table;; ++row) {
164 		fputs((*row)->content, stdout);
165 		if (!--entries)
166 			break;
167 		if (++col == numcols) {
168 			col = 0;
169 			putchar('\n');
170 		} else {
171 			while (INCR_NEXTTAB((*row)->width) <= *maxwidths)
172 				putchar('\t');
173 		}
174 	}
175 	putchar('\n');
176 }
177 
178 void
179 r_columnate(void)
180 {
181 	int base, col, numcols, numrows, row;
182 
183 	INCR_NEXTTAB(*maxwidths);
184 	if ((numcols = termwidth / *maxwidths) == 0)
185 		numcols = 1;
186 	numrows = entries / numcols;
187 	if (entries % numcols)
188 		++numrows;
189 
190 	for (base = row = 0; row < numrows; base = ++row) {
191 		for (col = 0; col < numcols; ++col, base += numrows) {
192 			fputs(table[base]->content, stdout);
193 			if (base + numrows >= entries)
194 				break;
195 			while (INCR_NEXTTAB(table[base]->width) <= *maxwidths)
196 				putchar('\t');
197 		}
198 		putchar('\n');
199 	}
200 }
201 
202 void
203 print(void)
204 {
205 	int row;
206 
207 	for (row = 0; row < entries; row++)
208 		puts(table[row]->content);
209 }
210 
211 
212 void
213 maketbl(void)
214 {
215 	struct field **row;
216 	int col;
217 
218 	for (row = table; entries--; ++row) {
219 		for (col = 0; (*row)[col + 1].content != NULL; ++col)
220 			printf("%s%*s  ", (*row)[col].content,
221 			    maxwidths[col] - (*row)[col].width, "");
222 		puts((*row)[col].content);
223 	}
224 }
225 
226 #define	DEFNUM		1000
227 #define	DEFCOLS		25
228 
229 void
230 input(FILE *fp)
231 {
232 	static int maxentry = 0;
233 	static int maxcols = 0;
234 	static struct field *cols = NULL;
235 	int col, width, twidth;
236 	size_t blen;
237 	ssize_t llen;
238 	char *p, *s, *buf = NULL;
239 	wchar_t wc;
240 	int wlen;
241 
242 	while ((llen = getline(&buf, &blen, fp)) > -1) {
243 		if (buf[llen - 1] == '\n')
244 			buf[llen - 1] = '\0';
245 
246 		p = buf;
247 		for (col = 0;; col++) {
248 
249 			/* Skip lines containing nothing but whitespace. */
250 
251 			for (s = p; (wlen = mbtowc(&wc, s, MB_CUR_MAX)) > 0;
252 			     s += wlen)
253 				if (!iswspace(wc))
254 					break;
255 			if (*s == '\0')
256 				break;
257 
258 			/* Skip leading, multiple, and trailing separators. */
259 
260 			while ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) > 0 &&
261 			    wcschr(separator, wc) != NULL)
262 				p += wlen;
263 			if (*p == '\0')
264 				break;
265 
266 			/*
267 			 * Found a non-empty field.
268 			 * Remember the start and measure the width.
269 			 */
270 
271 			s = p;
272 			width = 0;
273 			while (*p != '\0') {
274 				if ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) == -1) {
275 					width++;
276 					p++;
277 					continue;
278 				}
279 				if (wcschr(separator, wc) != NULL)
280 					break;
281 				if (*p == '\t')
282 					INCR_NEXTTAB(width);
283 				else  {
284 					width += (twidth = wcwidth(wc)) == -1 ?
285 					    1 : twidth;
286 				}
287 				p += wlen;
288 			}
289 
290 			if (col + 1 >= maxcols) {
291 				if (maxcols > INT_MAX - DEFCOLS)
292 					err(1, "too many columns");
293 				maxcols += DEFCOLS;
294 				cols = ereallocarray(cols, maxcols,
295 				    sizeof(*cols));
296 				maxwidths = ereallocarray(maxwidths, maxcols,
297 				    sizeof(*maxwidths));
298 				memset(maxwidths + col, 0,
299 				    DEFCOLS * sizeof(*maxwidths));
300 			}
301 
302 			/*
303 			 * Remember the width of the field,
304 			 * NUL-terminate and remeber the content,
305 			 * and advance beyond the separator, if any.
306 			 */
307 
308 			cols[col].width = width;
309 			if (maxwidths[col] < width)
310 				maxwidths[col] = width;
311 			if (*p != '\0') {
312 				*p = '\0';
313 				p += wlen;
314 			}
315 			if ((cols[col].content = strdup(s)) == NULL)
316 				err(1, NULL);
317 		}
318 		if (col == 0)
319 			continue;
320 
321 		/* Found a non-empty line; remember it. */
322 
323 		if (entries == maxentry) {
324 			if (maxentry > INT_MAX - DEFNUM)
325 				errx(1, "too many input lines");
326 			maxentry += DEFNUM;
327 			table = ereallocarray(table, maxentry, sizeof(*table));
328 		}
329 		table[entries] = ereallocarray(NULL, col + 1,
330 		    sizeof(*(table[entries])));
331 		table[entries][col].content = NULL;
332 		while (col--)
333 			table[entries][col] = cols[col];
334 		entries++;
335 	}
336 }
337 
338 void *
339 ereallocarray(void *ptr, size_t nmemb, size_t size)
340 {
341 	if ((ptr = reallocarray(ptr, nmemb, size)) == NULL)
342 		err(1, NULL);
343 	return ptr;
344 }
345 
346 void *
347 ecalloc(size_t nmemb, size_t size)
348 {
349 	void *ptr;
350 
351 	if ((ptr = calloc(nmemb, size)) == NULL)
352 		err(1, NULL);
353 	return ptr;
354 }
355 
356 __dead void
357 usage(void)
358 {
359 	(void)fprintf(stderr,
360 	    "usage: column [-tx] [-c columns] [-s sep] [file ...]\n");
361 	exit(1);
362 }
363