xref: /freebsd/usr.bin/fold/fold.c (revision 22cf89c938886d14f5796fc49f9f020c23ea8eaf)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Kevin Ruddy.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #ifndef lint
36 static const char copyright[] =
37 "@(#) Copyright (c) 1990, 1993\n\
38 	The Regents of the University of California.  All rights reserved.\n";
39 #endif /* not lint */
40 
41 #ifndef lint
42 #if 0
43 static char sccsid[] = "@(#)fold.c	8.1 (Berkeley) 6/6/93";
44 #endif
45 #endif /* not lint */
46 
47 #include <sys/cdefs.h>
48 #include <err.h>
49 #include <limits.h>
50 #include <locale.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <wchar.h>
56 #include <wctype.h>
57 
58 #define	DEFLINEWIDTH	80
59 
60 void fold(int);
61 static int newpos(int, wint_t);
62 static void usage(void) __dead2;
63 
64 static int bflag;		/* Count bytes, not columns */
65 static int sflag;		/* Split on word boundaries */
66 
67 int
68 main(int argc, char **argv)
69 {
70 	int ch, previous_ch;
71 	int rval, width;
72 
73 	(void) setlocale(LC_CTYPE, "");
74 
75 	width = -1;
76 	previous_ch = 0;
77 	while ((ch = getopt(argc, argv, "0123456789bsw:")) != -1) {
78 		switch (ch) {
79 		case 'b':
80 			bflag = 1;
81 			break;
82 		case 's':
83 			sflag = 1;
84 			break;
85 		case 'w':
86 			if ((width = atoi(optarg)) <= 0) {
87 				errx(1, "illegal width value");
88 			}
89 			break;
90 		case '0': case '1': case '2': case '3': case '4':
91 		case '5': case '6': case '7': case '8': case '9':
92 			/* Accept a width as eg. -30. Note that a width
93 			 * specified using the -w option is always used prior
94 			 * to this undocumented option. */
95 			switch (previous_ch) {
96 			case '0': case '1': case '2': case '3': case '4':
97 			case '5': case '6': case '7': case '8': case '9':
98 				/* The width is a number with multiple digits:
99 				 * add the last one. */
100 				width = width * 10 + (ch - '0');
101 				break;
102 			default:
103 				/* Set the width, unless it was previously
104 				 * set. For instance, the following options
105 				 * would all give a width of 5 and not 10:
106 				 *   -10 -w5
107 				 *   -5b10
108 				 *   -5 -10b */
109 				if (width == -1)
110 					width = ch - '0';
111 				break;
112 			}
113 			break;
114 		default:
115 			usage();
116 		}
117 		previous_ch = ch;
118 	}
119 	argv += optind;
120 	argc -= optind;
121 
122 	if (width == -1)
123 		width = DEFLINEWIDTH;
124 	rval = 0;
125 	if (!*argv)
126 		fold(width);
127 	else for (; *argv; ++argv)
128 		if (!freopen(*argv, "r", stdin)) {
129 			warn("%s", *argv);
130 			rval = 1;
131 		} else
132 			fold(width);
133 	exit(rval);
134 }
135 
136 static void
137 usage(void)
138 {
139 	(void)fprintf(stderr, "usage: fold [-bs] [-w width] [file ...]\n");
140 	exit(1);
141 }
142 
143 /*
144  * Fold the contents of standard input to fit within WIDTH columns (or bytes)
145  * and write to standard output.
146  *
147  * If sflag is set, split the line at the last space character on the line.
148  * This flag necessitates storing the line in a buffer until the current
149  * column > width, or a newline or EOF is read.
150  *
151  * The buffer can grow larger than WIDTH due to backspaces and carriage
152  * returns embedded in the input stream.
153  */
154 void
155 fold(int width)
156 {
157 	static wchar_t *buf;
158 	static int buf_max;
159 	int col, i, indx, space;
160 	wint_t ch;
161 
162 	col = indx = 0;
163 	while ((ch = getwchar()) != WEOF) {
164 		if (ch == '\n') {
165 			wprintf(L"%.*ls\n", indx, buf);
166 			col = indx = 0;
167 			continue;
168 		}
169 		if ((col = newpos(col, ch)) > width) {
170 			if (sflag) {
171 				i = indx;
172 				while (--i >= 0 && !iswblank(buf[i]))
173 					;
174 				space = i;
175 			}
176 			if (sflag && space != -1) {
177 				space++;
178 				wprintf(L"%.*ls\n", space, buf);
179 				wmemmove(buf, buf + space, indx - space);
180 				indx -= space;
181 				col = 0;
182 				for (i = 0; i < indx; i++)
183 					col = newpos(col, buf[i]);
184 			} else {
185 				wprintf(L"%.*ls\n", indx, buf);
186 				col = indx = 0;
187 			}
188 			col = newpos(col, ch);
189 		}
190 		if (indx + 1 > buf_max) {
191 			buf_max += LINE_MAX;
192 			buf = realloc(buf, sizeof(*buf) * buf_max);
193 			if (buf == NULL)
194 				err(1, "realloc()");
195 		}
196 		buf[indx++] = ch;
197 	}
198 
199 	if (indx != 0)
200 		wprintf(L"%.*ls", indx, buf);
201 }
202 
203 /*
204  * Update the current column position for a character.
205  */
206 static int
207 newpos(int col, wint_t ch)
208 {
209 	char buf[MB_LEN_MAX];
210 	size_t len;
211 	int w;
212 
213 	if (bflag) {
214 		len = wcrtomb(buf, ch, NULL);
215 		col += len;
216 	} else
217 		switch (ch) {
218 		case '\b':
219 			if (col > 0)
220 				--col;
221 			break;
222 		case '\r':
223 			col = 0;
224 			break;
225 		case '\t':
226 			col = (col + 8) & ~7;
227 			break;
228 		default:
229 			if ((w = wcwidth(ch)) > 0)
230 				col += w;
231 			break;
232 		}
233 
234 	return (col);
235 }
236