rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 1 | /* |
| 2 | * interactive diff, inspired/stolen from |
| 3 | * kernighan and pike, _unix programming environment_. |
| 4 | */ |
| 5 | |
| 6 | #include <u.h> |
| 7 | #include <libc.h> |
| 8 | #include <bio.h> |
| 9 | |
rsc | ca0c710 | 2004-03-21 05:20:37 +0000 | [diff] [blame] | 10 | #define opentemp idiffopentemp |
| 11 | |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 12 | int diffbflag; |
| 13 | int diffwflag; |
| 14 | |
| 15 | void copy(Biobuf*, char*, Biobuf*, char*); |
| 16 | void idiff(Biobuf*, char*, Biobuf*, char*, Biobuf*, char*, Biobuf*, char*); |
| 17 | int opentemp(char*, int, long); |
| 18 | void rundiff(char*, char*, int); |
| 19 | |
| 20 | void |
| 21 | usage(void) |
| 22 | { |
| 23 | fprint(2, "usage: idiff [-bw] file1 file2\n"); |
| 24 | exits("usage"); |
| 25 | } |
| 26 | |
| 27 | void |
| 28 | main(int argc, char **argv) |
| 29 | { |
| 30 | int fd, ofd; |
| 31 | char diffout[40], idiffout[40]; |
| 32 | Biobuf *b1, *b2, bdiff, bout, bstdout; |
| 33 | Dir *d; |
| 34 | |
| 35 | ARGBEGIN{ |
| 36 | default: |
| 37 | usage(); |
| 38 | case 'b': |
| 39 | diffbflag++; |
| 40 | break; |
| 41 | case 'w': |
| 42 | diffwflag++; |
| 43 | break; |
| 44 | }ARGEND |
| 45 | |
| 46 | if(argc != 2) |
| 47 | usage(); |
| 48 | |
| 49 | if((d = dirstat(argv[0])) == nil) |
| 50 | sysfatal("stat %s: %r", argv[0]); |
| 51 | if(d->mode&DMDIR) |
| 52 | sysfatal("%s is a directory", argv[0]); |
| 53 | free(d); |
| 54 | if((d = dirstat(argv[1])) == nil) |
| 55 | sysfatal("stat %s: %r", argv[1]); |
| 56 | if(d->mode&DMDIR) |
| 57 | sysfatal("%s is a directory", argv[1]); |
| 58 | free(d); |
| 59 | |
| 60 | if((b1 = Bopen(argv[0], OREAD)) == nil) |
| 61 | sysfatal("open %s: %r", argv[0]); |
| 62 | if((b2 = Bopen(argv[1], OREAD)) == nil) |
| 63 | sysfatal("open %s: %r", argv[1]); |
| 64 | |
| 65 | strcpy(diffout, "/tmp/idiff.XXXXXX"); |
| 66 | fd = opentemp(diffout, ORDWR|ORCLOSE, 0); |
| 67 | strcpy(idiffout, "/tmp/idiff.XXXXXX"); |
| 68 | ofd = opentemp(idiffout, ORDWR|ORCLOSE, 0); |
| 69 | rundiff(argv[0], argv[1], fd); |
| 70 | seek(fd, 0, 0); |
| 71 | Binit(&bdiff, fd, OREAD); |
| 72 | Binit(&bout, ofd, OWRITE); |
| 73 | idiff(b1, argv[0], b2, argv[1], &bdiff, diffout, &bout, idiffout); |
| 74 | Bterm(&bdiff); |
| 75 | Bflush(&bout); |
| 76 | seek(ofd, 0, 0); |
| 77 | Binit(&bout, ofd, OREAD); |
| 78 | Binit(&bstdout, 1, OWRITE); |
| 79 | copy(&bout, idiffout, &bstdout, "<stdout>"); |
| 80 | exits(nil); |
| 81 | } |
| 82 | |
| 83 | int |
| 84 | opentemp(char *template, int mode, long perm) |
| 85 | { |
rsc | d2c4ee9 | 2003-11-24 00:43:41 +0000 | [diff] [blame] | 86 | int fd; |
rsc | f701258 | 2003-11-25 01:40:27 +0000 | [diff] [blame] | 87 | Dir d; |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 88 | |
rsc | d2c4ee9 | 2003-11-24 00:43:41 +0000 | [diff] [blame] | 89 | fd = mkstemp(template); |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 90 | if(fd < 0) |
| 91 | sysfatal("could not create temporary file"); |
rsc | f701258 | 2003-11-25 01:40:27 +0000 | [diff] [blame] | 92 | nulldir(&d); |
| 93 | d.mode = perm; |
| 94 | dirfwstat(fd, &d); |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 95 | |
| 96 | return fd; |
| 97 | } |
| 98 | |
| 99 | void |
| 100 | rundiff(char *arg1, char *arg2, int outfd) |
| 101 | { |
| 102 | char *arg[10], *p; |
| 103 | int narg, pid; |
| 104 | Waitmsg *w; |
| 105 | |
| 106 | narg = 0; |
rsc | b4a659b | 2004-04-19 23:03:46 +0000 | [diff] [blame] | 107 | arg[narg++] = "diff"; |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 108 | arg[narg++] = "-n"; |
| 109 | if(diffbflag) |
| 110 | arg[narg++] = "-b"; |
| 111 | if(diffwflag) |
| 112 | arg[narg++] = "-w"; |
| 113 | arg[narg++] = arg1; |
| 114 | arg[narg++] = arg2; |
| 115 | arg[narg] = nil; |
| 116 | |
| 117 | switch(pid = fork()){ |
| 118 | case -1: |
| 119 | sysfatal("fork: %r"); |
| 120 | |
| 121 | case 0: |
| 122 | dup(outfd, 1); |
| 123 | close(0); |
rsc | b4a659b | 2004-04-19 23:03:46 +0000 | [diff] [blame] | 124 | exec("diff", arg); |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 125 | sysfatal("exec: %r"); |
| 126 | |
| 127 | default: |
| 128 | w = wait(); |
| 129 | if(w==nil) |
| 130 | sysfatal("wait: %r"); |
| 131 | if(w->pid != pid) |
| 132 | sysfatal("wait got unexpected pid %d", w->pid); |
| 133 | if((p = strchr(w->msg, ':')) && strcmp(p, ": some") != 0) |
| 134 | sysfatal("%s", w->msg); |
| 135 | free(w); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | void |
| 140 | runcmd(char *cmd) |
| 141 | { |
| 142 | char *arg[10]; |
| 143 | int narg, pid, wpid; |
| 144 | |
| 145 | narg = 0; |
rsc | b4a659b | 2004-04-19 23:03:46 +0000 | [diff] [blame] | 146 | arg[narg++] = "rc"; |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 147 | arg[narg++] = "-c"; |
| 148 | arg[narg++] = cmd; |
| 149 | arg[narg] = nil; |
| 150 | |
| 151 | switch(pid = fork()){ |
| 152 | case -1: |
| 153 | sysfatal("fork: %r"); |
| 154 | |
| 155 | case 0: |
rsc | b4a659b | 2004-04-19 23:03:46 +0000 | [diff] [blame] | 156 | exec("rc", arg); |
rsc | bc7cb1a | 2003-11-23 18:04:47 +0000 | [diff] [blame] | 157 | sysfatal("exec: %r"); |
| 158 | |
| 159 | default: |
| 160 | wpid = waitpid(); |
| 161 | if(wpid < 0) |
| 162 | sysfatal("wait: %r"); |
| 163 | if(wpid != pid) |
| 164 | sysfatal("wait got unexpected pid %d", wpid); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | void |
| 169 | parse(char *s, int *pfrom1, int *pto1, int *pcmd, int *pfrom2, int *pto2) |
| 170 | { |
| 171 | *pfrom1 = *pto1 = *pfrom2 = *pto2 = 0; |
| 172 | |
| 173 | s = strchr(s, ':'); |
| 174 | if(s == nil) |
| 175 | sysfatal("bad diff output0"); |
| 176 | s++; |
| 177 | *pfrom1 = strtol(s, &s, 10); |
| 178 | if(*s == ','){ |
| 179 | s++; |
| 180 | *pto1 = strtol(s, &s, 10); |
| 181 | }else |
| 182 | *pto1 = *pfrom1; |
| 183 | if(*s++ != ' ') |
| 184 | sysfatal("bad diff output1"); |
| 185 | *pcmd = *s++; |
| 186 | if(*s++ != ' ') |
| 187 | sysfatal("bad diff output2"); |
| 188 | s = strchr(s, ':'); |
| 189 | if(s == nil) |
| 190 | sysfatal("bad diff output3"); |
| 191 | s++; |
| 192 | *pfrom2 = strtol(s, &s, 10); |
| 193 | if(*s == ','){ |
| 194 | s++; |
| 195 | *pto2 = strtol(s, &s, 10); |
| 196 | }else |
| 197 | *pto2 = *pfrom2; |
| 198 | } |
| 199 | |
| 200 | void |
| 201 | skiplines(Biobuf *b, char *name, int n) |
| 202 | { |
| 203 | int i; |
| 204 | |
| 205 | for(i=0; i<n; i++){ |
| 206 | while(Brdline(b, '\n')==nil){ |
| 207 | if(Blinelen(b) <= 0) |
| 208 | sysfatal("early end of file on %s", name); |
| 209 | Bseek(b, Blinelen(b), 1); |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | void |
| 215 | copylines(Biobuf *bin, char *nin, Biobuf *bout, char *nout, int n) |
| 216 | { |
| 217 | char buf[4096], *p; |
| 218 | int i, m; |
| 219 | |
| 220 | for(i=0; i<n; i++){ |
| 221 | while((p=Brdline(bin, '\n'))==nil){ |
| 222 | if(Blinelen(bin) <= 0) |
| 223 | sysfatal("early end of file on %s", nin); |
| 224 | m = Blinelen(bin); |
| 225 | if(m > sizeof buf) |
| 226 | m = sizeof buf; |
| 227 | m = Bread(bin, buf, m); |
| 228 | if(Bwrite(bout, buf, m) != m) |
| 229 | sysfatal("error writing %s: %r", nout); |
| 230 | } |
| 231 | if(Bwrite(bout, p, Blinelen(bin)) != Blinelen(bin)) |
| 232 | sysfatal("error writing %s: %r", nout); |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | void |
| 237 | copy(Biobuf *bin, char *nin, Biobuf *bout, char *nout) |
| 238 | { |
| 239 | char buf[4096]; |
| 240 | int m; |
| 241 | |
| 242 | USED(nin); |
| 243 | while((m = Bread(bin, buf, sizeof buf)) > 0) |
| 244 | if(Bwrite(bout, buf, m) != m) |
| 245 | sysfatal("error writing %s: %r", nout); |
| 246 | } |
| 247 | |
| 248 | void |
| 249 | idiff(Biobuf *b1, char *name1, Biobuf *b2, char *name2, Biobuf *bdiff, char *namediff, Biobuf *bout, char *nameout) |
| 250 | { |
| 251 | char buf[256], *p; |
| 252 | int interactive, defaultanswer, cmd, diffoffset; |
| 253 | int n, from1, to1, from2, to2, nf1, nf2; |
| 254 | Biobuf berr; |
| 255 | |
| 256 | nf1 = 1; |
| 257 | nf2 = 1; |
| 258 | interactive = 1; |
| 259 | defaultanswer = 0; |
| 260 | Binit(&berr, 2, OWRITE); |
| 261 | while(diffoffset = Boffset(bdiff), p = Brdline(bdiff, '\n')){ |
| 262 | p[Blinelen(bdiff)-1] = '\0'; |
| 263 | parse(p, &from1, &to1, &cmd, &from2, &to2); |
| 264 | p[Blinelen(bdiff)-1] = '\n'; |
| 265 | n = to1-from1 + to2-from2 + 1; /* #lines from diff */ |
| 266 | if(cmd == 'c') |
| 267 | n += 2; |
| 268 | else if(cmd == 'a') |
| 269 | from1++; |
| 270 | else if(cmd == 'd') |
| 271 | from2++; |
| 272 | to1++; /* make half-open intervals */ |
| 273 | to2++; |
| 274 | if(interactive){ |
| 275 | p[Blinelen(bdiff)-1] = '\0'; |
| 276 | fprint(2, "%s\n", p); |
| 277 | p[Blinelen(bdiff)-1] = '\n'; |
| 278 | copylines(bdiff, namediff, &berr, "<stderr>", n); |
| 279 | Bflush(&berr); |
| 280 | }else |
| 281 | skiplines(bdiff, namediff, n); |
| 282 | do{ |
| 283 | if(interactive){ |
| 284 | fprint(2, "? "); |
| 285 | memset(buf, 0, sizeof buf); |
| 286 | if(read(0, buf, sizeof buf - 1) < 0) |
| 287 | sysfatal("read console: %r"); |
| 288 | }else |
| 289 | buf[0] = defaultanswer; |
| 290 | |
| 291 | switch(buf[0]){ |
| 292 | case '>': |
| 293 | copylines(b1, name1, bout, nameout, from1-nf1); |
| 294 | skiplines(b1, name1, to1-from1); |
| 295 | skiplines(b2, name2, from2-nf2); |
| 296 | copylines(b2, name2, bout, nameout, to2-from2); |
| 297 | break; |
| 298 | case '<': |
| 299 | copylines(b1, name1, bout, nameout, to1-nf1); |
| 300 | skiplines(b2, name2, to2-nf2); |
| 301 | break; |
| 302 | case '=': |
| 303 | copylines(b1, name1, bout, nameout, from1-nf1); |
| 304 | skiplines(b1, name1, to1-from1); |
| 305 | skiplines(b2, name2, to2-nf2); |
| 306 | if(Bseek(bdiff, diffoffset, 0) != diffoffset) |
| 307 | sysfatal("seek in diff output: %r"); |
| 308 | copylines(bdiff, namediff, bout, nameout, n+1); |
| 309 | break; |
| 310 | case '!': |
| 311 | runcmd(buf+1); |
| 312 | break; |
| 313 | case 'q': |
| 314 | if(buf[1]=='<' || buf[1]=='>' || buf[1]=='='){ |
| 315 | interactive = 0; |
| 316 | defaultanswer = buf[1]; |
| 317 | }else |
| 318 | fprint(2, "must be q<, q>, or q=\n"); |
| 319 | break; |
| 320 | default: |
| 321 | fprint(2, "expect: <, >, =, q<, q>, q=, !cmd\n"); |
| 322 | break; |
| 323 | } |
| 324 | }while(buf[0] != '<' && buf[0] != '>' && buf[0] != '='); |
| 325 | nf1 = to1; |
| 326 | nf2 = to2; |
| 327 | } |
| 328 | copy(b1, name1, bout, nameout); |
| 329 | } |