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