1 /* $NetBSD: qsubst.c,v 1.4 2001/04/22 05:35:35 simonb Exp $ */ 2 3 /* 4 * qsubst -- designed for renaming routines existing in a whole bunch 5 * of files. Needs -ltermcap. 6 * 7 * Usage: 8 * 9 * qsubst str1 str2 [ options ] 10 * 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. 15 * 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): 22 * 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. 38 * 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: 41 * 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. 64 * 65 * The default amount of context is -c2, that is, two lines above and 66 * two lines below the line with the match. 67 * 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. 71 * 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. 75 * 76 * The most context you can get is ten lines each, above and below 77 * (corresponding to -c10). 78 * 79 * Str1 is limited to 512 characters; there is no limit on the size of 80 * str2. Neither one may contain a NUL. 81 * 82 * NULs in the file may cause qsubst to make various mistakes. 83 * 84 * If any other program modifies the file while qsubst is running, all 85 * bets are off. 86 * 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. 92 * 93 * If you find any bugs I would appreciate hearing about them, 94 * especially if you also fix them. 95 * 96 * der Mouse 97 * 98 * mouse@rodents.montreal.qc.ca 99 */ 100 101 #include <sys/file.h> 102 103 #include <ctype.h> 104 #include <errno.h> 105 #include <signal.h> 106 #include <stdio.h> 107 #include <stdlib.h> 108 #include <strings.h> 109 #include <termcap.h> 110 #include <termios.h> 111 #include <unistd.h> 112 113 extern const char *__progname; 114 115 #define MAX_C_A 10 116 #define MAX_C_B 10 117 #define BUF_SIZ 1024 118 119 static int debugging; 120 static FILE *tempf; 121 static long tbeg; 122 static FILE *workf; 123 static char *str1; 124 static char *str2; 125 static int s1l; 126 static int s2l; 127 static long nls[MAX_C_A+1]; 128 static char buf[(BUF_SIZ*2)+2]; 129 static char *bufp; 130 static char *bufp0; 131 static char *bufpmax; 132 static int rahead; 133 static int cabove; 134 static int cbelow; 135 static int wordmode; 136 static int flying; 137 static int flystate; 138 static int allfly; 139 static const char *nullstr = ""; 140 static int ul_; 141 static char *current_file; 142 static const char *beginul; 143 static const char *endul; 144 static char tcp_buf[1024]; 145 static char cap_buf[1024]; 146 static struct termios orig_tio; 147 148 static void tstp_self(void) 149 { 150 void (*old_tstp)(int); 151 int mask; 152 153 mask = sigblock(0); 154 kill(getpid(),SIGTSTP); 155 old_tstp = signal(SIGTSTP,SIG_DFL); 156 sigsetmask(mask&~sigmask(SIGTSTP)); 157 signal(SIGTSTP,old_tstp); 158 } 159 160 /* ARGSUSED */ 161 static void sigtstp(int sig) 162 { 163 struct termios tio; 164 165 if (tcgetattr(0,&tio) < 0) 166 { tstp_self(); 167 return; 168 } 169 tcsetattr(0,TCSAFLUSH|TCSASOFT,&orig_tio); 170 tstp_self(); 171 tcsetattr(0,TCSADRAIN|TCSASOFT,&tio); 172 } 173 174 static void limit_above_below(void) 175 { 176 if (cabove > MAX_C_A) 177 { cabove = MAX_C_A; 178 } 179 if (cbelow > MAX_C_B) 180 { cbelow = MAX_C_B; 181 } 182 } 183 184 static int issymchar(char c) 185 { 186 return( isascii(c) && 187 ( isalnum(c) || 188 (c == '_') || 189 (c == '$') ) ); 190 } 191 192 static int foundit(void) 193 { 194 if (wordmode) 195 { return( !issymchar(bufp[-1]) && 196 !issymchar(bufp[-2-s1l]) && 197 !bcmp(bufp-1-s1l,str1,s1l) ); 198 } 199 else 200 { return(!bcmp(bufp-s1l,str1,s1l)); 201 } 202 } 203 204 static int putcharf(int c) 205 { 206 return(putchar(c)); 207 } 208 209 static void put_ul(char *s) 210 { 211 if (ul_) 212 { for (;*s;s++) 213 { printf("_\b%c",*s); 214 } 215 } 216 else 217 { tputs(beginul,1,putcharf); 218 fputs(s,stdout); 219 tputs(endul,1,putcharf); 220 } 221 } 222 223 static int getc_cbreak(void) 224 { 225 struct termios tio; 226 struct termios otio; 227 char c; 228 229 if (tcgetattr(0,&tio) < 0) return(getchar()); 230 otio = tio; 231 tio.c_lflag &= ~(ICANON|ECHOKE|ECHOE|ECHO|ECHONL); 232 tio.c_cc[VMIN] = 1; 233 tio.c_cc[VTIME] = 0; 234 tcsetattr(0,TCSANOW|TCSASOFT,&tio); 235 switch (read(0,&c,1)) 236 { case -1: 237 break; 238 case 0: 239 break; 240 case 1: 241 break; 242 } 243 tcsetattr(0,TCSANOW|TCSASOFT,&otio); 244 return(c); 245 } 246 247 static int doit(void) 248 { 249 long save; 250 int i; 251 int lastnl; 252 int use_replacement; 253 254 if (flying) 255 { return(flystate); 256 } 257 use_replacement = 0; 258 save = ftell(workf); 259 do 260 { for (i=MAX_C_A-cabove;nls[i]<0;i++) ; 261 fseek(workf,nls[i],0); 262 for (i=save-nls[i]-rahead;i;i--) 263 { putchar(getc(workf)); 264 } 265 put_ul(use_replacement?str2:str1); 266 fseek(workf,save+s1l-rahead,0); 267 lastnl = 0; 268 i = cbelow + 1; 269 while (i > 0) 270 { int c; 271 c = getc(workf); 272 if (c == EOF) 273 { clearerr(workf); 274 break; 275 } 276 putchar(c); 277 lastnl = 0; 278 if (c == '\n') 279 { i --; 280 lastnl = 1; 281 } 282 } 283 if (! lastnl) printf("\n[no final newline] "); 284 fseek(workf,save,0); 285 i = -1; 286 while (i == -1) 287 { switch (getc_cbreak()) 288 { case ' ': 289 i = 1; 290 break; 291 case '.': 292 i = 1; 293 flying = 1; 294 flystate = 0; 295 break; 296 case 'n': 297 i = 0; 298 break; 299 case '\7': 300 i = 0; 301 flying = 1; 302 flystate = 0; 303 break; 304 case '!': 305 i = 1; 306 flying = 1; 307 flystate = 1; 308 break; 309 case ',': 310 use_replacement = ! use_replacement; 311 i = -2; 312 printf("(using %s string gives)\n",use_replacement?"new":"old"); 313 break; 314 case '?': 315 printf("File is `%s'\n",current_file); 316 break; 317 default: 318 putchar('\7'); 319 break; 320 } 321 } 322 } while (i < 0); 323 if (i) 324 { printf("(replacing"); 325 } 326 else 327 { printf("(leaving"); 328 } 329 if (flying) 330 { if (flystate == i) 331 { printf(" this and all the rest"); 332 } 333 else if (flystate) 334 { printf(" this, replacing all the rest"); 335 } 336 else 337 { printf(" this, leaving all the rest"); 338 } 339 } 340 printf(")\n"); 341 return(i); 342 } 343 344 static void add_shift(long *a, long e, int n) 345 { 346 int i; 347 348 n --; 349 for (i=0;i<n;i++) 350 { a[i] = a[i+1]; 351 } 352 a[n] = e; 353 } 354 355 static void process_file(char *fn) 356 { 357 int i; 358 long n; 359 int c; 360 361 workf = fopen(fn,"r+"); 362 if (workf == NULL) 363 { fprintf(stderr,"%s: cannot read %s\n",__progname,fn); 364 return; 365 } 366 printf("(file: %s)\n",fn); 367 current_file = fn; 368 for (i=0;i<=MAX_C_A;i++) 369 { nls[i] = -1; 370 } 371 nls[MAX_C_A] = 0; 372 tbeg = -1; 373 if (wordmode) 374 { bufp0 = &buf[1]; 375 rahead = s1l + 1; 376 buf[0] = '\0'; 377 } 378 else 379 { bufp0 = &buf[0]; 380 rahead = s1l; 381 } 382 if (debugging) 383 { printf("[rahead = %d, bufp0-buf = %ld]\n",rahead,(long)(bufp0-&buf[0])); 384 } 385 n = 0; 386 bufp = bufp0; 387 bufpmax = &buf[sizeof(buf)-s1l-2]; 388 flying = allfly; 389 flystate = 1; 390 while (1) 391 { c = getc(workf); 392 if (c == EOF) 393 { if (tbeg >= 0) 394 { if (bufp > bufp0) fwrite(bufp0,1,bufp-bufp0,tempf); 395 fseek(workf,tbeg,0); 396 n = ftell(tempf); 397 fseek(tempf,0L,0); 398 for (;n;n--) 399 { putc(getc(tempf),workf); 400 } 401 fflush(workf); 402 ftruncate(fileno(workf),ftell(workf)); 403 } 404 fclose(workf); 405 return; 406 } 407 *bufp++ = c; 408 n ++; 409 if (debugging) 410 { printf("[got %c, n now %ld, bufp-buf %ld]\n",c,n,(long)(bufp-bufp0)); 411 } 412 if ((n >= rahead) && foundit() && doit()) 413 { int wbehind; 414 if (debugging) 415 { printf("[doing change]\n"); 416 } 417 wbehind = 1; 418 if (tbeg < 0) 419 { tbeg = ftell(workf) - rahead; 420 fseek(tempf,0L,0); 421 if (debugging) 422 { printf("[tbeg set to %d]\n",(int)tbeg); 423 } 424 wbehind = 0; 425 } 426 if (bufp[-1] == '\n') add_shift(nls,ftell(workf),MAX_C_A+1); 427 if ((n > rahead) && wbehind) 428 { fwrite(bufp0,1,n-rahead,tempf); 429 if (debugging) 430 { printf("[writing %ld from bufp0]\n",n-rahead); 431 } 432 } 433 fwrite(str2,1,s2l,tempf); 434 n = rahead - s1l; 435 if (debugging) 436 { printf("[n now %ld]\n",n); 437 } 438 if (n > 0) 439 { bcopy(bufp-n,bufp0,n); 440 if (debugging) 441 { printf("[copying %ld back]\n",n); 442 } 443 } 444 bufp = bufp0 + n; 445 } 446 else 447 { if (bufp[-1] == '\n') add_shift(nls,ftell(workf),MAX_C_A+1); 448 if (bufp >= bufpmax) 449 { if (tbeg >= 0) 450 { fwrite(bufp0,1,n-rahead,tempf); 451 if (debugging) 452 { printf("[flushing %ld]\n",n-rahead); 453 } 454 } 455 n = rahead; 456 bcopy(bufp-n,bufp0,n); 457 if (debugging) 458 { printf("[n now %ld]\n[copying %ld back]\n",n,n); 459 } 460 bufp = bufp0 + n; 461 } 462 } 463 } 464 } 465 466 static void process_indir_file(char *fn) 467 { 468 char newfn[1024]; 469 FILE *f; 470 471 f = fopen(fn,"r"); 472 if (f == NULL) 473 { fprintf(stderr,"%s: cannot read %s\n",__progname,fn); 474 return; 475 } 476 while (fgets(newfn,sizeof(newfn),f) == newfn) 477 { newfn[strlen(newfn)-1] = '\0'; 478 process_file(newfn); 479 } 480 fclose(f); 481 } 482 483 int main(int ac, char **av) 484 { 485 int skip; 486 char *cp; 487 488 if (ac < 3) 489 { fprintf(stderr,"Usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n", 490 __progname); 491 exit(1); 492 } 493 cp = getenv("TERM"); 494 if (cp == 0) 495 { beginul = nullstr; 496 endul = nullstr; 497 } 498 else 499 { if (tgetent(tcp_buf,cp) != 1) 500 { beginul = nullstr; 501 endul = nullstr; 502 } 503 else 504 { cp = cap_buf; 505 if (tgetflag("os") || tgetflag("ul")) 506 { ul_ = 1; 507 } 508 else 509 { ul_ = 0; 510 beginul = tgetstr("us",&cp); 511 if (beginul == 0) 512 { beginul = tgetstr("so",&cp); 513 if (beginul == 0) 514 { beginul = nullstr; 515 endul = nullstr; 516 } 517 else 518 { endul = tgetstr("se",&cp); 519 } 520 } 521 else 522 { endul = tgetstr("ue",&cp); 523 } 524 } 525 } 526 } 527 { static char tmp[] = "/tmp/qsubst.XXXXXX"; 528 int fd; 529 fd = mkstemp(&tmp[0]); 530 if (fd < 0) 531 { fprintf(stderr,"%s: cannot create temp file: %s\n",__progname,strerror(errno)); 532 exit(1); 533 } 534 tempf = fdopen(fd,"w+"); 535 } 536 if ( (access(av[1],R_OK|W_OK) == 0) && 537 (access(av[ac-1],R_OK|W_OK) < 0) && 538 (access(av[ac-2],R_OK|W_OK) < 0) ) 539 { fprintf(stderr,"%s: argument order has changed, it's now: str1 str2 files...\n",__progname); 540 } 541 str1 = av[1]; 542 str2 = av[2]; 543 av += 2; 544 ac -= 2; 545 s1l = strlen(str1); 546 s2l = strlen(str2); 547 if (s1l > BUF_SIZ) 548 { fprintf(stderr,"%s: search string too long (max %d chars)\n",__progname,BUF_SIZ); 549 exit(1); 550 } 551 tcgetattr(0,&orig_tio); 552 signal(SIGTSTP,sigtstp); 553 allfly = 0; 554 cabove = 2; 555 cbelow = 2; 556 skip = 0; 557 for (ac--,av++;ac;ac--,av++) 558 { if (skip > 0) 559 { skip --; 560 continue; 561 } 562 if (**av == '-') 563 { ++*av; 564 if (!strcmp(*av,"debug")) 565 { debugging ++; 566 } 567 else if (!strcmp(*av,"w")) 568 { wordmode = ! wordmode; 569 } 570 else if ( (strcmp(*av,"!") == 0) || 571 (strcmp(*av,"go") == 0) || 572 (strcmp(*av,"noask") == 0) ) 573 { allfly = 1; 574 } 575 else if ( (strcmp(*av,"nogo") == 0) || 576 (strcmp(*av,"ask") == 0) ) 577 { allfly = 0; 578 } 579 else if (**av == 'c') 580 { cabove = atoi(++*av); 581 cbelow = cabove; 582 limit_above_below(); 583 } 584 else if (**av == 'C') 585 { ++*av; 586 if (**av == 'A') 587 { cabove = atoi(++*av); 588 limit_above_below(); 589 } 590 else if (**av == 'B') 591 { cbelow = atoi(++*av); 592 limit_above_below(); 593 } 594 else 595 { fprintf(stderr,"%s: -C must be -CA or -CB\n",__progname); 596 } 597 } 598 else if ( (strcmp(*av,"f") == 0) || 599 (strcmp(*av,"F") == 0) ) 600 { if (++skip >= ac) 601 { fprintf(stderr,"%s: -%s what?\n",__progname,*av); 602 } 603 else 604 { if (**av == 'f') 605 { process_file(av[skip]); 606 } 607 else 608 { process_indir_file(av[skip]); 609 } 610 } 611 } 612 } 613 else 614 { process_file(*av); 615 } 616 } 617 exit(0); 618 } 619 620