Home | History | Annotate | Line # | Download | only in qsubst
qsubst.c revision 1.5
      1 /*	$NetBSD: qsubst.c,v 1.5 2002/12/08 21:29:27 perry 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 
    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
    149 tstp_self(void)
    150 {
    151 	void (*old_tstp) (int);
    152 	int mask;
    153 
    154 	mask = sigblock(0);
    155 	kill(getpid(), SIGTSTP);
    156 	old_tstp = signal(SIGTSTP, SIG_DFL);
    157 	sigsetmask(mask & ~sigmask(SIGTSTP));
    158 	signal(SIGTSTP, old_tstp);
    159 }
    160 
    161 /* ARGSUSED */
    162 static void
    163 sigtstp(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
    177 limit_above_below(void)
    178 {
    179 	if (cabove > MAX_C_A) {
    180 		cabove = MAX_C_A;
    181 	}
    182 	if (cbelow > MAX_C_B) {
    183 		cbelow = MAX_C_B;
    184 	}
    185 }
    186 
    187 static int
    188 issymchar(char c)
    189 {
    190 	return (isascii(c) && (isalnum(c) || (c == '_') || (c == '$')));
    191 }
    192 
    193 static int
    194 foundit(void)
    195 {
    196 	if (wordmode) {
    197 		return (!issymchar(bufp[-1]) &&
    198 		    !issymchar(bufp[-2 - s1l]) &&
    199 		    !bcmp(bufp - 1 - s1l, str1, s1l));
    200 	} else {
    201 		return (!bcmp(bufp - s1l, str1, s1l));
    202 	}
    203 }
    204 
    205 static int
    206 putcharf(int c)
    207 {
    208 	return (putchar(c));
    209 }
    210 
    211 static void
    212 put_ul(char *s)
    213 {
    214 	if (ul_) {
    215 		for (; *s; s++) {
    216 			printf("_\b%c", *s);
    217 		}
    218 	} else {
    219 		tputs(beginul, 1, putcharf);
    220 		fputs(s, stdout);
    221 		tputs(endul, 1, putcharf);
    222 	}
    223 }
    224 
    225 static int
    226 getc_cbreak(void)
    227 {
    228 	struct termios tio;
    229 	struct termios otio;
    230 	char c;
    231 
    232 	if (tcgetattr(0, &tio) < 0)
    233 		return (getchar());
    234 	otio = tio;
    235 	tio.c_lflag &= ~(ICANON | ECHOKE | ECHOE | ECHO | ECHONL);
    236 	tio.c_cc[VMIN] = 1;
    237 	tio.c_cc[VTIME] = 0;
    238 	tcsetattr(0, TCSANOW | TCSASOFT, &tio);
    239 	switch (read(0, &c, 1)) {
    240 	case -1:
    241 		break;
    242 	case 0:
    243 		break;
    244 	case 1:
    245 		break;
    246 	}
    247 	tcsetattr(0, TCSANOW | TCSASOFT, &otio);
    248 	return (c);
    249 }
    250 
    251 static int
    252 doit(void)
    253 {
    254 	long save;
    255 	int i;
    256 	int lastnl;
    257 	int use_replacement;
    258 
    259 	if (flying) {
    260 		return (flystate);
    261 	}
    262 	use_replacement = 0;
    263 	save = ftell(workf);
    264 	do {
    265 		for (i = MAX_C_A - cabove; nls[i] < 0; i++);
    266 		fseek(workf, nls[i], 0);
    267 		for (i = save - nls[i] - rahead; i; i--) {
    268 			putchar(getc(workf));
    269 		}
    270 		put_ul(use_replacement ? str2 : str1);
    271 		fseek(workf, save + s1l - rahead, 0);
    272 		lastnl = 0;
    273 		i = cbelow + 1;
    274 		while (i > 0) {
    275 			int c;
    276 			c = getc(workf);
    277 			if (c == EOF) {
    278 				clearerr(workf);
    279 				break;
    280 			}
    281 			putchar(c);
    282 			lastnl = 0;
    283 			if (c == '\n') {
    284 				i--;
    285 				lastnl = 1;
    286 			}
    287 		}
    288 		if (!lastnl)
    289 			printf("\n[no final newline] ");
    290 		fseek(workf, save, 0);
    291 		i = -1;
    292 		while (i == -1) {
    293 			switch (getc_cbreak()) {
    294 			case ' ':
    295 				i = 1;
    296 				break;
    297 			case '.':
    298 				i = 1;
    299 				flying = 1;
    300 				flystate = 0;
    301 				break;
    302 			case 'n':
    303 				i = 0;
    304 				break;
    305 			case '\7':
    306 				i = 0;
    307 				flying = 1;
    308 				flystate = 0;
    309 				break;
    310 			case '!':
    311 				i = 1;
    312 				flying = 1;
    313 				flystate = 1;
    314 				break;
    315 			case ',':
    316 				use_replacement = !use_replacement;
    317 				i = -2;
    318 				printf("(using %s string gives)\n",
    319 				    use_replacement ? "new" : "old");
    320 				break;
    321 			case '?':
    322 				printf("File is `%s'\n", current_file);
    323 				break;
    324 			default:
    325 				putchar('\7');
    326 				break;
    327 			}
    328 		}
    329 	} while (i < 0);
    330 	if (i) {
    331 		printf("(replacing");
    332 	} else {
    333 		printf("(leaving");
    334 	}
    335 	if (flying) {
    336 		if (flystate == i) {
    337 			printf(" this and all the rest");
    338 		} else if (flystate) {
    339 			printf(" this, replacing all the rest");
    340 		} else {
    341 			printf(" this, leaving all the rest");
    342 		}
    343 	}
    344 	printf(")\n");
    345 	return (i);
    346 }
    347 
    348 static void
    349 add_shift(long *a, long e, int n)
    350 {
    351 	int i;
    352 
    353 	n--;
    354 	for (i = 0; i < n; i++) {
    355 		a[i] = a[i + 1];
    356 	}
    357 	a[n] = e;
    358 }
    359 
    360 static void
    361 process_file(char *fn)
    362 {
    363 	int i;
    364 	long n;
    365 	int c;
    366 
    367 	workf = fopen(fn, "r+");
    368 	if (workf == NULL) {
    369 		fprintf(stderr, "%s: cannot read %s\n", __progname, fn);
    370 		return;
    371 	}
    372 	printf("(file: %s)\n", fn);
    373 	current_file = fn;
    374 	for (i = 0; i <= MAX_C_A; i++) {
    375 		nls[i] = -1;
    376 	}
    377 	nls[MAX_C_A] = 0;
    378 	tbeg = -1;
    379 	if (wordmode) {
    380 		bufp0 = &buf[1];
    381 		rahead = s1l + 1;
    382 		buf[0] = '\0';
    383 	} else {
    384 		bufp0 = &buf[0];
    385 		rahead = s1l;
    386 	}
    387 	if (debugging) {
    388 		printf("[rahead = %d, bufp0-buf = %ld]\n",
    389 		    rahead, (long) (bufp0 - &buf[0]));
    390 	}
    391 	n = 0;
    392 	bufp = bufp0;
    393 	bufpmax = &buf[sizeof(buf) - s1l - 2];
    394 	flying = allfly;
    395 	flystate = 1;
    396 	while (1) {
    397 		c = getc(workf);
    398 		if (c == EOF) {
    399 			if (tbeg >= 0) {
    400 				if (bufp > bufp0)
    401 					fwrite(bufp0, 1, bufp - bufp0, tempf);
    402 				fseek(workf, tbeg, 0);
    403 				n = ftell(tempf);
    404 				fseek(tempf, 0L, 0);
    405 				for (; n; n--) {
    406 					putc(getc(tempf), workf);
    407 				}
    408 				fflush(workf);
    409 				ftruncate(fileno(workf), ftell(workf));
    410 			}
    411 			fclose(workf);
    412 			return;
    413 		}
    414 		*bufp++ = c;
    415 		n++;
    416 		if (debugging) {
    417 			printf("[got %c, n now %ld, bufp-buf %ld]\n",
    418 			    c, n, (long) (bufp - bufp0));
    419 		}
    420 		if ((n >= rahead) && foundit() && doit()) {
    421 			int wbehind;
    422 			if (debugging) {
    423 				printf("[doing change]\n");
    424 			}
    425 			wbehind = 1;
    426 			if (tbeg < 0) {
    427 				tbeg = ftell(workf) - rahead;
    428 				fseek(tempf, 0L, 0);
    429 				if (debugging) {
    430 					printf("[tbeg set to %d]\n",
    431 					    (int)tbeg);
    432 				}
    433 				wbehind = 0;
    434 			}
    435 			if (bufp[-1] == '\n')
    436 				add_shift(nls, ftell(workf), MAX_C_A + 1);
    437 			if ((n > rahead) && wbehind) {
    438 				fwrite(bufp0, 1, n - rahead, tempf);
    439 				if (debugging) {
    440 					printf("[writing %ld from bufp0]\n",
    441 					    n - rahead);
    442 				}
    443 			}
    444 			fwrite(str2, 1, s2l, tempf);
    445 			n = rahead - s1l;
    446 			if (debugging) {
    447 				printf("[n now %ld]\n", n);
    448 			}
    449 			if (n > 0) {
    450 				bcopy(bufp - n, bufp0, n);
    451 				if (debugging) {
    452 					printf("[copying %ld back]\n", n);
    453 				}
    454 			}
    455 			bufp = bufp0 + n;
    456 		} else {
    457 			if (bufp[-1] == '\n')
    458 				add_shift(nls, ftell(workf), MAX_C_A + 1);
    459 			if (bufp >= bufpmax) {
    460 				if (tbeg >= 0) {
    461 					fwrite(bufp0, 1, n - rahead, tempf);
    462 					if (debugging) {
    463 						printf("[flushing %ld]\n",
    464 						    n - rahead);
    465 					}
    466 				}
    467 				n = rahead;
    468 				bcopy(bufp - n, bufp0, n);
    469 				if (debugging) {
    470 					printf("[n now %ld]\n[copying %ld back]\n", n, n);
    471 				}
    472 				bufp = bufp0 + n;
    473 			}
    474 		}
    475 	}
    476 }
    477 
    478 static void
    479 process_indir_file(char *fn)
    480 {
    481 	char newfn[1024];
    482 	FILE *f;
    483 
    484 	f = fopen(fn, "r");
    485 	if (f == NULL) {
    486 		fprintf(stderr, "%s: cannot read %s\n", __progname, fn);
    487 		return;
    488 	}
    489 	while (fgets(newfn, sizeof(newfn), f) == newfn) {
    490 		newfn[strlen(newfn) - 1] = '\0';
    491 		process_file(newfn);
    492 	}
    493 	fclose(f);
    494 }
    495 
    496 int
    497 main(int ac, char **av)
    498 {
    499 	int skip;
    500 	char *cp;
    501 
    502 	if (ac < 3) {
    503 		fprintf(stderr, "Usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n",
    504 		    __progname);
    505 		exit(1);
    506 	}
    507 	cp = getenv("TERM");
    508 	if (cp == 0) {
    509 		beginul = nullstr;
    510 		endul = nullstr;
    511 	} else {
    512 		if (tgetent(tcp_buf, cp) != 1) {
    513 			beginul = nullstr;
    514 			endul = nullstr;
    515 		} else {
    516 			cp = cap_buf;
    517 			if (tgetflag("os") || tgetflag("ul")) {
    518 				ul_ = 1;
    519 			} else {
    520 				ul_ = 0;
    521 				beginul = tgetstr("us", &cp);
    522 				if (beginul == 0) {
    523 					beginul = tgetstr("so", &cp);
    524 					if (beginul == 0) {
    525 						beginul = nullstr;
    526 						endul = nullstr;
    527 					} else {
    528 						endul = tgetstr("se", &cp);
    529 					}
    530 				} else {
    531 					endul = tgetstr("ue", &cp);
    532 				}
    533 			}
    534 		}
    535 	}
    536 	{
    537 		static char tmp[] = "/tmp/qsubst.XXXXXX";
    538 		int fd;
    539 		fd = mkstemp(&tmp[0]);
    540 		if (fd < 0) {
    541 			fprintf(stderr, "%s: cannot create temp file: %s\n",
    542 			    __progname, strerror(errno));
    543 			exit(1);
    544 		}
    545 		tempf = fdopen(fd, "w+");
    546 	}
    547 	if ((access(av[1], R_OK | W_OK) == 0) &&
    548 	    (access(av[ac - 1], R_OK | W_OK) < 0) &&
    549 	    (access(av[ac - 2], R_OK | W_OK) < 0)) {
    550 		fprintf(stderr, "%s: argument order has changed, it's now: str1 str2 files...\n", __progname);
    551 	}
    552 	str1 = av[1];
    553 	str2 = av[2];
    554 	av += 2;
    555 	ac -= 2;
    556 	s1l = strlen(str1);
    557 	s2l = strlen(str2);
    558 	if (s1l > BUF_SIZ) {
    559 		fprintf(stderr, "%s: search string too long (max %d chars)\n",
    560 		    __progname, BUF_SIZ);
    561 		exit(1);
    562 	}
    563 	tcgetattr(0, &orig_tio);
    564 	signal(SIGTSTP, sigtstp);
    565 	allfly = 0;
    566 	cabove = 2;
    567 	cbelow = 2;
    568 	skip = 0;
    569 	for (ac--, av++; ac; ac--, av++) {
    570 		if (skip > 0) {
    571 			skip--;
    572 			continue;
    573 		}
    574 		if (**av == '-') {
    575 			++*av;
    576 			if (!strcmp(*av, "debug")) {
    577 				debugging++;
    578 			} else if (!strcmp(*av, "w")) {
    579 				wordmode = !wordmode;
    580 			} else if ((strcmp(*av, "!") == 0) ||
    581 				    (strcmp(*av, "go") == 0) ||
    582 			    (strcmp(*av, "noask") == 0)) {
    583 				allfly = 1;
    584 			} else if ((strcmp(*av, "nogo") == 0) ||
    585 			    (strcmp(*av, "ask") == 0)) {
    586 				allfly = 0;
    587 			} else if (**av == 'c') {
    588 				cabove = atoi(++*av);
    589 				cbelow = cabove;
    590 				limit_above_below();
    591 			} else if (**av == 'C') {
    592 				++*av;
    593 				if (**av == 'A') {
    594 					cabove = atoi(++*av);
    595 					limit_above_below();
    596 				} else if (**av == 'B') {
    597 					cbelow = atoi(++*av);
    598 					limit_above_below();
    599 				} else {
    600 					fprintf(stderr, "%s: -C must be -CA or -CB\n", __progname);
    601 				}
    602 			} else if ((strcmp(*av, "f") == 0) ||
    603 			    (strcmp(*av, "F") == 0)) {
    604 				if (++skip >= ac) {
    605 					fprintf(stderr, "%s: -%s what?\n",
    606 					    __progname, *av);
    607 				} else {
    608 					if (**av == 'f') {
    609 						process_file(av[skip]);
    610 					} else {
    611 						process_indir_file(av[skip]);
    612 					}
    613 				}
    614 			}
    615 		} else {
    616 			process_file(*av);
    617 		}
    618 	}
    619 	exit(0);
    620 }
    621