No empty .Rs/.Re
[netbsd-mini2440.git] / usr.bin / qsubst / qsubst.c
blobfca9fc9db920f62155a8e0a2e29d785f4bbeff91
1 /* $NetBSD: qsubst.c,v 1.7 2004/01/05 23:23:36 jmmv Exp $ */
3 /*
4 * qsubst -- designed for renaming routines existing in a whole bunch
5 * of files. Needs -ltermcap.
7 * Usage:
9 * qsubst str1 str2 [ options ]
11 * qsubst reads its options (see below) to get a list of files. For
12 * each file on this list, it then replaces str1 with str2 wherever
13 * possible in that file, depending on user input (see below). The
14 * result is written back onto the original file.
16 * For each possible substitution, the user is prompted with a few
17 * lines before and after the line containing the string to be
18 * substituted. The string itself is displayed using the terminal's
19 * standout mode, if any. Then one character is read from the
20 * terminal. This is then interpreted as follows (this is designed to
21 * be like Emacs' query-replace-string):
23 * space replace this occurrence and go on to the next one
24 * . replace this occurrence and don't change any more in
25 * this file (ie, go on to the next file).
26 * , tentatively replace this occurrence. The lines as they
27 * would look if the substitution were made are printed
28 * out. Then another character is read and it is used to
29 * decide the result (possibly undoing the tentative
30 * replacement).
31 * n don't change this one, but go on to the next one
32 * ^G don't change this one or any others in this file, but
33 * instead go on to the next file.
34 * ! change the rest in this file without asking, then go on
35 * to the next file (at which point qsubst will start
36 * asking again).
37 * ? print out the current filename and ask again.
39 * The first two arguments to qsubst are always the string to replace
40 * and the string to replace it with. The options are as follows:
42 * -w The search string is considered as a C symbol; it must
43 * be bounded by non-symbol characters. This option
44 * toggles. (`w' for `word'.)
45 * -! Enter ! mode automatically at the beginning of each
46 * file.
47 * -go Same as -!
48 * -noask Same as -!
49 * -nogo Negate -go
50 * -ask Negate -noask (same as -nogo)
51 * -cN (N is a number) Give N lines of context above and below
52 * the line with the match when prompting the user.
53 * -CAN (N is a number) Give N lines of context above the line
54 * with the match when prompting the user.
55 * -CBN (N is a number) Give N lines of context below the line
56 * with the match when prompting the user.
57 * -f filename
58 * The filename following the -f argument is one of the
59 * files qsubst should perform substitutions in.
60 * -F filename
61 * qsubst should read the named file to get the names of
62 * files to perform substitutions in. The names should
63 * appear one to a line.
65 * The default amount of context is -c2, that is, two lines above and
66 * two lines below the line with the match.
68 * Arguments not beginning with a - sign in the options field are
69 * implicitly preceded by -f. Thus, -f is really needed only when the
70 * file name begins with a - sign.
72 * qsubst reads its options in order and processes files as it gets
73 * them. This means, for example, that a -go will affect only files
74 * from -f or -F options appearing after the -go option.
76 * The most context you can get is ten lines each, above and below
77 * (corresponding to -c10).
79 * Str1 is limited to 512 characters; there is no limit on the size of
80 * str2. Neither one may contain a NUL.
82 * NULs in the file may cause qsubst to make various mistakes.
84 * If any other program modifies the file while qsubst is running, all
85 * bets are off.
87 * This program is in the public domain. Anyone may use it in any way
88 * for any purpose. Of course, it's also up to you to determine
89 * whether what it does is suitable for you; the above comments may
90 * help, but I can't promise they're accurate. It's free, and you get
91 * what you pay for.
93 * If you find any bugs I would appreciate hearing about them,
94 * especially if you also fix them.
96 * der Mouse
98 * mouse@rodents.montreal.qc.ca
100 #include <sys/cdefs.h>
102 #ifndef lint
103 __RCSID("$NetBSD: qsubst.c,v 1.7 2004/01/05 23:23:36 jmmv Exp $");
104 #endif
106 #include <sys/file.h>
108 #include <ctype.h>
109 #include <errno.h>
110 #include <signal.h>
111 #include <stdio.h>
112 #include <stdlib.h>
113 #include <strings.h>
114 #include <termcap.h>
115 #include <termios.h>
116 #include <unistd.h>
118 extern const char *__progname;
120 #define MAX_C_A 10
121 #define MAX_C_B 10
122 #define BUF_SIZ 1024
124 static int debugging;
125 static FILE *tempf;
126 static long tbeg;
127 static FILE *workf;
128 static char *str1;
129 static char *str2;
130 static int s1l;
131 static int s2l;
132 static long nls[MAX_C_A + 1];
133 static char buf[(BUF_SIZ * 2) + 2];
134 static char *bufp;
135 static char *bufp0;
136 static char *bufpmax;
137 static int rahead;
138 static int cabove;
139 static int cbelow;
140 static int wordmode;
141 static int flying;
142 static int flystate;
143 static int allfly;
144 static const char *nullstr = "";
145 static int ul_;
146 static char *current_file;
147 static const char *beginul;
148 static const char *endul;
149 static char tcp_buf[1024];
150 static char cap_buf[1024];
151 static struct termios orig_tio;
153 static void
154 tstp_self(void)
156 void (*old_tstp) (int);
157 int mask;
159 mask = sigblock(0);
160 kill(getpid(), SIGTSTP);
161 old_tstp = signal(SIGTSTP, SIG_DFL);
162 sigsetmask(mask & ~sigmask(SIGTSTP));
163 signal(SIGTSTP, old_tstp);
166 /* ARGSUSED */
167 static void
168 sigtstp(int sig)
170 struct termios tio;
172 if (tcgetattr(0, &tio) < 0) {
173 tstp_self();
174 return;
176 tcsetattr(0, TCSAFLUSH | TCSASOFT, &orig_tio);
177 tstp_self();
178 tcsetattr(0, TCSADRAIN | TCSASOFT, &tio);
181 static void
182 limit_above_below(void)
184 if (cabove > MAX_C_A) {
185 cabove = MAX_C_A;
187 if (cbelow > MAX_C_B) {
188 cbelow = MAX_C_B;
192 static int
193 issymchar(unsigned char c)
195 return (isascii(c) && (isalnum(c) || (c == '_') || (c == '$')));
198 static int
199 foundit(void)
201 if (wordmode) {
202 return (!issymchar(bufp[-1]) &&
203 !issymchar(bufp[-2 - s1l]) &&
204 !bcmp(bufp - 1 - s1l, str1, s1l));
205 } else {
206 return (!bcmp(bufp - s1l, str1, s1l));
210 static int
211 putcharf(int c)
213 return (putchar(c));
216 static void
217 put_ul(char *s)
219 if (ul_) {
220 for (; *s; s++) {
221 printf("_\b%c", *s);
223 } else {
224 tputs(beginul, 1, putcharf);
225 fputs(s, stdout);
226 tputs(endul, 1, putcharf);
230 static int
231 getc_cbreak(void)
233 struct termios tio;
234 struct termios otio;
235 char c;
237 if (tcgetattr(0, &tio) < 0)
238 return (getchar());
239 otio = tio;
240 tio.c_lflag &= ~(ICANON | ECHOKE | ECHOE | ECHO | ECHONL);
241 tio.c_cc[VMIN] = 1;
242 tio.c_cc[VTIME] = 0;
243 tcsetattr(0, TCSANOW | TCSASOFT, &tio);
244 switch (read(0, &c, 1)) {
245 case -1:
246 break;
247 case 0:
248 break;
249 case 1:
250 break;
252 tcsetattr(0, TCSANOW | TCSASOFT, &otio);
253 return (c);
256 static int
257 doit(void)
259 long save;
260 int i;
261 int lastnl;
262 int use_replacement;
264 if (flying) {
265 return (flystate);
267 use_replacement = 0;
268 save = ftell(workf);
269 do {
270 for (i = MAX_C_A - cabove; nls[i] < 0; i++);
271 fseek(workf, nls[i], 0);
272 for (i = save - nls[i] - rahead; i; i--) {
273 putchar(getc(workf));
275 put_ul(use_replacement ? str2 : str1);
276 fseek(workf, save + s1l - rahead, 0);
277 lastnl = 0;
278 i = cbelow + 1;
279 while (i > 0) {
280 int c;
281 c = getc(workf);
282 if (c == EOF) {
283 clearerr(workf);
284 break;
286 putchar(c);
287 lastnl = 0;
288 if (c == '\n') {
289 i--;
290 lastnl = 1;
293 if (!lastnl)
294 printf("\n[no final newline] ");
295 fseek(workf, save, 0);
296 i = -1;
297 while (i == -1) {
298 switch (getc_cbreak()) {
299 case ' ':
300 i = 1;
301 break;
302 case '.':
303 i = 1;
304 flying = 1;
305 flystate = 0;
306 break;
307 case 'n':
308 i = 0;
309 break;
310 case '\7':
311 i = 0;
312 flying = 1;
313 flystate = 0;
314 break;
315 case '!':
316 i = 1;
317 flying = 1;
318 flystate = 1;
319 break;
320 case ',':
321 use_replacement = !use_replacement;
322 i = -2;
323 printf("(using %s string gives)\n",
324 use_replacement ? "new" : "old");
325 break;
326 case '?':
327 printf("File is `%s'\n", current_file);
328 break;
329 default:
330 putchar('\7');
331 break;
334 } while (i < 0);
335 if (i) {
336 printf("(replacing");
337 } else {
338 printf("(leaving");
340 if (flying) {
341 if (flystate == i) {
342 printf(" this and all the rest");
343 } else if (flystate) {
344 printf(" this, replacing all the rest");
345 } else {
346 printf(" this, leaving all the rest");
349 printf(")\n");
350 return (i);
353 static void
354 add_shift(long *a, long e, int n)
356 int i;
358 n--;
359 for (i = 0; i < n; i++) {
360 a[i] = a[i + 1];
362 a[n] = e;
365 static void
366 process_file(char *fn)
368 int i;
369 long n;
370 int c;
372 workf = fopen(fn, "r+");
373 if (workf == NULL) {
374 fprintf(stderr, "%s: cannot read %s\n", __progname, fn);
375 return;
377 printf("(file: %s)\n", fn);
378 current_file = fn;
379 for (i = 0; i <= MAX_C_A; i++) {
380 nls[i] = -1;
382 nls[MAX_C_A] = 0;
383 tbeg = -1;
384 if (wordmode) {
385 bufp0 = &buf[1];
386 rahead = s1l + 1;
387 buf[0] = '\0';
388 } else {
389 bufp0 = &buf[0];
390 rahead = s1l;
392 if (debugging) {
393 printf("[rahead = %d, bufp0-buf = %ld]\n",
394 rahead, (long) (bufp0 - &buf[0]));
396 n = 0;
397 bufp = bufp0;
398 bufpmax = &buf[sizeof(buf) - s1l - 2];
399 flying = allfly;
400 flystate = 1;
401 while (1) {
402 c = getc(workf);
403 if (c == EOF) {
404 if (tbeg >= 0) {
405 if (bufp > bufp0)
406 fwrite(bufp0, 1, bufp - bufp0, tempf);
407 fseek(workf, tbeg, 0);
408 n = ftell(tempf);
409 fseek(tempf, 0L, 0);
410 for (; n; n--) {
411 putc(getc(tempf), workf);
413 fflush(workf);
414 ftruncate(fileno(workf), ftell(workf));
416 fclose(workf);
417 return;
419 *bufp++ = c;
420 n++;
421 if (debugging) {
422 printf("[got %c, n now %ld, bufp-buf %ld]\n",
423 c, n, (long) (bufp - bufp0));
425 if ((n >= rahead) && foundit() && doit()) {
426 int wbehind;
427 if (debugging) {
428 printf("[doing change]\n");
430 wbehind = 1;
431 if (tbeg < 0) {
432 tbeg = ftell(workf) - rahead;
433 fseek(tempf, 0L, 0);
434 if (debugging) {
435 printf("[tbeg set to %d]\n",
436 (int)tbeg);
438 wbehind = 0;
440 if (bufp[-1] == '\n')
441 add_shift(nls, ftell(workf), MAX_C_A + 1);
442 if ((n > rahead) && wbehind) {
443 fwrite(bufp0, 1, n - rahead, tempf);
444 if (debugging) {
445 printf("[writing %ld from bufp0]\n",
446 n - rahead);
449 fwrite(str2, 1, s2l, tempf);
450 n = rahead - s1l;
451 if (debugging) {
452 printf("[n now %ld]\n", n);
454 if (n > 0) {
455 bcopy(bufp - n, bufp0, n);
456 if (debugging) {
457 printf("[copying %ld back]\n", n);
460 bufp = bufp0 + n;
461 } else {
462 if (bufp[-1] == '\n')
463 add_shift(nls, ftell(workf), MAX_C_A + 1);
464 if (bufp >= bufpmax) {
465 if (tbeg >= 0) {
466 fwrite(bufp0, 1, n - rahead, tempf);
467 if (debugging) {
468 printf("[flushing %ld]\n",
469 n - rahead);
472 n = rahead;
473 bcopy(bufp - n, bufp0, n);
474 if (debugging) {
475 printf("[n now %ld]\n[copying %ld back]\n", n, n);
477 bufp = bufp0 + n;
483 static void
484 process_indir_file(char *fn)
486 char newfn[1024];
487 FILE *f;
489 f = fopen(fn, "r");
490 if (f == NULL) {
491 fprintf(stderr, "%s: cannot read %s\n", __progname, fn);
492 return;
494 while (fgets(newfn, sizeof(newfn), f) == newfn) {
495 newfn[strlen(newfn) - 1] = '\0';
496 process_file(newfn);
498 fclose(f);
501 int
502 main(int ac, char **av)
504 int skip;
505 char *cp;
507 if (ac < 3) {
508 fprintf(stderr, "usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n",
509 __progname);
510 exit(1);
512 cp = getenv("TERM");
513 if (cp == 0) {
514 beginul = nullstr;
515 endul = nullstr;
516 } else {
517 if (tgetent(tcp_buf, cp) != 1) {
518 beginul = nullstr;
519 endul = nullstr;
520 } else {
521 cp = cap_buf;
522 if (tgetflag("os") || tgetflag("ul")) {
523 ul_ = 1;
524 } else {
525 ul_ = 0;
526 beginul = tgetstr("us", &cp);
527 if (beginul == 0) {
528 beginul = tgetstr("so", &cp);
529 if (beginul == 0) {
530 beginul = nullstr;
531 endul = nullstr;
532 } else {
533 endul = tgetstr("se", &cp);
535 } else {
536 endul = tgetstr("ue", &cp);
542 static char tmp[] = "/tmp/qsubst.XXXXXX";
543 int fd;
544 fd = mkstemp(&tmp[0]);
545 if (fd < 0) {
546 fprintf(stderr, "%s: cannot create temp file: %s\n",
547 __progname, strerror(errno));
548 exit(1);
550 tempf = fdopen(fd, "w+");
552 if ((access(av[1], R_OK | W_OK) == 0) &&
553 (access(av[ac - 1], R_OK | W_OK) < 0) &&
554 (access(av[ac - 2], R_OK | W_OK) < 0)) {
555 fprintf(stderr, "%s: argument order has changed, it's now: str1 str2 files...\n", __progname);
557 str1 = av[1];
558 str2 = av[2];
559 av += 2;
560 ac -= 2;
561 s1l = strlen(str1);
562 s2l = strlen(str2);
563 if (s1l > BUF_SIZ) {
564 fprintf(stderr, "%s: search string too long (max %d chars)\n",
565 __progname, BUF_SIZ);
566 exit(1);
568 tcgetattr(0, &orig_tio);
569 signal(SIGTSTP, sigtstp);
570 allfly = 0;
571 cabove = 2;
572 cbelow = 2;
573 skip = 0;
574 for (ac--, av++; ac; ac--, av++) {
575 if (skip > 0) {
576 skip--;
577 continue;
579 if (**av == '-') {
580 ++*av;
581 if (!strcmp(*av, "debug")) {
582 debugging++;
583 } else if (!strcmp(*av, "w")) {
584 wordmode = !wordmode;
585 } else if ((strcmp(*av, "!") == 0) ||
586 (strcmp(*av, "go") == 0) ||
587 (strcmp(*av, "noask") == 0)) {
588 allfly = 1;
589 } else if ((strcmp(*av, "nogo") == 0) ||
590 (strcmp(*av, "ask") == 0)) {
591 allfly = 0;
592 } else if (**av == 'c') {
593 cabove = atoi(++*av);
594 cbelow = cabove;
595 limit_above_below();
596 } else if (**av == 'C') {
597 ++*av;
598 if (**av == 'A') {
599 cabove = atoi(++*av);
600 limit_above_below();
601 } else if (**av == 'B') {
602 cbelow = atoi(++*av);
603 limit_above_below();
604 } else {
605 fprintf(stderr, "%s: -C must be -CA or -CB\n", __progname);
607 } else if ((strcmp(*av, "f") == 0) ||
608 (strcmp(*av, "F") == 0)) {
609 if (++skip >= ac) {
610 fprintf(stderr, "%s: -%s what?\n",
611 __progname, *av);
612 } else {
613 if (**av == 'f') {
614 process_file(av[skip]);
615 } else {
616 process_indir_file(av[skip]);
620 } else {
621 process_file(*av);
624 exit(0);