Annotation of src/usr.bin/sdiff/sdiff.c, Revision 1.14
1.14 ! otto 1: /* $OpenBSD: sdiff.c,v 1.13 2006/02/14 08:26:20 otto Exp $ */
1.1 tedu 2:
3: /*
4: * Written by Raymond Lai <ray@cyth.net>.
5: * Public domain.
6: */
7:
8: #include <sys/param.h>
9: #include <sys/queue.h>
10: #include <sys/types.h>
11: #include <sys/wait.h>
12:
13: #include <ctype.h>
14: #include <err.h>
15: #include <getopt.h>
16: #include <limits.h>
17: #include <stdio.h>
18: #include <stdlib.h>
19: #include <string.h>
20: #include <unistd.h>
21: #include <util.h>
22:
23: #include "extern.h"
24:
25: #define WIDTH 130
26: /*
27: * Each column must be at least one character wide, plus three
28: * characters between the columns (space, [<|>], space).
29: */
30: #define WIDTH_MIN 5
31:
32: /* A single diff line. */
33: struct diffline {
34: SIMPLEQ_ENTRY(diffline) diffentries;
1.14 ! otto 35: char *left;
! 36: char div;
! 37: char *right;
1.1 tedu 38: };
39:
40: static void astrcat(char **, const char *);
1.14 ! otto 41: static void enqueue(char *, char, char *);
! 42: static void freediff(struct diffline *);
1.1 tedu 43: static void int_usage(void);
1.13 otto 44: static int parsecmd(FILE *, FILE *, FILE *);
1.1 tedu 45: static void printa(FILE *, size_t);
46: static void printc(FILE *, size_t, FILE *, size_t);
47: static void printcol(const char *, size_t *, const size_t);
1.13 otto 48: static void printd(FILE *, size_t);
1.1 tedu 49: static void println(const char *, const char, const char *);
50: static void processq(void);
51: static void prompt(const char *, const char *);
52: __dead static void usage(void);
53: static char *xfgets(FILE *);
54:
55: SIMPLEQ_HEAD(, diffline) diffhead = SIMPLEQ_HEAD_INITIALIZER(diffhead);
56: size_t line_width; /* width of a line (two columns and divider) */
57: size_t width; /* width of each column */
58: size_t file1ln, file2ln; /* line number of file1 and file2 */
1.13 otto 59: int Iflag = 0; /* ignore sets matching regexp */
1.1 tedu 60: int lflag; /* print only left column for identical lines */
61: int sflag; /* skip identical lines */
1.3 tedu 62: FILE *outfile; /* file to save changes to */
1.1 tedu 63:
64: static struct option longopts[] = {
65: { "text", no_argument, NULL, 'a' },
66: { "ignore-blank-lines", no_argument, NULL, 'B' },
67: { "ignore-space-change", no_argument, NULL, 'b' },
68: { "minimal", no_argument, NULL, 'd' },
69: { "ignore-tab-expansion", no_argument, NULL, 'E' },
70: { "diff-program", required_argument, NULL, 'F' },
71: { "speed-large-files", no_argument, NULL, 'H' },
72: { "ignore-matching-lines", required_argument, NULL, 'I' },
73: { "left-column", no_argument, NULL, 'l' },
74: { "output", required_argument, NULL, 'o' },
75: { "strip-trailing-cr", no_argument, NULL, 'S' },
76: { "suppress-common-lines", no_argument, NULL, 's' },
77: { "expand-tabs", no_argument, NULL, 't' },
78: { "ignore-all-space", no_argument, NULL, 'W' },
79: { "width", required_argument, NULL, 'w' },
80: { NULL, 0, NULL, 0 }
81: };
82:
83: int
84: main(int argc, char **argv)
85: {
1.13 otto 86: FILE *diffpipe, *file1, *file2;
1.10 deraadt 87: size_t diffargc = 0, wflag = WIDTH;
1.1 tedu 88: int ch, fd[2], status;
89: pid_t pid;
1.14 ! otto 90: char **diffargv, *diffprog = "diff", *s1, *s2;
1.1 tedu 91:
92: /*
93: * Process diff flags.
94: */
95: /*
96: * Allocate memory for diff arguments and NULL.
97: * Each flag has at most one argument, so doubling argc gives an
98: * upper limit of how many diff args can be passed. argv[0],
99: * file1, and file2 won't have arguments so doubling them will
100: * waste some memory; however we need an extra space for the
101: * NULL at the end, so it sort of works out.
102: */
1.10 deraadt 103: if (!(diffargv = malloc(sizeof(char **) * argc * 2)))
1.6 tedu 104: err(2, "main");
1.1 tedu 105:
106: /* Add first argument, the program name. */
107: diffargv[diffargc++] = diffprog;
108:
1.8 tedu 109: while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
1.1 tedu 110: longopts, NULL)) != -1) {
111: const char *errstr;
112:
113: switch (ch) {
114: case 'a':
115: diffargv[diffargc++] = "-a";
116: break;
117: case 'B':
118: diffargv[diffargc++] = "-B";
119: break;
120: case 'b':
121: diffargv[diffargc++] = "-b";
122: break;
123: case 'd':
124: diffargv[diffargc++] = "-d";
125: break;
126: case 'E':
127: diffargv[diffargc++] = "-E";
128: break;
129: case 'F':
1.4 tedu 130: diffargv[0] = diffprog = optarg;
1.1 tedu 131: break;
132: case 'H':
133: diffargv[diffargc++] = "-H";
134: break;
135: case 'I':
1.13 otto 136: Iflag = 1;
1.1 tedu 137: diffargv[diffargc++] = "-I";
138: diffargv[diffargc++] = optarg;
139: break;
140: case 'i':
141: diffargv[diffargc++] = "-i";
142: break;
143: case 'l':
144: lflag = 1;
145: break;
146: case 'o':
147: if ((outfile = fopen(optarg, "w")) == NULL)
148: err(2, "could not open: %s", optarg);
149: break;
150: case 'S':
151: diffargv[diffargc++] = "--strip-trailing-cr";
152: break;
153: case 's':
154: sflag = 1;
155: break;
156: case 't':
157: diffargv[diffargc++] = "-t";
158: break;
159: case 'W':
160: diffargv[diffargc++] = "-w";
161: break;
162: case 'w':
163: wflag = strtonum(optarg, WIDTH_MIN,
1.9 tedu 164: INT_MAX, &errstr);
1.1 tedu 165: if (errstr)
166: errx(2, "width is %s: %s", errstr, optarg);
167: break;
168: default:
169: usage();
170: /* NOTREACHED */
171: }
172:
173: }
174: argc -= optind;
175: argv += optind;
176:
1.4 tedu 177: if (argc != 2) {
178: usage();
179: /* NOTREACHED */
180: }
181:
1.1 tedu 182: /* file1 */
183: diffargv[diffargc++] = argv[0];
184: /* file2 */
185: diffargv[diffargc++] = argv[1];
186: /* Add NULL to end of array to indicate end of array. */
187: diffargv[diffargc++] = NULL;
188:
189: /* Subtract column divider and divide by two. */
190: width = (wflag - 3) / 2;
191: /* Make sure line_width can fit in size_t. */
192: if (width > (SIZE_T_MAX - 3) / 2)
193: errx(2, "width is too large: %zu", width);
194: line_width = width * 2 + 3;
195:
196: if (pipe(fd))
197: err(2, "pipe");
198:
199: switch(pid = fork()) {
200: case 0:
201: /* child */
202: /* We don't read from the pipe. */
1.5 tedu 203: close(fd[0]);
1.1 tedu 204: if (dup2(fd[1], STDOUT_FILENO) == -1)
205: err(2, "child could not duplicate descriptor");
206: /* Free unused descriptor. */
1.5 tedu 207: close(fd[1]);
1.1 tedu 208:
1.14 ! otto 209: execvp(diffprog, diffargv);
1.1 tedu 210: err(2, "could not execute diff: %s", diffprog);
211: case -1:
212: err(2, "could not fork");
213: }
214:
215: /* parent */
216: /* We don't write to the pipe. */
1.5 tedu 217: close(fd[1]);
1.1 tedu 218:
219: /* Open pipe to diff command. */
1.13 otto 220: if ((diffpipe = fdopen(fd[0], "r")) == NULL)
1.1 tedu 221: err(2, "could not open diff pipe");
1.13 otto 222: /* If file1 or file2 were given as `-', open stdin. */
1.1 tedu 223: /* XXX - Does not work. */
224: if (strcmp(argv[0], "-") == 0)
1.13 otto 225: file1 = stdin;
1.1 tedu 226: /* Otherwise, open as normal file. */
1.13 otto 227: else if ((file1 = fopen(argv[0], "r")) == NULL)
1.1 tedu 228: err(2, "could not open file1: %s", argv[0]);
1.13 otto 229: /* XXX - Handle (file1 == file2 == stdin) case. */
230: if (strcmp(argv[1], "-") == 0)
231: file2 = stdin;
232: /* Otherwise, open as normal file. */
233: else if ((file2 = fopen(argv[1], "r")) == NULL)
234: err(2, "could not open file2: %s", argv[1]);
1.1 tedu 235: /* Line numbers start at one. */
236: file1ln = file2ln = 1;
237:
238: /* Read and parse diff output. */
1.13 otto 239: while (parsecmd(diffpipe, file1, file2) != EOF)
1.1 tedu 240: ;
1.13 otto 241: fclose(diffpipe);
1.1 tedu 242:
243: /* Wait for diff to exit. */
244: if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) ||
245: WEXITSTATUS(status) >= 2)
246: err(2, "diff exited abnormally");
247:
248: /* No more diffs, so print common lines. */
1.13 otto 249: if (lflag)
250: while ((s1 = xfgets(file1)))
251: enqueue(s1, ' ', NULL);
252: else
253: for (;;) {
254: s1 = xfgets(file1);
255: s2 = xfgets(file2);
256: if (s1 || s2)
257: enqueue(s1, ' ', s2);
258: else
259: break;
260: }
261: fclose(file1);
262: fclose(file2);
1.1 tedu 263: /* Process unmodified lines. */
264: processq();
265:
266: /* Return diff exit status. */
267: return (WEXITSTATUS(status));
268: }
269:
270: /*
271: * Prints an individual column (left or right), taking into account
272: * that tabs are variable-width. Takes a string, the current column
1.12 deraadt 273: * the cursor is on the screen, and the maximum value of the column.
1.1 tedu 274: * The column value is updated as we go along.
275: */
276: static void
277: printcol(const char *s, size_t *col, const size_t col_max)
278: {
279:
280: for (; *s && *col < col_max; ++s) {
281: size_t new_col;
282:
283: switch (*s) {
284: case '\t':
285: /*
286: * If rounding to next multiple of eight causes
287: * an integer overflow, just return.
288: */
289: if (*col > SIZE_T_MAX - 8)
290: return;
291:
292: /* Round to next multiple of eight. */
293: new_col = (*col / 8 + 1) * 8;
294:
295: /*
296: * If printing the tab goes past the column
297: * width, don't print it and just quit.
298: */
299: if (new_col > col_max)
300: return;
301: *col = new_col;
302: break;
303:
304: default:
305: ++(*col);
306: }
307:
308: putchar(*s);
309: }
310: }
311:
312: /*
313: * Prompts user to either choose between two strings or edit one, both,
314: * or neither.
315: */
316: static void
317: prompt(const char *s1, const char *s2)
318: {
1.14 ! otto 319: char *cmd;
1.1 tedu 320:
321: /* Print command prompt. */
322: putchar('%');
323:
324: /* Get user input. */
1.14 ! otto 325: for (; (cmd = xfgets(stdin)); free(cmd)) {
1.1 tedu 326: const char *p;
327:
328: /* Skip leading whitespace. */
329: for (p = cmd; isspace(*p); ++p)
330: ;
331:
332: switch (*p) {
333: case 'e':
334: /* Skip `e'. */
335: ++p;
336:
337: if (eparse(p, s1, s2) == -1)
338: goto USAGE;
339: break;
340:
341: case 'l':
342: /* Choose left column as-is. */
343: if (s1 != NULL)
344: fprintf(outfile, "%s\n", s1);
345:
346: /* End of command parsing. */
347: break;
348:
349: case 'q':
350: goto QUIT;
351:
352: case 'r':
353: /* Choose right column as-is. */
354: if (s2 != NULL)
355: fprintf(outfile, "%s\n", s2);
356:
357: /* End of command parsing. */
358: break;
359:
360: case 's':
361: sflag = 1;
362: goto PROMPT;
363:
364: case 'v':
365: sflag = 0;
366: /* FALLTHROUGH */
367:
368: default:
369: /* Interactive usage help. */
370: USAGE:
371: int_usage();
372: PROMPT:
373: putchar('%');
374:
375: /* Prompt user again. */
376: continue;
377: }
378:
1.14 ! otto 379: free(cmd);
1.1 tedu 380: return;
381: }
382:
383: /*
384: * If there was no error, we received an EOF from stdin, so we
385: * should quit.
386: */
387: QUIT:
1.5 tedu 388: fclose(outfile);
1.1 tedu 389: exit(0);
390: }
391:
392: /*
393: * Takes two strings, separated by a column divider. NULL strings are
394: * treated as empty columns. If the divider is the ` ' character, the
395: * second column is not printed (-l flag). In this case, the second
396: * string must be NULL. When the second column is NULL, the divider
397: * does not print the trailing space following the divider character.
398: *
399: * Takes into account that tabs can take multiple columns.
400: */
401: static void
402: println(const char *s1, const char div, const char *s2)
403: {
404: size_t col;
405:
406: /* Print first column. Skips if s1 == NULL. */
407: col = 0;
408: if (s1) {
409: /* Skip angle bracket and space. */
410: printcol(s1, &col, width);
411:
412: }
413:
414: /* Only print left column. */
415: if (div == ' ' && !s2) {
416: putchar('\n');
417: return;
418: }
419:
420: /* Otherwise, we pad this column up to width. */
421: for (; col < width; ++col)
422: putchar(' ');
423:
424: /*
425: * Print column divider. If there is no second column, we don't
426: * need to add the space for padding.
427: */
428: if (!s2) {
429: printf(" %c\n", div);
430: return;
431: }
432: printf(" %c ", div);
433: col += 3;
434:
435: /* Skip angle bracket and space. */
436: printcol(s2, &col, line_width);
437:
438: putchar('\n');
439: }
440:
441: /*
442: * Reads a line from file and returns as a string. If EOF is reached,
443: * NULL is returned. The returned string must be freed afterwards.
444: */
445: static char *
446: xfgets(FILE *file)
447: {
448: const char delim[3] = {'\0', '\0', '\0'};
449: char *s;
450:
451: /* XXX - Is this necessary? */
452: clearerr(file);
453:
454: if (!(s = fparseln(file, NULL, NULL, delim, 0)) &&
455: ferror(file))
456: err(2, "error reading file");
457:
458: if (!s) {
459: return (NULL);
460: }
461:
462: return (s);
463: }
464:
465: /*
1.13 otto 466: * Parse ed commands from diffpipe and print lines from file1 (lines
467: * to change or delete) or file2 (lines to add or change).
468: * Returns EOF or 0.
1.1 tedu 469: */
470: static int
1.13 otto 471: parsecmd(FILE *diffpipe, FILE *file1, FILE *file2)
1.1 tedu 472: {
1.13 otto 473: size_t file1start, file1end, file2start, file2end, n;
1.1 tedu 474: /* ed command line and pointer to characters in line */
1.11 tedu 475: char *line, *p, *q;
476: const char *errstr;
477: char c, cmd;
1.1 tedu 478:
479: /* Read ed command. */
1.13 otto 480: if (!(line = xfgets(diffpipe)))
1.1 tedu 481: return (EOF);
482:
483: p = line;
484: /* Go to character after line number. */
485: while (isdigit(*p))
486: ++p;
1.11 tedu 487: c = *p;
488: *p++ = 0;
489: file1start = strtonum(line, 0, INT_MAX, &errstr);
490: if (errstr)
491: errx(2, "file1 start is %s: %s", errstr, line);
1.1 tedu 492:
493: /* A range is specified for file1. */
1.11 tedu 494: if (c == ',') {
1.1 tedu 495:
1.11 tedu 496: q = p;
497: /* Go to character after file2end. */
498: while (isdigit(*p))
499: ++p;
500: c = *p;
501: *p++ = 0;
502: file1end = strtonum(q, 0, INT_MAX, &errstr);
503: if (errstr)
504: errx(2, "file1 end is %s: %s", errstr, line);
1.1 tedu 505: if (file1start > file1end)
506: errx(2, "invalid line range in file1: %s", line);
507:
508: } else
509: file1end = file1start;
510:
1.11 tedu 511: cmd = c;
1.1 tedu 512: /* Check that cmd is valid. */
513: if (!(cmd == 'a' || cmd == 'c' || cmd == 'd'))
514: errx(2, "ed command not recognized: %c: %s", cmd, line);
515:
1.11 tedu 516: q = p;
1.1 tedu 517: /* Go to character after line number. */
518: while (isdigit(*p))
519: ++p;
1.11 tedu 520: c = *p;
521: *p++ = 0;
522: file2start = strtonum(q, 0, INT_MAX, &errstr);
523: if (errstr)
524: errx(2, "file2 start is %s: %s", errstr, line);
1.1 tedu 525:
526: /*
527: * There should either be a comma signifying a second line
528: * number or the line should just end here.
529: */
1.11 tedu 530: if (c != ',' && c != '\0')
531: errx(2, "invalid line range in file2: %c: %s", c, line);
1.1 tedu 532:
1.11 tedu 533: if (c == ',') {
1.1 tedu 534:
1.11 tedu 535: file2end = strtonum(p, 0, INT_MAX, &errstr);
536: if (errstr)
537: errx(2, "file2 end is %s: %s", errstr, line);
1.1 tedu 538: if (file2start >= file2end)
539: errx(2, "invalid line range in file2: %s", line);
540: } else
541: file2end = file2start;
542:
543: /* Appends happen _after_ stated line. */
544: if (cmd == 'a') {
545: if (file1start != file1end)
546: errx(2, "append cannot have a file1 range: %s",
547: line);
548: if (file1start == SIZE_T_MAX)
549: errx(2, "file1 line range too high: %s", line);
550: file1start = ++file1end;
551: }
552: /*
553: * I'm not sure what the deal is with the line numbers for
554: * deletes, though.
555: */
556: else if (cmd == 'd') {
557: if (file2start != file2end)
558: errx(2, "delete cannot have a file2 range: %s",
559: line);
560: if (file2start == SIZE_T_MAX)
561: errx(2, "file2 line range too high: %s", line);
562: file2start = ++file2end;
563: }
564:
1.13 otto 565: /*
566: * Continue reading file1 and file2 until we reach line numbers
567: * specified by diff. Should only happen with -I flag.
568: */
569: for (; file1ln < file1start && file2ln < file2start;
570: ++file1ln, ++file2ln) {
1.14 ! otto 571: char *s1, *s2;
1.1 tedu 572:
1.13 otto 573: if (!(s1 = xfgets(file1)))
1.1 tedu 574: errx(2, "file1 shorter than expected");
1.13 otto 575: if (!(s2 = xfgets(file2)))
576: errx(2, "file2 shorter than expected");
1.1 tedu 577:
578: /* If the -l flag was specified, print only left column. */
1.13 otto 579: if (lflag) {
1.14 ! otto 580: free(s2);
1.13 otto 581: /*
582: * XXX - If -l and -I are both specified, all
583: * unchanged or ignored lines are shown with a
584: * `(' divider. This matches GNU sdiff, but I
585: * believe it is a bug. Just check out:
586: * gsdiff -l -I '^$' samefile samefile.
587: */
588: if (Iflag)
589: enqueue(s1, '(', NULL);
590: else
591: enqueue(s1, ' ', NULL);
592: } else
593: enqueue(s1, ' ', s2);
594: }
595: /* Ignore deleted lines. */
596: for (; file1ln < file1start; ++file1ln) {
1.14 ! otto 597: char *s;
1.13 otto 598:
599: if (!(s = xfgets(file1)))
600: errx(2, "file1 shorter than expected");
601:
602: enqueue(s, '(', NULL);
1.1 tedu 603: }
1.13 otto 604: /* Ignore added lines. */
605: for (; file2ln < file2start; ++file2ln) {
1.14 ! otto 606: char *s;
1.13 otto 607:
608: if (!(s = xfgets(file2)))
609: errx(2, "file2 shorter than expected");
610:
611: /* If -l flag was given, don't print right column. */
612: if (lflag)
1.14 ! otto 613: free(s);
1.13 otto 614: else
615: enqueue(NULL, ')', s);
616: }
617:
618: /* Process unmodified or skipped lines. */
1.1 tedu 619: processq();
620:
621: switch (cmd) {
622: case 'a':
1.13 otto 623: printa(file2, file2end);
624: n = file2end - file2start + 1;
1.1 tedu 625: break;
626:
627: case 'c':
1.13 otto 628: printc(file1, file1end, file2, file2end);
629: n = file1end - file1start + 1 + 1 + file2end - file2start + 1;
1.1 tedu 630: break;
631:
632: case 'd':
1.13 otto 633: printd(file1, file1end);
634: n = file1end - file1start + 1;
1.1 tedu 635: break;
636:
637: default:
638: errx(2, "invalid diff command: %c: %s", cmd, line);
639: }
640:
1.13 otto 641: /* Skip to next ed line. */
642: while (n--)
643: if (!xfgets(diffpipe))
644: errx(2, "diff ended early");
645:
1.3 tedu 646: return (0);
1.1 tedu 647: }
648:
649: /*
650: * Queues up a diff line.
651: */
652: static void
1.14 ! otto 653: enqueue(char *left, char div, char *right)
1.1 tedu 654: {
655: struct diffline *diffp;
656:
657: if (!(diffp = malloc(sizeof(struct diffline))))
1.6 tedu 658: err(2, "enqueue");
1.1 tedu 659: diffp->left = left;
660: diffp->div = div;
661: diffp->right = right;
662: SIMPLEQ_INSERT_TAIL(&diffhead, diffp, diffentries);
663: }
664:
665: /*
666: * Free a diffline structure and its elements.
667: */
668: static void
1.14 ! otto 669: freediff(struct diffline *diffp)
1.1 tedu 670: {
1.14 ! otto 671: free(diffp->left);
! 672: free(diffp->right);
! 673: free(diffp);
1.1 tedu 674: }
675:
676: /*
677: * Append second string into first. Repeated appends to the same string
678: * are cached, making this an O(n) function, where n = strlen(append).
679: */
680: static void
681: astrcat(char **s, const char *append)
682: {
683: /* Length of string in previous run. */
684: static size_t offset = 0;
1.14 ! otto 685: size_t newlen;
1.1 tedu 686: /*
687: * String from previous run. Compared to *s to see if we are
688: * dealing with the same string. If so, we can use offset.
689: */
1.14 ! otto 690: static const char *oldstr = NULL;
1.1 tedu 691: char *newstr;
692:
693:
694: /*
695: * First string is NULL, so just copy append.
696: */
697: if (!*s) {
698: if (!(*s = strdup(append)))
1.6 tedu 699: err(2, "astrcat");
1.1 tedu 700:
701: /* Keep track of string. */
702: offset = strlen(*s);
703: oldstr = *s;
704:
705: return;
706: }
707:
708: /*
709: * *s is a string so concatenate.
710: */
711:
712: /* Did we process the same string in the last run? */
713: /*
714: * If this is a different string from the one we just processed
715: * cache new string.
716: */
717: if (oldstr != *s) {
718: offset = strlen(*s);
719: oldstr = *s;
720: }
721:
722: /* Length = strlen(*s) + \n + strlen(append) + '\0'. */
723: newlen = offset + 1 + strlen(append) + 1;
724:
725: /* Resize *s to fit new string. */
726: newstr = realloc(*s, newlen);
727: if (newstr == NULL)
1.6 tedu 728: err(2, "astrcat");
1.1 tedu 729: *s = newstr;
730:
731: /* Concatenate. */
732: strlcpy(*s + offset, "\n", newlen - offset);
733:
734: /* Store generated string's values. */
1.7 tedu 735: offset = newlen - 1;
1.1 tedu 736: oldstr = *s;
737: }
738:
739: /*
740: * Process diff set queue, printing, prompting, and saving each diff
741: * line stored in queue.
742: */
743: static void
744: processq(void)
745: {
746: struct diffline *diffp;
1.14 ! otto 747: char divc, *left, *right;
1.1 tedu 748:
749: /* Don't process empty queue. */
750: if (SIMPLEQ_EMPTY(&diffhead))
751: return;
752:
1.14 ! otto 753: divc = '\0';
1.1 tedu 754: left = NULL;
755: right = NULL;
756: /*
757: * Go through set of diffs, concatenating each line in left or
758: * right column into two long strings, `left' and `right'.
759: */
760: SIMPLEQ_FOREACH(diffp, &diffhead, diffentries) {
761: /*
1.14 ! otto 762: * Make sure that divc is consistent throughout set.
! 763: * If divc is set, compare to next entry's divc. They
! 764: * should be the same. If divc is not set, then store
! 765: * this as this set's divc.
1.1 tedu 766: */
1.14 ! otto 767: if (divc == '\0')
! 768: divc = diffp->div;
1.1 tedu 769:
770: /*
1.13 otto 771: * Print changed lines if -s was given,
772: * print all lines if -s was not given.
1.1 tedu 773: */
1.14 ! otto 774: if (!sflag || divc == '|' || divc == '<' || divc == '>')
1.1 tedu 775: println(diffp->left, diffp->div, diffp->right);
776:
777: /* Append new lines to diff set. */
778: if (diffp->left)
779: astrcat(&left, diffp->left);
780: if (diffp->right)
781: astrcat(&right, diffp->right);
782: }
783:
784: /* Empty queue and free each diff line and its elements. */
785: while (!SIMPLEQ_EMPTY(&diffhead)) {
786: diffp = SIMPLEQ_FIRST(&diffhead);
1.13 otto 787: SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries);
1.1 tedu 788: freediff(diffp);
789: }
790:
791: /* Write to outfile, prompting user if lines are different. */
1.13 otto 792: if (outfile)
1.14 ! otto 793: switch (divc) {
1.13 otto 794: case ' ': case '(': case ')':
1.1 tedu 795: fprintf(outfile, "%s\n", left);
1.13 otto 796: break;
797: case '|': case '<': case '>':
1.1 tedu 798: prompt(left, right);
1.13 otto 799: break;
800: default:
1.14 ! otto 801: errx(2, "invalid divider: %c", divc);
1.13 otto 802: }
1.1 tedu 803:
804: /* Free left and right. */
1.14 ! otto 805: free(left);
! 806: free(right);
1.1 tedu 807: }
808:
809: /*
810: * Print lines following an (a)ppend command.
811: */
812: static void
813: printa(FILE *file, size_t line2)
814: {
815: char *line;
816:
817: for (; file2ln <= line2; ++file2ln) {
818: if (!(line = xfgets(file)))
819: errx(2, "append ended early");
820: enqueue(NULL, '>', line);
821: }
822:
823: processq();
824: }
825:
826: /*
827: * Print lines following a (c)hange command, from file1ln to file1end
828: * and from file2ln to file2end.
829: */
830: static void
831: printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end)
832: {
833: struct fileline {
1.14 ! otto 834: SIMPLEQ_ENTRY(fileline) fileentries;
! 835: char *line;
1.1 tedu 836: };
837: SIMPLEQ_HEAD(, fileline) delqhead = SIMPLEQ_HEAD_INITIALIZER(delqhead);
838:
839: /* Read lines to be deleted. */
840: for (; file1ln <= file1end; ++file1ln) {
841: struct fileline *linep;
1.14 ! otto 842: char *line1;
1.1 tedu 843:
844: /* Read lines from both. */
845: if (!(line1 = xfgets(file1)))
846: errx(2, "error reading file1 in delete in change");
847:
848: /* Add to delete queue. */
849: if (!(linep = malloc(sizeof(struct fileline))))
1.6 tedu 850: err(2, "printc");
1.1 tedu 851: linep->line = line1;
852: SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries);
853: }
854:
855: /* Process changed lines.. */
856: for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end;
857: ++file2ln) {
858: struct fileline *del;
859: char *add;
860:
861: /* Get add line. */
1.13 otto 862: if (!(add = xfgets(file2)))
863: errx(2, "error reading add in change");
1.1 tedu 864:
865: del = SIMPLEQ_FIRST(&delqhead);
866: enqueue(del->line, '|', add);
867: SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
868: /*
869: * Free fileline structure but not its elements since
870: * they are queued up.
871: */
872: free(del);
873: }
874: processq();
875:
876: /* Process remaining lines to add. */
877: for (; file2ln <= file2end; ++file2ln) {
878: char *add;
879:
880: /* Get add line. */
1.13 otto 881: if (!(add = xfgets(file2)))
882: errx(2, "error reading add in change");
1.1 tedu 883:
884: enqueue(NULL, '>', add);
885: }
886: processq();
887:
888: /* Process remaining lines to delete. */
889: while (!SIMPLEQ_EMPTY(&delqhead)) {
890: struct fileline *filep;
891:
892: filep = SIMPLEQ_FIRST(&delqhead);
893: enqueue(filep->line, '<', NULL);
894: SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
895: free(filep);
896: }
897: processq();
898: }
899:
900: /*
901: * Print deleted lines from file, from file1ln to file1end.
902: */
903: static void
1.13 otto 904: printd(FILE *file1, size_t file1end)
1.1 tedu 905: {
1.14 ! otto 906: char *line1;
1.1 tedu 907:
908: /* Print out lines file1ln to line2. */
909: for (; file1ln <= file1end; ++file1ln) {
910: /* XXX - Why can't this handle stdin? */
911: if (!(line1 = xfgets(file1)))
912: errx(2, "file1 ended early in delete");
913: enqueue(line1, '<', NULL);
914: }
915: processq();
916: }
917:
918: /*
919: * Interactive mode usage.
920: */
921: static void
922: int_usage(void)
923: {
924: puts("e:\tedit blank diff\n"
925: "eb:\tedit both diffs concatenated\n"
926: "el:\tedit left diff\n"
927: "er:\tedit right diff\n"
928: "l:\tchoose left diff\n"
929: "r:\tchoose right diff\n"
930: "s:\tsilent mode--don't print identical lines\n"
931: "v:\tverbose mode--print identical lines\n"
932: "q:\tquit");
933: }
934:
935: static void
936: usage(void)
937: {
938: extern char *__progname;
939:
940: fprintf(stderr,
1.8 tedu 941: "usage: %s [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n",
1.1 tedu 942: __progname);
943: exit(2);
944: }