chiark / gitweb /
Build system: Use Automake's `silent-rules' feature if it's available.
[misc] / space.c
CommitLineData
df33ee54
MW
1#include <ctype.h>
2#include <errno.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include <sys/types.h>
8#include <sys/stat.h>
9
10#include <fcntl.h>
11#include <getopt.h>
12#include <unistd.h>
13
14enum {
15 OK = 0,
16 BADNESS = 1,
17 TROUBLE = 32
18};
19
20static const char *ego = "<unset>";
21
22static const char *bkp = 0;
23
24static unsigned flags = 0;
25#define F_MIDLINETABS 1u
26#define F_INPLACE 2u
27#define F_CHECK 4u
28#define F_BOGUS 8u
29#define F_UNTABIFY 16u
30#define F_TABIFY 32u
31#define F_VERBOSE 64u
32
33static void usage(FILE *fp)
34 { fprintf(fp, "Usage: %s [-cmtuv] [-i[BKP]] [FILE...]\n\n", ego); }
35
36static char *augment(const char *name, const char *suffix)
37{
38 size_t n = strlen(name), nn = strlen(suffix);
39 char *p = malloc(n + nn + 1);
40
41 if (!p) {
42 fprintf(stderr, "%s: Out of memory!\n", ego);
43 return (0);
44 }
45 memcpy(p, name, n);
46 memcpy(p + n, suffix, nn + 1);
47 return (p);
48}
49
50static FILE *freshname(const char *name, char **newname, mode_t mode)
51{
52 char buf[16];
53 int i;
54 int fd;
55 FILE *fp;
56 char *n;
57
58 for (i = 0; i < 32767; i++) {
59 sprintf(buf, ".new%d", i);
60 if ((n = augment(name, buf)) == 0)
61 goto fail_0;
62 if ((fd = open(n, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
63 if (errno == EEXIST) {
64 free(n);
65 continue;
66 }
67 fprintf(stderr, "%s: Can't create new file for `%s': %s\n",
68 ego, name, strerror(errno));
69 goto fail_1;
70 }
71 goto win;
72 }
73 fprintf(stderr, "%s: Can't find new file to update `%s'\n", ego, name);
74 goto fail_1;
75
76win:
77 if (chmod(n, mode)) {
78 fprintf(stderr, "%s: Can't set permissions on `%s': %s\n",
79 ego, n, strerror(errno));
80 goto fail_2;
81 }
82 if ((fp = fdopen(fd, "w")) == 0) {
83 fprintf(stderr, "%s: fdopen on `%s' failed: %s\n",
84 ego, n, strerror(errno));
85 goto fail_2;
86 }
87 *newname = n;
88 return (fp);
89
90fail_2:
91 close(fd);
92fail_1:
93 free(n);
94fail_0:
95 return (0);
96}
97
98typedef struct buf {
99 char *b;
100 size_t n;
101 size_t sz;
102} buf;
103#define BUF_INIT { 0, 0, 0 }
104
105static void reset(buf *b) { b->n = 0; }
106
107static int put(buf *b, int ch)
108{
109 size_t w;
110
111 if (b->n >= b->sz) {
112 if (!b->sz) {
113 w = 64;
114 b->b = malloc(w);
115 } else {
116 w = b->sz * 2;
117 b->b = realloc(b->b, w);
118 }
119 if (!b->b) {
120 fprintf(stderr, "%s: Not enough memory for buffer!\n", ego);
121 return (-1);
122 }
123 b->sz = w;
124 }
125 b->b[b->n++] = ch;
126 return (0);
127}
128
129#define TABSTOP(n) (((n) + 8u) & ~7u)
130
131static int space(const char *name)
132{
133 static buf b = BUF_INIT;
134 FILE *fin, *fout = stdout;
135 char *newname = 0, *oldname = 0;
136 int rc = TROUBLE, status = OK;
137 int last = '\n';
138 unsigned nsp = 0, nwsp = 0, hpos = 0, ohpos = 0, nhpos = 0, nl = 1;
139 unsigned i;
140#define f_newline 1u
141#define f_warnspacetab 2u
142#define f_tabify 4u
143#define f_warntabs 8u
144#define f_warnspaces 16u
145#define f_tab 32u
146#define f_bad 64u
147#define f_forced 128u
148 unsigned f = f_newline | (flags & F_TABIFY ? f_tabify : 0);
149 int ch;
150
151 if (strcmp(name, "-") == 0) {
152 if (flags & F_INPLACE) {
153 fprintf(stderr, "%s: Can't modify stdin in-place.\n", ego);
154 goto done_0;
155 }
156 fin = stdin;
157 } else {
158 if ((fin = fopen(name, "r")) == 0) {
159 fprintf(stderr, "%s: Failed to open file `%s': %s.\n",
160 ego, name, strerror(errno));
161 goto done_0;
162 }
163 else if (flags & F_INPLACE) {
164 struct stat st;
165 if (stat(name, &st)) {
166 fprintf(stderr, "%s: Can't stat `%s': %s.\n",
167 ego, name, strerror(errno));
168 goto done_1;
169 }
170 if ((fout = freshname(name, &newname, st.st_mode)) == 0)
171 goto done_1;
172 }
173 }
174 if (flags & F_CHECK)
175 fout = 0;
176
177 for (;;) {
178 ch = getc(fin);
179 switch (ch) {
180 case ' ':
181 nsp++; nwsp++; hpos++;
182 if (put(&b, ' ')) goto done_2;
183 break;
184 case '\t':
185 if (flags & F_UNTABIFY) {
f4ed186a 186 if ((flags & F_VERBOSE) && !(f & f_warntabs)) {
df33ee54
MW
187 fprintf(stderr, "%s:%u: found tab\n", name, nl);
188 f |= f_warntabs;
df33ee54 189 }
f4ed186a 190 status = BADNESS;
df33ee54
MW
191 } else if (((flags & F_MIDLINETABS) || (f & f_newline)) && nsp) {
192 if ((flags & F_VERBOSE) && !(f & f_warnspacetab)) {
193 fprintf(stderr, "%s:%u: space followed by tab\n", name, nl);
194 f |= f_warnspacetab;
df33ee54 195 }
f4ed186a 196 status = BADNESS;
df33ee54
MW
197 f |= f_tabify | f_forced;
198 }
199 f |= f_tab;
200 nsp = 0; nwsp++; hpos = TABSTOP(hpos);
201 if (put(&b, '\t')) goto done_2;
202 break;
203 case EOF:
204 if (nwsp || !(f & f_newline)) {
205 if (flags & F_VERBOSE)
206 fprintf(stderr, "%s:%u: file ends in mid-line\n", name, nl);
207 status = BADNESS;
208 if (fout) putc('\n', fout);
209 }
210 goto end;
211 case '\n':
212 case '\v':
f4ed186a
MW
213 if (nwsp) {
214 if (flags & F_VERBOSE)
215 fprintf(stderr, "%s:%u: trailing whitespace\n", name, nl);
df33ee54
MW
216 status = BADNESS;
217 }
218 if (fout) putc('\n', fout);
219 reset(&b);
220 nsp = nwsp = hpos = ohpos = 0; nl++;
221 f |= f_newline;
222 f &= ~(f_tab | f_warnspacetab | f_warntabs | f_warnspaces);
223 if (flags & F_TABIFY)
224 f |= f_tabify;
225 else
226 f &= ~f_tabify;
227 last = '\n';
228 break;
229 default:
230 if (nwsp) {
231 if (flags & F_UNTABIFY) {
232 if (fout) for (; ohpos < hpos; ohpos++) putc(' ', fout);
233 } else if ((f & f_tabify) &&
234 ((hpos - ohpos >= (last == '.' || last == ':' ?
235 3 : 2)) ||
236 (f & (f_tab | f_newline)))) {
237 i = 0;
238 for (;;) {
239 nhpos = TABSTOP(ohpos);
240 if (nhpos > hpos) break;
241 if (fout) putc('\t', fout);
242 if ((flags & F_VERBOSE) && (flags & F_TABIFY) &&
243 i < b.n && b.b[i] != '\t' &&
244 !(f & (f_warnspaces | f_forced))) {
245 fprintf(stderr, "%s:%u: spaces could be turned into tabs\n",
246 name, nl);
247 f |= f_warnspaces;
248 }
249 ohpos = nhpos;
250 i++;
251 }
252 if (fout)
253 for (; ohpos < hpos; ohpos++) putc(' ', fout);
254 } else if (fout)
255 for (i = 0; i < b.n; i++) putc(b.b[i], fout);
256 }
257 reset(&b);
258 f &= ~(f_newline | f_tab | f_forced);
259 if (!(flags & F_TABIFY) || !(flags & F_MIDLINETABS)) f &= ~f_tabify;
260 nwsp = nsp = 0;
261 hpos++; ohpos = hpos;
262 if (fout) putc(ch, fout);
263 if (ch != '"' && ch != '\'')
264 last = ch;
265 break;
266 }
267 }
268end:;
269
270 if (ferror(fin)) {
271 fprintf(stderr, "%s: Error reading `%s': %s\n",
272 ego, name, strerror(errno));
273 goto done_2;
274 }
275
276 if (fout) {
277 if (fflush(fout) || ferror(fout)) f |= f_bad;
278 if (fout != stdout && fclose(fout)) f |= f_bad;
279 fout = 0;
280 if (f & f_bad) {
281 fprintf(stderr, "%s: Error writing `%s': %s\n",
282 ego, newname, strerror(errno));
283 goto done_2;
284 }
285 }
286
287 if (flags & F_INPLACE) {
288 if (bkp) {
289 if ((oldname = augment(name, bkp)) == 0)
290 goto done_2;
291 if (rename(name, oldname)) {
292 fprintf(stderr, "%s: Failed to back up `%s' as `%s': %s\n",
293 ego, name, oldname, strerror(errno));
294 goto done_2;
295 }
296 }
297 if (rename(newname, name)) {
298 if (oldname) rename(oldname, name);
299 fprintf(stderr, "%s: Failed to install `%s' as `%s': %s\n",
300 ego, newname, name, strerror(errno));
301 goto done_2;
302 }
303 }
304
305 rc = status;
306
307done_2:
308 if (oldname) free(oldname);
309 if (newname) {
310 remove(newname);
311 free(newname);
312 }
313done_1:
314 if (fout && fout != stdout) fclose(fout);
315 fclose(fin);
316done_0:
317 return (rc);
318}
319
320static int manysetp(unsigned f) { return (!!(f & (f - 1))); }
321
322int main(int argc, char *argv[])
323{
324 int i;
325 int rc = OK, st;
326
327 if ((ego = strrchr(argv[0], '/')) == 0)
328 ego = argv[0];
329 else
330 ego++;
331
332 for (;;) {
333 if ((i = getopt(argc, argv, "h" "cmtuv" "i::")) < 0)
334 break;
335 switch (i) {
336 case 'h':
337 printf("%s -- remove extraneous spaces from files\n\n", ego);
338 usage(stdout);
339 fputs("Options:\n\
340 -h Print this help text\n\
341 -c Check files for badness, but don't produce other output\n\
342 -m Fix spaces followed by tabs in mid-line\n\
343 -t Tabify file completely\n\
344 -u Untabify file completely\n\
345 -i[BKP] Modify files in place; leave FILEBKP as copy of old FILE\n\
346", stdout);
347 exit(0);
348 case 'i':
349 bkp = optarg;
350 flags |= F_INPLACE;
351 break;
352 case 'm':
353 flags |= F_MIDLINETABS;
354 break;
355 case 'c':
356 flags |= F_CHECK;
357 break;
358 case 't':
359 flags |= F_TABIFY;
360 break;
361 case 'u':
362 flags |= F_UNTABIFY;
363 break;
364 case 'v':
365 flags |= F_VERBOSE;
366 break;
367 default:
368 flags |= F_BOGUS;
369 break;
370 }
371 }
372 if (flags & F_BOGUS) {
373 usage(stderr);
374 exit(TROUBLE);
375 }
376 if (manysetp(flags & (F_CHECK | F_INPLACE))) {
377 fprintf(stderr, "%s: Options -c and -i are mutually exclusive.\n", ego);
378 exit(TROUBLE);
379 }
380 if (manysetp(flags & (F_TABIFY | F_UNTABIFY))) {
381 fprintf(stderr, "%s: Options -t and -u are mutually exclusive.\n", ego);
382 exit(TROUBLE);
383 }
384
385 if (optind == argc) {
386 if (isatty(0)) {
387 fprintf(stderr, "%s: No options given and stdin is a terminal.\n",
388 ego);
389 exit(TROUBLE);
390 }
391 rc = space("-");
392 } else for (i = optind; i < argc; i++) {
393 st = space(argv[i]);
394 if (st > rc) rc = st;
395 }
396 if (rc == BADNESS && !(flags & F_CHECK))
397 rc = OK;
398 return (rc);
399}