/* $NetBSD: qsubst.c,v 1.8 2004/11/01 21:36:11 dsl Exp $ */ /* * qsubst -- designed for renaming routines existing in a whole bunch * of files. Needs -ltermcap. * * Usage: * * qsubst str1 str2 [ options ] * * qsubst reads its options (see below) to get a list of files. For * each file on this list, it then replaces str1 with str2 wherever * possible in that file, depending on user input (see below). The * result is written back onto the original file. * * For each possible substitution, the user is prompted with a few * lines before and after the line containing the string to be * substituted. The string itself is displayed using the terminal's * standout mode, if any. Then one character is read from the * terminal. This is then interpreted as follows (this is designed to * be like Emacs' query-replace-string): * * space replace this occurrence and go on to the next one * . replace this occurrence and don't change any more in * this file (ie, go on to the next file). * , tentatively replace this occurrence. The lines as they * would look if the substitution were made are printed * out. Then another character is read and it is used to * decide the result (possibly undoing the tentative * replacement). * n don't change this one, but go on to the next one * ^G don't change this one or any others in this file, but * instead go on to the next file. * ! change the rest in this file without asking, then go on * to the next file (at which point qsubst will start * asking again). * ? print out the current filename and ask again. * * The first two arguments to qsubst are always the string to replace * and the string to replace it with. The options are as follows: * * -w The search string is considered as a C symbol; it must * be bounded by non-symbol characters. This option * toggles. (`w' for `word'.) * -! Enter ! mode automatically at the beginning of each * file. * -go Same as -! * -noask Same as -! * -nogo Negate -go * -ask Negate -noask (same as -nogo) * -cN (N is a number) Give N lines of context above and below * the line with the match when prompting the user. * -CAN (N is a number) Give N lines of context above the line * with the match when prompting the user. * -CBN (N is a number) Give N lines of context below the line * with the match when prompting the user. * -f filename * The filename following the -f argument is one of the * files qsubst should perform substitutions in. * -F filename * qsubst should read the named file to get the names of * files to perform substitutions in. The names should * appear one to a line. * * The default amount of context is -c2, that is, two lines above and * two lines below the line with the match. * * Arguments not beginning with a - sign in the options field are * implicitly preceded by -f. Thus, -f is really needed only when the * file name begins with a - sign. * * qsubst reads its options in order and processes files as it gets * them. This means, for example, that a -go will affect only files * from -f or -F options appearing after the -go option. * * The most context you can get is ten lines each, above and below * (corresponding to -c10). * * Str1 is limited to 512 characters; there is no limit on the size of * str2. Neither one may contain a NUL. * * NULs in the file may cause qsubst to make various mistakes. * * If any other program modifies the file while qsubst is running, all * bets are off. * * This program is in the public domain. Anyone may use it in any way * for any purpose. Of course, it's also up to you to determine * whether what it does is suitable for you; the above comments may * help, but I can't promise they're accurate. It's free, and you get * what you pay for. * * If you find any bugs I would appreciate hearing about them, * especially if you also fix them. * * der Mouse * * mouse@rodents.montreal.qc.ca */ #include #ifndef lint __RCSID("$NetBSD: qsubst.c,v 1.8 2004/11/01 21:36:11 dsl Exp $"); #endif #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #define MAX_C_A 10 #define MAX_C_B 10 #define BUF_SIZ 1024 static int debugging; static FILE *tempf; static long tbeg; static FILE *workf; static char *str1; static char *str2; static int s1l; static int s2l; static long nls[MAX_C_A + 1]; static char buf[(BUF_SIZ * 2) + 2]; static char *bufp; static char *bufp0; static char *bufpmax; static int rahead; static int cabove; static int cbelow; static int wordmode; static int flying; static int flystate; static int allfly; static const char *nullstr = ""; static int ul_; static char *current_file; static const char *beginul; static const char *endul; static char tcp_buf[1024]; static char cap_buf[1024]; static struct termios orig_tio; static void tstp_self(void) { void (*old_tstp) (int); int mask; mask = sigblock(0); kill(getpid(), SIGTSTP); old_tstp = signal(SIGTSTP, SIG_DFL); sigsetmask(mask & ~sigmask(SIGTSTP)); signal(SIGTSTP, old_tstp); } /* ARGSUSED */ static void sigtstp(int sig) { struct termios tio; if (tcgetattr(0, &tio) < 0) { tstp_self(); return; } tcsetattr(0, TCSAFLUSH | TCSASOFT, &orig_tio); tstp_self(); tcsetattr(0, TCSADRAIN | TCSASOFT, &tio); } static void limit_above_below(void) { if (cabove > MAX_C_A) { cabove = MAX_C_A; } if (cbelow > MAX_C_B) { cbelow = MAX_C_B; } } static int issymchar(unsigned char c) { return (isascii(c) && (isalnum(c) || (c == '_') || (c == '$'))); } static int foundit(void) { if (wordmode) { return (!issymchar(bufp[-1]) && !issymchar(bufp[-2 - s1l]) && !bcmp(bufp - 1 - s1l, str1, s1l)); } else { return (!bcmp(bufp - s1l, str1, s1l)); } } static int putcharf(int c) { return (putchar(c)); } static void put_ul(char *s) { if (ul_) { for (; *s; s++) { printf("_\b%c", *s); } } else { tputs(beginul, 1, putcharf); fputs(s, stdout); tputs(endul, 1, putcharf); } } static int getc_cbreak(void) { struct termios tio; struct termios otio; char c; if (tcgetattr(0, &tio) < 0) return (getchar()); otio = tio; tio.c_lflag &= ~(ICANON | ECHOKE | ECHOE | ECHO | ECHONL); tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW | TCSASOFT, &tio); switch (read(0, &c, 1)) { case -1: break; case 0: break; case 1: break; } tcsetattr(0, TCSANOW | TCSASOFT, &otio); return (c); } static int doit(void) { long save; int i; int lastnl; int use_replacement; if (flying) { return (flystate); } use_replacement = 0; save = ftell(workf); do { for (i = MAX_C_A - cabove; nls[i] < 0; i++); fseek(workf, nls[i], 0); for (i = save - nls[i] - rahead; i; i--) { putchar(getc(workf)); } put_ul(use_replacement ? str2 : str1); fseek(workf, save + s1l - rahead, 0); lastnl = 0; i = cbelow + 1; while (i > 0) { int c; c = getc(workf); if (c == EOF) { clearerr(workf); break; } putchar(c); lastnl = 0; if (c == '\n') { i--; lastnl = 1; } } if (!lastnl) printf("\n[no final newline] "); fseek(workf, save, 0); i = -1; while (i == -1) { switch (getc_cbreak()) { case ' ': i = 1; break; case '.': i = 1; flying = 1; flystate = 0; break; case 'n': i = 0; break; case '\7': i = 0; flying = 1; flystate = 0; break; case '!': i = 1; flying = 1; flystate = 1; break; case ',': use_replacement = !use_replacement; i = -2; printf("(using %s string gives)\n", use_replacement ? "new" : "old"); break; case '?': printf("File is `%s'\n", current_file); break; default: putchar('\7'); break; } } } while (i < 0); if (i) { printf("(replacing"); } else { printf("(leaving"); } if (flying) { if (flystate == i) { printf(" this and all the rest"); } else if (flystate) { printf(" this, replacing all the rest"); } else { printf(" this, leaving all the rest"); } } printf(")\n"); return (i); } static void add_shift(long *a, long e, int n) { int i; n--; for (i = 0; i < n; i++) { a[i] = a[i + 1]; } a[n] = e; } static void process_file(char *fn) { int i; long n; int c; workf = fopen(fn, "r+"); if (workf == NULL) { fprintf(stderr, "%s: cannot read %s\n", __progname, fn); return; } printf("(file: %s)\n", fn); current_file = fn; for (i = 0; i <= MAX_C_A; i++) { nls[i] = -1; } nls[MAX_C_A] = 0; tbeg = -1; if (wordmode) { bufp0 = &buf[1]; rahead = s1l + 1; buf[0] = '\0'; } else { bufp0 = &buf[0]; rahead = s1l; } if (debugging) { printf("[rahead = %d, bufp0-buf = %ld]\n", rahead, (long) (bufp0 - &buf[0])); } n = 0; bufp = bufp0; bufpmax = &buf[sizeof(buf) - s1l - 2]; flying = allfly; flystate = 1; while (1) { c = getc(workf); if (c == EOF) { if (tbeg >= 0) { if (bufp > bufp0) fwrite(bufp0, 1, bufp - bufp0, tempf); fseek(workf, tbeg, 0); n = ftell(tempf); fseek(tempf, 0L, 0); for (; n; n--) { putc(getc(tempf), workf); } fflush(workf); ftruncate(fileno(workf), ftell(workf)); } fclose(workf); return; } *bufp++ = c; n++; if (debugging) { printf("[got %c, n now %ld, bufp-buf %ld]\n", c, n, (long) (bufp - bufp0)); } if ((n >= rahead) && foundit() && doit()) { int wbehind; if (debugging) { printf("[doing change]\n"); } wbehind = 1; if (tbeg < 0) { tbeg = ftell(workf) - rahead; fseek(tempf, 0L, 0); if (debugging) { printf("[tbeg set to %d]\n", (int)tbeg); } wbehind = 0; } if (bufp[-1] == '\n') add_shift(nls, ftell(workf), MAX_C_A + 1); if ((n > rahead) && wbehind) { fwrite(bufp0, 1, n - rahead, tempf); if (debugging) { printf("[writing %ld from bufp0]\n", n - rahead); } } fwrite(str2, 1, s2l, tempf); n = rahead - s1l; if (debugging) { printf("[n now %ld]\n", n); } if (n > 0) { bcopy(bufp - n, bufp0, n); if (debugging) { printf("[copying %ld back]\n", n); } } bufp = bufp0 + n; } else { if (bufp[-1] == '\n') add_shift(nls, ftell(workf), MAX_C_A + 1); if (bufp >= bufpmax) { if (tbeg >= 0) { fwrite(bufp0, 1, n - rahead, tempf); if (debugging) { printf("[flushing %ld]\n", n - rahead); } } n = rahead; bcopy(bufp - n, bufp0, n); if (debugging) { printf("[n now %ld]\n[copying %ld back]\n", n, n); } bufp = bufp0 + n; } } } } static void process_indir_file(char *fn) { char newfn[1024]; FILE *f; f = fopen(fn, "r"); if (f == NULL) { fprintf(stderr, "%s: cannot read %s\n", __progname, fn); return; } while (fgets(newfn, sizeof(newfn), f) == newfn) { newfn[strlen(newfn) - 1] = '\0'; process_file(newfn); } fclose(f); } int main(int ac, char **av) { int skip; char *cp; if (ac < 3) { fprintf(stderr, "usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n", __progname); exit(1); } cp = getenv("TERM"); if (cp == 0) { beginul = nullstr; endul = nullstr; } else { if (tgetent(tcp_buf, cp) != 1) { beginul = nullstr; endul = nullstr; } else { cp = cap_buf; if (tgetflag("os") || tgetflag("ul")) { ul_ = 1; } else { ul_ = 0; beginul = tgetstr("us", &cp); if (beginul == 0) { beginul = tgetstr("so", &cp); if (beginul == 0) { beginul = nullstr; endul = nullstr; } else { endul = tgetstr("se", &cp); } } else { endul = tgetstr("ue", &cp); } } } } { static char tmp[] = "/tmp/qsubst.XXXXXX"; int fd; fd = mkstemp(&tmp[0]); if (fd < 0) { fprintf(stderr, "%s: cannot create temp file: %s\n", __progname, strerror(errno)); exit(1); } tempf = fdopen(fd, "w+"); } if ((access(av[1], R_OK | W_OK) == 0) && (access(av[ac - 1], R_OK | W_OK) < 0) && (access(av[ac - 2], R_OK | W_OK) < 0)) { fprintf(stderr, "%s: argument order has changed, it's now: str1 str2 files...\n", __progname); } str1 = av[1]; str2 = av[2]; av += 2; ac -= 2; s1l = strlen(str1); s2l = strlen(str2); if (s1l > BUF_SIZ) { fprintf(stderr, "%s: search string too long (max %d chars)\n", __progname, BUF_SIZ); exit(1); } tcgetattr(0, &orig_tio); signal(SIGTSTP, sigtstp); allfly = 0; cabove = 2; cbelow = 2; skip = 0; for (ac--, av++; ac; ac--, av++) { if (skip > 0) { skip--; continue; } if (**av == '-') { ++*av; if (!strcmp(*av, "debug")) { debugging++; } else if (!strcmp(*av, "w")) { wordmode = !wordmode; } else if ((strcmp(*av, "!") == 0) || (strcmp(*av, "go") == 0) || (strcmp(*av, "noask") == 0)) { allfly = 1; } else if ((strcmp(*av, "nogo") == 0) || (strcmp(*av, "ask") == 0)) { allfly = 0; } else if (**av == 'c') { cabove = atoi(++*av); cbelow = cabove; limit_above_below(); } else if (**av == 'C') { ++*av; if (**av == 'A') { cabove = atoi(++*av); limit_above_below(); } else if (**av == 'B') { cbelow = atoi(++*av); limit_above_below(); } else { fprintf(stderr, "%s: -C must be -CA or -CB\n", __progname); } } else if ((strcmp(*av, "f") == 0) || (strcmp(*av, "F") == 0)) { if (++skip >= ac) { fprintf(stderr, "%s: -%s what?\n", __progname, *av); } else { if (**av == 'f') { process_file(av[skip]); } else { process_indir_file(av[skip]); } } } } else { process_file(*av); } } exit(0); }