1 /* Produce ed(1) script output from a diff_result. */
2 /*
3  * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
4  * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <errno.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdbool.h>
24 
25 #include <arraylist.h>
26 #include <diff_main.h>
27 #include <diff_output.h>
28 
29 #include "diff_internal.h"
30 
31 static int
32 output_edscript_chunk(struct diff_output_info *outinfo,
33     FILE *dest, const struct diff_input_info *info,
34     const struct diff_result *result,
35     struct diff_chunk_context *cc)
36 {
37 	off_t outoff = 0, *offp;
38 	int left_start, left_len, right_start, right_len;
39 	int rc;
40 
41 	left_len = cc->left.end - cc->left.start;
42 	if (left_len < 0)
43 		return EINVAL;
44 	else if (result->left->atoms.len == 0)
45 		left_start = 0;
46 	else if (left_len == 0 && cc->left.start > 0)
47 		left_start = cc->left.start;
48 	else if (cc->left.end > 0)
49 		left_start = cc->left.start + 1;
50 	else
51 		left_start = cc->left.start;
52 
53 	right_len = cc->right.end - cc->right.start;
54 	if (right_len < 0)
55 		return EINVAL;
56 	else if (result->right->atoms.len == 0)
57 		right_start = 0;
58 	else if (right_len == 0 && cc->right.start > 0)
59 		right_start = cc->right.start;
60 	else if (cc->right.end > 0)
61 		right_start = cc->right.start + 1;
62 	else
63 		right_start = cc->right.start;
64 
65 	if (left_len == 0) {
66 		/* addition */
67 		if (right_len == 1) {
68 			rc = fprintf(dest, "%da%d\n", left_start, right_start);
69 		} else {
70 			rc = fprintf(dest, "%da%d,%d\n", left_start,
71 			    right_start, cc->right.end);
72 		}
73 	} else if (right_len == 0) {
74 		/* deletion */
75 		if (left_len == 1) {
76 			rc = fprintf(dest, "%dd%d\n", left_start,
77 			    right_start);
78 		} else {
79 			rc = fprintf(dest, "%d,%dd%d\n", left_start,
80 			    cc->left.end, right_start);
81 		}
82 	} else {
83 		/* change */
84 		if (left_len == 1 && right_len == 1) {
85 			rc = fprintf(dest, "%dc%d\n", left_start, right_start);
86 		} else if (left_len == 1) {
87 			rc = fprintf(dest, "%dc%d,%d\n", left_start,
88 			    right_start, cc->right.end);
89 		} else if (right_len == 1) {
90 			rc = fprintf(dest, "%d,%dc%d\n", left_start,
91 			    cc->left.end, right_start);
92 		} else {
93 			rc = fprintf(dest, "%d,%dc%d,%d\n", left_start,
94 			    cc->left.end, right_start, cc->right.end);
95 		}
96 	}
97 	if (rc < 0)
98 		return errno;
99 	if (outinfo) {
100 		ARRAYLIST_ADD(offp, outinfo->line_offsets);
101 		if (offp == NULL)
102 			return ENOMEM;
103 		outoff += rc;
104 		*offp = outoff;
105 	}
106 
107 	return DIFF_RC_OK;
108 }
109 
110 int
111 diff_output_edscript(struct diff_output_info **output_info,
112     FILE *dest, const struct diff_input_info *info,
113     const struct diff_result *result)
114 {
115 	struct diff_output_info *outinfo = NULL;
116 	struct diff_chunk_context cc = {};
117 	int atomizer_flags = (result->left->atomizer_flags|
118 	    result->right->atomizer_flags);
119 	int flags = (result->left->root->diff_flags |
120 	    result->right->root->diff_flags);
121 	bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA);
122 	bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA);
123 	int i, rc;
124 
125 	if (!result)
126 		return EINVAL;
127 	if (result->rc != DIFF_RC_OK)
128 		return result->rc;
129 
130 	if (output_info) {
131 		*output_info = diff_output_info_alloc();
132 		if (*output_info == NULL)
133 			return ENOMEM;
134 		outinfo = *output_info;
135 	}
136 
137 	if (have_binary && !force_text) {
138 		for (i = 0; i < result->chunks.len; i++) {
139 			struct diff_chunk *c = &result->chunks.head[i];
140 			enum diff_chunk_type t = diff_chunk_type(c);
141 
142 			if (t != CHUNK_MINUS && t != CHUNK_PLUS)
143 				continue;
144 
145 			fprintf(dest, "Binary files %s and %s differ\n",
146 			    diff_output_get_label_left(info),
147 			    diff_output_get_label_right(info));
148 			break;
149 		}
150 
151 		return DIFF_RC_OK;
152 	}
153 
154 	for (i = 0; i < result->chunks.len; i++) {
155 		struct diff_chunk *chunk = &result->chunks.head[i];
156 		enum diff_chunk_type t = diff_chunk_type(chunk);
157 		struct diff_chunk_context next;
158 
159 		if (t != CHUNK_MINUS && t != CHUNK_PLUS)
160 			continue;
161 
162 		if (diff_chunk_context_empty(&cc)) {
163 			/* Note down the start point, any number of subsequent
164 			 * chunks may be joined up to this chunk by being
165 			 * directly adjacent. */
166 			diff_chunk_context_get(&cc, result, i, 0);
167 			continue;
168 		}
169 
170 		/* There already is a previous chunk noted down for being
171 		 * printed. Does it join up with this one? */
172 		diff_chunk_context_get(&next, result, i, 0);
173 
174 		if (diff_chunk_contexts_touch(&cc, &next)) {
175 			/* This next context touches or overlaps the previous
176 			 * one, join. */
177 			diff_chunk_contexts_merge(&cc, &next);
178 			continue;
179 		}
180 
181 		rc = output_edscript_chunk(outinfo, dest, info, result, &cc);
182 		if (rc != DIFF_RC_OK)
183 			return rc;
184 		cc = next;
185 	}
186 
187 	if (!diff_chunk_context_empty(&cc))
188 		return output_edscript_chunk(outinfo, dest, info, result, &cc);
189 	return DIFF_RC_OK;
190 }
191