UNPKG

9.32 kBJavaScriptView Raw
1var Block = require('./block').Block;
2
3// A Hunk is a group of Blocks which overlap because of the context
4// surrounding each block. (So if we're not using context, every hunk will
5// contain one block.) Used in the diff program (bin/diff).
6var Hunk = exports.Hunk = function(data_old, data_new, piece, context, file_length_difference) {
7 // Internal variables
8 var _flag_context = null;
9 var self = this;
10
11 // At first, a hunk will have just one Block in it
12 this.blocks = [new Block(piece)];
13 this.data_old = data_old;
14 this.data_new = data_new;
15
16 var before = file_length_difference, after = file_length_difference;
17 after = after + this.blocks[0].diff_size;
18 this.file_length_difference = after; // The caller must get this manually
19 // Other parameters
20 var a1 = null, a2 = null;
21 var b1 = null, b2 = null;
22
23 // Save the start & end of each array. If the array doesn't exist
24 // (e.g., we're only adding items in this block), then figure out the
25 // line number based on the line number of the other file and the
26 // current difference in file lengths.
27 if(this.blocks[0].remove.length > 0) {
28 a1 = this.blocks[0].remove[0].position;
29 a2 = this.blocks[0].remove[this.blocks[0].remove.length - 1].position;
30 }
31
32 if(this.blocks[0].insert.length > 0) {
33 b1 = this.blocks[0].insert[0].position;
34 b2 = this.blocks[0].insert[this.blocks[0].insert.length - 1].position;
35 }
36
37 this.start_old = a1 || (b1 - before);
38 this.start_new = b1 || (a1 + before);
39 this.end_old = a2 || (b2 - after);
40 this.end_new = b2 || (a2 + after);
41
42 // Change the "start" and "end" fields to note that context should be added
43 // to this hunk
44 Object.defineProperty(this, "flag_context", { get: function() { return _flag_context; }, set: function(context) {
45 if(context == null || context == 0) return null;
46
47 var add_start = (context > self.start_old) ? self.start_old : context;
48 var add_end = null;
49
50 self.start_old = self.start_old - add_start;
51 self.start_new = self.start_new - add_start;
52
53 if((self.end_old + context) > self.data_old.length) {
54 add_end = self.data_old.length - self.end_old;
55 } else {
56 add_end = context;
57 }
58
59 self.end_old = self.end_old + add_end;
60 self.end_new = self.end_new + add_end;
61 _flag_context = context;
62 }, enumerable: true});
63
64 // Set the flag_context
65 this.flag_context = context;
66}
67
68Hunk.prototype.unshift = function(hunk) {
69 this.start_old = hunk.start_old;
70 this.start_new = hunk.start_new;
71 this.blocks = hunk.blocks.concat(this.blocks);
72}
73
74// Is there an overlap between hunk arg0 and old hunk arg1? Note: if end
75// of old hunk is one less than beginning of second, they overlap
76Hunk.prototype.overlaps = function(hunk) {
77 if(hunk == null) return null;
78
79 var a = (this.start_old - hunk.end_old) <= 1;
80 var b = (this.start_new - hunk.end_new) <= 1;
81 return (a || b);
82}
83
84Hunk.prototype.diff = function(format) {
85 if(format == "old") {
86 return old_diff(this);
87 } else if(format == 'unified') {
88 return unified_diff(this);
89 } else if(format == 'context') {
90 return context_diff(this);
91 } else if(format == 'ed') {
92 return this;
93 } else if(format == 'reverse_ed' || format == 'ed_finish') {
94 return ed_diff(this, format);
95 } else {
96 throw "unknown diff format " + format;
97 }
98}
99
100Hunk.prototype.each_old = function(block) {
101 var entries = this.data_old.slice(this.start_old, this.end_old);
102 entries.forEach(function(e) {
103 block(e);
104 });
105}
106
107// Note that an old diff can't have any context. Therefore, we know that
108// there's only one block in the hunk.
109var old_diff = function(hunk) {
110 if(hunk.blocks.length > 1) sys.puts("expecting only one block in an old diff hunk!");
111 // Set up operation actions
112 var opt_act = {'+':'a', '-':'d', '!':'c'};
113 var block = hunk.blocks[0];
114
115 // Calculate item number range. Old diff range is just like a context
116 // diff range, except the ranges are on one line with the action between
117 // them.
118 var s = "" + context_rang("old") + opt_act[block.op] + context_rang("new") + "\n";
119 // If removing anything, just print out all the remove lines in the hunk
120 // which is just all the remove lines in the block.
121 if(block.remove.length > 0) {
122 hunk.data_old.slice(hunk.start_old, hunk.end_old).forEach(function(e) {
123 s = s + "< " + e + "\n";
124 });
125 }
126
127 if(block.insert.length > 0) {
128 hunk.data_new.slice(hunk.start_new, hunk.end_new).forEach(function(e) {
129 s = s + "> " + e + "\n;"
130 });
131 }
132 // Return the diff string
133 return s;
134}
135
136var unified_diff = function(hunk) {
137 // Calculate item number range.
138 var s = "@@ -" + unified_range(hunk, 'old') + " +" + unified_range(hunk, 'new') + " @@\n";
139
140 // Outlist starts containing the hunk of the old file. Removing an item
141 // just means putting a '-' in front of it. Inserting an item requires
142 // getting it from the new file and splicing it in. We splice in
143 // +num_added+ items. Remove blocks use +num_added+ because splicing
144 // changed the length of outlist.
145 //
146 // We remove +num_removed+ items. Insert blocks use +num_removed+
147 // because their item numbers -- corresponding to positions in the NEW
148 // file -- don't take removed items into account.
149 var lo = hunk.start_old;
150 var hi = hunk.end_old;
151 var num_added = 0;
152 var num_removed = 0;
153
154 // Create list of stripped entries
155 var outlist = hunk.data_old.slice(lo, hi + 1).map(function(e) { return e.replace(/^/g, ' '); });
156 // Process all the blocks
157 hunk.blocks.forEach(function(block) {
158 block.remove.forEach(function(item) {
159 var op = item.action.toString(); // -
160 var offset = item.position - lo + num_added;
161 outlist[offset] = outlist[offset].replace(/^ /g, op.toString());
162 num_removed = num_removed + 1;
163 })
164
165 block.insert.forEach(function(item) {
166 var op = item.action.toString(); // +
167 var offset = item.position - hunk.start_new + num_removed;
168 outlist.splice(offset, 0, ("" + op + hunk.data_new[item.position]));
169 num_added = num_added + 1;
170 });
171 });
172
173 // Return the list
174 return s + outlist.join('\n');
175}
176
177var context_diff = function(hunk) {
178 var s = '***************\n';
179 s = s + '*** ' + context_range(hunk, 'old') + ' ****\n';
180 // Retrieve the context
181 var r = context_range(hunk, 'new');
182 var outlist = null;
183
184 // Print out file 1 part for each block in context diff format if there
185 // are any blocks that remove items
186 var lo = hunk.start_old;
187 var hi = hunk.end_old;
188 var removes = hunk.blocks.filter(function(e) { return !(e.remove.length == 0); });
189
190 if(removes) {
191 outlist = hunk.data_old.slice(lo, hi).map(function(e) { return e.replace(/^/g, ' '); });
192 removes.forEach(function(block) {
193 block.remove.forEach(function(item) {
194 outlist[item.position - lo] = outlist[item.position - lo].replace(/^ /g, block.op); // - or !
195 });
196 });
197 // Add to diff string
198 s = s + outlist.join('\n');
199 }
200
201 s = s + '\n-- ' + r + ' ----\n';
202 lo = hunk.start_new;
203 hi = hunk.end_new;
204 var inserts = hunk.blocks.filter(function(e) { return !(e.insert.length == 0); });
205
206 if(inserts) {
207 outlist = hunk.data_new.slice(lo, hi).map(function(e) { return e.replace(/^/g, ' '); });
208 inserts.forEach(function(block) {
209 block.insert.forEach(function(item) {
210 outlist[item.position - lo] = outlist[item.position - lo].replace(/^ /g, block.op); // + or !
211 });
212 });
213 // Add to diff string
214 s = s + outlist.join('\n');
215 }
216 // Return the diff string
217 return s;
218}
219
220var ed_diff = function(hunk, format) {
221 var opt_act = {'+':'a', '-':'d', '!':'c'};
222 if(hunk.blocks.length > 1) sys.puts("expecting only one block in an old diff hunk!");
223 var s = null;
224
225 if(format == 'reverse_ed') {
226 s = "" + op_act[hunk.blocks[0].op] + context_range(hunk, 'old') + '\n';
227 } else {
228 s = "" + context_range(hunk, 'old').replace(/,/g, ' ') + op_act[hunk.blocks[0].op] + '\n';
229 }
230
231 if(hunk.blocks[0].insert.length > 0) {
232 hunk.data_new.slice(hunk.start_new, hunk.end_new).forEach(function(e) {
233 s = s + '' + e + '\n';
234 });
235 // Add final marker
236 s = s + '.\n';
237 }
238 // Return diff string
239 return s;
240}
241
242// Generate a range of item numbers to print. Only print 1 number if the
243// range has only one item in it. Otherwise, it's 'start,end'
244var context_range = function(hunk, mode) {
245 var s = null, e = null;
246
247 if(mode == 'old') {
248 s = (hunk.start_old + 1);
249 e = (hunk.end_old + 1);
250 } else if(mode == 'new') {
251 s = (hunk.start_new + 1);
252 e = (hunk.end_new + 1);
253 }
254
255 return (s < e) ? ("" + s + "," + e) : ("" + e);
256}
257
258// Generate a range of item numbers to print for unified diff. Print
259// number where block starts, followed by number of lines in the block
260// (don't print number of lines if it's 1)
261var unified_range = function(hunk, mode) {
262 var s = null, e = null;
263
264 if(mode == 'old') {
265 s = (hunk.start_old + 1);
266 e = (hunk.end_old + 1);
267 } else if(mode == 'new') {
268 s = (hunk.start_new + 1);
269 e = (hunk.end_new + 1);
270 }
271
272 var length = e - s + 1;
273 var first = (length < 2) ? e : s; // something weird
274 return (length == 1) ? ("" + first) : (first + "," + length);
275}
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291