smart_diff 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +38 -0
- data/Rakefile +13 -0
- data/bin/smart_diff +69 -0
- data/doc/Htmlize.html +1209 -0
- data/doc/SmartDiff.html +790 -0
- data/doc/Tag.html +484 -0
- data/doc/Utils.html +370 -0
- data/doc/_index.html +149 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +338 -0
- data/doc/file.README.html +127 -0
- data/doc/file_list.html +55 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +127 -0
- data/doc/js/app.js +214 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +250 -0
- data/doc/top-level-namespace.html +114 -0
- data/example/bar.rb +52 -0
- data/example/foo.rb +51 -0
- data/foo.rb-bar.rb.html +498 -0
- data/lib/smart_diff/htmlize.rb +181 -0
- data/lib/smart_diff/utils.rb +29 -0
- data/lib/smart_diff.rb +49 -0
- data/spec/smart_diff/htmlize_spec.rb +33 -0
- data/spec/smart_diff/utils_spec.rb +34 -0
- data/spec/smart_diff_spec.rb +31 -0
- data/web/diff.css +96 -0
- data/web/nav.js +275 -0
- metadata +133 -0
data/foo.rb-bar.rb.html
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
<html>
|
2
|
+
|
3
|
+
<head>
|
4
|
+
|
5
|
+
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
|
6
|
+
|
7
|
+
<style>
|
8
|
+
|
9
|
+
.d { /* deleted */
|
10
|
+
border: solid 1px #CC929A;
|
11
|
+
border-radius: 3px;
|
12
|
+
background-color: #FCBFBA;
|
13
|
+
}
|
14
|
+
|
15
|
+
.i { /* inserted */
|
16
|
+
border: solid 1px #73BE73;
|
17
|
+
border-radius: 3px;
|
18
|
+
background-color: #98FB98;
|
19
|
+
}
|
20
|
+
|
21
|
+
.c { /* changed */
|
22
|
+
border: solid 1px #8AADB8;
|
23
|
+
background-color: LightBlue;
|
24
|
+
border-radius: 3px;
|
25
|
+
cursor: pointer;
|
26
|
+
}
|
27
|
+
|
28
|
+
.m { /* moved */
|
29
|
+
border: solid 1px #A9A9A9;
|
30
|
+
border-radius: 3px;
|
31
|
+
cursor: crosshair;
|
32
|
+
}
|
33
|
+
|
34
|
+
.mc {
|
35
|
+
border: solid 1px LightPink;
|
36
|
+
background-color: LightBlue;
|
37
|
+
cursor: pointer;
|
38
|
+
}
|
39
|
+
|
40
|
+
.u { /* unchanged */
|
41
|
+
border: solid 1px #A9A9A9;
|
42
|
+
border-radius: 4px;
|
43
|
+
cursor: crosshair;
|
44
|
+
}
|
45
|
+
|
46
|
+
span.lineno {
|
47
|
+
color: lightgrey;
|
48
|
+
-webkit-user-select: none;
|
49
|
+
-moz-user-select: none;
|
50
|
+
}
|
51
|
+
|
52
|
+
span.keyword {
|
53
|
+
/* color: #007070; */
|
54
|
+
font-weight: 700;
|
55
|
+
}
|
56
|
+
|
57
|
+
div.line {
|
58
|
+
}
|
59
|
+
|
60
|
+
div.src {
|
61
|
+
width:48%;
|
62
|
+
height:98%;
|
63
|
+
overflow:scroll;
|
64
|
+
float:left;
|
65
|
+
padding:0.5%;
|
66
|
+
border: solid 2px LightGrey;
|
67
|
+
border-radius: 5px;
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
div.stats {
|
72
|
+
border: solid 1px grey;
|
73
|
+
z-index: 1000;
|
74
|
+
width: 80%;
|
75
|
+
padding-left: 5%;
|
76
|
+
}
|
77
|
+
|
78
|
+
pre.stats {
|
79
|
+
color: grey;
|
80
|
+
-webkit-user-select: none;
|
81
|
+
-moz-user-select: none;
|
82
|
+
}
|
83
|
+
|
84
|
+
pre {
|
85
|
+
line-height: 200%;
|
86
|
+
}
|
87
|
+
|
88
|
+
p {
|
89
|
+
line-height: 200%;
|
90
|
+
}
|
91
|
+
|
92
|
+
::-webkit-scrollbar {
|
93
|
+
width: 10px;
|
94
|
+
}
|
95
|
+
|
96
|
+
::-webkit-scrollbar-track {
|
97
|
+
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
98
|
+
border-radius: 10px;
|
99
|
+
}
|
100
|
+
|
101
|
+
::-webkit-scrollbar-thumb {
|
102
|
+
border-radius: 10px;
|
103
|
+
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
|
104
|
+
}
|
105
|
+
|
106
|
+
|
107
|
+
</style>
|
108
|
+
|
109
|
+
<script type="text/javascript">
|
110
|
+
|
111
|
+
// convenience function for document.getElementById().
|
112
|
+
window['$']=function(a){return document.getElementById(a)};
|
113
|
+
|
114
|
+
|
115
|
+
/////////////////////// debug flag ////////////////////////
|
116
|
+
var debug = false;
|
117
|
+
|
118
|
+
|
119
|
+
/////////////////////// adjustable parameters //////////////////
|
120
|
+
var minStep = 10;
|
121
|
+
var nSteps = 30;
|
122
|
+
var stepInterval = 10;
|
123
|
+
var blockRange = 5; // how far consider one page blocked
|
124
|
+
var nodeHLColor = '#C9B0A9';
|
125
|
+
var lineHLColor = '#FFFF66';
|
126
|
+
var lineBlockedColor = '#E9AB17';
|
127
|
+
var bgColor = '';
|
128
|
+
var bodyBlockedColor = '#FAF0E6';
|
129
|
+
|
130
|
+
|
131
|
+
///////////////////////// globals ////////////////////////
|
132
|
+
var eventCount = { 'left' : 0, 'right' : 0};
|
133
|
+
var moving = false;
|
134
|
+
var matchId1 = 'leftstart';
|
135
|
+
var matchId2 = 'rightstart';
|
136
|
+
var matchLineId1 = -1;
|
137
|
+
var matchLineId2 = -1;
|
138
|
+
var cTimeout;
|
139
|
+
|
140
|
+
|
141
|
+
///////////////////////// utilities ///////////////////////
|
142
|
+
|
143
|
+
// No Math.sign() in JS?
|
144
|
+
function sign(x) {
|
145
|
+
if (x > 0) {
|
146
|
+
return 1;
|
147
|
+
} else if (x < 0) {
|
148
|
+
return -1;
|
149
|
+
} else {
|
150
|
+
return 0;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
|
155
|
+
function log(msg) {
|
156
|
+
if (debug) {
|
157
|
+
console.log(msg);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
function elementPosition(id) {
|
164
|
+
obj = $(id);
|
165
|
+
var curleft = 0, curtop = 0;
|
166
|
+
|
167
|
+
if (obj && obj.offsetParent) {
|
168
|
+
curleft = obj.offsetLeft;
|
169
|
+
curtop = obj.offsetTop;
|
170
|
+
|
171
|
+
while (obj = obj.offsetParent) {
|
172
|
+
curleft += obj.offsetLeft;
|
173
|
+
curtop += obj.offsetTop;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
return { x: curleft, y: curtop };
|
178
|
+
}
|
179
|
+
|
180
|
+
|
181
|
+
/*
|
182
|
+
* Scroll the window to relative position, detecting blocking positions.
|
183
|
+
*/
|
184
|
+
function scrollWithBlockCheck(container, distX, distY) {
|
185
|
+
var oldTop = container.scrollTop;
|
186
|
+
var oldLeft = container.scrollLeft;
|
187
|
+
|
188
|
+
container.scrollTop += distY; // the ONLY place for actual scrolling
|
189
|
+
container.scrollLeft += distX;
|
190
|
+
|
191
|
+
var actualX = container.scrollLeft - oldLeft;
|
192
|
+
var actualY = container.scrollTop - oldTop;
|
193
|
+
log("distY=" + distY + ", actualY=" + actualY);
|
194
|
+
log("distX=" + distX + ", actualX=" + actualX);
|
195
|
+
|
196
|
+
// extra leewaw here because Chrome scrolling is horribly inacurate
|
197
|
+
if ((Math.abs(distX) > blockRange && actualX === 0)
|
198
|
+
|| Math.abs(distY) > blockRange && actualY === 0) {
|
199
|
+
log("blocked");
|
200
|
+
container.style.backgroundColor = bodyBlockedColor;
|
201
|
+
return true;
|
202
|
+
} else {
|
203
|
+
eventCount[container.id] += 1;
|
204
|
+
container.style.backgroundColor = bgColor;
|
205
|
+
return false;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
|
210
|
+
function getContainer(elm) {
|
211
|
+
while (elm && elm.tagName !== 'DIV') {
|
212
|
+
elm = elm.parentElement || elm.parentNode;
|
213
|
+
}
|
214
|
+
return elm;
|
215
|
+
}
|
216
|
+
|
217
|
+
|
218
|
+
/*
|
219
|
+
* timed animation function for scrolling the current window
|
220
|
+
*/
|
221
|
+
function matchWindow(linkId, targetId, n)
|
222
|
+
{
|
223
|
+
moving = true;
|
224
|
+
|
225
|
+
var link = $(linkId);
|
226
|
+
var target = $(targetId);
|
227
|
+
var linkContainer = getContainer(link);
|
228
|
+
var targetContainer = getContainer(target);
|
229
|
+
|
230
|
+
var linkPos = elementPosition(linkId).y - linkContainer.scrollTop;
|
231
|
+
var targetPos = elementPosition(targetId).y - targetContainer.scrollTop;
|
232
|
+
var distY = targetPos - linkPos;
|
233
|
+
var distX = linkContainer.scrollLeft - targetContainer.scrollLeft;
|
234
|
+
|
235
|
+
|
236
|
+
log("matching window... " + n + " distY=" + distY + " distX=" + distX);
|
237
|
+
|
238
|
+
if (distY === 0 && distX === 0) {
|
239
|
+
clearTimeout(cTimeout);
|
240
|
+
moving = false;
|
241
|
+
} else if (n <= 1) {
|
242
|
+
scrollWithBlockCheck(targetContainer, distX, distY);
|
243
|
+
moving = false;
|
244
|
+
} else {
|
245
|
+
var stepSize = Math.floor(Math.abs(distY) / n);
|
246
|
+
actualMinStep = Math.min(minStep, Math.abs(distY));
|
247
|
+
if (Math.abs(stepSize) < minStep) {
|
248
|
+
var step = actualMinStep * sign(distY);
|
249
|
+
} else {
|
250
|
+
var step = stepSize * sign(distY);
|
251
|
+
}
|
252
|
+
var blocked = scrollWithBlockCheck(targetContainer, distX, step);
|
253
|
+
var rest = Math.floor(distY / step) - 1;
|
254
|
+
log("blocked?" + blocked + ", rest steps=" + rest);
|
255
|
+
if (!blocked) {
|
256
|
+
cTimeout = setTimeout(function () {
|
257
|
+
return matchWindow(linkId, targetId, rest);
|
258
|
+
}, stepInterval);
|
259
|
+
} else {
|
260
|
+
clearTimeout(cTimeout);
|
261
|
+
moving = false;
|
262
|
+
}
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
|
267
|
+
function showArrow(linkId, targetId)
|
268
|
+
{
|
269
|
+
var link = $(linkId);
|
270
|
+
var target = $(targetId);
|
271
|
+
var linkContainer = getContainer(link);
|
272
|
+
var targetContainer = getContainer(target);
|
273
|
+
|
274
|
+
var linkPos = elementPosition(linkId).y - linkContainer.scrollTop;
|
275
|
+
var targetPos = elementPosition(targetId).y - targetContainer.scrollTop;
|
276
|
+
var distY = targetPos - linkPos;
|
277
|
+
var distX = linkContainer.scrollLeft - targetContainer.scrollLeft;
|
278
|
+
|
279
|
+
|
280
|
+
log("targetPos = " + targetPos);
|
281
|
+
}
|
282
|
+
|
283
|
+
|
284
|
+
////////////////////////// highlighting /////////////////////////////
|
285
|
+
|
286
|
+
var highlighted = []
|
287
|
+
function putHighlight(id, color) {
|
288
|
+
var elm = $(id);
|
289
|
+
if (elm !== null) {
|
290
|
+
elm.style.backgroundColor = color;
|
291
|
+
if (color !== bgColor) {
|
292
|
+
highlighted.push(id);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
|
298
|
+
function clearHighlight() {
|
299
|
+
for (i = 0; i < highlighted.length; i += 1) {
|
300
|
+
putHighlight(highlighted[i], bgColor);
|
301
|
+
}
|
302
|
+
highlighted = [];
|
303
|
+
}
|
304
|
+
|
305
|
+
|
306
|
+
|
307
|
+
/*
|
308
|
+
* Highlight the link, target nodes and their lines,
|
309
|
+
* then start animation to move the other window to match.
|
310
|
+
*/
|
311
|
+
function highlight(me, linkId, targetId)
|
312
|
+
{
|
313
|
+
if (me.id === 'left') {
|
314
|
+
matchId1 = linkId;
|
315
|
+
matchId2 = targetId;
|
316
|
+
} else {
|
317
|
+
matchId1 = targetId;
|
318
|
+
matchId2 = linkId;
|
319
|
+
}
|
320
|
+
|
321
|
+
clearHighlight();
|
322
|
+
|
323
|
+
putHighlight(linkId, nodeHLColor);
|
324
|
+
putHighlight(targetId, nodeHLColor);
|
325
|
+
}
|
326
|
+
|
327
|
+
|
328
|
+
function instantMoveOtherWindow (me) {
|
329
|
+
log("me=" + me.id + ", eventcount=" + eventCount[me.id]);
|
330
|
+
log("matchId1=" + matchId1 + ", matchId2=" + matchId2);
|
331
|
+
|
332
|
+
me.style.backgroundColor = bgColor;
|
333
|
+
|
334
|
+
if (!moving && eventCount[me.id] === 0) {
|
335
|
+
if (me.id === 'left') {
|
336
|
+
matchWindow(matchId1, matchId2, 1);
|
337
|
+
} else {
|
338
|
+
matchWindow(matchId2, matchId1, 1);
|
339
|
+
}
|
340
|
+
}
|
341
|
+
if (eventCount[me.id] > 0) {
|
342
|
+
eventCount[me.id] -= 1;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
|
347
|
+
function getTarget(x){
|
348
|
+
x = x || window.event;
|
349
|
+
return x.target || x.srcElement;
|
350
|
+
}
|
351
|
+
|
352
|
+
|
353
|
+
window.onload =
|
354
|
+
function (e) {
|
355
|
+
var tags = document.getElementsByTagName("A")
|
356
|
+
for (var i = 0; i < tags.length; i++) {
|
357
|
+
tags[i].onmouseover =
|
358
|
+
function (e) {
|
359
|
+
var t = getTarget(e)
|
360
|
+
var lid = t.id
|
361
|
+
var tid = t.getAttribute('tid')
|
362
|
+
var container = getContainer(t)
|
363
|
+
highlight(container, lid, tid)
|
364
|
+
showArrow(lid, tid)
|
365
|
+
}
|
366
|
+
tags[i].onclick =
|
367
|
+
function (e) {
|
368
|
+
var t = getTarget(e)
|
369
|
+
var lid = t.id
|
370
|
+
var tid = t.getAttribute('tid')
|
371
|
+
var container = getContainer(t)
|
372
|
+
highlight(container, lid, tid)
|
373
|
+
matchWindow(lid, tid, nSteps)
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
tags = document.getElementsByTagName("DIV")
|
378
|
+
for (var i = 0; i < tags.length; i++) {
|
379
|
+
tags[i].onscroll =
|
380
|
+
function (e) {
|
381
|
+
instantMoveOtherWindow(getTarget(e))
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
}
|
386
|
+
|
387
|
+
|
388
|
+
</script>
|
389
|
+
|
390
|
+
</head>
|
391
|
+
|
392
|
+
<body>
|
393
|
+
<div id="left" class="src"><pre><a id="leftstart" tid="rightstart"></a>require "pathname"
|
394
|
+
require "filetree/simple_tree"
|
395
|
+
|
396
|
+
class Pathname
|
397
|
+
alias :_parent :parent
|
398
|
+
alias :_children :children
|
399
|
+
end
|
400
|
+
|
401
|
+
class FileTree < Pathname
|
402
|
+
include SimpleTree
|
403
|
+
|
404
|
+
attr_accessor :name, :id, :identifier
|
405
|
+
|
406
|
+
<a id='0' tid='1' class='c'>def name
|
407
|
+
@name ||= self.inspect
|
408
|
+
end</a>
|
409
|
+
|
410
|
+
<a id='2' tid='3' class='c'>def id
|
411
|
+
@id ||= self.inspect
|
412
|
+
end</a>
|
413
|
+
|
414
|
+
<a id='4' tid='5' class='c'>def identifier
|
415
|
+
@identifier ||= self.inspect
|
416
|
+
end</a>
|
417
|
+
|
418
|
+
#
|
419
|
+
# See {http://rubydoc.info/stdlib/pathname/Pathname:parent Pathname.parent}
|
420
|
+
#
|
421
|
+
# @return [FileTree] The directory immediately above self.
|
422
|
+
#
|
423
|
+
<a id='6' tid='7' class='c'>def parent
|
424
|
+
<span class='i'>puts <span class='i'>"Here's Johnny!"</span></span>
|
425
|
+
FileTree.new(_parent)
|
426
|
+
end</a>
|
427
|
+
|
428
|
+
#
|
429
|
+
# See {http://rubydoc.info/stdlib/pathname/Pathname:children Pathname.children}
|
430
|
+
#
|
431
|
+
# @return [Array] an Array of all entries contained in self.
|
432
|
+
#
|
433
|
+
<a id='8' tid='9' class='c'>def children(*args)
|
434
|
+
if self.directory?
|
435
|
+
_children(*args)
|
436
|
+
else
|
437
|
+
[]
|
438
|
+
end
|
439
|
+
end</a>
|
440
|
+
|
441
|
+
end
|
442
|
+
|
443
|
+
puts <span class='i'>"And now for something completely different"</span>
|
444
|
+
</pre></div><div id="right" class="src"><pre><a id="rightstart" tid="leftstart"></a>require "pathname"
|
445
|
+
require "filetree/simple_tree"
|
446
|
+
|
447
|
+
class Pathname
|
448
|
+
alias :_parent :parent
|
449
|
+
alias :_children :children
|
450
|
+
end
|
451
|
+
|
452
|
+
class FileTree < Pathname
|
453
|
+
include SimpleTree
|
454
|
+
|
455
|
+
attr_accessor :name, :id, :identifier
|
456
|
+
|
457
|
+
#
|
458
|
+
# See {http://rubydoc.info/stdlib/pathname/Pathname:parent Pathname.parent}
|
459
|
+
#
|
460
|
+
# @return [FileTree] The directory immediately above self.
|
461
|
+
#
|
462
|
+
<a id='7' tid='6' class='c'>def parent
|
463
|
+
ft = FileTree.new(_parent)
|
464
|
+
<span class='d'><span class='d'>return ft</span></span>
|
465
|
+
end</a>
|
466
|
+
|
467
|
+
<a id='3' tid='2' class='c'>def id
|
468
|
+
@id ||= self.inspect
|
469
|
+
<span class='d'>puts <span class='d'>@id</span></span>
|
470
|
+
end</a>
|
471
|
+
|
472
|
+
<a id='1' tid='0' class='c'>def name
|
473
|
+
@name ||= self.inspect
|
474
|
+
end</a>
|
475
|
+
|
476
|
+
#
|
477
|
+
# See {http://rubydoc.info/stdlib/pathname/Pathname:children Pathname.children}
|
478
|
+
#
|
479
|
+
# @return [Array] an Array of all entries contained in self.
|
480
|
+
#
|
481
|
+
<a id='9' tid='8' class='c'>def children(*args)
|
482
|
+
if self.directory?
|
483
|
+
_children(*args)
|
484
|
+
else
|
485
|
+
[]
|
486
|
+
end
|
487
|
+
end</a>
|
488
|
+
|
489
|
+
<a id='5' tid='4' class='c'>def identifier
|
490
|
+
@identifier ||= self.inspect
|
491
|
+
end</a>
|
492
|
+
|
493
|
+
end
|
494
|
+
|
495
|
+
puts <span class='d'>"Some stuff"</span>
|
496
|
+
</pre></div></body>
|
497
|
+
|
498
|
+
</html>
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require_relative "utils.rb"
|
3
|
+
|
4
|
+
class Tag
|
5
|
+
attr_accessor :tag, :idx, :start
|
6
|
+
|
7
|
+
def initialize(tag, idx, start=-1)
|
8
|
+
@tag = tag
|
9
|
+
@idx = idx
|
10
|
+
@start = start
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"tag: #{@tag}, idx: #{@idx} start: #{@start}"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Htmlize
|
20
|
+
include Utils
|
21
|
+
attr_accessor :uid_count, :uid_hash
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@uid_count = -1
|
25
|
+
@uid_hash = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear_uid()
|
29
|
+
@uid_count = -1
|
30
|
+
@uid_hash = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def uid(node)
|
34
|
+
if @uid_hash.has_key?(node)
|
35
|
+
return @uid_hash[node]
|
36
|
+
end
|
37
|
+
|
38
|
+
@uid_count = @uid_count + 1
|
39
|
+
@uid_hash[node] = @uid_count.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def html_header
|
43
|
+
install_path = get_install_path
|
44
|
+
|
45
|
+
js_filename = Pathname.new(install_path).join('web/nav.js')
|
46
|
+
js_text = js_filename.read
|
47
|
+
|
48
|
+
css_filename = Pathname.new(install_path).join('web/diff.css')
|
49
|
+
css_text = css_filename.read
|
50
|
+
|
51
|
+
out = %Q{<html>\n
|
52
|
+
<head>\n
|
53
|
+
<META http-equiv="Content-Type" content="text/html; charset=utf-8">\n
|
54
|
+
<style>\n
|
55
|
+
#{css_text}
|
56
|
+
\n</style>\n
|
57
|
+
<script type="text/javascript">\n
|
58
|
+
#{js_text}
|
59
|
+
\n</script>\n
|
60
|
+
</head>\n
|
61
|
+
<body>\n}
|
62
|
+
end
|
63
|
+
|
64
|
+
def html_footer
|
65
|
+
out = %Q{</body>\n
|
66
|
+
</html>\n}
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_html(text, side)
|
70
|
+
out = ""
|
71
|
+
|
72
|
+
out << '<div id="' + side + '" class="src">'
|
73
|
+
out << '<pre>'
|
74
|
+
|
75
|
+
if side == 'left'
|
76
|
+
out << '<a id="leftstart" tid="rightstart"></a>'
|
77
|
+
else
|
78
|
+
out << '<a id="rightstart" tid="leftstart"></a>'
|
79
|
+
end
|
80
|
+
|
81
|
+
out << text
|
82
|
+
out << '</pre>'
|
83
|
+
out << '</div>'
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_html(changes, file1, file2, text1, text2)
|
87
|
+
tags1 = change_tags(changes, 'left')
|
88
|
+
tags2 = change_tags(changes, 'right')
|
89
|
+
tagged_text1 = apply_tags(text1, tags1)
|
90
|
+
tagged_text2 = apply_tags(text2, tags2)
|
91
|
+
|
92
|
+
output_filename = "#{Pathname.new(file1).basename}-#{Pathname.new(file2).basename}.html"
|
93
|
+
File.open(output_filename, "w") do |f|
|
94
|
+
f.write html_header
|
95
|
+
f.write write_html(tagged_text1, 'left')
|
96
|
+
f.write write_html(tagged_text2, 'right')
|
97
|
+
f.write html_footer
|
98
|
+
end
|
99
|
+
output_filename
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
def apply_tags(s, tags)
|
104
|
+
tags = tags.sort_by { |x| [x.idx, -x.start] }
|
105
|
+
curr = 0
|
106
|
+
out = ""
|
107
|
+
tags.each do |t|
|
108
|
+
while curr < t.idx && curr < s.length
|
109
|
+
out << CGI::escapeHTML(s[curr])
|
110
|
+
curr += 1
|
111
|
+
end
|
112
|
+
out << t.tag
|
113
|
+
end
|
114
|
+
while curr < s.length
|
115
|
+
out << CGI::escapeHTML(s[curr])
|
116
|
+
curr += 1
|
117
|
+
end
|
118
|
+
return out
|
119
|
+
end
|
120
|
+
|
121
|
+
def change_tags(changes, side)
|
122
|
+
tags = []
|
123
|
+
changes.each do |c|
|
124
|
+
|
125
|
+
key = side =~ /left/ ? c.old_node : c.new_node
|
126
|
+
if key
|
127
|
+
nd_start = node_start(key)
|
128
|
+
nd_end = node_end(key)
|
129
|
+
|
130
|
+
if c.old_node && c.new_node
|
131
|
+
if inside_anchor?(tags, nd_start, nd_end)
|
132
|
+
if change_class(c) =~ /c/
|
133
|
+
# no op
|
134
|
+
else
|
135
|
+
tags << Tag.new(span_start(c), nd_start)
|
136
|
+
tags << Tag.new('</span>', nd_end, nd_start)
|
137
|
+
end
|
138
|
+
else
|
139
|
+
tags << Tag.new(link_start(c, side), nd_start)
|
140
|
+
tags << Tag.new('</a>', nd_end, nd_start)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
tags << Tag.new(span_start(c), nd_start)
|
144
|
+
tags << Tag.new('</span>', nd_end, nd_start)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
return tags
|
149
|
+
end
|
150
|
+
|
151
|
+
def change_class(change)
|
152
|
+
if !change.old_node
|
153
|
+
return 'd'
|
154
|
+
elsif !change.new_node
|
155
|
+
return 'i'
|
156
|
+
else
|
157
|
+
return 'c'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def span_start(change)
|
162
|
+
"<span class=#{qs(change_class(change))}>"
|
163
|
+
end
|
164
|
+
|
165
|
+
def link_start(change, side)
|
166
|
+
cls = change_class(change)
|
167
|
+
|
168
|
+
if side == 'left'
|
169
|
+
me, other = change.old_node, change.new_node
|
170
|
+
else
|
171
|
+
me, other = change.new_node, change.old_node
|
172
|
+
end
|
173
|
+
|
174
|
+
"<a id=#{qs(uid(me))} tid=#{qs(uid(other))} class=#{qs(cls)}>"
|
175
|
+
end
|
176
|
+
|
177
|
+
def qs(s)
|
178
|
+
"'#{s}'"
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module Utils
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def get_install_path
|
7
|
+
utils_path = Pathname.new(__FILE__).dirname
|
8
|
+
# There should be a more elegant way to do this
|
9
|
+
install_path = utils_path.parent.parent
|
10
|
+
end
|
11
|
+
|
12
|
+
def node_start(node)
|
13
|
+
node.position.start_offset
|
14
|
+
end
|
15
|
+
|
16
|
+
def node_end(node)
|
17
|
+
node.position.end_offset
|
18
|
+
end
|
19
|
+
|
20
|
+
def inside_anchor?(tags, nd_start, nd_end)
|
21
|
+
tags.each do |t|
|
22
|
+
if nd_end < t.idx && nd_start > t.start && t.start != -1
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|