| /* | 
 |  * interactive diff, inspired/stolen from | 
 |  * kernighan and pike, _unix programming environment_. | 
 |  */ | 
 |  | 
 | #include <u.h> | 
 | #include <libc.h> | 
 | #include <bio.h> | 
 |  | 
 | #define opentemp idiffopentemp | 
 |  | 
 | int diffbflag; | 
 | int diffwflag; | 
 |  | 
 | void copy(Biobuf*, char*, Biobuf*, char*); | 
 | void idiff(Biobuf*, char*, Biobuf*, char*, Biobuf*, char*, Biobuf*, char*); | 
 | int opentemp(char*, int, long); | 
 | void rundiff(char*, char*, int); | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "usage: idiff [-bw] file1 file2\n"); | 
 | 	exits("usage"); | 
 | } | 
 |  | 
 | void | 
 | main(int argc, char **argv) | 
 | { | 
 | 	int fd, ofd; | 
 | 	char diffout[40], idiffout[40]; | 
 | 	Biobuf *b1, *b2, bdiff, bout, bstdout; | 
 | 	Dir *d; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		usage(); | 
 | 	case 'b': | 
 | 		diffbflag++; | 
 | 		break; | 
 | 	case 'w': | 
 | 		diffwflag++; | 
 | 		break; | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc != 2) | 
 | 		usage(); | 
 |  | 
 | 	if((d = dirstat(argv[0])) == nil) | 
 | 		sysfatal("stat %s: %r", argv[0]); | 
 | 	if(d->mode&DMDIR) | 
 | 		sysfatal("%s is a directory", argv[0]); | 
 | 	free(d); | 
 | 	if((d = dirstat(argv[1])) == nil) | 
 | 		sysfatal("stat %s: %r", argv[1]); | 
 | 	if(d->mode&DMDIR) | 
 | 		sysfatal("%s is a directory", argv[1]); | 
 | 	free(d); | 
 |  | 
 | 	if((b1 = Bopen(argv[0], OREAD)) == nil) | 
 | 		sysfatal("open %s: %r", argv[0]); | 
 | 	if((b2 = Bopen(argv[1], OREAD)) == nil) | 
 | 		sysfatal("open %s: %r", argv[1]); | 
 |  | 
 | 	strcpy(diffout, "/tmp/idiff.XXXXXX"); | 
 | 	fd = opentemp(diffout, ORDWR|ORCLOSE, 0); | 
 | 	strcpy(idiffout, "/tmp/idiff.XXXXXX"); | 
 | 	ofd = opentemp(idiffout, ORDWR|ORCLOSE, 0); | 
 | 	rundiff(argv[0], argv[1], fd); | 
 | 	seek(fd, 0, 0); | 
 | 	Binit(&bdiff, fd, OREAD); | 
 | 	Binit(&bout, ofd, OWRITE); | 
 | 	idiff(b1, argv[0], b2, argv[1], &bdiff, diffout, &bout, idiffout); | 
 | 	Bterm(&bdiff); | 
 | 	Bflush(&bout); | 
 | 	seek(ofd, 0, 0); | 
 | 	Binit(&bout, ofd, OREAD); | 
 | 	Binit(&bstdout, 1, OWRITE); | 
 | 	copy(&bout, idiffout, &bstdout, "<stdout>"); | 
 | 	exits(nil); | 
 | } | 
 |  | 
 | int | 
 | opentemp(char *template, int mode, long perm) | 
 | { | 
 | 	int fd; | 
 | 	Dir d; | 
 |  | 
 | 	fd = mkstemp(template); | 
 | 	if(fd < 0) | 
 | 		sysfatal("could not create temporary file"); | 
 | 	nulldir(&d); | 
 | 	d.mode = perm; | 
 | 	dirfwstat(fd, &d); | 
 |  | 
 | 	return fd; | 
 | } | 
 |  | 
 | void | 
 | rundiff(char *arg1, char *arg2, int outfd) | 
 | { | 
 | 	char *arg[10], *p; | 
 | 	int narg, pid; | 
 | 	Waitmsg *w; | 
 |  | 
 | 	narg = 0; | 
 | 	arg[narg++] = "9"; | 
 | 	arg[narg++] = "diff"; | 
 | 	arg[narg++] = "-n"; | 
 | 	if(diffbflag) | 
 | 		arg[narg++] = "-b"; | 
 | 	if(diffwflag) | 
 | 		arg[narg++] = "-w"; | 
 | 	arg[narg++] = arg1; | 
 | 	arg[narg++] = arg2; | 
 | 	arg[narg] = nil; | 
 |  | 
 | 	switch(pid = fork()){ | 
 | 	case -1: | 
 | 		sysfatal("fork: %r"); | 
 |  | 
 | 	case 0: | 
 | 		dup(outfd, 1); | 
 | 		close(0); | 
 | 		exec("9", arg); | 
 | 		sysfatal("exec: %r"); | 
 |  | 
 | 	default: | 
 | 		w = wait(); | 
 | 		if(w==nil) | 
 | 			sysfatal("wait: %r"); | 
 | 		if(w->pid != pid) | 
 | 			sysfatal("wait got unexpected pid %d", w->pid); | 
 | 		if((p = strchr(w->msg, ':')) && strcmp(p, ": some") != 0) | 
 | 			sysfatal("%s", w->msg); | 
 | 		free(w); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | runcmd(char *cmd) | 
 | { | 
 | 	char *arg[10]; | 
 | 	int narg, pid, wpid; | 
 |  | 
 | 	narg = 0; | 
 | 	arg[narg++] = "rc"; | 
 | 	arg[narg++] = "-c"; | 
 | 	arg[narg++] = cmd; | 
 | 	arg[narg] = nil; | 
 |  | 
 | 	switch(pid = fork()){ | 
 | 	case -1: | 
 | 		sysfatal("fork: %r"); | 
 |  | 
 | 	case 0: | 
 | 		exec("rc", arg); | 
 | 		sysfatal("exec: %r"); | 
 |  | 
 | 	default: | 
 | 		wpid = waitpid(); | 
 | 		if(wpid < 0) | 
 | 			sysfatal("wait: %r"); | 
 | 		if(wpid != pid) | 
 | 			sysfatal("wait got unexpected pid %d", wpid); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | parse(char *s, int *pfrom1, int *pto1, int *pcmd, int *pfrom2, int *pto2) | 
 | { | 
 | 	*pfrom1 = *pto1 = *pfrom2 = *pto2 = 0; | 
 |  | 
 | 	s = strchr(s, ':'); | 
 | 	if(s == nil) | 
 | 		sysfatal("bad diff output0"); | 
 | 	s++; | 
 | 	*pfrom1 = strtol(s, &s, 10); | 
 | 	if(*s == ','){ | 
 | 		s++; | 
 | 		*pto1 = strtol(s, &s, 10); | 
 | 	}else | 
 | 		*pto1 = *pfrom1; | 
 | 	if(*s++ != ' ') | 
 | 		sysfatal("bad diff output1"); | 
 | 	*pcmd = *s++; | 
 | 	if(*s++ != ' ') | 
 | 		sysfatal("bad diff output2"); | 
 | 	s = strchr(s, ':'); | 
 | 	if(s == nil) | 
 | 		sysfatal("bad diff output3"); | 
 | 	s++; | 
 | 	*pfrom2 = strtol(s, &s, 10); | 
 | 	if(*s == ','){ | 
 | 		s++; | 
 | 		*pto2 = strtol(s, &s, 10); | 
 | 	}else | 
 | 		*pto2 = *pfrom2; | 
 | } | 
 |  | 
 | void | 
 | skiplines(Biobuf *b, char *name, int n) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for(i=0; i<n; i++){ | 
 | 		while(Brdline(b, '\n')==nil){ | 
 | 			if(Blinelen(b) <= 0) | 
 | 				sysfatal("early end of file on %s", name); | 
 | 			Bseek(b, Blinelen(b), 1); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | copylines(Biobuf *bin, char *nin, Biobuf *bout, char *nout, int n) | 
 | { | 
 | 	char buf[4096], *p; | 
 | 	int i, m; | 
 |  | 
 | 	for(i=0; i<n; i++){ | 
 | 		while((p=Brdline(bin, '\n'))==nil){ | 
 | 			if(Blinelen(bin) <= 0) | 
 | 				sysfatal("early end of file on %s", nin); | 
 | 			m = Blinelen(bin); | 
 | 			if(m > sizeof buf) | 
 | 				m = sizeof buf; | 
 | 			m = Bread(bin, buf, m); | 
 | 			if(Bwrite(bout, buf, m) != m) | 
 | 				sysfatal("error writing %s: %r", nout); | 
 | 		} | 
 | 		if(Bwrite(bout, p, Blinelen(bin)) != Blinelen(bin)) | 
 | 			sysfatal("error writing %s: %r", nout); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | copy(Biobuf *bin, char *nin, Biobuf *bout, char *nout) | 
 | { | 
 | 	char buf[4096]; | 
 | 	int m; | 
 |  | 
 | 	USED(nin); | 
 | 	while((m = Bread(bin, buf, sizeof buf)) > 0) | 
 | 		if(Bwrite(bout, buf, m) != m) | 
 | 			sysfatal("error writing %s: %r", nout); | 
 | } | 
 |  | 
 | void | 
 | idiff(Biobuf *b1, char *name1, Biobuf *b2, char *name2, Biobuf *bdiff, char *namediff, Biobuf *bout, char *nameout) | 
 | { | 
 | 	char buf[256], *p; | 
 | 	int interactive, defaultanswer, cmd, diffoffset; | 
 | 	int n, from1, to1, from2, to2, nf1, nf2; | 
 | 	Biobuf berr; | 
 |  | 
 | 	nf1 = 1; | 
 | 	nf2 = 1; | 
 | 	interactive = 1; | 
 | 	defaultanswer = 0; | 
 | 	Binit(&berr, 2, OWRITE); | 
 | 	while(diffoffset = Boffset(bdiff), p = Brdline(bdiff, '\n')){ | 
 | 		p[Blinelen(bdiff)-1] = '\0'; | 
 | 		parse(p, &from1, &to1, &cmd, &from2, &to2); | 
 | 		p[Blinelen(bdiff)-1] = '\n'; | 
 | 		n = to1-from1 + to2-from2 + 1;	/* #lines from diff */ | 
 | 		if(cmd == 'c') | 
 | 			n += 2; | 
 | 		else if(cmd == 'a') | 
 | 			from1++; | 
 | 		else if(cmd == 'd') | 
 | 			from2++; | 
 | 		to1++;	/* make half-open intervals */ | 
 | 		to2++; | 
 | 		if(interactive){ | 
 | 			p[Blinelen(bdiff)-1] = '\0'; | 
 | 			fprint(2, "%s\n", p); | 
 | 			p[Blinelen(bdiff)-1] = '\n'; | 
 | 			copylines(bdiff, namediff, &berr, "<stderr>", n); | 
 | 			Bflush(&berr); | 
 | 		}else | 
 | 			skiplines(bdiff, namediff, n); | 
 | 		do{ | 
 | 			if(interactive){ | 
 | 				fprint(2, "? "); | 
 | 				memset(buf, 0, sizeof buf); | 
 | 				if(read(0, buf, sizeof buf - 1) < 0) | 
 | 					sysfatal("read console: %r"); | 
 | 			}else | 
 | 				buf[0] = defaultanswer; | 
 |  | 
 | 			switch(buf[0]){ | 
 | 			case '>': | 
 | 				copylines(b1, name1, bout, nameout, from1-nf1); | 
 | 				skiplines(b1, name1, to1-from1); | 
 | 				skiplines(b2, name2, from2-nf2); | 
 | 				copylines(b2, name2, bout, nameout, to2-from2); | 
 | 				break; | 
 | 			case '<': | 
 | 				copylines(b1, name1, bout, nameout, to1-nf1); | 
 | 				skiplines(b2, name2, to2-nf2); | 
 | 				break; | 
 | 			case '=': | 
 | 				copylines(b1, name1, bout, nameout, from1-nf1); | 
 | 				skiplines(b1, name1, to1-from1); | 
 | 				skiplines(b2, name2, to2-nf2); | 
 | 				if(Bseek(bdiff, diffoffset, 0) != diffoffset) | 
 | 					sysfatal("seek in diff output: %r"); | 
 | 				copylines(bdiff, namediff, bout, nameout, n+1); | 
 | 				break; | 
 | 			case '!': | 
 | 				runcmd(buf+1); | 
 | 				break; | 
 | 			case 'q': | 
 | 				if(buf[1]=='<' || buf[1]=='>' || buf[1]=='='){ | 
 | 					interactive = 0; | 
 | 					defaultanswer = buf[1]; | 
 | 				}else | 
 | 					fprint(2, "must be q<, q>, or q=\n"); | 
 | 				break; | 
 | 			default: | 
 | 				fprint(2, "expect: <, >, =, q<, q>, q=, !cmd\n"); | 
 | 				break; | 
 | 			} | 
 | 		}while(buf[0] != '<' && buf[0] != '>' && buf[0] != '='); | 
 | 		nf1 = to1; | 
 | 		nf2 = to2; | 
 | 	} | 
 | 	copy(b1, name1, bout, nameout); | 
 | } |