xref: /freebsd/usr.bin/column/column.c (revision 0b8224d1cc9dc6c9778ba04a75b2c8d47e5d7481)
18a16b7a1SPedro F. Giffuni /*-
28a16b7a1SPedro F. Giffuni  * SPDX-License-Identifier: BSD-3-Clause
38a16b7a1SPedro F. Giffuni  *
40da30e9aSPeter Wemm  * Copyright (c) 1989, 1993, 1994
50da30e9aSPeter Wemm  *	The Regents of the University of California.  All rights reserved.
60da30e9aSPeter Wemm  *
70da30e9aSPeter Wemm  * Redistribution and use in source and binary forms, with or without
80da30e9aSPeter Wemm  * modification, are permitted provided that the following conditions
90da30e9aSPeter Wemm  * are met:
100da30e9aSPeter Wemm  * 1. Redistributions of source code must retain the above copyright
110da30e9aSPeter Wemm  *    notice, this list of conditions and the following disclaimer.
120da30e9aSPeter Wemm  * 2. Redistributions in binary form must reproduce the above copyright
130da30e9aSPeter Wemm  *    notice, this list of conditions and the following disclaimer in the
140da30e9aSPeter Wemm  *    documentation and/or other materials provided with the distribution.
15fbbd9655SWarner Losh  * 3. Neither the name of the University nor the names of its contributors
160da30e9aSPeter Wemm  *    may be used to endorse or promote products derived from this software
170da30e9aSPeter Wemm  *    without specific prior written permission.
180da30e9aSPeter Wemm  *
190da30e9aSPeter Wemm  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
200da30e9aSPeter Wemm  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
210da30e9aSPeter Wemm  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
220da30e9aSPeter Wemm  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
230da30e9aSPeter Wemm  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
240da30e9aSPeter Wemm  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
250da30e9aSPeter Wemm  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
260da30e9aSPeter Wemm  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
270da30e9aSPeter Wemm  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
280da30e9aSPeter Wemm  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
290da30e9aSPeter Wemm  * SUCH DAMAGE.
300da30e9aSPeter Wemm  */
310da30e9aSPeter Wemm 
320da30e9aSPeter Wemm #include <sys/types.h>
330da30e9aSPeter Wemm #include <sys/ioctl.h>
34e2816c14SJuli Mallett #include <sys/param.h>
350da30e9aSPeter Wemm 
360da30e9aSPeter Wemm #include <err.h>
370da30e9aSPeter Wemm #include <limits.h>
385480f120STim J. Robbins #include <locale.h>
390da30e9aSPeter Wemm #include <stdio.h>
400da30e9aSPeter Wemm #include <stdlib.h>
410da30e9aSPeter Wemm #include <string.h>
420da30e9aSPeter Wemm #include <unistd.h>
435480f120STim J. Robbins #include <wchar.h>
445480f120STim J. Robbins #include <wctype.h>
450da30e9aSPeter Wemm 
46983e044aSDavid Malone #define	TAB	8
47983e044aSDavid Malone 
48316b87cbSEd Schouten static void	c_columnate(void);
49316b87cbSEd Schouten static void	input(FILE *);
50316b87cbSEd Schouten static void	maketbl(void);
51316b87cbSEd Schouten static void	print(void);
52316b87cbSEd Schouten static void	r_columnate(void);
53316b87cbSEd Schouten static void	usage(void);
54316b87cbSEd Schouten static int	width(const wchar_t *);
550da30e9aSPeter Wemm 
56316b87cbSEd Schouten static int	termwidth = 80;		/* default terminal width */
570da30e9aSPeter Wemm 
58316b87cbSEd Schouten static int	entries;		/* number of records */
59316b87cbSEd Schouten static int	eval;			/* exit value */
60316b87cbSEd Schouten static int	maxlength;		/* longest record */
61316b87cbSEd Schouten static wchar_t	**list;			/* array of pointers to records */
62316b87cbSEd Schouten static const wchar_t *separator = L"\t "; /* field separator for table option */
630da30e9aSPeter Wemm 
640da30e9aSPeter Wemm int
main(int argc,char ** argv)65823ab23fSDavid Malone main(int argc, char **argv)
660da30e9aSPeter Wemm {
670da30e9aSPeter Wemm 	struct winsize win;
680da30e9aSPeter Wemm 	FILE *fp;
690da30e9aSPeter Wemm 	int ch, tflag, xflag;
700da30e9aSPeter Wemm 	char *p;
715480f120STim J. Robbins 	const char *src;
725480f120STim J. Robbins 	wchar_t *newsep;
735480f120STim J. Robbins 	size_t seplen;
745480f120STim J. Robbins 
755480f120STim J. Robbins 	setlocale(LC_ALL, "");
760da30e9aSPeter Wemm 
770da30e9aSPeter Wemm 	if (ioctl(1, TIOCGWINSZ, &win) == -1 || !win.ws_col) {
78fa146c53SArchie Cobbs 		if ((p = getenv("COLUMNS")))
790da30e9aSPeter Wemm 			termwidth = atoi(p);
800da30e9aSPeter Wemm 	} else
810da30e9aSPeter Wemm 		termwidth = win.ws_col;
820da30e9aSPeter Wemm 
830da30e9aSPeter Wemm 	tflag = xflag = 0;
841c8af878SWarner Losh 	while ((ch = getopt(argc, argv, "c:s:tx")) != -1)
850da30e9aSPeter Wemm 		switch(ch) {
860da30e9aSPeter Wemm 		case 'c':
870da30e9aSPeter Wemm 			termwidth = atoi(optarg);
880da30e9aSPeter Wemm 			break;
890da30e9aSPeter Wemm 		case 's':
905480f120STim J. Robbins 			src = optarg;
915480f120STim J. Robbins 			seplen = mbsrtowcs(NULL, &src, 0, NULL);
925480f120STim J. Robbins 			if (seplen == (size_t)-1)
935480f120STim J. Robbins 				err(1, "bad separator");
945480f120STim J. Robbins 			newsep = malloc((seplen + 1) * sizeof(wchar_t));
955480f120STim J. Robbins 			if (newsep == NULL)
965480f120STim J. Robbins 				err(1, NULL);
975480f120STim J. Robbins 			mbsrtowcs(newsep, &src, seplen + 1, NULL);
985480f120STim J. Robbins 			separator = newsep;
990da30e9aSPeter Wemm 			break;
1000da30e9aSPeter Wemm 		case 't':
1010da30e9aSPeter Wemm 			tflag = 1;
1020da30e9aSPeter Wemm 			break;
1030da30e9aSPeter Wemm 		case 'x':
1040da30e9aSPeter Wemm 			xflag = 1;
1050da30e9aSPeter Wemm 			break;
1060da30e9aSPeter Wemm 		case '?':
1070da30e9aSPeter Wemm 		default:
1080da30e9aSPeter Wemm 			usage();
1090da30e9aSPeter Wemm 		}
1100da30e9aSPeter Wemm 	argc -= optind;
1110da30e9aSPeter Wemm 	argv += optind;
1120da30e9aSPeter Wemm 
1130da30e9aSPeter Wemm 	if (!*argv)
1140da30e9aSPeter Wemm 		input(stdin);
1150da30e9aSPeter Wemm 	else for (; *argv; ++argv)
116fa146c53SArchie Cobbs 		if ((fp = fopen(*argv, "r"))) {
1170da30e9aSPeter Wemm 			input(fp);
1180da30e9aSPeter Wemm 			(void)fclose(fp);
1190da30e9aSPeter Wemm 		} else {
1200da30e9aSPeter Wemm 			warn("%s", *argv);
1210da30e9aSPeter Wemm 			eval = 1;
1220da30e9aSPeter Wemm 		}
1230da30e9aSPeter Wemm 
1240da30e9aSPeter Wemm 	if (!entries)
1250da30e9aSPeter Wemm 		exit(eval);
1260da30e9aSPeter Wemm 
127090782f5SRuslan Ermilov 	maxlength = roundup(maxlength + 1, TAB);
1280da30e9aSPeter Wemm 	if (tflag)
1290da30e9aSPeter Wemm 		maketbl();
1300da30e9aSPeter Wemm 	else if (maxlength >= termwidth)
1310da30e9aSPeter Wemm 		print();
1320da30e9aSPeter Wemm 	else if (xflag)
1330da30e9aSPeter Wemm 		c_columnate();
1340da30e9aSPeter Wemm 	else
1350da30e9aSPeter Wemm 		r_columnate();
1360da30e9aSPeter Wemm 	exit(eval);
1370da30e9aSPeter Wemm }
1380da30e9aSPeter Wemm 
139316b87cbSEd Schouten static void
c_columnate(void)140823ab23fSDavid Malone c_columnate(void)
1410da30e9aSPeter Wemm {
1420da30e9aSPeter Wemm 	int chcnt, col, cnt, endcol, numcols;
1435480f120STim J. Robbins 	wchar_t **lp;
1440da30e9aSPeter Wemm 
1450da30e9aSPeter Wemm 	numcols = termwidth / maxlength;
1460da30e9aSPeter Wemm 	endcol = maxlength;
1470da30e9aSPeter Wemm 	for (chcnt = col = 0, lp = list;; ++lp) {
1485480f120STim J. Robbins 		wprintf(L"%ls", *lp);
1495480f120STim J. Robbins 		chcnt += width(*lp);
1500da30e9aSPeter Wemm 		if (!--entries)
1510da30e9aSPeter Wemm 			break;
1520da30e9aSPeter Wemm 		if (++col == numcols) {
1530da30e9aSPeter Wemm 			chcnt = col = 0;
1540da30e9aSPeter Wemm 			endcol = maxlength;
1555480f120STim J. Robbins 			putwchar('\n');
1560da30e9aSPeter Wemm 		} else {
157090782f5SRuslan Ermilov 			while ((cnt = roundup(chcnt + 1, TAB)) <= endcol) {
1585480f120STim J. Robbins 				(void)putwchar('\t');
1590da30e9aSPeter Wemm 				chcnt = cnt;
1600da30e9aSPeter Wemm 			}
1610da30e9aSPeter Wemm 			endcol += maxlength;
1620da30e9aSPeter Wemm 		}
1630da30e9aSPeter Wemm 	}
1640da30e9aSPeter Wemm 	if (chcnt)
1655480f120STim J. Robbins 		putwchar('\n');
1660da30e9aSPeter Wemm }
1670da30e9aSPeter Wemm 
168316b87cbSEd Schouten static void
r_columnate(void)169823ab23fSDavid Malone r_columnate(void)
1700da30e9aSPeter Wemm {
1710da30e9aSPeter Wemm 	int base, chcnt, cnt, col, endcol, numcols, numrows, row;
1720da30e9aSPeter Wemm 
1730da30e9aSPeter Wemm 	numcols = termwidth / maxlength;
1740da30e9aSPeter Wemm 	numrows = entries / numcols;
1750da30e9aSPeter Wemm 	if (entries % numcols)
1760da30e9aSPeter Wemm 		++numrows;
1770da30e9aSPeter Wemm 
1780da30e9aSPeter Wemm 	for (row = 0; row < numrows; ++row) {
1790da30e9aSPeter Wemm 		endcol = maxlength;
1800da30e9aSPeter Wemm 		for (base = row, chcnt = col = 0; col < numcols; ++col) {
1815480f120STim J. Robbins 			wprintf(L"%ls", list[base]);
1825480f120STim J. Robbins 			chcnt += width(list[base]);
1830da30e9aSPeter Wemm 			if ((base += numrows) >= entries)
1840da30e9aSPeter Wemm 				break;
185090782f5SRuslan Ermilov 			while ((cnt = roundup(chcnt + 1, TAB)) <= endcol) {
1865480f120STim J. Robbins 				(void)putwchar('\t');
1870da30e9aSPeter Wemm 				chcnt = cnt;
1880da30e9aSPeter Wemm 			}
1890da30e9aSPeter Wemm 			endcol += maxlength;
1900da30e9aSPeter Wemm 		}
1915480f120STim J. Robbins 		putwchar('\n');
1920da30e9aSPeter Wemm 	}
1930da30e9aSPeter Wemm }
1940da30e9aSPeter Wemm 
195316b87cbSEd Schouten static void
print(void)196823ab23fSDavid Malone print(void)
1970da30e9aSPeter Wemm {
1980da30e9aSPeter Wemm 	int cnt;
1995480f120STim J. Robbins 	wchar_t **lp;
2000da30e9aSPeter Wemm 
2010da30e9aSPeter Wemm 	for (cnt = entries, lp = list; cnt--; ++lp)
2025480f120STim J. Robbins 		(void)wprintf(L"%ls\n", *lp);
2030da30e9aSPeter Wemm }
2040da30e9aSPeter Wemm 
2050da30e9aSPeter Wemm typedef struct _tbl {
2065480f120STim J. Robbins 	wchar_t **list;
2070da30e9aSPeter Wemm 	int cols, *len;
2080da30e9aSPeter Wemm } TBL;
2090da30e9aSPeter Wemm #define	DEFCOLS	25
2100da30e9aSPeter Wemm 
211316b87cbSEd Schouten static void
maketbl(void)212823ab23fSDavid Malone maketbl(void)
2130da30e9aSPeter Wemm {
2140da30e9aSPeter Wemm 	TBL *t;
2150da30e9aSPeter Wemm 	int coloff, cnt;
2165480f120STim J. Robbins 	wchar_t *p, **lp;
2170da30e9aSPeter Wemm 	int *lens, maxcols;
2180da30e9aSPeter Wemm 	TBL *tbl;
2195480f120STim J. Robbins 	wchar_t **cols;
2205480f120STim J. Robbins 	wchar_t *last;
2210da30e9aSPeter Wemm 
222d54a8ce7SDavid E. O'Brien 	if ((t = tbl = calloc(entries, sizeof(TBL))) == NULL)
22318463c77SKevin Lo 		err(1, NULL);
2245480f120STim J. Robbins 	if ((cols = calloc((maxcols = DEFCOLS), sizeof(*cols))) == NULL)
22518463c77SKevin Lo 		err(1, NULL);
226d54a8ce7SDavid E. O'Brien 	if ((lens = calloc(maxcols, sizeof(int))) == NULL)
22718463c77SKevin Lo 		err(1, NULL);
2280da30e9aSPeter Wemm 	for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) {
2295480f120STim J. Robbins 		for (coloff = 0, p = *lp;
2305480f120STim J. Robbins 		    (cols[coloff] = wcstok(p, separator, &last));
2310da30e9aSPeter Wemm 		    p = NULL)
2320da30e9aSPeter Wemm 			if (++coloff == maxcols) {
2336508a1ecSIan Dowse 				if (!(cols = realloc(cols, ((u_int)maxcols +
234c9e1c304SUlrich Spörlein 				    DEFCOLS) * sizeof(wchar_t *))) ||
2350da30e9aSPeter Wemm 				    !(lens = realloc(lens,
2366508a1ecSIan Dowse 				    ((u_int)maxcols + DEFCOLS) * sizeof(int))))
2370da30e9aSPeter Wemm 					err(1, NULL);
2380da30e9aSPeter Wemm 				memset((char *)lens + maxcols * sizeof(int),
2390da30e9aSPeter Wemm 				    0, DEFCOLS * sizeof(int));
2400da30e9aSPeter Wemm 				maxcols += DEFCOLS;
2410da30e9aSPeter Wemm 			}
2425480f120STim J. Robbins 		if ((t->list = calloc(coloff, sizeof(*t->list))) == NULL)
24318463c77SKevin Lo 			err(1, NULL);
244d54a8ce7SDavid E. O'Brien 		if ((t->len = calloc(coloff, sizeof(int))) == NULL)
24518463c77SKevin Lo 			err(1, NULL);
2460da30e9aSPeter Wemm 		for (t->cols = coloff; --coloff >= 0;) {
2470da30e9aSPeter Wemm 			t->list[coloff] = cols[coloff];
2485480f120STim J. Robbins 			t->len[coloff] = width(cols[coloff]);
2490da30e9aSPeter Wemm 			if (t->len[coloff] > lens[coloff])
2500da30e9aSPeter Wemm 				lens[coloff] = t->len[coloff];
2510da30e9aSPeter Wemm 		}
2520da30e9aSPeter Wemm 	}
2530da30e9aSPeter Wemm 	for (cnt = 0, t = tbl; cnt < entries; ++cnt, ++t) {
2540da30e9aSPeter Wemm 		for (coloff = 0; coloff < t->cols  - 1; ++coloff)
2555480f120STim J. Robbins 			(void)wprintf(L"%ls%*ls", t->list[coloff],
2565480f120STim J. Robbins 			    lens[coloff] - t->len[coloff] + 2, L" ");
2575480f120STim J. Robbins 		(void)wprintf(L"%ls\n", t->list[coloff]);
258*290ce7d2SWarner Losh 		free(t->list);
259*290ce7d2SWarner Losh 		free(t->len);
2600da30e9aSPeter Wemm 	}
261*290ce7d2SWarner Losh 	free(lens);
262*290ce7d2SWarner Losh 	free(cols);
263*290ce7d2SWarner Losh 	free(tbl);
2640da30e9aSPeter Wemm }
2650da30e9aSPeter Wemm 
2660da30e9aSPeter Wemm #define	DEFNUM		1000
2670da30e9aSPeter Wemm #define	MAXLINELEN	(LINE_MAX + 1)
2680da30e9aSPeter Wemm 
269316b87cbSEd Schouten static void
input(FILE * fp)270823ab23fSDavid Malone input(FILE *fp)
2710da30e9aSPeter Wemm {
2720da30e9aSPeter Wemm 	static int maxentry;
2730da30e9aSPeter Wemm 	int len;
2745480f120STim J. Robbins 	wchar_t *p, buf[MAXLINELEN];
2750da30e9aSPeter Wemm 
2760da30e9aSPeter Wemm 	if (!list)
2775480f120STim J. Robbins 		if ((list = calloc((maxentry = DEFNUM), sizeof(*list))) ==
278d54a8ce7SDavid E. O'Brien 		    NULL)
27918463c77SKevin Lo 			err(1, NULL);
2805480f120STim J. Robbins 	while (fgetws(buf, MAXLINELEN, fp)) {
2815480f120STim J. Robbins 		for (p = buf; *p && iswspace(*p); ++p);
2820da30e9aSPeter Wemm 		if (!*p)
2830da30e9aSPeter Wemm 			continue;
2845480f120STim J. Robbins 		if (!(p = wcschr(p, L'\n'))) {
2850da30e9aSPeter Wemm 			warnx("line too long");
2860da30e9aSPeter Wemm 			eval = 1;
2870da30e9aSPeter Wemm 			continue;
2880da30e9aSPeter Wemm 		}
2895480f120STim J. Robbins 		*p = L'\0';
2905480f120STim J. Robbins 		len = width(buf);
2910da30e9aSPeter Wemm 		if (maxlength < len)
2920da30e9aSPeter Wemm 			maxlength = len;
2930da30e9aSPeter Wemm 		if (entries == maxentry) {
2940da30e9aSPeter Wemm 			maxentry += DEFNUM;
2950da30e9aSPeter Wemm 			if (!(list = realloc(list,
2965480f120STim J. Robbins 			    (u_int)maxentry * sizeof(*list))))
2970da30e9aSPeter Wemm 				err(1, NULL);
2980da30e9aSPeter Wemm 		}
2995480f120STim J. Robbins 		list[entries] = malloc((wcslen(buf) + 1) * sizeof(wchar_t));
3005480f120STim J. Robbins 		if (list[entries] == NULL)
3015480f120STim J. Robbins 			err(1, NULL);
3025480f120STim J. Robbins 		wcscpy(list[entries], buf);
3035480f120STim J. Robbins 		entries++;
3040da30e9aSPeter Wemm 	}
3050da30e9aSPeter Wemm }
3060da30e9aSPeter Wemm 
3075480f120STim J. Robbins /* Like wcswidth(), but ignores non-printing characters. */
308316b87cbSEd Schouten static int
width(const wchar_t * wcs)3095480f120STim J. Robbins width(const wchar_t *wcs)
3105480f120STim J. Robbins {
3115480f120STim J. Robbins 	int w, cw;
3125480f120STim J. Robbins 
3135480f120STim J. Robbins 	for (w = 0; *wcs != L'\0'; wcs++)
3145480f120STim J. Robbins 		if ((cw = wcwidth(*wcs)) > 0)
3155480f120STim J. Robbins 			w += cw;
3165480f120STim J. Robbins 	return (w);
3175480f120STim J. Robbins }
3185480f120STim J. Robbins 
319316b87cbSEd Schouten static void
usage(void)320823ab23fSDavid Malone usage(void)
3210da30e9aSPeter Wemm {
3220da30e9aSPeter Wemm 
3230da30e9aSPeter Wemm 	(void)fprintf(stderr,
3243b5ec5aaSPhilippe Charnier 	    "usage: column [-tx] [-c columns] [-s sep] [file ...]\n");
3250da30e9aSPeter Wemm 	exit(1);
3260da30e9aSPeter Wemm }
327