xref: /freebsd/usr.bin/column/column.c (revision 8a16b7a18f5d0b031f09832fd7752fba717e2a97)
1*8a16b7a1SPedro F. Giffuni /*-
2*8a16b7a1SPedro F. Giffuni  * SPDX-License-Identifier: BSD-3-Clause
3*8a16b7a1SPedro 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 #ifndef lint
33fa146c53SArchie Cobbs static const char copyright[] =
340da30e9aSPeter Wemm "@(#) Copyright (c) 1989, 1993, 1994\n\
350da30e9aSPeter Wemm 	The Regents of the University of California.  All rights reserved.\n";
3692407069SMark Murray #endif
370da30e9aSPeter Wemm 
389f5b04e9SDavid Malone #if 0
390da30e9aSPeter Wemm #ifndef lint
409f5b04e9SDavid Malone static char sccsid[] = "@(#)column.c	8.4 (Berkeley) 5/4/95";
4192407069SMark Murray #endif
429f5b04e9SDavid Malone #endif
439f5b04e9SDavid Malone 
449f5b04e9SDavid Malone #include <sys/cdefs.h>
459f5b04e9SDavid Malone __FBSDID("$FreeBSD$");
460da30e9aSPeter Wemm 
470da30e9aSPeter Wemm #include <sys/types.h>
480da30e9aSPeter Wemm #include <sys/ioctl.h>
49e2816c14SJuli Mallett #include <sys/param.h>
500da30e9aSPeter Wemm 
510da30e9aSPeter Wemm #include <err.h>
520da30e9aSPeter Wemm #include <limits.h>
535480f120STim J. Robbins #include <locale.h>
540da30e9aSPeter Wemm #include <stdio.h>
550da30e9aSPeter Wemm #include <stdlib.h>
560da30e9aSPeter Wemm #include <string.h>
570da30e9aSPeter Wemm #include <unistd.h>
585480f120STim J. Robbins #include <wchar.h>
595480f120STim J. Robbins #include <wctype.h>
600da30e9aSPeter Wemm 
61983e044aSDavid Malone #define	TAB	8
62983e044aSDavid Malone 
63316b87cbSEd Schouten static void	c_columnate(void);
64316b87cbSEd Schouten static void	input(FILE *);
65316b87cbSEd Schouten static void	maketbl(void);
66316b87cbSEd Schouten static void	print(void);
67316b87cbSEd Schouten static void	r_columnate(void);
68316b87cbSEd Schouten static void	usage(void);
69316b87cbSEd Schouten static int	width(const wchar_t *);
700da30e9aSPeter Wemm 
71316b87cbSEd Schouten static int	termwidth = 80;		/* default terminal width */
720da30e9aSPeter Wemm 
73316b87cbSEd Schouten static int	entries;		/* number of records */
74316b87cbSEd Schouten static int	eval;			/* exit value */
75316b87cbSEd Schouten static int	maxlength;		/* longest record */
76316b87cbSEd Schouten static wchar_t	**list;			/* array of pointers to records */
77316b87cbSEd Schouten static const wchar_t *separator = L"\t "; /* field separator for table option */
780da30e9aSPeter Wemm 
790da30e9aSPeter Wemm int
80823ab23fSDavid Malone main(int argc, char **argv)
810da30e9aSPeter Wemm {
820da30e9aSPeter Wemm 	struct winsize win;
830da30e9aSPeter Wemm 	FILE *fp;
840da30e9aSPeter Wemm 	int ch, tflag, xflag;
850da30e9aSPeter Wemm 	char *p;
865480f120STim J. Robbins 	const char *src;
875480f120STim J. Robbins 	wchar_t *newsep;
885480f120STim J. Robbins 	size_t seplen;
895480f120STim J. Robbins 
905480f120STim J. Robbins 	setlocale(LC_ALL, "");
910da30e9aSPeter Wemm 
920da30e9aSPeter Wemm 	if (ioctl(1, TIOCGWINSZ, &win) == -1 || !win.ws_col) {
93fa146c53SArchie Cobbs 		if ((p = getenv("COLUMNS")))
940da30e9aSPeter Wemm 			termwidth = atoi(p);
950da30e9aSPeter Wemm 	} else
960da30e9aSPeter Wemm 		termwidth = win.ws_col;
970da30e9aSPeter Wemm 
980da30e9aSPeter Wemm 	tflag = xflag = 0;
991c8af878SWarner Losh 	while ((ch = getopt(argc, argv, "c:s:tx")) != -1)
1000da30e9aSPeter Wemm 		switch(ch) {
1010da30e9aSPeter Wemm 		case 'c':
1020da30e9aSPeter Wemm 			termwidth = atoi(optarg);
1030da30e9aSPeter Wemm 			break;
1040da30e9aSPeter Wemm 		case 's':
1055480f120STim J. Robbins 			src = optarg;
1065480f120STim J. Robbins 			seplen = mbsrtowcs(NULL, &src, 0, NULL);
1075480f120STim J. Robbins 			if (seplen == (size_t)-1)
1085480f120STim J. Robbins 				err(1, "bad separator");
1095480f120STim J. Robbins 			newsep = malloc((seplen + 1) * sizeof(wchar_t));
1105480f120STim J. Robbins 			if (newsep == NULL)
1115480f120STim J. Robbins 				err(1, NULL);
1125480f120STim J. Robbins 			mbsrtowcs(newsep, &src, seplen + 1, NULL);
1135480f120STim J. Robbins 			separator = newsep;
1140da30e9aSPeter Wemm 			break;
1150da30e9aSPeter Wemm 		case 't':
1160da30e9aSPeter Wemm 			tflag = 1;
1170da30e9aSPeter Wemm 			break;
1180da30e9aSPeter Wemm 		case 'x':
1190da30e9aSPeter Wemm 			xflag = 1;
1200da30e9aSPeter Wemm 			break;
1210da30e9aSPeter Wemm 		case '?':
1220da30e9aSPeter Wemm 		default:
1230da30e9aSPeter Wemm 			usage();
1240da30e9aSPeter Wemm 		}
1250da30e9aSPeter Wemm 	argc -= optind;
1260da30e9aSPeter Wemm 	argv += optind;
1270da30e9aSPeter Wemm 
1280da30e9aSPeter Wemm 	if (!*argv)
1290da30e9aSPeter Wemm 		input(stdin);
1300da30e9aSPeter Wemm 	else for (; *argv; ++argv)
131fa146c53SArchie Cobbs 		if ((fp = fopen(*argv, "r"))) {
1320da30e9aSPeter Wemm 			input(fp);
1330da30e9aSPeter Wemm 			(void)fclose(fp);
1340da30e9aSPeter Wemm 		} else {
1350da30e9aSPeter Wemm 			warn("%s", *argv);
1360da30e9aSPeter Wemm 			eval = 1;
1370da30e9aSPeter Wemm 		}
1380da30e9aSPeter Wemm 
1390da30e9aSPeter Wemm 	if (!entries)
1400da30e9aSPeter Wemm 		exit(eval);
1410da30e9aSPeter Wemm 
142090782f5SRuslan Ermilov 	maxlength = roundup(maxlength + 1, TAB);
1430da30e9aSPeter Wemm 	if (tflag)
1440da30e9aSPeter Wemm 		maketbl();
1450da30e9aSPeter Wemm 	else if (maxlength >= termwidth)
1460da30e9aSPeter Wemm 		print();
1470da30e9aSPeter Wemm 	else if (xflag)
1480da30e9aSPeter Wemm 		c_columnate();
1490da30e9aSPeter Wemm 	else
1500da30e9aSPeter Wemm 		r_columnate();
1510da30e9aSPeter Wemm 	exit(eval);
1520da30e9aSPeter Wemm }
1530da30e9aSPeter Wemm 
154316b87cbSEd Schouten static void
155823ab23fSDavid Malone c_columnate(void)
1560da30e9aSPeter Wemm {
1570da30e9aSPeter Wemm 	int chcnt, col, cnt, endcol, numcols;
1585480f120STim J. Robbins 	wchar_t **lp;
1590da30e9aSPeter Wemm 
1600da30e9aSPeter Wemm 	numcols = termwidth / maxlength;
1610da30e9aSPeter Wemm 	endcol = maxlength;
1620da30e9aSPeter Wemm 	for (chcnt = col = 0, lp = list;; ++lp) {
1635480f120STim J. Robbins 		wprintf(L"%ls", *lp);
1645480f120STim J. Robbins 		chcnt += width(*lp);
1650da30e9aSPeter Wemm 		if (!--entries)
1660da30e9aSPeter Wemm 			break;
1670da30e9aSPeter Wemm 		if (++col == numcols) {
1680da30e9aSPeter Wemm 			chcnt = col = 0;
1690da30e9aSPeter Wemm 			endcol = maxlength;
1705480f120STim J. Robbins 			putwchar('\n');
1710da30e9aSPeter Wemm 		} else {
172090782f5SRuslan Ermilov 			while ((cnt = roundup(chcnt + 1, TAB)) <= endcol) {
1735480f120STim J. Robbins 				(void)putwchar('\t');
1740da30e9aSPeter Wemm 				chcnt = cnt;
1750da30e9aSPeter Wemm 			}
1760da30e9aSPeter Wemm 			endcol += maxlength;
1770da30e9aSPeter Wemm 		}
1780da30e9aSPeter Wemm 	}
1790da30e9aSPeter Wemm 	if (chcnt)
1805480f120STim J. Robbins 		putwchar('\n');
1810da30e9aSPeter Wemm }
1820da30e9aSPeter Wemm 
183316b87cbSEd Schouten static void
184823ab23fSDavid Malone r_columnate(void)
1850da30e9aSPeter Wemm {
1860da30e9aSPeter Wemm 	int base, chcnt, cnt, col, endcol, numcols, numrows, row;
1870da30e9aSPeter Wemm 
1880da30e9aSPeter Wemm 	numcols = termwidth / maxlength;
1890da30e9aSPeter Wemm 	numrows = entries / numcols;
1900da30e9aSPeter Wemm 	if (entries % numcols)
1910da30e9aSPeter Wemm 		++numrows;
1920da30e9aSPeter Wemm 
1930da30e9aSPeter Wemm 	for (row = 0; row < numrows; ++row) {
1940da30e9aSPeter Wemm 		endcol = maxlength;
1950da30e9aSPeter Wemm 		for (base = row, chcnt = col = 0; col < numcols; ++col) {
1965480f120STim J. Robbins 			wprintf(L"%ls", list[base]);
1975480f120STim J. Robbins 			chcnt += width(list[base]);
1980da30e9aSPeter Wemm 			if ((base += numrows) >= entries)
1990da30e9aSPeter Wemm 				break;
200090782f5SRuslan Ermilov 			while ((cnt = roundup(chcnt + 1, TAB)) <= endcol) {
2015480f120STim J. Robbins 				(void)putwchar('\t');
2020da30e9aSPeter Wemm 				chcnt = cnt;
2030da30e9aSPeter Wemm 			}
2040da30e9aSPeter Wemm 			endcol += maxlength;
2050da30e9aSPeter Wemm 		}
2065480f120STim J. Robbins 		putwchar('\n');
2070da30e9aSPeter Wemm 	}
2080da30e9aSPeter Wemm }
2090da30e9aSPeter Wemm 
210316b87cbSEd Schouten static void
211823ab23fSDavid Malone print(void)
2120da30e9aSPeter Wemm {
2130da30e9aSPeter Wemm 	int cnt;
2145480f120STim J. Robbins 	wchar_t **lp;
2150da30e9aSPeter Wemm 
2160da30e9aSPeter Wemm 	for (cnt = entries, lp = list; cnt--; ++lp)
2175480f120STim J. Robbins 		(void)wprintf(L"%ls\n", *lp);
2180da30e9aSPeter Wemm }
2190da30e9aSPeter Wemm 
2200da30e9aSPeter Wemm typedef struct _tbl {
2215480f120STim J. Robbins 	wchar_t **list;
2220da30e9aSPeter Wemm 	int cols, *len;
2230da30e9aSPeter Wemm } TBL;
2240da30e9aSPeter Wemm #define	DEFCOLS	25
2250da30e9aSPeter Wemm 
226316b87cbSEd Schouten static void
227823ab23fSDavid Malone maketbl(void)
2280da30e9aSPeter Wemm {
2290da30e9aSPeter Wemm 	TBL *t;
2300da30e9aSPeter Wemm 	int coloff, cnt;
2315480f120STim J. Robbins 	wchar_t *p, **lp;
2320da30e9aSPeter Wemm 	int *lens, maxcols;
2330da30e9aSPeter Wemm 	TBL *tbl;
2345480f120STim J. Robbins 	wchar_t **cols;
2355480f120STim J. Robbins 	wchar_t *last;
2360da30e9aSPeter Wemm 
237d54a8ce7SDavid E. O'Brien 	if ((t = tbl = calloc(entries, sizeof(TBL))) == NULL)
23818463c77SKevin Lo 		err(1, NULL);
2395480f120STim J. Robbins 	if ((cols = calloc((maxcols = DEFCOLS), sizeof(*cols))) == NULL)
24018463c77SKevin Lo 		err(1, NULL);
241d54a8ce7SDavid E. O'Brien 	if ((lens = calloc(maxcols, sizeof(int))) == NULL)
24218463c77SKevin Lo 		err(1, NULL);
2430da30e9aSPeter Wemm 	for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) {
2445480f120STim J. Robbins 		for (coloff = 0, p = *lp;
2455480f120STim J. Robbins 		    (cols[coloff] = wcstok(p, separator, &last));
2460da30e9aSPeter Wemm 		    p = NULL)
2470da30e9aSPeter Wemm 			if (++coloff == maxcols) {
2486508a1ecSIan Dowse 				if (!(cols = realloc(cols, ((u_int)maxcols +
249c9e1c304SUlrich Spörlein 				    DEFCOLS) * sizeof(wchar_t *))) ||
2500da30e9aSPeter Wemm 				    !(lens = realloc(lens,
2516508a1ecSIan Dowse 				    ((u_int)maxcols + DEFCOLS) * sizeof(int))))
2520da30e9aSPeter Wemm 					err(1, NULL);
2530da30e9aSPeter Wemm 				memset((char *)lens + maxcols * sizeof(int),
2540da30e9aSPeter Wemm 				    0, DEFCOLS * sizeof(int));
2550da30e9aSPeter Wemm 				maxcols += DEFCOLS;
2560da30e9aSPeter Wemm 			}
2575480f120STim J. Robbins 		if ((t->list = calloc(coloff, sizeof(*t->list))) == NULL)
25818463c77SKevin Lo 			err(1, NULL);
259d54a8ce7SDavid E. O'Brien 		if ((t->len = calloc(coloff, sizeof(int))) == NULL)
26018463c77SKevin Lo 			err(1, NULL);
2610da30e9aSPeter Wemm 		for (t->cols = coloff; --coloff >= 0;) {
2620da30e9aSPeter Wemm 			t->list[coloff] = cols[coloff];
2635480f120STim J. Robbins 			t->len[coloff] = width(cols[coloff]);
2640da30e9aSPeter Wemm 			if (t->len[coloff] > lens[coloff])
2650da30e9aSPeter Wemm 				lens[coloff] = t->len[coloff];
2660da30e9aSPeter Wemm 		}
2670da30e9aSPeter Wemm 	}
2680da30e9aSPeter Wemm 	for (cnt = 0, t = tbl; cnt < entries; ++cnt, ++t) {
2690da30e9aSPeter Wemm 		for (coloff = 0; coloff < t->cols  - 1; ++coloff)
2705480f120STim J. Robbins 			(void)wprintf(L"%ls%*ls", t->list[coloff],
2715480f120STim J. Robbins 			    lens[coloff] - t->len[coloff] + 2, L" ");
2725480f120STim J. Robbins 		(void)wprintf(L"%ls\n", t->list[coloff]);
2730da30e9aSPeter Wemm 	}
2740da30e9aSPeter Wemm }
2750da30e9aSPeter Wemm 
2760da30e9aSPeter Wemm #define	DEFNUM		1000
2770da30e9aSPeter Wemm #define	MAXLINELEN	(LINE_MAX + 1)
2780da30e9aSPeter Wemm 
279316b87cbSEd Schouten static void
280823ab23fSDavid Malone input(FILE *fp)
2810da30e9aSPeter Wemm {
2820da30e9aSPeter Wemm 	static int maxentry;
2830da30e9aSPeter Wemm 	int len;
2845480f120STim J. Robbins 	wchar_t *p, buf[MAXLINELEN];
2850da30e9aSPeter Wemm 
2860da30e9aSPeter Wemm 	if (!list)
2875480f120STim J. Robbins 		if ((list = calloc((maxentry = DEFNUM), sizeof(*list))) ==
288d54a8ce7SDavid E. O'Brien 		    NULL)
28918463c77SKevin Lo 			err(1, NULL);
2905480f120STim J. Robbins 	while (fgetws(buf, MAXLINELEN, fp)) {
2915480f120STim J. Robbins 		for (p = buf; *p && iswspace(*p); ++p);
2920da30e9aSPeter Wemm 		if (!*p)
2930da30e9aSPeter Wemm 			continue;
2945480f120STim J. Robbins 		if (!(p = wcschr(p, L'\n'))) {
2950da30e9aSPeter Wemm 			warnx("line too long");
2960da30e9aSPeter Wemm 			eval = 1;
2970da30e9aSPeter Wemm 			continue;
2980da30e9aSPeter Wemm 		}
2995480f120STim J. Robbins 		*p = L'\0';
3005480f120STim J. Robbins 		len = width(buf);
3010da30e9aSPeter Wemm 		if (maxlength < len)
3020da30e9aSPeter Wemm 			maxlength = len;
3030da30e9aSPeter Wemm 		if (entries == maxentry) {
3040da30e9aSPeter Wemm 			maxentry += DEFNUM;
3050da30e9aSPeter Wemm 			if (!(list = realloc(list,
3065480f120STim J. Robbins 			    (u_int)maxentry * sizeof(*list))))
3070da30e9aSPeter Wemm 				err(1, NULL);
3080da30e9aSPeter Wemm 		}
3095480f120STim J. Robbins 		list[entries] = malloc((wcslen(buf) + 1) * sizeof(wchar_t));
3105480f120STim J. Robbins 		if (list[entries] == NULL)
3115480f120STim J. Robbins 			err(1, NULL);
3125480f120STim J. Robbins 		wcscpy(list[entries], buf);
3135480f120STim J. Robbins 		entries++;
3140da30e9aSPeter Wemm 	}
3150da30e9aSPeter Wemm }
3160da30e9aSPeter Wemm 
3175480f120STim J. Robbins /* Like wcswidth(), but ignores non-printing characters. */
318316b87cbSEd Schouten static int
3195480f120STim J. Robbins width(const wchar_t *wcs)
3205480f120STim J. Robbins {
3215480f120STim J. Robbins 	int w, cw;
3225480f120STim J. Robbins 
3235480f120STim J. Robbins 	for (w = 0; *wcs != L'\0'; wcs++)
3245480f120STim J. Robbins 		if ((cw = wcwidth(*wcs)) > 0)
3255480f120STim J. Robbins 			w += cw;
3265480f120STim J. Robbins 	return (w);
3275480f120STim J. Robbins }
3285480f120STim J. Robbins 
329316b87cbSEd Schouten static void
330823ab23fSDavid Malone usage(void)
3310da30e9aSPeter Wemm {
3320da30e9aSPeter Wemm 
3330da30e9aSPeter Wemm 	(void)fprintf(stderr,
3343b5ec5aaSPhilippe Charnier 	    "usage: column [-tx] [-c columns] [-s sep] [file ...]\n");
3350da30e9aSPeter Wemm 	exit(1);
3360da30e9aSPeter Wemm }
337