1 /* $NetBSD: qsubst.c,v 1.7 2004/01/05 23:23:36 jmmv Exp $ */
4 * qsubst -- designed for renaming routines existing in a whole bunch
5 * of files. Needs -ltermcap.
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
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
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
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.
58 * The filename following the -f argument is one of the
59 * files qsubst should perform substitutions in.
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
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
93 * If you find any bugs I would appreciate hearing about them,
94 * especially if you also fix them.
98 * mouse@rodents.montreal.qc.ca
100 #include <sys/cdefs.h>
103 __RCSID("$NetBSD: qsubst.c,v 1.7 2004/01/05 23:23:36 jmmv Exp $");
106 #include <sys/file.h>
118 extern const char *__progname
;
124 static int debugging
;
132 static long nls
[MAX_C_A
+ 1];
133 static char buf
[(BUF_SIZ
* 2) + 2];
136 static char *bufpmax
;
144 static const char *nullstr
= "";
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
;
156 void (*old_tstp
) (int);
160 kill(getpid(), SIGTSTP
);
161 old_tstp
= signal(SIGTSTP
, SIG_DFL
);
162 sigsetmask(mask
& ~sigmask(SIGTSTP
));
163 signal(SIGTSTP
, old_tstp
);
172 if (tcgetattr(0, &tio
) < 0) {
176 tcsetattr(0, TCSAFLUSH
| TCSASOFT
, &orig_tio
);
178 tcsetattr(0, TCSADRAIN
| TCSASOFT
, &tio
);
182 limit_above_below(void)
184 if (cabove
> MAX_C_A
) {
187 if (cbelow
> MAX_C_B
) {
193 issymchar(unsigned char c
)
195 return (isascii(c
) && (isalnum(c
) || (c
== '_') || (c
== '$')));
202 return (!issymchar(bufp
[-1]) &&
203 !issymchar(bufp
[-2 - s1l
]) &&
204 !bcmp(bufp
- 1 - s1l
, str1
, s1l
));
206 return (!bcmp(bufp
- s1l
, str1
, s1l
));
224 tputs(beginul
, 1, putcharf
);
226 tputs(endul
, 1, putcharf
);
237 if (tcgetattr(0, &tio
) < 0)
240 tio
.c_lflag
&= ~(ICANON
| ECHOKE
| ECHOE
| ECHO
| ECHONL
);
243 tcsetattr(0, TCSANOW
| TCSASOFT
, &tio
);
244 switch (read(0, &c
, 1)) {
252 tcsetattr(0, TCSANOW
| TCSASOFT
, &otio
);
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);
294 printf("\n[no final newline] ");
295 fseek(workf
, save
, 0);
298 switch (getc_cbreak()) {
321 use_replacement
= !use_replacement
;
323 printf("(using %s string gives)\n",
324 use_replacement
? "new" : "old");
327 printf("File is `%s'\n", current_file
);
336 printf("(replacing");
342 printf(" this and all the rest");
343 } else if (flystate
) {
344 printf(" this, replacing all the rest");
346 printf(" this, leaving all the rest");
354 add_shift(long *a
, long e
, int n
)
359 for (i
= 0; i
< n
; i
++) {
366 process_file(char *fn
)
372 workf
= fopen(fn
, "r+");
374 fprintf(stderr
, "%s: cannot read %s\n", __progname
, fn
);
377 printf("(file: %s)\n", fn
);
379 for (i
= 0; i
<= MAX_C_A
; i
++) {
393 printf("[rahead = %d, bufp0-buf = %ld]\n",
394 rahead
, (long) (bufp0
- &buf
[0]));
398 bufpmax
= &buf
[sizeof(buf
) - s1l
- 2];
406 fwrite(bufp0
, 1, bufp
- bufp0
, tempf
);
407 fseek(workf
, tbeg
, 0);
411 putc(getc(tempf
), workf
);
414 ftruncate(fileno(workf
), ftell(workf
));
422 printf("[got %c, n now %ld, bufp-buf %ld]\n",
423 c
, n
, (long) (bufp
- bufp0
));
425 if ((n
>= rahead
) && foundit() && doit()) {
428 printf("[doing change]\n");
432 tbeg
= ftell(workf
) - rahead
;
435 printf("[tbeg set to %d]\n",
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
);
445 printf("[writing %ld from bufp0]\n",
449 fwrite(str2
, 1, s2l
, tempf
);
452 printf("[n now %ld]\n", n
);
455 bcopy(bufp
- n
, bufp0
, n
);
457 printf("[copying %ld back]\n", n
);
462 if (bufp
[-1] == '\n')
463 add_shift(nls
, ftell(workf
), MAX_C_A
+ 1);
464 if (bufp
>= bufpmax
) {
466 fwrite(bufp0
, 1, n
- rahead
, tempf
);
468 printf("[flushing %ld]\n",
473 bcopy(bufp
- n
, bufp0
, n
);
475 printf("[n now %ld]\n[copying %ld back]\n", n
, n
);
484 process_indir_file(char *fn
)
491 fprintf(stderr
, "%s: cannot read %s\n", __progname
, fn
);
494 while (fgets(newfn
, sizeof(newfn
), f
) == newfn
) {
495 newfn
[strlen(newfn
) - 1] = '\0';
502 main(int ac
, char **av
)
508 fprintf(stderr
, "usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n",
517 if (tgetent(tcp_buf
, cp
) != 1) {
522 if (tgetflag("os") || tgetflag("ul")) {
526 beginul
= tgetstr("us", &cp
);
528 beginul
= tgetstr("so", &cp
);
533 endul
= tgetstr("se", &cp
);
536 endul
= tgetstr("ue", &cp
);
542 static char tmp
[] = "/tmp/qsubst.XXXXXX";
544 fd
= mkstemp(&tmp
[0]);
546 fprintf(stderr
, "%s: cannot create temp file: %s\n",
547 __progname
, strerror(errno
));
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
);
564 fprintf(stderr
, "%s: search string too long (max %d chars)\n",
565 __progname
, BUF_SIZ
);
568 tcgetattr(0, &orig_tio
);
569 signal(SIGTSTP
, sigtstp
);
574 for (ac
--, av
++; ac
; ac
--, av
++) {
581 if (!strcmp(*av
, "debug")) {
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)) {
589 } else if ((strcmp(*av
, "nogo") == 0) ||
590 (strcmp(*av
, "ask") == 0)) {
592 } else if (**av
== 'c') {
593 cabove
= atoi(++*av
);
596 } else if (**av
== 'C') {
599 cabove
= atoi(++*av
);
601 } else if (**av
== 'B') {
602 cbelow
= atoi(++*av
);
605 fprintf(stderr
, "%s: -C must be -CA or -CB\n", __progname
);
607 } else if ((strcmp(*av
, "f") == 0) ||
608 (strcmp(*av
, "F") == 0)) {
610 fprintf(stderr
, "%s: -%s what?\n",
614 process_file(av
[skip
]);
616 process_indir_file(av
[skip
]);