Home | History | Annotate | Line # | Download | only in qsubst
      1 /*	$NetBSD: qsubst.c,v 1.8 2004/11/01 21:36:11 dsl 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 (at) rodents.montreal.qc.ca
     99  */
    100 #include <sys/cdefs.h>
    101 
    102 #ifndef lint
    103 __RCSID("$NetBSD: qsubst.c,v 1.8 2004/11/01 21:36:11 dsl Exp $");
    104 #endif
    105 
    106 #include <sys/file.h>
    107 
    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>
    117 
    118 extern const char *__progname;
    119 
    120 #define MAX_C_A 10
    121 #define MAX_C_B 10
    122 #define BUF_SIZ 1024
    123 
    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;
    152 
    153 static void
    154 tstp_self(void)
    155 {
    156 	void (*old_tstp) (int);
    157 	int mask;
    158 
    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);
    164 }
    165 
    166 /* ARGSUSED */
    167 static void
    168 sigtstp(int sig)
    169 {
    170 	struct termios tio;
    171 
    172 	if (tcgetattr(0, &tio) < 0) {
    173 		tstp_self();
    174 		return;
    175 	}
    176 	tcsetattr(0, TCSAFLUSH | TCSASOFT, &orig_tio);
    177 	tstp_self();
    178 	tcsetattr(0, TCSADRAIN | TCSASOFT, &tio);
    179 }
    180 
    181 static void
    182 limit_above_below(void)
    183 {
    184 	if (cabove > MAX_C_A) {
    185 		cabove = MAX_C_A;
    186 	}
    187 	if (cbelow > MAX_C_B) {
    188 		cbelow = MAX_C_B;
    189 	}
    190 }
    191 
    192 static int
    193 issymchar(unsigned char c)
    194 {
    195 	return (isascii(c) && (isalnum(c) || (c == '_') || (c == '$')));
    196 }
    197 
    198 static int
    199 foundit(void)
    200 {
    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));
    207 	}
    208 }
    209 
    210 static int
    211 putcharf(int c)
    212 {
    213 	return (putchar(c));
    214 }
    215 
    216 static void
    217 put_ul(char *s)
    218 {
    219 	if (ul_) {
    220 		for (; *s; s++) {
    221 			printf("_\b%c", *s);
    222 		}
    223 	} else {
    224 		tputs(beginul, 1, putcharf);
    225 		fputs(s, stdout);
    226 		tputs(endul, 1, putcharf);
    227 	}
    228 }
    229 
    230 static int
    231 getc_cbreak(void)
    232 {
    233 	struct termios tio;
    234 	struct termios otio;
    235 	char c;
    236 
    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;
    251 	}
    252 	tcsetattr(0, TCSANOW | TCSASOFT, &otio);
    253 	return (c);
    254 }
    255 
    256 static int
    257 doit(void)
    258 {
    259 	long save;
    260 	int i;
    261 	int lastnl;
    262 	int use_replacement;
    263 
    264 	if (flying) {
    265 		return (flystate);
    266 	}
    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));
    274 		}
    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;
    285 			}
    286 			putchar(c);
    287 			lastnl = 0;
    288 			if (c == '\n') {
    289 				i--;
    290 				lastnl = 1;
    291 			}
    292 		}
    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;
    332 			}
    333 		}
    334 	} while (i < 0);
    335 	if (i) {
    336 		printf("(replacing");
    337 	} else {
    338 		printf("(leaving");
    339 	}
    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");
    347 		}
    348 	}
    349 	printf(")\n");
    350 	return (i);
    351 }
    352 
    353 static void
    354 add_shift(long *a, long e, int n)
    355 {
    356 	int i;
    357 
    358 	n--;
    359 	for (i = 0; i < n; i++) {
    360 		a[i] = a[i + 1];
    361 	}
    362 	a[n] = e;
    363 }
    364 
    365 static void
    366 process_file(char *fn)
    367 {
    368 	int i;
    369 	long n;
    370 	int c;
    371 
    372 	workf = fopen(fn, "r+");
    373 	if (workf == NULL) {
    374 		fprintf(stderr, "%s: cannot read %s\n", __progname, fn);
    375 		return;
    376 	}
    377 	printf("(file: %s)\n", fn);
    378 	current_file = fn;
    379 	for (i = 0; i <= MAX_C_A; i++) {
    380 		nls[i] = -1;
    381 	}
    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;
    391 	}
    392 	if (debugging) {
    393 		printf("[rahead = %d, bufp0-buf = %ld]\n",
    394 		    rahead, (long) (bufp0 - &buf[0]));
    395 	}
    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);
    412 				}
    413 				fflush(workf);
    414 				ftruncate(fileno(workf), ftell(workf));
    415 			}
    416 			fclose(workf);
    417 			return;
    418 		}
    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));
    424 		}
    425 		if ((n >= rahead) && foundit() && doit()) {
    426 			int wbehind;
    427 			if (debugging) {
    428 				printf("[doing change]\n");
    429 			}
    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);
    437 				}
    438 				wbehind = 0;
    439 			}
    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);
    447 				}
    448 			}
    449 			fwrite(str2, 1, s2l, tempf);
    450 			n = rahead - s1l;
    451 			if (debugging) {
    452 				printf("[n now %ld]\n", n);
    453 			}
    454 			if (n > 0) {
    455 				bcopy(bufp - n, bufp0, n);
    456 				if (debugging) {
    457 					printf("[copying %ld back]\n", n);
    458 				}
    459 			}
    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);
    470 					}
    471 				}
    472 				n = rahead;
    473 				bcopy(bufp - n, bufp0, n);
    474 				if (debugging) {
    475 					printf("[n now %ld]\n[copying %ld back]\n", n, n);
    476 				}
    477 				bufp = bufp0 + n;
    478 			}
    479 		}
    480 	}
    481 }
    482 
    483 static void
    484 process_indir_file(char *fn)
    485 {
    486 	char newfn[1024];
    487 	FILE *f;
    488 
    489 	f = fopen(fn, "r");
    490 	if (f == NULL) {
    491 		fprintf(stderr, "%s: cannot read %s\n", __progname, fn);
    492 		return;
    493 	}
    494 	while (fgets(newfn, sizeof(newfn), f) == newfn) {
    495 		newfn[strlen(newfn) - 1] = '\0';
    496 		process_file(newfn);
    497 	}
    498 	fclose(f);
    499 }
    500 
    501 int
    502 main(int ac, char **av)
    503 {
    504 	int skip;
    505 	char *cp;
    506 
    507 	if (ac < 3) {
    508 		fprintf(stderr, "usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n",
    509 		    __progname);
    510 		exit(1);
    511 	}
    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);
    534 					}
    535 				} else {
    536 					endul = tgetstr("ue", &cp);
    537 				}
    538 			}
    539 		}
    540 	}
    541 	{
    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);
    549 		}
    550 		tempf = fdopen(fd, "w+");
    551 	}
    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);
    556 	}
    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);
    567 	}
    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;
    578 		}
    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);
    606 				}
    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]);
    617 					}
    618 				}
    619 			}
    620 		} else {
    621 			process_file(*av);
    622 		}
    623 	}
    624 	exit(0);
    625 }
    626