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