xref: /freebsd/usr.bin/unexpand/unexpand.c (revision 63f537551380d2dab29fa402ad1269feae17e594)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1980, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 
33 #ifndef lint
34 static const char copyright[] =
35 "@(#) Copyright (c) 1980, 1993\n\
36 	The Regents of the University of California.  All rights reserved.\n";
37 #endif
38 
39 #ifndef lint
40 static const char sccsid[] = "@(#)unexpand.c	8.1 (Berkeley) 6/6/93";
41 #endif
42 
43 /*
44  * unexpand - put tabs into a file replacing blanks
45  */
46 #include <ctype.h>
47 #include <err.h>
48 #include <limits.h>
49 #include <locale.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <wchar.h>
55 #include <wctype.h>
56 
57 static int	all;
58 static int	nstops;
59 static int	tabstops[100];
60 
61 static void getstops(const char *);
62 static void usage(void) __dead2;
63 static int tabify(const char *);
64 
65 int
66 main(int argc, char *argv[])
67 {
68 	int ch, failed;
69 	char *filename;
70 
71 	setlocale(LC_CTYPE, "");
72 
73 	nstops = 1;
74 	tabstops[0] = 8;
75 	while ((ch = getopt(argc, argv, "at:")) != -1) {
76 		switch (ch) {
77 		case 'a':	/* Un-expand all spaces, not just leading. */
78 			all = 1;
79 			break;
80 		case 't':	/* Specify tab list, implies -a. */
81 			getstops(optarg);
82 			all = 1;
83 			break;
84 		default:
85 			usage();
86 			/*NOTREACHED*/
87 		}
88 	}
89 	argc -= optind;
90 	argv += optind;
91 
92 	failed = 0;
93 	if (argc == 0)
94 		failed |= tabify("stdin");
95 	else {
96 		while ((filename = *argv++) != NULL) {
97 			if (freopen(filename, "r", stdin) == NULL) {
98 				warn("%s", filename);
99 				failed = 1;
100 			} else
101 				failed |= tabify(filename);
102 		}
103 	}
104 	exit(failed != 0);
105 }
106 
107 static void
108 usage(void)
109 {
110 	fprintf(stderr, "usage: unexpand [-a | -t tablist] [file ...]\n");
111 	exit(1);
112 }
113 
114 static int
115 tabify(const char *curfile)
116 {
117 	int dcol, doneline, limit, n, ocol, width;
118 	wint_t ch;
119 
120 	limit = nstops == 1 ? INT_MAX : tabstops[nstops - 1] - 1;
121 
122 	doneline = ocol = dcol = 0;
123 	while ((ch = getwchar()) != WEOF) {
124 		if (ch == ' ' && !doneline) {
125 			if (++dcol >= limit)
126 				doneline = 1;
127 			continue;
128 		} else if (ch == '\t') {
129 			if (nstops == 1) {
130 				dcol = (1 + dcol / tabstops[0]) *
131 				    tabstops[0];
132 				continue;
133 			} else {
134 				for (n = 0; n < nstops &&
135 				    tabstops[n] - 1 < dcol; n++)
136 					;
137 				if (n < nstops - 1 && tabstops[n] - 1 < limit) {
138 					dcol = tabstops[n];
139 					continue;
140 				}
141 				doneline = 1;
142 			}
143 		}
144 
145 		/* Output maximal number of tabs. */
146 		if (nstops == 1) {
147 			while (((ocol + tabstops[0]) / tabstops[0])
148 			    <= (dcol / tabstops[0])) {
149 				if (dcol - ocol < 2)
150 					break;
151 				putwchar('\t');
152 				ocol = (1 + ocol / tabstops[0]) *
153 				    tabstops[0];
154 			}
155 		} else {
156 			for (n = 0; n < nstops && tabstops[n] - 1 < ocol; n++)
157 				;
158 			while (ocol < dcol && n < nstops && ocol < limit) {
159 				putwchar('\t');
160 				ocol = tabstops[n++];
161 			}
162 		}
163 
164 		/* Then spaces. */
165 		while (ocol < dcol && ocol < limit) {
166 			putwchar(' ');
167 			ocol++;
168 		}
169 
170 		if (ch == '\b') {
171 			putwchar('\b');
172 			if (ocol > 0)
173 				ocol--, dcol--;
174 		} else if (ch == '\n') {
175 			putwchar('\n');
176 			doneline = ocol = dcol = 0;
177 			continue;
178 		} else if (ch != ' ' || dcol > limit) {
179 			putwchar(ch);
180 			if ((width = wcwidth(ch)) > 0)
181 				ocol += width, dcol += width;
182 		}
183 
184 		/*
185 		 * Only processing leading blanks or we've gone past the
186 		 * last tab stop. Emit remainder of this line unchanged.
187 		 */
188 		if (!all || dcol >= limit) {
189 			while ((ch = getwchar()) != '\n' && ch != WEOF)
190 				putwchar(ch);
191 			if (ch == '\n')
192 				putwchar('\n');
193 			doneline = ocol = dcol = 0;
194 		}
195 	}
196 	if (ferror(stdin)) {
197 		warn("%s", curfile);
198 		return (1);
199 	}
200 	return (0);
201 }
202 
203 static void
204 getstops(const char *cp)
205 {
206 	int i;
207 
208 	nstops = 0;
209 	for (;;) {
210 		i = 0;
211 		while (*cp >= '0' && *cp <= '9')
212 			i = i * 10 + *cp++ - '0';
213 		if (i <= 0)
214 			errx(1, "bad tab stop spec");
215 		if (nstops > 0 && i <= tabstops[nstops-1])
216 			errx(1, "bad tab stop spec");
217 		if (nstops == sizeof(tabstops) / sizeof(*tabstops))
218 			errx(1, "too many tab stops");
219 		tabstops[nstops++] = i;
220 		if (*cp == 0)
221 			break;
222 		if (*cp != ',' && !isblank((unsigned char)*cp))
223 			errx(1, "bad tab stop spec");
224 		cp++;
225 	}
226 }
227