xref: /freebsd/usr.bin/fold/fold.c (revision 8aac90f18aef7c9eea906c3ff9a001ca7b94f375)
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 #include <err.h>
36 #include <limits.h>
37 #include <locale.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <wchar.h>
43 #include <wctype.h>
44 
45 #define	DEFLINEWIDTH	80
46 
47 void fold(int);
48 static int newpos(int, wint_t);
49 static void usage(void) __dead2;
50 
51 static int bflag;		/* Count bytes, not columns */
52 static int sflag;		/* Split on word boundaries */
53 
54 int
55 main(int argc, char **argv)
56 {
57 	int ch, previous_ch;
58 	int rval, width;
59 
60 	(void) setlocale(LC_CTYPE, "");
61 
62 	width = -1;
63 	previous_ch = 0;
64 	while ((ch = getopt(argc, argv, "0123456789bsw:")) != -1) {
65 		switch (ch) {
66 		case 'b':
67 			bflag = 1;
68 			break;
69 		case 's':
70 			sflag = 1;
71 			break;
72 		case 'w':
73 			if ((width = atoi(optarg)) <= 0) {
74 				errx(1, "illegal width value");
75 			}
76 			break;
77 		case '0': case '1': case '2': case '3': case '4':
78 		case '5': case '6': case '7': case '8': case '9':
79 			/* Accept a width as eg. -30. Note that a width
80 			 * specified using the -w option is always used prior
81 			 * to this undocumented option. */
82 			switch (previous_ch) {
83 			case '0': case '1': case '2': case '3': case '4':
84 			case '5': case '6': case '7': case '8': case '9':
85 				/* The width is a number with multiple digits:
86 				 * add the last one. */
87 				width = width * 10 + (ch - '0');
88 				break;
89 			default:
90 				/* Set the width, unless it was previously
91 				 * set. For instance, the following options
92 				 * would all give a width of 5 and not 10:
93 				 *   -10 -w5
94 				 *   -5b10
95 				 *   -5 -10b */
96 				if (width == -1)
97 					width = ch - '0';
98 				break;
99 			}
100 			break;
101 		default:
102 			usage();
103 		}
104 		previous_ch = ch;
105 	}
106 	argv += optind;
107 	argc -= optind;
108 
109 	if (width == -1)
110 		width = DEFLINEWIDTH;
111 	rval = 0;
112 	if (!*argv)
113 		fold(width);
114 	else for (; *argv; ++argv)
115 		if (!freopen(*argv, "r", stdin)) {
116 			warn("%s", *argv);
117 			rval = 1;
118 		} else
119 			fold(width);
120 	exit(rval);
121 }
122 
123 static void
124 usage(void)
125 {
126 	(void)fprintf(stderr, "usage: fold [-bs] [-w width] [file ...]\n");
127 	exit(1);
128 }
129 
130 /*
131  * Fold the contents of standard input to fit within WIDTH columns (or bytes)
132  * and write to standard output.
133  *
134  * If sflag is set, split the line at the last space character on the line.
135  * This flag necessitates storing the line in a buffer until the current
136  * column > width, or a newline or EOF is read.
137  *
138  * The buffer can grow larger than WIDTH due to backspaces and carriage
139  * returns embedded in the input stream.
140  */
141 void
142 fold(int width)
143 {
144 	static wchar_t *buf;
145 	static int buf_max;
146 	int col, i, indx, space;
147 	wint_t ch;
148 
149 	col = indx = 0;
150 	while ((ch = getwchar()) != WEOF) {
151 		if (ch == '\n') {
152 			wprintf(L"%.*ls\n", indx, buf);
153 			col = indx = 0;
154 			continue;
155 		}
156 		if ((col = newpos(col, ch)) > width) {
157 			if (sflag) {
158 				i = indx;
159 				while (--i >= 0 && !iswblank(buf[i]))
160 					;
161 				space = i;
162 			}
163 			if (sflag && space != -1) {
164 				space++;
165 				wprintf(L"%.*ls\n", space, buf);
166 				wmemmove(buf, buf + space, indx - space);
167 				indx -= space;
168 				col = 0;
169 				for (i = 0; i < indx; i++)
170 					col = newpos(col, buf[i]);
171 			} else {
172 				wprintf(L"%.*ls\n", indx, buf);
173 				col = indx = 0;
174 			}
175 			col = newpos(col, ch);
176 		}
177 		if (indx + 1 > buf_max) {
178 			buf_max += LINE_MAX;
179 			buf = realloc(buf, sizeof(*buf) * buf_max);
180 			if (buf == NULL)
181 				err(1, "realloc()");
182 		}
183 		buf[indx++] = ch;
184 	}
185 
186 	if (indx != 0)
187 		wprintf(L"%.*ls", indx, buf);
188 }
189 
190 /*
191  * Update the current column position for a character.
192  */
193 static int
194 newpos(int col, wint_t ch)
195 {
196 	char buf[MB_LEN_MAX];
197 	size_t len;
198 	int w;
199 
200 	if (bflag) {
201 		len = wcrtomb(buf, ch, NULL);
202 		col += len;
203 	} else
204 		switch (ch) {
205 		case '\b':
206 			if (col > 0)
207 				--col;
208 			break;
209 		case '\r':
210 			col = 0;
211 			break;
212 		case '\t':
213 			col = (col + 8) & ~7;
214 			break;
215 		default:
216 			if ((w = wcwidth(ch)) > 0)
217 				col += w;
218 			break;
219 		}
220 
221 	return (col);
222 }
223