626 lines
14 KiB
C
626 lines
14 KiB
C
/* $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 <sys/cdefs.h>
|
|
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: qsubst.c,v 1.8 2004/11/01 21:36:11 dsl Exp $");
|
|
#endif
|
|
|
|
#include <sys/file.h>
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <strings.h>
|
|
#include <termcap.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
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);
|
|
}
|