smart_diff 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 &quot;pathname&quot;
394
+ require &quot;filetree/simple_tree&quot;
395
+
396
+ class Pathname
397
+ alias :_parent :parent
398
+ alias :_children :children
399
+ end
400
+
401
+ class FileTree &lt; 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'>&quot;Here's Johnny!&quot;</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'>&quot;And now for something completely different&quot;</span>
444
+ </pre></div><div id="right" class="src"><pre><a id="rightstart" tid="leftstart"></a>require &quot;pathname&quot;
445
+ require &quot;filetree/simple_tree&quot;
446
+
447
+ class Pathname
448
+ alias :_parent :parent
449
+ alias :_children :children
450
+ end
451
+
452
+ class FileTree &lt; 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'>&quot;Some stuff&quot;</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